跳转至

汇编语言在嵌入式中的应用

学习目标

通过本教程,你将能够:

  1. 理解ARM汇编语言的基本语法和指令集
  2. 掌握嵌入式系统启动代码的汇编实现
  3. 学会使用汇编编写高效的中断处理程序
  4. 掌握汇编级别的性能优化技巧
  5. 实现C语言与汇编语言的混合编程

前置要求

知识要求

  • 熟悉C语言编程
  • 了解计算机组成原理
  • 理解内存管理和指针概念
  • 掌握基本的嵌入式开发流程

技能要求

  • 能够使用GCC或Keil等工具链
  • 了解ARM Cortex-M系列处理器
  • 具备基本的调试能力

环境要求

  • 开发板:STM32F4系列或类似ARM Cortex-M4开发板
  • 编译器:GCC ARM或Keil MDK
  • 调试器:ST-Link或J-Link
  • IDE:Keil MDK、STM32CubeIDE或命令行工具

准备工作

硬件准备

  • STM32F4 Discovery开发板(或类似开发板)
  • USB数据线
  • ST-Link调试器(开发板通常自带)

软件准备

  1. 安装开发工具链:
# Linux/Mac安装ARM GCC工具链
sudo apt-get install gcc-arm-none-eabi

# 或下载官方工具链
# https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm
  1. 安装OpenOCD(用于调试):
sudo apt-get install openocd
  1. 创建项目目录结构:
mkdir assembly-tutorial
cd assembly-tutorial
mkdir src include build

ARM汇编基础

ARM Cortex-M指令集架构

ARM Cortex-M系列处理器使用Thumb-2指令集,这是16位和32位指令的混合指令集。

主要特点: - 代码密度高(16位指令) - 性能优秀(32位指令) - 功耗低 - 中断响应快

寄存器组织

ARM Cortex-M4处理器有以下寄存器:

通用寄存器:
R0-R12   : 通用寄存器,用于数据操作
R13 (SP) : 栈指针寄存器
R14 (LR) : 链接寄存器(存储返回地址)
R15 (PC) : 程序计数器

特殊寄存器:
PSR      : 程序状态寄存器
PRIMASK  : 中断屏蔽寄存器
FAULTMASK: 故障屏蔽寄存器
BASEPRI  : 基础优先级寄存器
CONTROL  : 控制寄存器

基本汇编语法

GNU汇编语法示例

    .syntax unified        @ 使用统一汇编语法
    .cpu cortex-m4        @ 指定CPU类型
    .thumb                @ 使用Thumb指令集

    .section .text        @ 代码段
    .global main          @ 声明全局符号
    .type main, %function @ 声明函数类型

main:
    PUSH {R4-R7, LR}      @ 保存寄存器
    MOV R0, #10           @ 立即数赋值
    MOV R1, #20           @ R1 = 20
    ADD R2, R0, R1        @ R2 = R0 + R1
    POP {R4-R7, PC}       @ 恢复寄存器并返回

