记一次SM32F407ZG死机原因分析

主芯片:STM32F407ZG
产品软件架构:

  1. bootloader + upgrader / application (upgrader 是一个系统升级器,升级系统专用)
  2. 在bootloader模式下可以加载upgrader或application并启动
  3. 在upgrader模式下可以更新bootloader和整个application.
  4. upgrader和appplication同时只存在一个,由bootloader从文件系统载入STM32F407ZG应用区.

问题描述:两台故障产品在使用过程中意外死机,无法开机,分别编号为1号和2号。
故障机分析处理:
1号已经被重刷程序后修复,故障无法再复现,怀疑是STM32 flash程序区被改,所以首先分析了操作flash的代码,经查擦写flash只发生在成功开机进入系统或者在"系统升级"选项中进行操作时,其他使用过程中不涉及flash擦写。同时编写程序,每隔1秒擦除flash一次,一共执行2万次擦写, 在擦写期间进行各种操作,均无发现死机现象。
2号机拆机检查: 系统上电,STM32 3V3电压正常,但串口无法检测到系统log输出,无法正常开机。

尝试用Jlink Command测试:
撤掉USB和电池供电,飞线连接3V3,SWDIO,SWCLK,GND至JLink Command
执行connect指令,成功连接STM32F407ZG主控
执行halt停机指令,成功停机
J-Link>halt
PC = 08000294, CycleCnt = 00000000
R0 = 00000000, R1 = 00000000, R2 = 00000000, R3 = 00000000
R4 = 00000000, R5 = 00000000, R6 = 00000000, R7 = 00000000
R8 = 00000000, R9 = 00000000, R10= 00000000, R11= 00000000
R12= 00000000
SP(R13)= 20013378, MSP= 20013378, PSP= 00000000, R14(LR) = FFFFFFFF
XPSR = 01000000: APSR = nzcvq, EPSR = 01000000, IPSR = 000 (NoException)
CFBP = 00000000, CONTROL = 00, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00
FPU regs: FPU not enabled / not implemented on connected CPU.
执行go全速运行,指令响应OK,但是产品仍无法成功进开机流程,再次执行halt指令:
J-Link>halt
PC = 0800022E, CycleCnt = 06EA7AC4
R0 = 0805A76C, R1 = 20001E78, R2 = 00011500, R3 = 00000000
R4 = 00000000, R5 = 00000000, R6 = 00000000, R7 = 08059AAF
R8 = 00000000, R9 = 00000000, R10= 08059AD0, R11= 08059AD0
R12= 00000000
SP(R13)= 20013378, MSP= 20013378, PSP= 00000000, R14(LR) = 0800019F
XPSR = 21000000: APSR = nzCvq, EPSR = 01000000, IPSR = 000 (NoException)
CFBP = 00000000, CONTROL = 00, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00
FPS0 = 00000000, FPS1 = 00000000, FPS2 = 00000000, FPS3 = 00000000
FPS4 = 00000000, FPS5 = 00000000, FPS6 = 00000000, FPS7 = 00000000
FPS8 = 00000000, FPS9 = 00000000, FPS10= 00000000, FPS11= 00000000
FPS12= 00000000, FPS13= 00000000, FPS14= 00000000, FPS15= 00000000
FPS16= 00000000, FPS17= 00000000, FPS18= 00000000, FPS19= 00000000
FPS20= 00000000, FPS21= 00000000, FPS22= 00000000, FPS23= 00000000
FPS24= 00000000, FPS25= 00000000, FPS26= 00000000, FPS27= 00000000
FPS28= 00000000, FPS29= 00000000, FPS30= 00000000, FPS31= 00000000
FPSCR= 00000000
初步发现PC和MSP数值正常,但PC停留在代码很靠前的部分,怀疑是反复重启,并且是指在很小一段代码内反复执行,尝试用savebin指令dump芯片内部flash,成功读取1MB的flash数据,经过对flash数据的分析,发现如下信息:
1. app程序区起始地址为0x8080000, 关键字符串upgrader,确定app区域存储的是升级程序upgrader, 版本未定
2. bootloader区起始地址为0x8000000, 经匹配,bootloader程序为xx.x.x版本,为产品发布的第一个正式版本。其中bootloader偏移0x4000-0x8000一共16KB的数据被改写,解析偏移16KB前64字节发现刚好与系统保存的参数格式匹配,但部分参数无法解析
3. 根据上述信息得知upgrader程序是新版本,参数区使用了flash偏移0x4000-0x8000的区域,那么原来的参数在偏移0x60000出原因该会有保留。尝试读取解析:

5E 4F B5 63 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 		FF FF FF FF FF FF FF 0F 00 00 00 38 30 30 31 30 31 59 44 33 31 30 36 30 39 38 37 00 AA AA

