嵌入式系统基础:MCU选型、RTOS基础、外设驱动开发

好,咱们进入第二章。说实话,很多做可穿戴设备的朋友一上来就追着算法跑,结果硬件选型就翻车了。我见过不止一个项目,因为MCU选型没想清楚,后面功耗压不下去,或者外设驱动死活调不通,最后整个方案推倒重来。这一章,咱们就把地基打牢。

2.1 MCU选型:别只看主频和Flash

选MCU,说白了就是一场权衡。可穿戴设备空间小、电池也小,你想想看,一颗Cortex-M4F的芯片,主频跑到200MHz,听起来很爽,但功耗可能直接飙到几十毫安。这在手环上根本扛不住。

我个人习惯,先列三个硬指标:

  • 功耗曲线:看工作模式、睡眠模式、深度睡眠模式的电流。比如STM32L系列,深度睡眠能做到1μA以下,这对需要常亮显示的心率手环就很关键。
  • 外设集成度:可穿戴设备需要什么?I2C接传感器、SPI接屏幕、QSPI接Flash。如果MCU自带LCD控制器或者DMA,能省掉一颗外部芯片。
  • 安全特性:这是咱们课程的核心。有没有硬件加密引擎?有没有安全启动?有没有独立的OTP区域存密钥?

重要提醒:别只看数据手册上的“典型值”。我在项目中遇到过,某款国产MCU标称深度睡眠1.5μA,结果实际测出来是8μA,因为它的RTC模块没关干净。所以,选型阶段一定要拿开发板实测。

举个例子,Nordic的nRF52840,它集成了Cortex-M4F、BLE 5.0、2.4G射频,还有ARM TrustZone。很多高端手环都用它。但如果你只是做一个简单的计步器,用一颗Cortex-M0+的芯片,比如STM32G0,成本能降一半,功耗也更低。

2.2 RTOS基础:别让任务饿死

裸机编程在简单场景下够用,但可穿戴设备往往要同时处理传感器采集、蓝牙通信、UI刷新、电源管理。这时候,RTOS就是必需品。

我建议初学者从FreeRTOS入手。它开源、轻量、资料多。核心概念就三个:任务、队列、信号量。

  • 任务:每个功能一个任务。比如传感器任务、显示任务、通信任务。注意优先级别乱设,我曾经把显示任务优先级设得比通信高,结果蓝牙一直断连。
  • 队列:任务间传数据。传感器任务采集到心率数据,通过队列发给显示任务。注意队列长度要够,否则数据会丢。
  • 信号量:同步用的。比如I2C总线是共享资源,两个任务不能同时访问,用互斥信号量保护。

小技巧:在FreeRTOS里,任务栈大小要留余量。我习惯先设一个较大的值,跑起来后用uxTaskGetStackHighWaterMark()函数查看实际使用量,再调小。这样既省内存,又不会栈溢出。

看一个简单的任务创建代码:

void sensor_task(void *pvParameters) {
    while(1) {
        // 读取加速度计数据
        accel_data_t data = read_accel();
        // 发送到队列
        xQueueSend(accel_queue, &data, portMAX_DELAY);
        // 每10ms采集一次
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void display_task(void *pvParameters) {
    accel_data_t received_data;
    while(1) {
        // 等待队列数据
        if(xQueueReceive(accel_queue, &received_data, portMAX_DELAY)) {
            // 更新屏幕显示
            update_display(&received_data);
        }
    }
}

嗯,这里要注意:vTaskDelay()的精度取决于系统时钟节拍。如果节拍是1ms,那延迟10ms就是10个tick。但如果你的RTOS节拍是10ms,那最小延迟就是10ms。所以,对时间敏感的任务,节拍要设细一点。

2.3 外设驱动开发:别信“即插即用”

外设驱动,说白了就是让MCU和外部芯片“说上话”。可穿戴设备里最常见的就是I2C和SPI。

I2C驱动:接传感器。比如心率传感器MAX30102、加速度计LIS3DH。I2C只有两根线,SDA和SCL。但坑很多。

  • 上拉电阻:I2C总线需要外部上拉。我见过有人忘了焊上拉电阻,结果通信时好时坏。一般4.7kΩ就够。
  • 地址冲突:两个传感器用了同一个I2C地址。解决办法是看数据手册,有些芯片可以通过引脚改变地址。
  • 时钟延展:从设备忙的时候会拉低SCL,主设备要等。如果主设备没做超时处理,可能死锁。

避坑指南:我曾经在调试一款心率传感器时,发现读回来的数据全是0xFF。查了两天,最后发现是I2C的时钟频率设成了400kHz,而那颗传感器只支持100kHz。降速后一切正常。所以,外设驱动开发的第一步,永远是仔细看数据手册的时序图。

SPI驱动:接屏幕或Flash。SPI比I2C快,但线多:SCK、MOSI、MISO、CS。注意片选信号要正确拉低拉高。

看一个SPI写寄存器的例子:

void spi_write_reg(uint8_t reg, uint8_t data) {
    // 拉低片选
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
    // 发送寄存器地址(写操作,最高位置0)
    HAL_SPI_Transmit(&hspi1, ®, 1, HAL_MAX_DELAY);
    // 发送数据
    HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
    // 拉高片选
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}

这里有个细节:很多SPI设备在片选拉高后才会解析命令。所以片选操作一定要干净利落,中间别插中断。我习惯在片选操作前后关中断,或者用DMA传输。

2.4 电源管理与低功耗设计

可穿戴设备最怕什么?一天一充。所以电源管理是重中之重。

MCU的功耗模式一般分几种:

模式 典型电流 唤醒源 适用场景
运行模式 几mA ~ 几十mA 采集数据、处理算法
睡眠模式 几百μA 定时器、外部中断 等待传感器数据
深度睡眠 几μA RTC、外部中断 待机、夜间

我建议的做法是:大部分时间让MCU待在深度睡眠,用RTC定时唤醒。比如每100ms醒来一次,采集传感器数据,处理完再睡回去。这样平均功耗能降到几十μA。

个人经验:别忽略外设的功耗。有些传感器在睡眠模式下也有几十μA的漏电流。我习惯在不用的时候,用MCU的GPIO直接切断传感器的电源。对,就是加一个MOS管做电源开关。虽然多了一个元件,但省下的功耗很可观。

另外,时钟频率也影响功耗。跑64MHz和跑16MHz,功耗差好几倍。所以,在不需要高性能的时候,动态降频是个好办法。很多MCU支持在运行时切换时钟源,比如从PLL切到内部RC振荡器。

好,这一章就到这里。MCU选型、RTOS基础、外设驱动、低功耗设计,这四个点你吃透了,嵌入式系统的基础就算打牢了。下一章咱们开始聊安全启动和固件加密,那才是真正有意思的地方。