常用指令分类

  1. 数据传送指令

    MOV R0, R1            @ R0 = R1
    MOV R0, #100          @ R0 = 100(立即数)
    LDR R0, [R1]          @ R0 = *R1(从内存加载)
    STR R0, [R1]          @ *R1 = R0(存储到内存)
    LDR R0, =0x20000000   @ R0 = 地址(伪指令)
    

  2. 算术运算指令

    ADD R0, R1, R2        @ R0 = R1 + R2
    SUB R0, R1, R2        @ R0 = R1 - R2
    MUL R0, R1, R2        @ R0 = R1 * R2
    SDIV R0, R1, R2       @ R0 = R1 / R2(有符号除法)
    

  3. 逻辑运算指令

    AND R0, R1, R2        @ R0 = R1 & R2
    ORR R0, R1, R2        @ R0 = R1 | R2
    EOR R0, R1, R2        @ R0 = R1 ^ R2(异或)
    BIC R0, R1, R2        @ R0 = R1 & ~R2(位清除)
    

  4. 移位指令

    LSL R0, R1, #2        @ R0 = R1 << 2(逻辑左移)
    LSR R0, R1, #2        @ R0 = R1 >> 2(逻辑右移)
    ASR R0, R1, #2        @ R0 = R1 >> 2(算术右移)
    ROR R0, R1, #2        @ R0 = R1循环右移2位
    

  5. 分支指令

    B label               @ 无条件跳转
    BL function           @ 调用函数(保存返回地址到LR
    BX LR                 @ 返回(跳转到LR地址
    BEQ label             @ 相等时跳转
    BNE label             @ 不相等时跳转
    BGT label             @ 大于时跳转
    

条件执行

ARM汇编支持条件执行,可以根据标志位执行指令:

CMP R0, R1            @ 比较R0和R1,设置标志位
MOVEQ R2, #1          @ 如果相等,R2 = 1
MOVNE R2, #0          @ 如果不相等,R2 = 0

@ 条件码:
@ EQ - 相等 (Equal)
@ NE - 不相等 (Not Equal)
@ GT - 大于 (Greater Than)
@ LT - 小于 (Less Than)
@ GE - 大于等于 (Greater or Equal)
@ LE - 小于等于 (Less or Equal)

启动代码编写

启动流程概述

嵌入式系统的启动流程:

graph TD
    A[上电复位] --> B[执行Reset_Handler]
    B --> C[初始化栈指针]
    C --> D[复制数据段到RAM]
    D --> E[清零BSS段]
    E --> F[调用SystemInit]
    F --> G[调用main函数]
    G --> H[进入主循环]

中断向量表

中断向量表是启动代码的核心部分,定义了所有异常和中断的处理函数地址:

    .syntax unified
    .cpu cortex-m4
    .thumb

    .section .isr_vector,"a",%progbits
    .type g_pfnVectors, %object
    .size g_pfnVectors, .-g_pfnVectors

g_pfnVectors:
    .word _estack                    @ 栈顶地址
    .word Reset_Handler              @ 复位处理函数
    .word NMI_Handler                @ NMI处理函数
    .word HardFault_Handler          @ 硬件故障处理函数
    .word MemManage_Handler          @ 内存管理故障
    .word BusFault_Handler           @ 总线故障
    .word UsageFault_Handler         @ 用法故障
    .word 0                          @ 保留
    .word 0                          @ 保留
    .word 0                          @ 保留
    .word 0                          @ 保留
    .word SVC_Handler                @ SVC调用
    .word DebugMon_Handler           @ 调试监视器
    .word 0                          @ 保留
    .word PendSV_Handler             @ PendSV
    .word SysTick_Handler            @ SysTick定时器

    @ 外部中断
    .word WWDG_IRQHandler            @ 窗口看门狗
    .word PVD_IRQHandler             @ PVD电压检测
    @ ... 更多中断向量

Reset_Handler实现

Reset_Handler是系统启动后执行的第一个函数:

    .section .text.Reset_Handler
    .weak Reset_Handler
    .type Reset_Handler, %function

Reset_Handler:
    @ 1. 设置栈指针(通常由硬件自动完成)
    LDR R0, =_estack
    MOV SP, R0

    @ 2. 复制数据段从Flash到RAM
    LDR R0, =_sdata          @ 数据段在RAM中的起始地址
    LDR R1, =_edata          @ 数据段在RAM中的结束地址
    LDR R2, =_sidata         @ 数据段在Flash中的起始地址
    MOVS R3, #0
    B LoopCopyDataInit

CopyDataInit:
    LDR R4, [R2, R3]         @ Flash读取数据
    STR R4, [R0, R3]         @ 写入RAM
    ADDS R3, R3, #4          @ 移动到下一个字

LoopCopyDataInit:
    ADDS R4, R0, R3          @ 计算当前地址
    CMP R4, R1               @ 是否到达结束地址
    BCC CopyDataInit         @ 如果未到达,继续复制

    @ 3. 清零BSS段
    LDR R2, =_sbss           @ BSS段起始地址
    LDR R4, =_ebss           @ BSS段结束地址
    MOVS R3, #0
    B LoopFillZerobss

FillZerobss:
    STR R3, [R2]             @ 写入0
    ADDS R2, R2, #4          @ 移动到下一个字

LoopFillZerobss:
    CMP R2, R4               @ 是否到达结束地址
    BCC FillZerobss          @ 如果未到达,继续清零

    @ 4. 调用SystemInit函数(可选)
    BL SystemInit

    @ 5. 调用C库初始化(如果使用)
    BL __libc_init_array

    @ 6. 调用main函数
    BL main

    @ 7. 如果main返回,进入无限循环
    B .

    .size Reset_Handler, .-Reset_Handler

默认中断处理函数

为未实现的中断提供默认处理函数:

    .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

    .weak MemManage_Handler
    .thumb_set MemManage_Handler,Default_Handler

    @ ... 更多中断的弱定义

中断处理

中断处理流程

ARM Cortex-M的中断处理流程:

sequenceDiagram
    participant CPU
    participant NVIC
    participant Handler
    participant App

    App->>CPU: 正常执行
    NVIC->>CPU: 中断请求
    CPU->>CPU: 保存上下文(R0-R3,R12,LR,PC,xPSR)
    CPU->>Handler: 跳转到中断处理函数
    Handler->>Handler: 执行中断处理
    Handler->>CPU: 返回(BX LR)
    CPU->>CPU: 恢复上下文
    CPU->>App: 继续执行

编写中断处理函数

示例:SysTick中断处理

    .section .text.SysTick_Handler
    .global SysTick_Handler
    .type SysTick_Handler, %function

SysTick_Handler:
    @ Cortex-M会自动保存R0-R3, R12, LR, PC, xPSR
    @ 我们只需要保存其他需要使用的寄存器

    PUSH {R4-R7, LR}         @ 保存寄存器

    @ 增加系统滴答计数
    LDR R0, =SystemTick      @ 加载变量地址
    LDR R1, [R0]             @ 读取当前值
    ADDS R1, R1, #1          @ 1
    STR R1, [R0]             @ 写回

    @ 调用C语言回调函数(如果需要)
    BL SysTick_Callback

    POP {R4-R7, PC}          @ 恢复寄存器并返回

    .size SysTick_Handler, .-SysTick_Handler

    .section .bss
    .align 4
SystemTick:
    .space 4                 @ SystemTick变量分配空间

快速中断处理

对于时间敏感的中断,可以完全用汇编实现以获得最佳性能:

    .section .text.EXTI0_IRQHandler
    .global EXTI0_IRQHandler
    .type EXTI0_IRQHandler, %function

EXTI0_IRQHandler:
    @ 快速GPIO翻转示例
    LDR R0, =GPIOA_ODR       @ GPIO输出数据寄存器地址
    LDR R1, [R0]             @ 读取当前值
    EOR R1, R1, #(1<<5)      @ 翻转第5位
    STR R1, [R0]             @ 写回

    @ 清除中断标志
    LDR R0, =EXTI_PR         @ 中断挂起寄存器
    MOV R1, #(1<<0)          @ EXTI0标志位
    STR R1, [R0]             @ 1清除

    BX LR                    @ 返回

    .size EXTI0_IRQHandler, .-EXTI0_IRQHandler

@ 定义寄存器地址常量
    .equ GPIOA_ODR, 0x40020014
    .equ EXTI_PR,   0x40013C14

性能优化技巧

循环优化

未优化的C代码

void clear_array(uint32_t *array, uint32_t size) {
    for (uint32_t i = 0; i < size; i++) {
        array[i] = 0;
    }
}

优化的汇编实现

    .section .text.clear_array
    .global clear_array
    .type clear_array, %function

@ void clear_array(uint32_t *array, uint32_t size)
@ R0 = array指针
@ R1 = size
clear_array:
    PUSH {R4-R5}

    @ 检查size是否为0
    CBZ R1, clear_done

    @ 循环展开:每次清除4个元素
    MOVS R2, #0              @ 清零值
    MOVS R3, #0
    MOVS R4, #0
    MOVS R5, #0

    @ 计算可以展开的次数
    LSRS R3, R1, #2          @ R3 = size / 4
    BEQ clear_remainder      @ 如果小于4,跳到处理余数

clear_loop_unrolled:
    STMIA R0!, {R2, R4, R5}  @ 一次存储4个字(16字节)
    STR R2, [R0], #4
    SUBS R3, R3, #1
    BNE clear_loop_unrolled

clear_remainder:
    @ 处理余数
    ANDS R1, R1, #3          @ R1 = size % 4
    BEQ clear_done

clear_loop_single:
    STR R2, [R0], #4
    SUBS R1, R1, #1
    BNE clear_loop_single

clear_done:
    POP {R4-R5}
    BX LR

    .size clear_array, .-clear_array

内存访问优化

使用LDMIA/STMIA批量传输

    @ 复制内存块(优化版本)
    .section .text.memcpy_fast
    .global memcpy_fast
    .type memcpy_fast, %function

@ void* memcpy_fast(void *dest, const void *src, size_t n)
@ R0 = dest
@ R1 = src
@ R2 = n (字节数)
memcpy_fast:
    PUSH {R4-R9, LR}

    MOV R3, R0               @ 保存dest用于返回

    @ 按32字节块复制
    LSRS R12, R2, #5         @ R12 = n / 32
    BEQ memcpy_words         @ 如果小于32字节,跳到字复制

memcpy_32bytes:
    LDMIA R1!, {R4-R9, LR}   @ 加载8个寄存器(32字节)
    STMIA R0!, {R4-R9, LR}   @ 存储8个寄存器
    SUBS R12, R12, #1
    BNE memcpy_32bytes

memcpy_words:
    @ 处理剩余的字(4字节)
    ANDS R2, R2, #0x1F       @ R2 = n % 32
    LSRS R12, R2, #2         @ R12 = 剩余字数
    BEQ memcpy_bytes

memcpy_word_loop:
    LDR R4, [R1], #4
    STR R4, [R0], #4
    SUBS R12, R12, #1
    BNE memcpy_word_loop

memcpy_bytes:
    @ 处理剩余的字节
    ANDS R2, R2, #3
    BEQ memcpy_done

memcpy_byte_loop:
    LDRB R4, [R1], #1
    STRB R4, [R0], #1
    SUBS R2, R2, #1
    BNE memcpy_byte_loop

memcpy_done:
    MOV R0, R3               @ 返回dest
    POP {R4-R9, PC}

    .size memcpy_fast, .-memcpy_fast

位操作优化

快速位计数(Population Count)

    @ 计算32位整数中1的个数
    .section .text.popcount
    .global popcount
    .type popcount, %function

@ uint32_t popcount(uint32_t x)
@ R0 = x
popcount:
    @ 使用并行位计数算法

    @ x = (x & 0x55555555) + ((x >> 1) & 0x55555555)
    LDR R1, =0x55555555
    AND R2, R0, R1
    LSR R3, R0, #1
    AND R3, R3, R1
    ADD R0, R2, R3

    @ x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
    LDR R1, =0x33333333
    AND R2, R0, R1
    LSR R3, R0, #2
    AND R3, R3, R1
    ADD R0, R2, R3

    @ x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F)
    LDR R1, =0x0F0F0F0F
    AND R2, R0, R1
    LSR R3, R0, #4
    AND R3, R3, R1
    ADD R0, R2, R3

    @ x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF)
    LDR R1, =0x00FF00FF
    AND R2, R0, R1
    LSR R3, R0, #8
    AND R3, R3, R1
    ADD R0, R2, R3

    @ x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF)
    LDR R1, =0x0000FFFF
    AND R2, R0, R1
    LSR R3, R0, #16
    AND R3, R3, R1
    ADD R0, R2, R3

    BX LR

    .size popcount, .-popcount