解析得到SN号:xxxxxxxxxxxxxxxx,其中生产日期为xxxx.xx.xx,可知原来app区域运行的是xxxx.xx.xx版本,现在已被upgrader程序代替.
4. 根据第3步得到的版本信息,查找对应版本的upgrader文件,与芯片dump出来的upgrader完全匹配得到上述信息后,查看该upgrader的MSP地址(0-3)和程序入口地址(4-7)分别为: 0x2000EA, 0x808029d,使用setPC指令重置MCU PC指针,然后执行go指令,成功跳过bootloader直接开机运行upgrader代码,成功进入upgrader后,通过串口可以登录系统,查看文件系统和关键文件,可以佐证前面步骤的信息

经过上述分析得知: 不开机的原因是bootloader被非法改写。
根据flash镜像的分析推测故障发生的场景: 用户版本为xxxx.xx.xx,用户想要升级系统到xxxx.xx.xy版本,其中upgrader程序已经成功刷到了MCU的应用区,新版本的upgrader会将参数保存在0x4000处,此处会破坏旧版本bootloader, 但是后续没有升级成功,新版本的bootloader没有被写入,此时用户关机、断电、重启等操作将会导致系统完全变砖。联想到最近由于软件架构调整,各个模块的flash地址偏移发生了很大变化,xxxx.xx.xx升级到xxxx.xx.xy及以上版本时存在参数区地址调整,存在此问题,xxxx.xx.xy及以上系统升级不存在这个问题。

解决办法: 修改upgrader程序,在新版本的bootloader成功升级前,不可操作新的参数区,避免旧版本bootloader被非法改写

总结:

  1. 针对这种需要修改底层bootloader和系统更新的系统,还是需要完善的测试
  2. flash空间的划分一定要慎重,避免在中途调整,本案例就是由于前期bootloader和application的地址空间分配不合理,application空间过小,需要缩小bootloader的空间来增大application空间,涉及到底层程序的更新,这给对已量产的产品带来很大风险。
  3. 善于使用工具,jlink command是个好东西
  4. 遇事不慌,仔细分析,芯片一般不会有问题,不稳定的外围电路和软件bug带来问题的可能性更大
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的基于STM32的串口协议示例: 1. 定义协议格式 首先,我们需要定义协议格式。一个简单的协议可以包含以下几个部分: - 帧头:表示该帧的开始,一般为一个固定的字节或字节序列。 - 数据长度:表示该帧数据的长度。 - 数据:表示该帧携带的数据。 - 校验和:用于检验数据的完整性,一般为数据长度和数据的异或值。 - 帧尾:表示该帧的结束,一般为一个固定的字节或字节序列。 根据这个格式,我们可以定义一个结构体来表示一个帧: ```c typedef struct { uint8_t header; // 帧头 uint8_t length; // 数据长度 uint8_t data[256]; // 数据,最大长度为256个字节 uint8_t checksum; // 校验和 uint8_t tail; // 帧尾 } Frame; ``` 2. 实现发送函数 接下来,我们需要实现一个发送函数,用于将数据打包成协议帧并发送出去。 ```c void send_data(uint8_t* data, uint8_t length) { Frame frame; frame.header = 0xAA; // 帧头 frame.length = length; // 数据长度 memcpy(frame.data, data, length); // 数据 frame.checksum = frame.length ^ data[0] ^ data[1] ^ ... ^ data[length-1]; // 校验和 frame.tail = 0x55; // 帧尾 // 发送帧 HAL_UART_Transmit(&huart1, &frame.header, 1, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, &frame.length, 1, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, frame.data, length, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, &frame.checksum, 1, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, &frame.tail, 1, HAL_MAX_DELAY); } ``` 这个函数接收一个指针和一个长度作为参数,将数据打包成协议帧后通过串口发送出去。 3. 实现接收函数 最后,我们需要实现一个接收函数,用于从串口接收数据并解析成协议帧。 ```c void receive_data(void) { static uint8_t buffer[256]; static uint8_t index = 0; uint8_t data; // 读取串口数据 HAL_UART_Receive(&huart1, &data, 1, HAL_MAX_DELAY); // 判断是否为帧头 if (data == 0xAA) { index = 0; // 重置缓冲区索引 buffer[index++] = data; } // 判断是否为数据长度 else if (index == 1) { buffer[index++] = data; } // 读取数据 else if (index > 1 && index < buffer[1] + 2) { buffer[index++] = data; } // 判断校验和是否正确 else if (index == buffer[1] + 2) { uint8_t checksum = buffer[1] ^ buffer[2] ^ ... ^ buffer[index-2]; if (checksum == data) { // 校验和正确,处理数据 // ... } index = 0; // 重置缓冲区索引 } // 不符合协议格式,丢弃数据 else { index = 0; // 重置缓冲区索引 } } ``` 这个函数使用一个静态的缓冲区和一个索引变量来缓存接收到的数据。当接收到一个字节时,根据协议格式判断该字节的类型,最终得到一个完整的协议帧后,再进行数据处理。 以上就是一个简单的基于STM32的串口协议示例。需要注意的是,这只是一个示例,具体的协议格式和实现方式还需要根据具体的应用场景进行设计和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值