RK3506 裸机开发(二):手写 Thumb 汇编实现 LED 闪烁
发布时间:2026/9/27 3:18:06 锦皓数字建站
:手写 Thumb 汇编实现 LED 闪烁`)
1 先算好 5 个常数GPIO0_IOC_GPIO0D_CON先算地址textGPIO0_IOC(0xFF950000) 0x0830 0xFF950830参考Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf在写任何代码之前先把需要的数值算出来。这一步是“从手册到代码”的桥梁。已知ATK-DLRK3506 嵌入式 Linux 驱动开发指南 V1.2常数怎么算值写允许位1 (16 8) 1 240x01000000输出高点亮0x01000000 | (1 8)0x01000100输出低熄灭0x01000000 | 00x01000000IOMUX 值[17:16]0b11允许写[1:0][1:0]00选 GPIO0x00030000时钟值bit241允许写bit80保持开启0x01000000注意0x00030000的高 16 位是0x0003对应 bit17、bit16 都置 1也就是[17:16]0b11表示允许修改低 16 位的[1:0]。2 骨架伪指令 入口新建文件假设叫led_thumb.s先写头部asm.syntax unified .arch armv7-a .cpu cortex-a7 .thumb /* ★ 关键按 Thumb 生成 */ .global _start .text .thumb_func /* ★ 关键让 _start 地址最低位置 1 */ _start: push {r4, r5, r6, r7, lr}push {r4, r5, r6, r7, lr}为什么必须有AAPCSARM 过程调用标准规定 r4~r8、r10、r11、SP 是被调用者必须原样保存的寄存器。虽然本程序最后是死循环、不返回但这条push让程序有可能安全返回也避免破坏 U-Boot 留下的寄存器状态。养习惯入口先push。3 第 1 步引脚复用 IOMUXasm/* GPIO0_IOC_GPIO0D_CON 0xFF950830 * [17:16] 写允许[1:0] gpio0d0_sel 00选 GPIO0D0 */ ldr r0, 0xFF950830 /* 寄存器地址 */ ldr r1, 0x00030000 /* [17:16]0b11 允许写 [1:0][1:0]00 选 GPIO */ str r1, [r0] /* 写入 */为什么用ldr r0, 0xFF950830而不是movmov的立即数字段装不下 32 位常数。ldr r0, 值是伪指令汇编器会把它变成“一条 PC 相对的ldr2 字节 末尾常量池里的一个 4 字节.word”。这就是为什么反汇编最后会出现 3 个.word见 §10.1。注意写的是0x00030000不是0x00000000低 16 位是选择值0 GPIO高 16 位是写允许掩码。0x0003表示只允许改[1:0]这两位不碰同一寄存器里的其他位。这一步在复位后本来是“已经对”的复位值0x0000072C[1:0]00。保留它的价值是显式化如果 U-Boot 或前级代码动过这个寄存器这段能兜住。4 第 2 步打开时钟门控asm/* CRU_PMU_GATE_CON00 0xFF9B0800 * bit8 pclk_gpio0_en1关0开 */ ldr r0, 0xFF9B0800 ldr r1, 0x01000000 /* bit24写允许bit80 → 时钟保持开启 */ str r1, [r0]两个陷阱§6.5 已讲这个寄存器在CRU_PMU(0xFF9B0000)不在主 CRU极性是反的1 关。同样复位值本来就是 0时钟已开保留是为了显式。5 第 3 步设为输出asm/* GPIO0_SWPORT_DDR_H 0xFF940000 0x0C * bit8 1 → Outputbit24 写允许 */ ldr r0, 0xFF940000 /* 把 GPIO0 基址放进 r0后面复用 */ ldr r1, 0x01000100 /* bit24写允许bit81 → 输出 */ str r1, [r0, #0x0C] /* GPIO_SWPORT_DDR_H */为什么是#0x0C而不是#0x04DDR_H方向偏移是0x0CDR_H数据偏移是0x04。别搞反。为什么是_Hpin 24 ≥ 16§6.3。这里把0xFF940000放进r0是为了下一步复用——省一条ldr。6 第 4 步输出高电平点亮asm/* GPIO0_SWPORT_DR_H 0xFF940000 0x04 * bit8 1 → 高电平 → 点亮 */ ldr r1, 0x01000100 /* bit24写允许bit81 → 高电平 */ str r1, [r0, #0x04] /* GPIO_SWPORT_DR_H */注意这里只重新加载了r1r0还是上一步的0xFF940000。到这一步LED 应该已经常亮了。如果 §7.7 的循环不加程序会一直往下跑然后跑飞。7 第 5 步闪烁循环asmblink: /* 点亮 */ ldr r1, 0x01000100 /* bit24写允许bit81 → 亮 */ str r1, [r0, #0x04] ldr r2, 0x20000000 /* 亮的延时计数 */ delay_on: subs r2, r2, #1 /* 减 1并更新标志位 */ bne delay_on /* 不为 0 → 继续循环 */ /* 熄灭 */ ldr r1, 0x01000000 /* bit24写允许bit80 → 灭 */ str r1, [r0, #0x04] ldr r2, 0x10000000 /* 灭的延时计数 */ delay_off: subs r2, r2, #1 bne delay_off b blink /* 回到开头无限循环 */★subs而不是sub只有subs会更新 Z 标志位bne才有意义。写成sub会变成永不退出的死循环bne永远成立——这是经典坑。为什么用“空转循环”做延时这是最粗暴的延时方式——CPU 就傻数数。真实项目应该用 TIMER。这里够用因为目的只是让闪烁肉眼可见。延时比例亮0x20000000灭0x10000000亮:灭 2:1。所以占空比不是 50%视觉上亮的时间稍长。这是原始源码的值未修改。循环里没有pop程序以b blink结尾是无限循环永不返回。所以go之后 U-Boot 命令行不会再回来要按 RESET 键才能恢复。led_thumb.s源码可见https://github.com/joyful-jogo/rk3506-baremetal/tree/main/1_leds
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。