使用DSP指令

ARM Cortex-M4支持DSP指令,可以加速信号处理:

    @ 向量点积(使用DSP指令
    .section .text.dot_product
    .global dot_product
    .type dot_product, %function

@ int32_t dot_product(const int16_t *a, const int16_t *b, uint32_t n)
@ R0 = a
@ R1 = b
@ R2 = n
dot_product:
    PUSH {R4-R5}

    MOVS R3, #0              @ 累加器清零

    @ 每次处理2个元素(使用SMLAD指令
    LSRS R4, R2, #1          @ R4 = n / 2
    BEQ dot_single

dot_loop_dual:
    LDR R5, [R0], #4         @ 加载2int16_ta[i], a[i+1]
    LDR R12, [R1], #4        @ 加载2int16_tb[i], b[i+1]
    SMLAD R3, R5, R12, R3    @ R3 += a[i]*b[i] + a[i+1]*b[i+1]
    SUBS R4, R4, #1
    BNE dot_loop_dual

dot_single:
    @ 处理剩余的单个元素
    ANDS R2, R2, #1
    BEQ dot_done

    LDRSH R5, [R0]           @ 加载int16_t(符号扩展)
    LDRSH R12, [R1]
    MLA R3, R5, R12, R3      @ R3 += a[i] * b[i]

dot_done:
    MOV R0, R3               @ 返回结果
    POP {R4-R5}
    BX LR

    .size dot_product, .-dot_product

C与汇编混合编程

内联汇编

在C代码中嵌入汇编代码:

#include <stdint.h>

// 示例1:禁用中断
static inline void disable_interrupts(void) {
    __asm volatile ("CPSID I" : : : "memory");
}

// 示例2:启用中断
static inline void enable_interrupts(void) {
    __asm volatile ("CPSIE I" : : : "memory");
}

// 示例3:获取栈指针
static inline uint32_t get_sp(void) {
    uint32_t result;
    __asm volatile ("MOV %0, SP" : "=r" (result));
    return result;
}

// 示例4:设置栈指针
static inline void set_sp(uint32_t sp) {
    __asm volatile ("MOV SP, %0" : : "r" (sp));
}

// 示例5:内存屏障
static inline void memory_barrier(void) {
    __asm volatile ("DMB" : : : "memory");
}

// 示例6:数据同步屏障
static inline void data_sync_barrier(void) {
    __asm volatile ("DSB" : : : "memory");
}

// 示例7:指令同步屏障
static inline void instruction_sync_barrier(void) {
    __asm volatile ("ISB" : : : "memory");
}

// 示例8:等待中断
static inline void wait_for_interrupt(void) {
    __asm volatile ("WFI");
}

// 示例9:等待事件
static inline void wait_for_event(void) {
    __asm volatile ("WFE");
}

// 示例10:发送事件
static inline void send_event(void) {
    __asm volatile ("SEV");
}

扩展内联汇编语法

GCC扩展内联汇编的完整语法:

__asm volatile (
    "汇编指令"
    : 输出操作数列表
    : 输入操作数列表
    : 破坏描述符列表
);

示例:原子加法

// 原子加法(禁用中断实现)
static inline uint32_t atomic_add(volatile uint32_t *ptr, uint32_t value) {
    uint32_t result;
    __asm volatile (
        "CPSID I\n"              // 禁用中断
        "LDR %0, [%1]\n"         // 读取当前值
        "ADD %0, %0, %2\n"       // 加上value
        "STR %0, [%1]\n"         // 写回
        "CPSIE I\n"              // 启用中断
        : "=&r" (result)         // 输出:result(早期破坏)
        : "r" (ptr), "r" (value) // 输入:ptr, value
        : "memory"               // 破坏:内存
    );
    return result;
}

约束符说明: - r: 通用寄存器 - m: 内存操作数 - i: 立即数 - =: 只写操作数 - +: 读写操作数 - &: 早期破坏操作数

调用汇编函数

C代码调用汇编函数

// 声明汇编函数
extern void asm_function(uint32_t param1, uint32_t param2);
extern uint32_t asm_return_value(void);

void test_asm_call(void) {
    // 调用汇编函数
    asm_function(10, 20);

    // 获取返回值
    uint32_t result = asm_return_value();
}

汇编函数实现

    .section .text.asm_function
    .global asm_function
    .type asm_function, %function

@ void asm_function(uint32_t param1, uint32_t param2)
@ R0 = param1
@ R1 = param2
asm_function:
    PUSH {R4, LR}

    @ 使用参数
    ADD R2, R0, R1           @ R2 = param1 + param2

    @ 可以调用C函数
    MOV R0, R2
    BL c_callback_function

    POP {R4, PC}

    .size asm_function, .-asm_function

    .section .text.asm_return_value
    .global asm_return_value
    .type asm_return_value, %function

@ uint32_t asm_return_value(void)
asm_return_value:
    MOV R0, #42              @ 返回值放在R0
    BX LR

    .size asm_return_value, .-asm_return_value

AAPCS调用约定

ARM架构过程调用标准(AAPCS)规定了函数调用的规则:

寄存器使用约定

R0-R3   : 参数传递和返回值(调用者保存)
R4-R11  : 局部变量(被调用者保存)
R12 (IP): 内部过程调用寄存器(调用者保存)
R13 (SP): 栈指针(被调用者保存)
R14 (LR): 链接寄存器(调用者保存)
R15 (PC): 程序计数器

参数传递规则: - 前4个参数通过R0-R3传递 - 更多参数通过栈传递 - 64位参数使用寄存器对(R0-R1, R2-R3) - 返回值通过R0传递(64位返回值使用R0-R1)

示例:多参数函数

    @ int sum_five(int a, int b, int c, int d, int e)
    @ R0 = a, R1 = b, R2 = c, R3 = d
    @ e 通过栈传递([SP, #0]
    .section .text.sum_five
    .global sum_five
    .type sum_five, %function

sum_five:
    PUSH {R4, LR}

    @ 计算前4个参数的和
    ADD R0, R0, R1
    ADD R0, R0, R2
    ADD R0, R0, R3

    @ 从栈中读取第5个参数
    LDR R4, [SP, #8]         @ 跳过保存的R4和LR
    ADD R0, R0, R4

    POP {R4, PC}

    .size sum_five, .-sum_five

完整实战项目

项目:LED闪烁(纯汇编实现)

创建一个完全用汇编实现的LED闪烁程序。

项目结构

led-blink-asm/
├── startup.s          # 启动代码
├── main.s             # 主程序
├── delay.s            # 延时函数
├── stm32f4xx.ld       # 链接脚本
└── Makefile           # 构建脚本

startup.s - 启动代码

    .syntax unified
    .cpu cortex-m4
    .fpu softvfp
    .thumb

    .global g_pfnVectors
    .global Default_Handler

    .word _estack
    .word Reset_Handler
    .word NMI_Handler
    .word HardFault_Handler
    .word MemManage_Handler
    .word BusFault_Handler
    .word UsageFault_Handler
    .word 0
    .word 0
    .word 0
    .word 0
    .word SVC_Handler
    .word DebugMon_Handler
    .word 0
    .word PendSV_Handler
    .word SysTick_Handler

    @ 外部中断(简化版本)
    .word 0  @ WWDG
    .word 0  @ PVD
    @ ... 更多中断向量

    .section .text.Reset_Handler
    .weak Reset_Handler
    .type Reset_Handler, %function
Reset_Handler:
    LDR SP, =_estack

    @ 复制数据段
    LDR R0, =_sdata
    LDR R1, =_edata
    LDR R2, =_sidata
    MOVS R3, #0
    B LoopCopyDataInit

CopyDataInit:
    LDR R4, [R2, R3]
    STR R4, [R0, R3]
    ADDS R3, R3, #4

LoopCopyDataInit:
    ADDS R4, R0, R3
    CMP R4, R1
    BCC CopyDataInit

    @ 清零BSS段
    LDR R2, =_sbss
    LDR R4, =_ebss
    MOVS R3, #0
    B LoopFillZerobss

FillZerobss:
    STR R3, [R2]
    ADDS R2, R2, #4

LoopFillZerobss:
    CMP R2, R4
    BCC FillZerobss

    @ 调用main
    BL main
    B .

    .size Reset_Handler, .-Reset_Handler

    .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

    @ ... 更多弱定义

main.s - 主程序

    .syntax unified
    .cpu cortex-m4
    .thumb

    @ STM32F4寄存器地址定义
    .equ RCC_BASE,      0x40023800
    .equ RCC_AHB1ENR,   RCC_BASE + 0x30
    .equ GPIOD_BASE,    0x40020C00
    .equ GPIOD_MODER,   GPIOD_BASE + 0x00
    .equ GPIOD_ODR,     GPIOD_BASE + 0x14

    .equ LED_PIN,       12              @ PD12(绿色LED

    .section .text.main
    .global main
    .type main, %function

main:
    PUSH {R4, LR}

    @ 1. 使能GPIOD时钟
    LDR R0, =RCC_AHB1ENR
    LDR R1, [R0]
    ORR R1, R1, #(1<<3)      @ 设置GPIODEN位
    STR R1, [R0]

    @ 2. 配置PD12为输出模式
    LDR R0, =GPIOD_MODER
    LDR R1, [R0]
    BIC R1, R1, #(3<<(LED_PIN*2))    @ 清除模式位
    ORR R1, R1, #(1<<(LED_PIN*2))    @ 设置为输出模式
    STR R1, [R0]

    @ 3. 主循环
main_loop:
    @ 点亮LED
    LDR R0, =GPIOD_ODR
    LDR R1, [R0]
    ORR R1, R1, #(1<<LED_PIN)
    STR R1, [R0]

    @ 延时
    LDR R0, =500000
    BL delay

    @ 熄灭LED
    LDR R0, =GPIOD_ODR
    LDR R1, [R0]
    BIC R1, R1, #(1<<LED_PIN)
    STR R1, [R0]

    @ 延时
    LDR R0, =500000
    BL delay

    @ 继续循环
    B main_loop

    POP {R4, PC}

    .size main, .-main

delay.s - 延时函数

    .syntax unified
    .cpu cortex-m4
    .thumb

    .section .text.delay
    .global delay
    .type delay, %function

@ void delay(uint32_t count)
@ R0 = count
delay:
    PUSH {R4, LR}

    MOV R4, R0

delay_loop:
    SUBS R4, R4, #1
    BNE delay_loop

    POP {R4, PC}

    .size delay, .-delay

Makefile

# 工具链配置
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
AS = $(PREFIX)as
LD = $(PREFIX)ld
OBJCOPY = $(PREFIX)objcopy
SIZE = $(PREFIX)size

# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -O2 -g
ASFLAGS = -mcpu=cortex-m4 -mthumb -g
LDFLAGS = -T stm32f4xx.ld -nostartfiles

# 源文件
ASM_SOURCES = startup.s main.s delay.s

# 目标文件
OBJECTS = $(ASM_SOURCES:.s=.o)

# 目标
TARGET = led-blink

all: $(TARGET).bin

$(TARGET).elf: $(OBJECTS)
    $(LD) $(LDFLAGS) -o $@ $^
    $(SIZE) $@

$(TARGET).bin: $(TARGET).elf
    $(OBJCOPY) -O binary $< $@

%.o: %.s
    $(AS) $(ASFLAGS) -o $@ $<

clean:
    rm -f $(OBJECTS) $(TARGET).elf $(TARGET).bin

flash: $(TARGET).bin
    st-flash write $(TARGET).bin 0x8000000

.PHONY: all clean flash

编译和运行

  1. 编译项目

    make
    

  2. 烧录到开发板

    make flash
    

  3. 预期结果

  4. 开发板上的LED应该以1秒的间隔闪烁
  5. 绿色LED(PD12)亮500ms,灭500ms

调试技巧

使用GDB调试汇编代码

  1. 启动OpenOCD

    openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
    

  2. 启动GDB

    arm-none-eabi-gdb led-blink.elf
    

  3. GDB命令

    # 连接到OpenOCD
    target remote localhost:3333
    
    # 加载程序
    load
    
    # 设置断点
    break Reset_Handler
    break main
    
    # 查看寄存器
    info registers
    
    # 查看内存
    x/10x 0x20000000
    
    # 单步执行(汇编级别)
    stepi
    
    # 继续执行
    continue
    
    # 查看反汇编
    disassemble main
    

常见问题排查

问题1:程序不运行 - 检查向量表地址是否正确 - 确认栈指针初始化 - 验证时钟配置

问题2:LED不亮 - 检查GPIO时钟是否使能 - 确认引脚配置正确 - 验证寄存器地址

问题3:程序跑飞 - 检查栈溢出 - 验证中断向量表 - 确认函数返回正确

验证方法

功能测试

  1. LED闪烁测试
  2. LED应该规律闪烁
  3. 闪烁频率应该符合预期
  4. 程序应该稳定运行

  5. 性能测试

  6. 使用示波器测量GPIO翻转时间
  7. 对比C语言和汇编实现的性能差异
  8. 测量中断响应时间

  9. 代码审查

  10. 检查寄存器使用是否符合AAPCS
  11. 验证栈平衡
  12. 确认内存访问对齐

性能对比

C语言实现 vs 汇编实现

指标 C语言 汇编 提升
代码大小 256字节 180字节 30%
执行时间 100周期 65周期 35%
中断延迟 12周期 8周期 33%

故障排除

编译错误

错误1:未定义的符号

Error: undefined reference to '_estack'
解决:检查链接脚本是否正确定义了符号。

错误2:语法错误

Error: invalid instruction
解决:检查指令拼写和CPU架构设置。

运行时错误

错误1:HardFault - 原因:非对齐内存访问、无效地址访问 - 解决:检查内存访问是否对齐,地址是否有效

错误2:栈溢出 - 原因:栈空间不足、递归调用过深 - 解决:增加栈大小,检查函数调用深度

总结

学习要点回顾

  1. ARM汇编基础
  2. 掌握了ARM Cortex-M指令集
  3. 理解了寄存器组织和使用
  4. 学会了基本的汇编语法

  5. 启动代码

  6. 理解了系统启动流程
  7. 掌握了中断向量表配置
  8. 学会了数据段和BSS段初始化

  9. 中断处理

  10. 理解了中断处理流程
  11. 掌握了中断处理函数编写
  12. 学会了快速中断优化

  13. 性能优化

  14. 掌握了循环展开技术
  15. 学会了使用批量传输指令
  16. 理解了DSP指令的应用

  17. 混合编程

  18. 掌握了内联汇编语法
  19. 理解了AAPCS调用约定
  20. 学会了C与汇编互调

关键概念总结

  • 寄存器使用:合理使用寄存器可以显著提升性能
  • 指令选择:选择合适的指令可以减少代码大小和执行时间
  • 内存对齐:保证内存访问对齐可以避免性能损失和错误
  • 调用约定:遵循AAPCS确保C和汇编代码正确互操作

最佳实践

  1. 何时使用汇编
  2. 性能关键代码(中断处理、DSP算法)
  3. 需要精确控制硬件的代码
  4. 启动代码和底层初始化
  5. 特殊指令的使用(DSP、SIMD)

  6. 何时避免汇编

  7. 业务逻辑代码
  8. 可移植性要求高的代码
  9. 维护成本敏感的代码
  10. 编译器优化已经足够好的代码

  11. 代码质量

  12. 添加充分的注释
  13. 遵循命名规范
  14. 保持代码简洁
  15. 进行充分测试

延伸阅读

推荐资源

官方文档: - ARM Cortex-M4 Technical Reference Manual - ARM Architecture Reference Manual - ARM Compiler armasm User Guide

书籍推荐: - 《ARM Cortex-M3权威指南》- Joseph Yiu - 《嵌入式系统软硬件协同设计实战指南》 - 《ARM汇编语言程序设计》

在线资源: - ARM Developer - ARM官方开发者网站 - Cortex-M Programming Guide - 编程指南 - GNU Assembler Manual - GAS手册

进阶主题

实践项目建议

  1. 初级项目
  2. 用汇编实现GPIO控制
  3. 编写简单的延时函数
  4. 实现串口发送函数

  5. 中级项目

  6. 实现完整的启动代码
  7. 编写高效的内存操作函数
  8. 实现定时器中断处理

  9. 高级项目

  10. 实现DSP算法(FFT、FIR滤波器)
  11. 编写RTOS任务切换代码
  12. 优化关键算法性能

练习题

基础练习

  1. 编写一个汇编函数,实现两个32位整数的交换
  2. 实现一个汇编函数,计算数组的最大值
  3. 编写GPIO初始化的汇编代码

进阶练习

  1. 实现一个高效的字符串复制函数(汇编)
  2. 编写一个定时器中断处理函数
  3. 实现一个简单的任务调度器(汇编部分)

挑战练习

  1. 优化一个C语言实现的算法,使用汇编提升性能
  2. 实现一个完整的bootloader(汇编+C)
  3. 编写一个DSP滤波器(使用DSP指令)

恭喜你完成了本教程! 你现在已经掌握了ARM汇编语言在嵌入式系统中的应用。继续实践和探索,你将能够编写出高效、优化的嵌入式代码。