跳转至

嵌入式软件开发

概述

嵌入式软件运行在资源受限的硬件上,与硬件紧密耦合。本文涵盖裸机编程、RTOS、硬件抽象层、Bootloader、固件更新和调试技术。

相关内容:嵌入式系统


1. 裸机编程(Bare-Metal Programming)

1.1 寄存器级操作

嵌入式编程常常直接操作硬件寄存器:

// STM32 GPIO 寄存器级操作
#define GPIOA_BASE      0x40020000
#define GPIOA_MODER     (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_ODR       (*(volatile uint32_t *)(GPIOA_BASE + 0x14))

// 设置 PA5 为输出模式
GPIOA_MODER &= ~(3 << (5 * 2));  // 清除位
GPIOA_MODER |=  (1 << (5 * 2));  // 设置为输出 (01)

// 点亮 LED (PA5)
GPIOA_ODR |= (1 << 5);   // 置高
GPIOA_ODR &= ~(1 << 5);  // 置低

volatile 关键字:告诉编译器该变量可能被外部(硬件、中断)修改,禁止优化。

1.2 启动代码(Startup Code)

MCU 上电后执行的第一段代码:

1. 中断向量表(Vector Table)
   - 初始栈指针 (SP)
   - Reset Handler 地址
   - 各中断处理函数地址

2. Reset Handler:
   a. 初始化 .data 段(从 Flash 复制到 RAM)
   b. 清零 .bss 段
   c. 初始化系统时钟
   d. 调用 main()
// 简化的启动代码
extern uint32_t _sdata, _edata, _sidata;  // 链接器脚本定义
extern uint32_t _sbss, _ebss;

void Reset_Handler(void) {
    // 复制 .data 段
    uint32_t *src = &_sidata;
    uint32_t *dst = &_sdata;
    while (dst < &_edata) *dst++ = *src++;

    // 清零 .bss 段
    dst = &_sbss;
    while (dst < &_ebss) *dst++ = 0;

    // 初始化系统
    SystemInit();

    // 进入 main
    main();

    // main 不应返回
    while (1);
}

1.3 中断处理

// 中断服务例程 (ISR)
void EXTI0_IRQHandler(void) {
    if (EXTI->PR & (1 << 0)) {  // 检查中断标志
        // 处理中断
        button_pressed = 1;
        EXTI->PR = (1 << 0);    // 清除中断标志
    }
}

// 中断优先级配置 (NVIC)
NVIC_SetPriority(EXTI0_IRQn, 2);  // 优先级 2
NVIC_EnableIRQ(EXTI0_IRQn);        // 使能中断

ISR 编写原则:

  • 尽可能短:只做标志设置,复杂处理放到主循环
  • 不要使用阻塞操作:不调用 printfmallocsleep
  • 注意重入:共享变量用 volatile 修饰

2. 实时操作系统(RTOS)

2.1 FreeRTOS

FreeRTOS 是最流行的嵌入式 RTOS,提供任务调度、同步和通信原语。

任务创建

void vLEDTask(void *pvParameters) {
    while (1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        vTaskDelay(pdMS_TO_TICKS(500));  // 延迟 500ms
    }
}

