嵌入式系统基础: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基础、外设驱动、低功耗设计,这四个点你吃透了,嵌入式系统的基础就算打牢了。下一章咱们开始聊安全启动和固件加密,那才是真正有意思的地方。