本文记录了使用纯汇编语言在AT32F423芯片上实现流水灯的完整过程,总结了开发中遇到的坑点和调试经验,为后续Zephyr RTOS学习打下基础。 一、背景介绍在嵌入式开发中,使用纯汇编语言直接操作硬件寄存器是理解MCU内部运行机制的最佳途径。本文使用雅特力(AT)公司的AT32F423芯片,该芯片基于ARM Cortex-M4内核,与ST的STM32系列相比,在GPIO寄存器上有较大差异。开发目标:使用纯汇编(不依赖任何HAL库)实现LED流水灯深入理解AT32F423的启动流程和寄存器配置为后续学习Zephyr RTOS打下基础二、失败经历与问题总结2.1 第一次尝试:寄存器映射错误问题现象:下载程序后LED不亮,调试发现GPIOD寄存器全为0x00根本原因: 使用了STM32的GPIO寄存器映射| 功能 | STM32寄存器 | AT32寄存器 |
| 模式配置 | MODER | CFGR |
| 输出速度 | OSPEEDR | ODRVR |
| 输出数据 | ODR | ODT |
| 置位/清零 | BSRR | SCR |
教训: AT32与STM32的GPIO寄存器完全不同,不能照搬STM32代码
2.2 第二次尝试:时钟未使能问题现象: GPIO寄存器配置正确,但仍不工作
根本原因:未使能GPIOD时钟
解决: 通过CRM_AHBEN1寄存器使能GPIOD时钟
ldr r1, [r0, #CRM_AHBEN1]
orr.w r1, r1, #(1 << 3) @ GPIODEN
str r1, [r0, #CRM_AHBEN1]
2.3 第三次尝试:LED极性搞反问题现象: 代码运行但LED不亮
根本原因: LED是低电平点亮,错误地使用高电平
解决: 输出低电平(0)点亮LED
2.4 第四次尝试:SystemInit函数问题问题现象: 调试器无法继续运行,程序停留在初始化阶段
根本原因: SystemInit函数实现过于复杂
解决: 简化SystemInit,AT32复位后默认使用HICK 8MHz,GPIO时钟默认使能,直接返回即可
三、AT32F423 关键寄存器映射3.1 外设基地址.equ PERIPH_BASE, 0x40000000
.equ AHBPERIPH1, PERIPH_BASE + 0x20000 @ 0x40020000
.equ GPIOA_BASE, AHBPERIPH1 + 0x0000 @ 0x40020000
.equ GPIOB_BASE, AHBPERIPH1 + 0x0400 @ 0x40020400
.equ GPIOC_BASE, AHBPERIPH1 + 0x0800 @ 0x40020800
.equ GPIOD_BASE, AHBPERIPH1 + 0x0C00 @ 0x40020C00
.equ CRM_BASE, PERIPH_BASE + 0x23800 @ 0x40023800
3.2 GPIO寄存器偏移(AT32特有).equ GPIO_CFGR, 0x00 @ 模式配置寄存器(每引脚2位)
.equ GPIO_OMODE, 0x04 @ 输出类型寄存器
.equ GPIO_ODRVR, 0x08 @ 驱动强度寄存器
.equ GPIO_PULL, 0x0C @ 上下拉寄存器
.equ GPIO_ODT, 0x14 @ 输出数据寄存器
.equ GPIO_SCR, 0x18 @ 置位/清零寄存器
3.3 CRM寄存器偏移.equ CRM_CTRL, 0x00 @ 控制寄存器
.equ CRM_CFG, 0x04 @ 时钟配置寄存器
.equ CRM_AHBEN1, 0x30 @ AHB1外设时钟使能寄存器
四、完整代码实现4.1 启动文件 startup_asm.S/**
******************************************************************************
* @file startup_asm.S
* @brief AT32F423 纯汇编启动文件 - 流水灯
* 不使用任何 HAL 库,直接操作寄存器
******************************************************************************
*/
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
/*==============================================================================
寄存器地址定义
==============================================================================*/
.equ PERIPH_BASE, 0x40000000
.equ AHBPERIPH1, PERIPH_BASE + 0x20000
.equ GPIOA_BASE, AHBPERIPH1 + 0x0000
.equ GPIOB_BASE, AHBPERIPH1 + 0x0400
.equ GPIOC_BASE, AHBPERIPH1 + 0x0800
.equ GPIOD_BASE, AHBPERIPH1 + 0x0C00
.equ GPIOE_BASE, AHBPERIPH1 + 0x1000
.equ GPIOF_BASE, AHBPERIPH1 + 0x1400
.equ CRM_BASE, PERIPH_BASE + 0x23800
/* CRM 寄存器偏移 */
.equ CRM_CTRL, 0x00
.equ CRM_CFG, 0x04
.equ CRM_AHBEN1, 0x30
/* GPIO 寄存器偏移(AT32特有) */
.equ GPIO_CFGR, 0x00
.equ GPIO_OMODE, 0x04
.equ GPIO_ODRVR, 0x08
.equ GPIO_PULL, 0x0C
.equ GPIO_ODT, 0x14
.equ GPIO_SCR, 0x18
/* LED 引脚定义 */
.equ LED2_PIN, 0x2000 @ GPIO_PINS_13
.equ LED3_PIN, 0x4000 @ GPIO_PINS_14
.equ LED4_PIN, 0x8000 @ GPIO_PINS_15
.equ LED_ALL, 0xE000
/* 内存定义 */
.equ FLASH_BASE, 0x08000000
.equ RAM_BASE, 0x20000000
.equ RAM_SIZE, 0xC000 @ 48KB
.equ _estack, RAM_BASE + RAM_SIZE
/*==============================================================================
向量表
==============================================================================*/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
/*==============================================================================
启动代码
==============================================================================*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
/* 复制 .data 段 */
movs r1, #0
b LoopCopyData
CopyData:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyData:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyData
/* 清零 .bss 段 */
ldr r2, =_sbss
b LoopFillZerobss
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, =_ebss
cmp r2, r3
bcc FillZerobss
/* 初始化系统时钟 */
bl SystemInit
/* 初始化 LED */
bl LedInit
/* 跳转到 main */
bl main
Loop:
b Loop
/*==============================================================================
SystemInit - 最小化版本
==============================================================================*/
.section .text.SystemInit
.type SystemInit, %function
SystemInit:
bx lr
.size SystemInit, .-SystemInit
/*==============================================================================
LedInit - 初始化 LED (GPIOD pin 13/14/15)
==============================================================================*/
.section .text.LedInit
.type LedInit, %function
LedInit:
ldr r0, =CRM_BASE
/* 使能 GPIOD 时钟 (AHB1ENR bit 3) */
ldr r1, [r0, #CRM_AHBEN1]
orr.w r1, r1, #(1 << 3)
str r1, [r0, #CRM_AHBEN1]
/* 配置 GPIOD 引脚为输出 */
ldr r0, =GPIOD_BASE
/* CFGR: 设置 Pin 13/14/15 为输出模式 */
ldr r1, [r0, #GPIO_CFGR]
bic.w r1, r1, #(0x03 << 26)
orr.w r1, r1, #(0x01 << 26)
bic.w r1, r1, #(0x03 << 28)
orr.w r1, r1, #(0x01 << 28)
bic.w r1, r1, #(0x03 << 30)
orr.w r1, r1, #(0x01 << 30)
str r1, [r0, #GPIO_CFGR]
/* ODRVR: 驱动强度 - 最强 */
ldr r1, [r0, #GPIO_ODRVR]
orr.w r1, r1, #(0x03 << 26)
orr.w r1, r1, #(0x03 << 28)
orr.w r1, r1, #(0x03 << 30)
str r1, [r0, #GPIO_ODRVR]
/* 初始状态: LED 关闭 */
ldr r1, =LED_ALL
str r1, [r0, #GPIO_ODT]
bx lr
.size LedInit, .-LedInit
/*==============================================================================
main 函数 - 流水灯
==============================================================================*/
.section .text.main
.type main, %function
main:
ldr r0, =GPIOD_BASE
led_loop:
/* LED2 点亮 - 低电平点亮 */
ldr r1, =LED2_PIN
lsl r1, r1, #16 @ SCR 高16位 = 清零 ODT 位
str r1, [r0, #GPIO_SCR]
bl delay
/* LED2 关闭 */
ldr r1, =LED2_PIN
str r1, [r0, #GPIO_ODT]
/* LED3 点亮 */
ldr r1, =LED3_PIN
lsl r1, r1, #16
str r1, [r0, #GPIO_SCR]
bl delay
/* LED3 关闭 */
ldr r1, =LED3_PIN
str r1, [r0, #GPIO_ODT]
/* LED4 点亮 */
ldr r1, =LED4_PIN
lsl r1, r1, #16
str r1, [r0, #GPIO_SCR]
bl delay
/* LED4 关闭 */
ldr r1, =LED4_PIN
str r1, [r0, #GPIO_ODT]
b led_loop
/* 延时函数 */
delay:
movs r5, #0
delay_loop:
adds r5, r5, #1
cmp r5, #0x80000
bne delay_loop
bx lr
.size main, .-main
/*==============================================================================
默认中断处理
==============================================================================*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
/*==============================================================================
外部符号
==============================================================================*/
.section .data
.align 2
.global _sidata
.global _sdata
.global _edata
.global _sbss
.global _ebss
_sidata:
.word 0
.section .bss
.align 2
_sdata:
.word 0
_edata:
.word 0
_sbss:
.word 0
_ebss:
.word 0
```
五、Makefile配置TARGET = at32f423_asm_led
DEBUG = 1
OPT = -Og
BUILD_DIR = build
C_SOURCES =
ASM_SOURCES = ./project/src/startup_asm.S
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
CPU = -mcpu=cortex-m4
FPU = -mfpu=fpv4-sp-d16
FLOAT-ABI = -mfloat-abi=soft
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
ASFLAGS = $(MCU) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS = $(MCU) $(OPT) -Wall -fdata-sections -ffunction-sections
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
LDSCRIPT = project/misc/AT32F423xC_FLASH.ld
LIBS =
LIBDIR =
LDFLAGS = $(MCU) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES)))
$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
$(AS) -c $(ASFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(CP) -O ihex $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(CP) -O binary -S $< $@
$(BUILD_DIR):
mkdir $@
clean:
rm -rf $(BUILD_DIR)
六、下载验证使用OpenOCD下载:
openocd.exe -f scripts/interface/atlink.cfg -f scripts/target/at32f423xx.cfg -c "program build/at32f423_asm_led.hex verify reset exit"验证结果: LED2 → LED3 → LED4 循环点亮流水灯效果正常
七、与Zephyr的关系7.1为什么学习寄存器级别开发?Zephyr RTOS虽然提供了丰富的驱动抽象,但:
深入调试时需要理解底层硬件
移植新板级支持 需要掌握寄存器操作
-性能优化 需要了解外设工作原理
7.2 汇编技能在Zephyr中的应用1. 启动代码:Zephyr的arch层使用汇编实现中断向量表、堆栈初始化
2. 上下文切换:任务切换需要汇编实现寄存器保存/恢复
3. 临界区保护:CPS指令操作需要在汇编级别理解
7.3 下一步学习路径1. 学习Zephyr的AT32F423板级支持代码
2. 分析Zephyr的GPIO子系统实现
3. 在Zephyr中添加自定义外设驱动
八、总结本文通过纯汇编实现AT32F423流水灯,详细记录了:
1. AT32与STM32 GPIO寄存器的差异2. 调试过程中的关键发现3. 完整的代码实现掌握这些底层技能,将为后续学习Zephyr RTOS打下坚实基础。理解硬件的工作原理,才能更好地使用操作系统提供的抽象层。
阅读全文