void vSensorTask(void *pvParameters) {
    while (1) {
        float temp = read_temperature();
        xQueueSend(xTempQueue, &temp, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main(void) {
    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, NULL);
    xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, NULL);
    vTaskStartScheduler();  // 启动调度器,不返回
}

队列(Queue)

QueueHandle_t xTempQueue = xQueueCreate(10, sizeof(float));

// 发送
float temp = 25.5;
xQueueSend(xTempQueue, &temp, pdMS_TO_TICKS(100));

// 接收
float received;
if (xQueueReceive(xTempQueue, &received, pdMS_TO_TICKS(1000)) == pdTRUE) {
    process_temperature(received);
}

信号量(Semaphore)

// 二值信号量:用于中断-任务同步
SemaphoreHandle_t xBinarySem = xSemaphoreCreateBinary();

// ISR 中释放
void UART_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xBinarySem, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// 任务中等待
void vUARTTask(void *pvParameters) {
    while (1) {
        xSemaphoreTake(xBinarySem, portMAX_DELAY);
        process_uart_data();
    }
}

互斥量(Mutex)

SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

void safe_write(const char *msg) {
    xSemaphoreTake(xMutex, portMAX_DELAY);
    uart_send(msg);  // 临界区
    xSemaphoreGive(xMutex);
}

2.2 Zephyr RTOS

Zephyr 是 Linux 基金会支持的现代嵌入式 RTOS:

  • 支持 200+ 开发板
  • 完整的网络栈(BLE、WiFi、Thread、LTE)
  • 设备树(Devicetree)描述硬件
  • Kconfig 配置系统
  • 内置 POSIX 兼容层

3. 硬件抽象层(HAL)

HAL 将硬件操作封装为统一接口,提高代码可移植性:

// HAL 接口定义
typedef struct {
    void (*init)(void);
    void (*write)(uint8_t *data, uint16_t len);
    uint16_t (*read)(uint8_t *buf, uint16_t max_len);
} uart_driver_t;

// STM32 实现
void stm32_uart_init(void) { /* ... */ }
void stm32_uart_write(uint8_t *data, uint16_t len) { /* ... */ }

uart_driver_t stm32_uart = {
    .init = stm32_uart_init,
    .write = stm32_uart_write,
    .read = stm32_uart_read,
};

// 应用层使用统一接口
void app_send_message(uart_driver_t *uart, const char *msg) {
    uart->write((uint8_t *)msg, strlen(msg));
}

4. Bootloader

Bootloader 是系统上电后运行的第一个程序,负责初始化硬件和加载应用固件。

Flash 布局:
┌─────────────────┐ 0x08000000
│   Bootloader    │ 16-64KB
├─────────────────┤
│  App Header     │ 元数据(版本、CRC、大小)
├─────────────────┤
│  Application    │ 主应用固件
├─────────────────┤
│  OTA Staging    │ 新固件暂存区(可选)
└─────────────────┘

Bootloader 启动流程:

1. 硬件初始化(时钟、GPIO)
2. 检查是否有固件更新请求
   ├── 有 → 从暂存区复制新固件到应用区,验证 CRC
   └── 无 → 继续
3. 验证应用固件完整性(CRC/签名)
   ├── 通过 → 跳转到应用
   └── 失败 → 等待重新刷写
4. 设置栈指针,跳转到应用的 Reset Handler

5. 固件更新(OTA)

OTA(Over-The-Air)允许远程更新设备固件。

5.1 更新策略

策略 说明 安全性
A/B 分区 两个固件分区交替使用 高(可回滚)
差分更新 只传输变更部分 中(节省带宽)
压缩更新 压缩完整固件

5.2 A/B 分区更新流程

1. 设备从 分区A 运行当前固件
2. 下载新固件到 分区B
3. 验证 分区B 的完整性(CRC + 签名)
4. 标记 分区B 为待启动
5. 重启 → Bootloader 加载 分区B
6. 新固件自检
   ├── 成功 → 确认,分区B 成为活动分区
   └── 失败 → 回滚到 分区A

5.3 安全考虑

  • 签名验证:使用 RSA/ECDSA 验证固件来源
  • 加密传输:TLS 加密下载通道
  • 回滚保护:版本号单调递增,防止降级攻击
  • 安全启动链:从 Bootloader 到应用逐级验证

6. 交叉编译

嵌入式开发通常在 PC 上编译,在目标硬件上运行。

# ARM 交叉编译工具链
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard \
    -Os -Wall -ffunction-sections -fdata-sections \
    -c main.c -o main.o

# 链接
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb \
    -T linker_script.ld -Wl,--gc-sections \
    main.o -o firmware.elf

# 生成 bin/hex 文件
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

# 查看大小
arm-none-eabi-size firmware.elf
#    text    data     bss     dec     hex filename
#   12456     128    2048   14632    3928 firmware.elf

CMake 交叉编译配置

# toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

7. 调试

7.1 JTAG/SWD

接口 引脚数 速度 说明
JTAG 4-5 传统接口,功能完整
SWD 2 ARM 专用,引脚少
UART 2 调试打印,简单

7.2 GDB 远程调试

# 启动 OpenOCD(连接调试器)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

# 另一个终端启动 GDB
arm-none-eabi-gdb firmware.elf
(gdb) target remote :3333     # 连接 OpenOCD
(gdb) monitor reset halt       # 复位并暂停
(gdb) load                     # 下载固件
(gdb) break main               # 设置断点
(gdb) continue                 # 运行
(gdb) print sensor_value       # 查看变量
(gdb) x/16xw 0x40020000       # 查看内存/寄存器

7.3 常用调试技术

技术 说明 适用场景
断点调试 JTAG/SWD + GDB 逻辑问题
串口打印 UART printf 快速调试
逻辑分析仪 捕获数字信号波形 时序问题
示波器 观察模拟/数字信号 电气问题
GPIO 翻转 用示波器测量代码执行时间 性能分析
硬件断点 片上调试单元(DWT) 数据访问监控

8. 嵌入式开发最佳实践

实践 说明
防御性编程 检查所有输入、返回值、指针有效性
看门狗 使用 WDT 检测死机并自动复位
内存管理 避免动态分配,使用静态分配或内存池
低功耗 使用睡眠模式,按需唤醒外设
代码审查 嵌入式 bug 修复成本高,预防为主
单元测试 在 PC 上测试与硬件无关的逻辑

与其他主题的关系

参考文献

  • "Making Embedded Systems" - Elecia White
  • "Real-Time Operating Systems for ARM Cortex-M" - Jonathan Valvano
  • FreeRTOS 官方文档:https://freertos.org
  • Zephyr 官方文档:https://docs.zephyrproject.org

评论 #