最近在整理嵌入式开发的学习笔记时,发现很多初学者对裸机编程中的汇编LED驱动实验存在不少困惑。这个实验虽然基础,但却是理解ARM架构下硬件直接控制的关键一步。本文将基于Ubuntu 20.04环境,详细拆解如何用汇编语言实现LED的点亮控制。
裸机编程指的是不依赖任何操作系统,直接操作硬件寄存器的开发方式。对于嵌入式开发者而言,掌握这种底层控制能力至关重要。LED驱动作为最基础的GPIO控制实验,能帮助我们建立对内存映射、寄存器操作等核心概念的直观认识。
我推荐使用性价比高的开发板进行实验,比如STM32F103C8T6最小系统板(俗称"蓝色药丸")。这块板子价格低廉(约20元),但功能齐全,特别适合学习用途。板上自带一颗用户LED(通常连接在PC13引脚),正好满足我们的实验需求。
硬件连接非常简单:
注意:不同型号的开发板LED连接引脚可能不同,务必查阅对应板子的原理图确认GPIO引脚号。
在Ubuntu 20.04上需要安装ARM交叉编译工具链:
bash复制sudo apt update
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi
验证安装是否成功:
bash复制arm-none-eabi-gcc --version
此外还需要安装烧录工具:
bash复制sudo apt install stlink-tools
以STM32F103为例,其GPIO外设的寄存器起始地址为0x40010800(GPIOA)。每个GPIO端口有多个寄存器:
对于连接LED的PC13引脚,我们需要操作GPIOC的寄存器组,其基地址为0x40011000。
创建led.s汇编文件,核心代码如下:
assembly复制.syntax unified
.cpu cortex-m3
.thumb
.equ RCC_APB2ENR, 0x40021018
.equ GPIOC_CRH, 0x40011004
.equ GPIOC_ODR, 0x4001100C
.global _start
.section .text
_start:
// 1. 使能GPIOC时钟
ldr r0, =RCC_APB2ENR
ldr r1, [r0]
orr r1, #(1<<4) // IOPCEN位
str r1, [r0]
// 2. 配置PC13为推挽输出(50MHz)
ldr r0, =GPIOC_CRH
ldr r1, [r0]
bic r1, #(0xF<<20) // 清除CNF13/MODE13
orr r1, #(0x3<<20) // 输出模式,50MHz
str r1, [r0]
// 3. LED控制循环
loop:
// 点亮LED(PC13置低)
ldr r0, =GPIOC_ODR
ldr r1, [r0]
bic r1, #(1<<13)
str r1, [r0]
// 延时
ldr r2, =1000000
delay_on:
subs r2, #1
bne delay_on
// 熄灭LED(PC13置高)
ldr r0, =GPIOC_ODR
ldr r1, [r0]
orr r1, #(1<<13)
str r1, [r0]
// 延时
ldr r2, =1000000
delay_off:
subs r2, #1
bne delay_off
b loop
创建linker.ld文件定义内存布局:
ld复制MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
SECTIONS
{
.text : {
*(.vectors*)
*(.text*)
} > FLASH
.data : {
*(.data*)
} > SRAM AT > FLASH
.bss : {
*(.bss*)
} > SRAM
}
使用以下命令编译:
bash复制arm-none-eabi-as -mcpu=cortex-m3 -g led.s -o led.o
arm-none-eabi-ld -T linker.ld -nostdlib led.o -o led.elf
arm-none-eabi-objcopy -O binary led.elf led.bin
连接ST-Link调试器后,使用以下命令烧录:
bash复制st-flash write led.bin 0x08000000
烧录成功后复位开发板,应该能看到LED开始闪烁。
检查硬件连接:
检查软件配置:
检查ST-Link连接:
bash复制st-info --probe
如果没有显示设备,检查USB连接和驱动
尝试擦除芯片:
bash复制st-flash erase
然后再重新烧录
检查Boot引脚设置:
前面的示例使用了简单的循环延时,不够精确。可以改用SysTick定时器实现毫秒级精确延时:
assembly复制.equ STK_CTRL, 0xE000E010
.equ STK_LOAD, 0xE000E014
.equ STK_VAL, 0xE000E018
// 初始化SysTick (72MHz时钟,1ms中断)
ldr r0, =STK_LOAD
ldr r1, =72000 // 72000/72000000 = 1ms
str r1, [r0]
ldr r0, =STK_CTRL
mov r1, #0x7 // 使能SysTick
str r1, [r0]
为了代码更完整,应该添加最基本的中断向量表:
assembly复制.section .vectors
.word 0x20001000 // 初始栈指针
.word _start // 复位向量
.word hang // NMI
.word hang // HardFault
// ...其他中断向量省略
hang: b hang // 默认中断处理
虽然本文重点在汇编,但实际项目中通常会混合使用C和汇编。可以在汇编中初始化硬件后跳转到C代码:
assembly复制bl main // 跳转到C的main函数
对应的简单C代码:
c复制void main() {
while(1) {
GPIOC->ODR ^= (1<<13); // 翻转LED状态
delay_ms(500);
}
}
安装调试工具:
bash复制sudo apt install gdb-multiarch
启动调试会话:
bash复制arm-none-eabi-gdb led.elf
target extended-remote :4242
load
monitor reset halt
break _start
continue
在GDB中可以使用以下命令:
code复制info registers
x/4x 0x40011000 // 查看GPIOC寄存器
如果条件允许,使用Saleae逻辑分析仪可以直观看到GPIO引脚的电平变化,验证代码是否正确执行。