2、硬件平台选型与启动流程:主流MCU平台对比(STM32MP1/i.MX8)、存储介质选型(NOR Flash/eMMC)、典型上电启动流程分析
做内窥镜的Bootloader,说白了第一步就是选硬件。这个选择直接决定了你后面几个月是顺风顺水,还是天天跟硬件干架。我个人习惯是先定平台,再定存储,最后才去梳理启动流程。顺序搞反了,后面全是坑。
2.1 主流MCU平台对比:STM32MP1 vs i.MX8
这两个平台我都深度用过。STM32MP1我是在一个便携式内窥镜项目上用的,i.MX8则是在一个高端手术导航系统上接触的。感受完全不同。
| 对比维度 | STM32MP1 | i.MX8 |
|---|---|---|
| 核心架构 | 单核/双核Cortex-A7 + Cortex-M4 | 多核Cortex-A53/A72 + Cortex-M4/M7 |
| 主频 | 650MHz - 800MHz | 1.2GHz - 1.6GHz |
| 典型功耗 | 1.5W - 2.5W | 3W - 8W |
| 启动ROM大小 | 128KB | 256KB |
| 内置SRAM | 256KB | 512KB |
| 典型成本 | 中低 | 中高 |
| 生态成熟度 | ST官方Cube生态,文档全 | NXP Yocto/BSP,门槛稍高 |
我个人的建议是:如果你的内窥镜产品对功耗和成本敏感,STM32MP1是更务实的选择。它那个Cortex-M4核做实时控制,A7核跑Linux做UI和网络,分工很清晰。我在项目中就遇到过一个问题——用i.MX8做便携设备,散热成了大麻烦,最后不得不降频运行,性能优势根本没发挥出来。
但如果你做的是高端产品,需要同时处理4K视频、AI辅助诊断、多路传感器数据,那i.MX8的多核优势就体现出来了。嗯,这里要注意:i.MX8的启动流程比STM32MP1复杂得多,它的ROM代码会先初始化DDR,再加载FSBL,这一步如果DDR时序没调好,板子直接变砖。
2.2 存储介质选型:NOR Flash vs eMMC
存储选型这块,我踩过的坑最多。你想想看,内窥镜的固件动不动就几十兆,还要存校准参数、用户配置、日志数据,选错了存储,Bootloader根本跑不起来。
核心原则:Bootloader本身必须放在NOR Flash里,因为它是XIP(就地执行)的。eMMC只能用来存固件镜像和文件系统。
| 对比维度 | NOR Flash | eMMC |
|---|---|---|
| 接口 | SPI/QSPI/Parallel | MMC/SDIO |
| 读取速度 | 快(支持XIP) | 较快(需先加载到RAM) |
| 写入速度 | 慢(擦除+写入) | 快(有FTL层) |
| 擦除寿命 | 10万次 | 3000-5000次 |
| 典型容量 | 1MB - 64MB | 4GB - 128GB |
| 成本(每MB) | 高 | 低 |
| 启动支持 | 原生支持 | 需BootROM支持 |
我曾经在一个项目里,为了省成本,把Bootloader也放到了eMMC里。结果每次上电,BootROM都要先初始化eMMC控制器,再加载前512字节的启动代码。这个过程慢不说,一旦eMMC的FTL层出问题,板子直接变砖。从那以后,我再也不敢这么干了。
我的经验:对于内窥镜产品,我建议用16MB或32MB的QSPI NOR Flash存Bootloader和内核,再用一个8GB或16GB的eMMC存文件系统和固件升级包。这样既保证了启动速度,又兼顾了存储容量。
2.3 典型上电启动流程分析
好了,平台和存储都定了,接下来就是最核心的部分——上电后到底发生了什么?我把它拆成四个阶段来讲。
阶段一:BootROM执行
芯片上电后,CPU从复位向量取第一条指令。这条指令在哪儿?就在芯片内部的BootROM里。BootROM是出厂固化的,你改不了。它的任务很简单:初始化最基本的硬件(时钟、PLL、引脚复用),然后根据启动引脚的电平状态,决定从哪个设备加载下一级代码。
为什么会这样设计?说白了,芯片厂商不知道你会用什么样的外部存储,所以先搞一个通用的ROM loader,让你能灵活选择启动介质。
// BootROM伪代码示意
void BootROM_Main(void) {
// 1. 初始化内部时钟
SystemClock_Init();
// 2. 读取启动引脚状态
uint8_t boot_mode = GPIO_ReadBootPins();
// 3. 根据模式选择启动设备
switch(boot_mode) {
case BOOT_FROM_QSPI:
LoadFromQSPI(0x0, SRAM_BASE, BOOTLOADER_SIZE);
break;
case BOOT_FROM_EMMC:
LoadFromEMMC(0x0, SRAM_BASE, 512); // 只加载前512字节
break;
case BOOT_FROM_UART:
WaitForUARTDownload();
break;
}
// 4. 跳转到加载的代码
JumpTo(SRAM_BASE);
}
阶段二:FSBL(First Stage Boot Loader)
BootROM加载到SRAM里的代码,就是FSBL。FSBL通常很小,几KB到几十KB。它的任务是初始化DDR控制器、配置时钟树、加载下一级Bootloader到DDR里。
我记得在调试STM32MP1时,FSBL这一步最容易出问题。DDR的时序参数稍微配错一点,板子就卡死。后来我学乖了,直接用ST官方提供的DDR tuning工具,先跑一遍压力测试,再把参数写死到FSBL里。
阶段三:SSBL(Second Stage Boot Loader)
SSBL就是我们常说的U-Boot或Little Kernel。它已经跑在DDR里了,有充足的内存空间。SSBL的任务包括:初始化更多外设(网卡、USB、显示)、解析设备树、加载内核镜像到DDR、设置内核启动参数。
对于内窥镜产品,我通常会在SSBL里加入一个升级检测逻辑:上电时检查某个GPIO电平,如果被拉低,就进入固件升级模式;否则正常启动。
注意:SSBL阶段一定要做CRC校验。我曾经遇到过eMMC里固件镜像损坏,SSBL没检查就直接跳转,结果内核解压到一半就崩溃了。从那以后,我在SSBL里加了完整的镜像校验流程。
阶段四:内核启动
SSBL跳转到内核入口后,内核开始解压、初始化、挂载根文件系统。这一步其实跟Bootloader关系不大了,但有一个关键点:内核启动参数里要指定根文件系统的位置。对于eMMC方案,通常是root=/dev/mmcblk0p2;对于NOR Flash方案,可能是root=/dev/mtdblock4。
嗯,这里还要提一句:如果你的内窥镜需要快速启动(比如按下开关后1秒内出画面),那就要考虑在SSBL阶段直接初始化显示控制器,把开机Logo刷上去。这个我在一个客户项目里做过,效果很好,用户感知上会觉得设备启动很快。
2.4 避坑指南
最后,分享几个我亲身踩过的坑:
- 启动引脚配置错误:有一次样板回来,死活起不来。查了两天才发现,启动电阻焊错了,芯片一直试图从UART启动。嗯,从此以后我都在原理图上用醒目的注释标出启动配置。
- DDR时序不匹配:换了不同批次的DDR颗粒,FSBL就挂了。原因是新颗粒的时序参数有细微差异。解决方案:在FSBL里用自动训练模式,而不是写死参数。
- eMMC分区表丢失:固件升级过程中意外断电,eMMC的分区表被破坏。后来我在Bootloader里加了一个备份分区表,主分区表损坏时自动恢复。
- NOR Flash擦除时间过长:升级时擦除一个大扇区,花了将近2秒,用户以为死机了。优化方案:采用流式升级,边擦边写,配合进度条显示。
好了,硬件平台选型和启动流程就讲到这里。下一章我们会深入Bootloader的具体实现,包括如何从NOR Flash加载固件、如何做双备份升级、如何处理异常断电。这些东西,都是实战中真正会用到的。