3、MCU选型与启动流程:主流医疗级MCU对比、启动文件分析、时钟树配置、堆栈设置
好,咱们进入第三章。这一章我打算聊聊MCU选型和启动流程。说实话,这是整个OTA设计的地基。地基没打好,后面再漂亮的升级方案都是空中楼阁。我在医疗贴片项目里吃过这个亏,所以今天把经验掰开揉碎了讲给你听。
3.1 主流医疗级MCU对比:STM32、NXP、TI
医疗贴片对MCU的要求很明确:低功耗、高可靠性、小封装、还得有足够的安全特性。市面上主流的就这三家——ST、NXP、TI。我三个平台都用过,各有各的脾气。
| 特性 | STM32 (如L4/U5系列) | NXP (如LPC55xx/K32W) | TI (如MSP430/CC26xx) |
|---|---|---|---|
| 功耗 | 优秀,有超低功耗模式 | 良好,动态功耗控制不错 | 极低,MSP430是标杆 |
| 安全特性 | 硬件加密、TrustZone (U5) | 内置PUF、安全启动 | AES加速器、安全密钥存储 |
| OTA支持 | 双Bank Flash,方便 | 支持双区,但需注意映射 | Flash较小,需外扩 |
| 生态系统 | 最丰富,HAL/LL库成熟 | MCUXpresso,上手略复杂 | CCS,资料相对少 |
| 典型封装 | BGA、QFN,适合贴片 | QFN、WLCSP | QFN、TSSOP |
我个人习惯:如果项目对功耗要求极其苛刻,比如纽扣电池用一年,我会优先考虑TI的MSP430。但如果你需要跑复杂的OTA协议栈,比如带差分升级、加密校验,那STM32的生态能帮你省不少时间。NXP的LPC55系列在安全启动方面做得很好,适合需要通过FDA认证的医疗设备。
3.2 启动文件分析:从Reset到main()
启动文件,说白了就是MCU上电后执行的第一段代码。很多工程师觉得这是芯片厂商写好的,不用管。但做OTA时,你不得不跟它打交道——因为你要修改中断向量表,要管理启动地址。
以STM32为例,启动文件(startup_stm32l4xx.s)主要干了这几件事:
- 设置堆栈指针:从向量表的第一个字加载SP值
- 复位处理:调用SystemInit()初始化时钟
- 数据段搬运:把RW段从Flash搬到RAM
- BSS段清零:把未初始化的全局变量清零
- 跳转到main():调用C库的__main,最终进入你的代码
你想想看,如果OTA升级后,新的固件放在不同的Flash地址,那向量表的位置就变了。这时候你需要手动修改SCB->VTOR寄存器。我见过有人直接在启动文件里硬编码向量表地址,结果升级一次改一次,太痛苦了。
OTA关键点:启动文件中的向量表偏移必须在系统启动早期设置。我建议在SystemInit()函数里,根据当前运行的固件分区,动态计算VTOR的值。这样无论固件在Bank0还是Bank1,都能正确响应中断。
3.3 时钟树配置:别让时钟成为瓶颈
时钟树配置,嗯,这是个容易翻车的地方。医疗贴片对时钟精度有要求——比如心电采样需要精确的时基,OTA通信需要稳定的波特率。
我一般遵循这个原则:
- 系统时钟:用外部晶振,精度高。贴片空间有限,我常用8MHz或16MHz的晶振
- RTC时钟:用32.768kHz的晶振,或者内部低速RC。但内部RC温漂大,做长时间计时会不准
- 外设时钟:能关就关,省电。OTA升级时才打开必要的通信外设
举个例子,STM32L4的时钟树配置代码:
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置HSE为8MHz,PLL倍频到80MHz
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置AHB、APB分频
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
}
3.4 堆栈设置:别让堆栈溢出毁了你的OTA
堆栈设置,看似简单,实则暗藏杀机。医疗贴片的RAM通常只有几十KB,堆栈分配不合理,很容易在OTA升级过程中崩溃。
栈(Stack):用于局部变量、函数调用。我建议至少分配2KB,如果OTA协议栈比较复杂,比如用到mbedTLS做签名验证,栈可能需要4KB甚至更多。
堆(Heap):用于动态内存分配。说实话,在医疗贴片这种资源受限的设备上,我尽量不用动态分配。malloc/free容易产生碎片,而且一旦分配失败,整个系统就挂了。OTA升级时,我倾向于用静态缓冲区。
在启动文件中,堆栈大小是这样定义的:
Stack_Size EQU 0x00001000 ; 4KB栈
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
Heap_Size EQU 0x00000200 ; 512字节堆
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
另外,OTA升级时,Bootloader和App的堆栈设置要协调好。Bootloader的栈可以小一点,因为它的任务简单。但App的栈一定要留足余量,特别是当你在升级过程中还要处理蓝牙通信、Flash擦写、CRC校验这些并发任务时。
好了,这一章就到这里。MCU选型是战略决策,启动流程是战术细节,两者都马虎不得。下一章我们聊聊Bootloader的设计——那才是OTA的核心战场。