第二章 嵌入式系统基础:嵌入式处理器选型与操作系统

大家好,我是你们这门课的主讲。今天咱们聊聊嵌入式系统里最基础、也最绕不开的几个话题——处理器怎么选、操作系统怎么搭、硬件抽象层怎么设计。

说实话,我刚开始做嵌入式那会儿,也踩过不少坑。选型选错了,后面整个项目都得重来。所以这一章,我把自己这些年积累的经验,掰开了揉碎了讲给你听。

2.1 ARM Cortex-M/R/A系列:三兄弟各有所长

ARM Cortex系列,说白了就是三兄弟:M系列、R系列、A系列。它们虽然都姓ARM,但脾气秉性完全不同。

Cortex-M系列,主打低功耗、低成本。你想想看,智能手表、传感器节点、电机控制,这些对功耗敏感的场景,M系列是首选。我做过一个智能水表项目,用的就是Cortex-M4,电池撑了两年多没换。

Cortex-R系列,实时性要求高的场合。比如汽车刹车系统、工业机器人控制。R系列有硬件实时响应能力,中断延迟极低。嗯,这里要注意,R系列不是跑操作系统的首选,它更适合裸机或RTOS。

Cortex-A系列,性能怪兽。跑Linux、Android都没问题。手机、平板、路由器,都是A系列的天下。我有个朋友做智能摄像头,一开始想用M系列,结果发现视频编解码根本跑不动,最后换了A系列才搞定。

系列 典型应用 功耗 性能 典型主频
Cortex-M 传感器、电机控制、IoT终端 极低 中等 几十MHz~几百MHz
Cortex-R 汽车电子、工业控制 较低 较高 几百MHz~1GHz
Cortex-A 手机、平板、网关 较高 1GHz~3GHz

选型口诀:M系列省电,R系列实时,A系列性能。别搞混了。

2.2 嵌入式操作系统:FreeRTOS vs uCOS-II

操作系统这块,我个人的习惯是:小项目用FreeRTOS,大项目或商业产品用uCOS-II。为什么?

FreeRTOS,开源免费,社区活跃。你随便搜个STM32的例程,十有八九都是FreeRTOS。它的任务调度、信号量、消息队列,该有的都有。我做过一个智能家居网关,跑了8个任务,FreeRTOS稳得很。

uCOS-II,商业授权,代码质量极高。它的内核设计非常严谨,任务切换时间可预测。我在军工项目里用过uCOS-II,客户要求必须通过安全认证,uCOS-II有DO-178B认证,FreeRTOS就没有。

举个例子,任务创建在FreeRTOS里是这样的:

// FreeRTOS 任务创建
xTaskCreate(
    vTaskFunction,    // 任务函数
    "Task1",          // 任务名称
    128,              // 栈大小(字)
    NULL,             // 参数
    1,                // 优先级
    NULL              // 任务句柄
);

而在uCOS-II里,写法略有不同:

// uCOS-II 任务创建
OSTaskCreate(
    (void (*)(void *))TaskFunction,  // 任务函数
    (void *)0,                       // 参数
    (OS_STK *)&TaskStk[TaskStkSize-1], // 栈顶指针
    5                                // 优先级
);

我的建议:如果你刚开始学嵌入式,先从FreeRTOS入手。等理解了任务调度、中断管理这些概念,再去看uCOS-II的源码,会有更深的理解。

2.3 硬件抽象层设计:让代码跑在任意芯片上

硬件抽象层,英文叫HAL(Hardware Abstraction Layer)。说白了,就是给上层软件一个统一的接口,底层硬件怎么变,上层代码不用改。

我曾经接手过一个项目,原来用的是STM32F103,后来要换成GD32F103。如果没有HAL层,那得把所有GPIO、UART、SPI的代码全部重写。但有了HAL层,只需要改底层驱动,上层逻辑纹丝不动。

HAL层设计的原则很简单:

  • 接口统一:比如所有GPIO的初始化,都叫HAL_GPIO_Init()
  • 参数抽象:用枚举或结构体,不要直接传寄存器地址
  • 错误处理:每个函数返回状态码,方便调试

举个例子,一个简单的HAL层GPIO接口:

// hal_gpio.h
typedef enum {
    GPIO_MODE_INPUT,
    GPIO_MODE_OUTPUT,
    GPIO_MODE_AF,       // 复用功能
    GPIO_MODE_ANALOG
} GPIO_Mode_t;

typedef struct {
    uint8_t pin;
    GPIO_Mode_t mode;
    uint8_t pull_up_down;  // 0:无, 1:上拉, 2:下拉
} GPIO_Config_t;

// 统一的初始化接口
int32_t HAL_GPIO_Init(GPIO_Config_t *config);

底层实现时,再根据具体芯片去写:

// hal_gpio_stm32.c
int32_t HAL_GPIO_Init(GPIO_Config_t *config) {
    // STM32 特有的寄存器操作
    RCC->AHB1ENR |= (1 << 0);  // 使能GPIOA时钟
    GPIOA->MODER &= ~(3 << (config->pin * 2));
    GPIOA->MODER |= (config->mode << (config->pin * 2));
    return 0;
}

避坑指南:我曾经在HAL层里直接用了芯片特有的寄存器名字,结果换芯片时改得想哭。记住,HAL层接口里不要出现任何芯片相关的宏或寄存器名,全部封装在底层实现里。

2.4 选型实战:一个智能温控器的故事

讲个真实案例吧。去年我帮朋友设计一个智能温控器,需求是这样的:

  • 采集温度传感器数据(DS18B20)
  • 控制继电器开关加热器
  • 通过WiFi上传数据到云端
  • 电池供电,要求续航6个月以上

处理器选型时,我首先排除了Cortex-A系列——功耗太高,电池扛不住。Cortex-R系列又太贵,杀鸡用牛刀。最后选了Cortex-M4,具体型号是STM32L4系列,低功耗模式能到几十微安。

操作系统呢?因为任务不多(采集、控制、通信),用FreeRTOS就够了。任务优先级这样分配:

  1. 最高优先级:温度采集(每5秒一次)
  2. 中等优先级:WiFi通信(非实时)
  3. 最低优先级:继电器控制(可延迟)

硬件抽象层,我设计了三个模块:hal_sensorhal_relayhal_wifi。后来客户想换成国产芯片,我只改了底层驱动,上层代码一行没动。

总结一下:选型不是越贵越好,也不是越便宜越好。关键是匹配需求。M系列+FreeRTOS+HAL层,是绝大多数嵌入式项目的黄金组合。

好了,这一章就聊到这儿。下一章咱们深入讲讲中断管理和任务调度,那才是RTOS的核心玩法。


公众号:蓝海资料掘金营,微信deep3321