LPC845 I2C SBL实战:嵌入式固件远程更新与内存布局解析
1. 项目概述为什么我们需要Secondary Bootloader在嵌入式产品开发中尤其是那些部署在远端、难以物理接触的设备比如智能电表、环境传感器、工业网关固件更新一直是个让人头疼的问题。想象一下你的设备已经安装在几十米高的塔架上或者封装在密闭的工业机柜里这时发现软件有个Bug需要修复或者要增加一个新功能。难道要派人一个个拆下来用调试器重新烧录吗这成本和时间都是不可接受的。这就是Bootloader特别是Secondary BootloaderSBL次级引导加载程序大显身手的地方。它本质上是一段预先烧录在微控制器MCUFlash中的小程序其唯一使命就是在系统上电或复位后不直接运行用户的主应用程序而是先“听候指令”。它通过一个预设的通信接口比如UART、I2C、SPI、CAN等与外部世界连接等待主机发送新的固件数据包并利用MCU内部的在应用编程IAP功能安全地将新固件写入到Flash的指定区域最后跳转到新程序执行。NXP LPC845这款基于Cortex-M0内核的MCU其内部Boot ROM已经集成了通过UART0进行ISP在系统编程的基础能力。但UART在很多场景下并不理想比如在由主处理器AP控制的传感器模组中I2C或SPI才是更常见、更节省引脚的内置通信总线。LPC845的I2C SBL正是为了解决这个问题而生它绕过了内置Bootloader对UART0的依赖将固件更新的通道扩展到了I2C总线。这意味着你可以用一个主控芯片比如一颗应用处理器或另一颗MCU通过最普通的I2C总线就能对从属的LPC845进行固件更新整个架构变得异常简洁和标准化。我最近在一个电池供电的无线传感节点项目中就用了这个方案。节点主控是LPC845它通过I2C连接各种传感器同时这个I2C总线也预留给了网关处理器。当网关需要批量更新所有节点固件时无需复杂的无线OTA协议直接通过I2C总线调用每个节点的SBL即可完成稳定又高效。接下来我就结合官方文档和实际踩坑经验带你彻底搞懂LPC845 I2C SBL的实现与应用。2. SBL核心机制与内存布局解析要玩转SBL首先得理解它在LPC845内存地图中的“地盘”划分以及它和主应用程序之间如何“交接班”。这就像一套房子的户型图SBL和你的App各住哪个房间门牌号地址是多少必须一清二楚否则就会“撞车”导致系统无法启动。2.1 内存地图SBL与用户应用的“楚河汉界”LPC845最大支持64KB的Flash这64KB空间被均匀地划分为32个扇区Sector每个扇区大小为1KB。更进一步每个扇区又包含16个页Page每页64字节。IAP擦除操作可以针对整个扇区或单个页进行这给了我们更灵活的空间管理能力。SBL代码本身需要占用一定的Flash空间。根据官方设计SBL被放置在Flash最开始的8个扇区即地址范围0x0000 0000到0x0000 1FFF共8KB。这是一个非常重要的约定意味着用户应用程序绝对不可以占用或修改这片区域否则会破坏SBL代码导致系统再也无法通过I2C更新。因此用户应用程序的起始地址必须从0x0000 2000开始。这直接影响了我们开发App时的链接脚本Linker Script配置你的中断向量表Vector Table的起始地址必须设置为0x0000 2000而不是通常的0x0000 0000。注意很多IDE如Keil MDK在创建新工程时默认的链接脚本会将代码起始地址设为0。如果你直接使用默认设置编译生成的二进制文件会试图覆盖SBL区域后果就是SBL被破坏板子“变砖”只能通过SWD调试器重新擦除整个Flash才能恢复。这是新手最容易踩的第一个大坑。除了向量表地址SBL还要求应用程序在固定位置放置一个特殊的“图像头”Image Header。这个头结构位于应用程序镜像内部的偏移0x100处对应到Flash的绝对地址就是0x0000 2100。这个头里包含了固件类型、CRC校验码等信息是SBL判断能否启动该应用程序的关键依据。我们后面会详细讲如何生成这个头。2.2 启动流程SBL的“决策树”理解了内存布局我们再看SBL上电后究竟干了什么。它的行为逻辑可以用一个清晰的决策树来描述上电/复位无论是冷启动、看门狗复位还是外部引脚复位CPU首先执行的是固化在ROM里的Primary Bootloader主引导程序。它的工作很简单检查特定引脚状态决定是否进入ISP模式然后无条件地跳转到Flash的0x0000 0000地址执行也就是我们的SBL代码。SBL接管SBL开始执行。它首先会去检查0x0000 2100地址是否存在有效的图像头。如果头不存在或无效SBL认为Flash里没有可启动的用户程序或者程序不完整。于是它初始化I2C外设配置为从机模式然后进入一个“命令等待循环”。在这个状态下它就像一名待命的士兵通过I2C总线监听来自主机处理器Host的指令准备接收新的固件。如果头存在且有效SBL会根据头中的“图像类型”字段做出不同决策。普通图像IMG_NORMALSBL会计算整个应用程序镜像的CRC32校验码并与头中存储的CRC值比对。如果一致说明固件完整无误SBL便执行一个跳转指令将CPU的执行权交给位于0x0000 2000的用户应用程序。至此启动完成。无CRC图像IMG_NO_CRCSBL跳过CRC校验直接尝试跳转到应用程序。这种模式风险较高一般用于调试阶段。其他类型或主机中断在某些情况下即使有有效镜像主机也可以通过一个特定的GPIO引脚nHostIRQ在启动瞬间发出信号强制SBL停留在命令模式以便进行固件更新。这个机制实现了“在已有App的情况下触发更新”。这个流程确保了系统的健壮性有合法App就启动没有或损坏就等待更新。同时通过主机中断机制为现场设备提供了“主动更新”的入口。2.3 IAPSBL更新固件的“武器库”SBL之所以能写Flash靠的是LPC845芯片内部提供的IAPIn-Application Programming命令集。IAP是一组固化在芯片Boot ROM中的函数用户代码可以通过特定的调用方式通常是触发一个软件中断并传递命令号和参数来使用它们实现擦除Flash、编程Flash、校验CRC等操作。SBL代码的核心任务之一就是封装和调用这些IAP命令。它接收主机通过I2C发送过来的数据包解析出要执行的命令如“擦除第10扇区”、“向地址0x2100写入256字节数据”然后调用对应的IAP函数去执行。作为开发者我们通常不需要深入理解每个IAP命令的底层寄存器操作但需要知道SBL提供了哪些高层命令如GetVersion,PrepareSector,CopyRAMToFlash等以及如何通过I2C协议去调用它们。实操心得虽然文档说不需要深究IAP但我建议你至少浏览一下UM11029用户手册中关于IAP的章节。了解IAP命令在执行前需要将代码搬到RAM中运行因为Flash不能擦写自身以及命令执行期间的时序要求能帮助你在调试SBL通信超时或失败时更快地定位问题是出在主机端、I2C链路还是SBL本身的IAP调用上。3. 开发环境搭建与SBL下载理论讲完了我们动手把环境搭起来。这里我以最常用的Keil MDK和LPCXpresso845 MAX开发板为例带你走一遍流程。3.1 硬件连接与工具准备你需要准备以下硬件LPCXpresso845 MAX 开发板 (OM13097)这是我们的目标板上面有LPC845芯片。LPCXpresso54102 开发板 (OM13077)这块板子在这里扮演一个“USB转I2C适配器”的角色。它上面的LPC4322芯片运行着CMSIS-DAP固件和特定的桥接程序能将PC的USB命令转换为I2C总线信号。连接线用杜邦线将两块板子的I2C总线连接起来。SCL连接两块板子的I2C时钟线。SDA连接两块板子的I2C数据线。GND务必将两块板子的地GND连接在一起这是保证通信稳定的基础。nHostIRQ (可选)将LPC845的某个GPIO例如PIO0_24连接到LPC54102的某个GPIO。这个信号线用于主机中断机制在初次体验时可以不接但完整测试需要。软件方面你需要Keil MDK-ARM (v5.25或更高)用于编译SBL和测试应用程序。NXP LPC845 I2C SBL软件包从NXP官网下载AN12393的应用笔记及其附带的软件包。里面包含了SBL的Keil工程、测试程序、以及最重要的PC端工具I2C-Util.exe和lpc845_secimgcr.exe。Flash Magic 或 LPCScrypt用于通过UART-ISP模式初次下载SBL到目标板。如果你有J-Link等调试器也可以直接通过SWD下载更简单。3.2 将SBL烧录到LPC845 Flash拿到一块全新的LPC845板子它的Flash是空的。我们需要先把SBL程序烧录进去。这里介绍两种最常用的方法方法一使用板载调试器推荐最简单如果你的LPCXpresso845 MAX板通过USB连接电脑后能在设备管理器中看到“LPC-Link2 CMSIS-DAP”之类的设备那么你可以直接使用Keil IDE进行下载。打开SBL软件包中的Keil工程文件通常位于\Keil project\sbl_project。在Keil中确保调试器选择为“CMSIS-DAP”接口选择“SWD”。点击“Load”按钮或按F8。Keil会编译代码并通过板载的LPC-Link2调试器将SBL的二进制文件直接下载到LPC845 Flash的0x0000 0000地址。这是最无痛的方式。方法二使用Flash Magic通过UART-ISP下载如果板载调试器不可用或者你是在自定义板上可以通过UART0进入ISP模式来下载。进入ISP模式找到板子上的ISP按钮SW1和复位按钮SW3。先按住ISP按钮不放然后短暂按下复位按钮并松开最后再松开ISP按钮。此时LPC845芯片会停留在内部的Boot ROM中等待通过UART0接收ISP命令。连接串口将板子的UART0TX/RX通过USB转串口模块连接到电脑。使用Flash Magic打开Flash Magic选择正确的芯片型号“LPC845”。选择正确的COM口和波特率通常115200。在“ISP”菜单中勾选“Erase all Flash”以确保干净的环境。在“Hex File”区域选择SBL软件包中提供的lpc845_I2C_sbl.hex文件。点击“Start”按钮。Flash Magic会通过UART0与芯片Boot ROM通信完成擦除和编程。复位编程完成后再次按下复位按钮SW3。此时芯片会从0地址启动运行刚刚烧录进去的SBL程序。注意事项使用Flash Magic时务必确认芯片的时钟源配置如外部晶振频率与Flash Magic设置中的一致否则可能导致通信失败。LPC845 MAX板通常使用12MHz外部晶振。无论用哪种方法成功下载SBL后你可以通过一个简单的测试来验证将I2C-Util工具连接到作为USB-I2C桥的LPC54102板发送一个“GetVersion”命令通常是命令8。如果SBL运行正常它会通过I2C返回其版本号。如果收不到回复请检查硬件连接、I2C从机地址默认为0x50以及LPC54102板的桥接固件是否正确。4. 用户应用程序的适配与编译SBL已经就位现在我们需要编译一个能在它“统治下”正常工作的用户程序。这个程序需要满足两个硬性要求1) 链接地址从0x2000开始2) 包含正确的图像头。4.1 修改链接脚本Linker Script以Keil MDK为例链接脚本是一个后缀为.sct的分散加载文件。SBL软件包中通常会提供一个示例链接脚本例如firmware1.sct。我们需要在自己的工程中使用它或者参照它修改自己的链接脚本。关键修改如下LR_IROM1 0x00002000 0x0000E000 { ; 加载区域起始地址为0x2000大小根据Flash剩余空间调整 ER_IROM1 0x00002000 0x0000E000 { ; 执行区域地址同样从0x2000开始 *.o (RESET, First) ; 中断向量表放在最前面 *(InRoot$$Sections) .ANY (RO) ; 所有只读代码和常量 } RW_IRAM1 0x10000000 0x00004000 { ; RAM区域地址不变 .ANY (RW ZI) } }你需要在自己的Keil工程设置中指定使用这个修改后的.sct文件。路径通常在Options for Target - Linker - Scatter File。4.2 中断向量表重映射由于程序起始地址变了中断向量表也需要相应调整。在系统启动初期需要手动将向量表从0x0000 2000重映射到0x0000 0000因为Cortex-M内核在取中断向量时默认是从0地址开始计算的。这通常在system_LPC845.c文件的SystemInit()函数中完成添加如下代码// 在SystemInit()函数内添加 SCB-VTOR 0x00002000UL; // 将向量表偏移寄存器设置为0x2000对于使用CMSIS的工程也可以在main()函数最开始调用int main(void) { SCB-VTOR 0x00002000UL; // 重映射向量表 // ... 其他初始化代码 }4.3 生成带CRC头的应用程序二进制文件编译链接后我们会得到一个.axf或.out文件。我们需要从中提取出纯二进制文件.bin供SBL下载。在Keil中可以通过Options for Target - User - After Build/Rebuild配置调用fromelf.exe工具来生成.bin文件。但这样生成的.bin文件还不完整它缺少SBL要求的图像头。这时就需要用到软件包中的lpc845_secimgcr.exe工具。这个工具的作用是读取原始的应用程序.bin文件在文件内部偏移0x100的位置插入一个包含CRC校验码和其他信息的头结构并输出一个新的.bin文件。操作步骤如下打开命令提示符CMD并切换到lpc845_secimgcr.exe工具所在的目录。执行命令lpc845_secimgcr.exe -n1 input_app.bin output_app_crc.bin-n1参数指定CRC校验的范围。-n1表示对整个应用程序镜像从0x2000开始的部分计算CRC。-n2则只对图像头本身计算CRC。通常使用-n1以确保固件完整性。input_app.bin是你从Keil生成的原始二进制文件。output_app_crc.bin是添加了CRC头后的最终文件这个文件才是要通过I2C下载到板子里的。执行成功后工具会输出生成的CRC值。你可以用二进制查看工具打开output_app_crc.bin会看到在文件开头部分多出了一段数据图像头而你的应用程序代码紧随其后。避坑技巧务必确保你传递给lpc845_secimgcr.exe的输入文件是纯净的应用程序二进制码不包含任何额外的调试信息或填充。最好是从Keil工程直接生成并确认链接地址正确。我曾遇到过因为从错误地址提取二进制文件导致CRC计算范围错误SBL始终校验失败的问题。5. 通过I2C-Util工具进行固件更新实战环境备好程序编好头也加好了现在进入最激动人心的环节通过I2C总线用电脑给LPC845更新固件。我们将使用软件包里的I2C-Util.exe这个命令行工具。5.1 连接与初始化确保硬件连接正确I2C和GND并将作为USB-I2C桥的LPC54102板连接到电脑。以管理员身份打开命令提示符导航到I2C-Util.exe所在目录。运行I2C-Util.exe。工具会自动检测连接的LPC54102板并进入一个交互式命令行界面。界面会显示可用的命令列表。5.2 固件更新完整流程假设我们要更新一个简单的LED闪烁程序。以下是详细的交互步骤和命令解析# 1. 启动I2C-Util选择I2C模式通常默认就是 C:\SBL_Tools I2C-Util.exe I2C Utility Tool Started. Type help for commands. # 2. 发送 f 命令拉低nHostIRQ线如果连接了。 # 这个信号告诉SBL“先别启动App等我命令”。如果没接nHostIRQ线此步可省略但需要确保Flash里当前没有有效的App镜像即0x2100处无有效头否则SBL会直接启动旧App。 i2c f nHostIRQ set as output and driven low. # 3. 按下目标板LPC845 MAX板的复位按钮SW3。 # 这会触发芯片复位SBL开始运行。由于nHostIRQ为低或没有有效AppSBL会进入I2C命令等待模式。 # 4. 发送 g 命令将nHostIRQ线重新配置为输入释放控制权。 # 完成握手告诉SBL“我准备好了你可以控制这根线了”。 i2c g nHostIRQ set as input. # 5. 发送 8 命令获取SBL版本号。这是一个很好的连通性测试。 i2c 8 SBL Version: 1.0 # 6. 发送 1 命令开始固件更新流程。工具会提示你输入固件文件名。 i2c 1 Enter firmware image file name: led_blinky_crc.bin此时工具会开始执行一系列底层操作擦除根据新固件的大小计算需要擦除哪些Flash扇区从0x2000开始往后。发送IAP命令擦除这些扇区。编程将led_blinky_crc.bin文件中的数据分块通常是256字节一块通过I2C发送给SBLSBL再调用IAP命令写入到对应的Flash地址。校验编程完成后SBL可能会自动或根据命令进行校验如CRC校验。 整个过程会在命令行中显示进度条或日志。I2C通信本身不算快更新一个几十KB的固件可能需要几秒到十几秒。# 7. 更新完成后发送 b 命令让SBL启动刚刚烧录的应用程序。 i2c b Booting application...如果一切顺利你应该立刻看到LPC845 MAX板上的蓝色LED开始闪烁。这表明新的用户程序已经成功运行5.3 从应用程序中重新调用SBL一个更高级的功能是用户应用程序在运行过程中可以主动跳转回SBL以便主机随时发起更新而无需物理复位按钮。这在实现“软件触发更新”时非常有用。在SBL工程中定义了一个函数指针indrectAppJump它被强制链接到地址0x00001F00并指向SBL的入口函数。在用户应用程序中你可以调用一个名为bootSecondaryLoader()的函数其原型和实现可以在SBL的头文件中找到。这个函数会执行以下操作禁用所有中断。将必要的参数如I2C引脚配置存入特定寄存器或内存位置。执行一个跳转指令跳转到0x00001F00这个地址。CPU从0x00001F00取出函数指针进而执行SBL的入口代码重新进入I2C命令等待模式。这样只要主机在I2C总线上发送一个特定信号或应用程序根据某个条件自行决定就能“软重启”到Bootloader模式实现了真正的在线更新。注意事项在应用程序中跳转回SBL前必须妥善关闭所有外设特别是定时器、中断、DMA等并确保没有正在进行的Flash操作。不干净的跳转可能导致硬件状态错乱使SBL无法正常工作。最好将跳转代码放在一个尽可能“干净”的上下文环境中执行。6. 常见问题排查与调试心得在实际操作中你几乎一定会遇到各种问题。下面是我总结的一些常见故障和排查思路希望能帮你快速定位。6.1 通信失败I2C-Util无响应症状运行I2C-Util后发送任何命令如8都没有回应或者提示“I2C device not found”。排查步骤检查硬件连接这是最最常见的原因用万用表蜂鸣档确认SDA、SCL、GND三根线是否连通有没有虚焊或接错。特别注意GND必须共地。检查I2C上拉电阻I2C总线需要上拉电阻通常4.7kΩ。LPCXpresso开发板一般已内置。如果是自定义板请确保SDA和SCL线上有上拉电阻到VCC。确认从机地址SBL的I2C从机地址默认是0x507位地址。使用逻辑分析仪或示波器抓取I2C总线波形看主机发出的地址是否正确。也可以尝试用I2C-Util的扫描命令如果有扫描总线上的设备。确认LPC54102桥接板确保LPC54102板上的桥接固件是正确且最新的。有时需要重新用LPCScrypt工具烧录其固件。检查SBL是否成功运行测量LPC845的某个GPIO在SBL代码中初始化为输出并周期性翻转用示波器看是否有波形以确认SBL代码确实在运行而不是卡死了。6.2 CRC校验失败症状固件下载过程似乎成功但发送b命令启动时失败或者SBL直接拒绝启动通过调试信息发现是CRC错误。排查步骤确认链接地址百分之九十的问题出在这里。用二进制编辑器打开你生成的input_app.bin确认它的内容是否确实是从0x2000开始的应用程序代码。检查Keil的map文件看代码和数据的起始地址是否正确。确认CRC工具参数检查你调用lpc845_secimgcr.exe时使用的-n参数。如果应用程序镜像本身是从0x2000开始链接的那么应该使用-n1计算整个镜像的CRC。如果镜像文件的开头包含了0x0000到0x1FFF的填充数据通常不会那计算范围就错了。手动计算校验可以使用其他CRC32计算工具如一些十六进制编辑器自带对你生成的output_app_crc.bin文件中从图像头之后的数据进行计算比对是否与头中存储的CRC值一致。这能帮你确定是生成环节还是SBL计算环节出了问题。6.3 应用程序无法启动或运行异常症状SBL报告启动成功但应用程序没反应LED不闪或者运行一会儿就死机、复位。排查步骤检查向量表重映射在应用程序的main()函数最开始处或者SystemInit()函数中确认SCB-VTOR 0x00002000UL;这行代码被执行了。如果没有中断发生时CPU会去错误的地址找中断服务函数导致硬件错误。检查时钟初始化SBL可能会初始化系统时钟。你的应用程序在启动时是重新初始化时钟还是沿用SBL的设置如果不一致可能导致外设如UART、定时器的时钟频率不对从而工作异常。建议在App中完整地重新初始化一遍系统时钟和外设。检查堆栈指针确保应用程序的中断向量表第一个字初始堆栈指针设置正确。链接脚本通常会处理好这个。使用调试器如果条件允许在跳转到应用程序后用调试器SWD挂载上去单步调试看程序死在哪个位置。这通常是最直接的定位方法。6.4 关于nHostIRQ引脚的使用nHostIRQ机制很实用但它增加了硬件连线的复杂性。在实际产品设计中你需要权衡如果使用需要占用LPC845的一个GPIO并连接到主机。优点是可以在不切断电源的情况下通过主机信号强制进入Bootloader模式实现真正的“随时更新”。如果不使用硬件更简单。但更新固件需要确保Flash中当前没有有效的应用程序即CRC头无效。这通常意味着要么第一次烧录要么在更新前先通过I2C发送命令擦除应用程序区域。或者也可以利用应用程序中的“软件跳转”功能来主动进入SBL。我的经验是在原型开发和测试阶段可以先不接nHostIRQ专注于把基础的I2C更新流程跑通。在产品定型时如果更新流程要求主机能随时中断应用程序则再考虑添加此引脚。通过以上这些步骤和问题排查指南你应该能够独立完成LPC845 I2C SBL的集成与应用。这套方案的核心思想——利用次级Bootloader和标准通信接口实现远程更新——可以迁移到很多其他MCU平台上。理解其内存布局、启动流程和通信协议是灵活运用和定制化开发的关键。