第2章:嵌入式系统基础回顾:MCU选型、RTOS任务调度与中断管理
各位同学,咱们直接进入正题。做除颤仪这种医疗设备,MCU选型可不是随便挑个便宜的完事。我当年刚入行时,就吃过选型太激进的亏——芯片算力够了,但外设资源不够,最后不得不改板,那叫一个痛苦。
2.1 MCU选型:STM32与ARM Cortex的实战考量
除颤仪的核心MCU,我个人习惯首选ARM Cortex-M4或M7内核。为什么?因为除颤仪需要同时处理高压充放电控制、心电信号采集、实时波形显示、通信协议栈,还得跑个RTOS。M3有时候真扛不住。
具体到型号,STM32F4或H7系列是常见选择。我列个表,大家对比一下:
| 需求 | STM32F4 (M4) | STM32H7 (M7) | 我的建议 |
|---|---|---|---|
| 主频 | 168-180 MHz | 400-480 MHz | H7更适合多任务 |
| SRAM | 192 KB - 1 MB | 1 MB+ | 除颤仪波形缓存至少512KB |
| ADC精度 | 12位 | 16位 | 心电采集建议16位 |
| 定时器 | 14个 | 22个 | 高压时序控制需要多个 |
选型时还要注意:通信外设数量。除颤仪通常需要:
- 2路UART(一路接蓝牙/WiFi模块,一路接PC调试)
- 1路SPI(接SD卡或外部Flash)
- 1路I2C(接传感器或EEPROM)
- 1路CAN(如果接医院中央监护系统)
你想想看,如果选个STM32F103,只有3个UART,用起来就捉襟见肘了。我建议至少选F4系列起步。
2.2 FreeRTOS任务调度:别让任务饿死
除颤仪这种设备,任务优先级设计不好,会出人命。真的,不开玩笑。
我一般把任务分成三个等级:
- 紧急任务(最高优先级):高压放电控制、心电R波检测。这些任务必须在微秒级响应。
- 实时任务(中等优先级):波形数据采集、通信协议处理。毫秒级响应即可。
- 后台任务(低优先级):日志记录、自检、界面刷新。几十毫秒甚至秒级都行。
FreeRTOS的任务调度,我习惯用抢占式调度。配置如下:
// FreeRTOSConfig.h 关键配置
#define configUSE_PREEMPTION 1 // 抢占式调度
#define configUSE_TIME_SLICING 1 // 时间片轮转
#define configMAX_PRIORITIES 5 // 5个优先级足够
// 任务创建示例
xTaskCreate(vHighVoltageTask, "HV", 256, NULL, 4, NULL); // 优先级4
xTaskCreate(vCommTask, "COM", 512, NULL, 2, NULL); // 优先级2
xTaskCreate(vLogTask, "LOG", 128, NULL, 1, NULL); // 优先级1
这里要注意:任务栈大小。通信协议栈因为要处理数据包,栈需求大。我一般给512字节,如果用到printf调试,得给到1024。否则会栈溢出,系统莫名其妙重启。
2.3 中断管理:通信的命脉
在除颤仪通信中,中断是灵魂。为什么?因为通信数据随时可能来,你不能让CPU轮询等待——那会耽误高压控制。
我常用的中断分配方案:
| 中断源 | 优先级 | 处理内容 | 中断时间要求 |
|---|---|---|---|
| 定时器中断(高压PWM) | 最高(0) | 充放电时序控制 | < 1μs |
| UART接收中断 | 高(1) | 接收通信数据包 | < 10μs |
| ADC转换完成中断 | 中(2) | 读取心电数据 | < 50μs |
| 外部中断(按键等) | 低(3) | 用户交互 | < 1ms |
举个例子,UART接收中断的正确写法:
// UART中断服务函数 - 只做数据接收
void USART1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint8_t data;
if(LL_USART_IsActiveFlag_RXNE(USART1))
{
data = LL_USART_ReceiveData8(USART1);
// 把数据放入队列,通知通信任务处理
xQueueSendFromISR(xCommQueue, &data, &xHigherPriorityTaskWoken);
}
// 如果通信任务优先级更高,立即切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 通信任务 - 在任务上下文中解析协议
void vCommTask(void *pvParameters)
{
uint8_t rxData;
while(1)
{
if(xQueueReceive(xCommQueue, &rxData, portMAX_DELAY) == pdTRUE)
{
// 在这里解析协议包,可以放心使用延时、打印等操作
ProcessProtocolData(rxData);
}
}
}
这种设计的好处是:中断响应极快,不会阻塞其他紧急中断。通信任务在低优先级运行,不影响高压控制。
2.4 中断与RTOS的协同:别让优先级反转搞死你
嗯,这里有个坑。当RTOS任务和中断共享资源时,会出现优先级反转。比如:
- 低优先级的日志任务占用了互斥锁
- 高优先级的通信任务想用同一个锁,被阻塞
- 中优先级的界面刷新任务趁机运行,导致高优先级任务迟迟得不到锁
我建议的做法:中断和任务之间尽量用队列通信,不要用互斥锁。队列是FreeRTOS里最安全的中断-任务通信方式。
最后总结一下:MCU选型要留余量,RTOS任务要分优先级,中断处理要短平快。这三条做好了,除颤仪的通信协议栈才能稳定运行。下一章我们开始讲具体的协议设计——嗯,那才是真正有意思的部分。