通过PC端上位机实现MCU本地的OTA升级,本篇文章对实现的目的、需要用到的第三方工具、LIN诊断帧、升级协议、MCU端升级过程以及PC端升级过程做详细说明。
最近在做MCU项目时需要将样机寄给客户进行验证,在客户的验证过程中要求参数可调试,如果需要修改软件升级MCU就很麻烦。于是就想做个上位机实现MCU升级功能,项目中MCU只有LIN接口可以用来作为调试接口,于是就想通过LIN诊断协议进行参数调试和实现本地OTA升级功能。
一般LIN开发用的调试工具是CANOE,但是CANOE太贵了,客户端也不一定有CANOE工具,于是选用了在淘宝就可以买到的一款USB转LIN的调试工具-图莫斯UTA0503,如下图:
用这款工具的好处是厂家提供了二次开发的接口库,用户可以使用这个库进行二次开发,实现自己的PC上位机,支持QT等实现。
从发送格式上,诊断帧的PDU 单元U (Packet Data Unit,分组数据单元)可分为单帧(Single Frame,SF)、首帧(First Frame,FF)和续帧(Consecutive Frames, CF)三种。从发送源上,主机发送请求 PDU,从机发送应答 PDU。 如下图所示,为 PDU 格式,包括节点地址(NAD),协议控制信息(PCI),LEN,服务 ID(SID),应答服务 ID(RSID), 消息字节段(D1~D6)。首字节 NAD 首先发送,末字节 D4,D5,D6 最后发送。
第一个字节是 NAD(node address),用于区分不同从机节点的地址。 0x00 用于休眠命令; 0x01~0x7D 从机节点地址,即 NAD 0x7E 功能节点地址(功能 NAD); 0x7F 广播节点地址(广播 NAD); 0x80~0xFF 用户自定义。
第二个字节是 PCI(Protocol Control Information)信息,包含了 PDU 单元类型和消息字节长度 的信息。如下表:
单帧中,附加信息 Length 表示消息字节数加 1。首帧中,附加信息只表示 Length 的高 4 位,低 8 位在 LEN 中表示。因此在消息长度为 12 位数据,最大长度为 4095(0xFFF)。 续帧中的附加信息表示首帧后,跟随的续帧的编号,第一个续帧编号为 1,之后累加 1。如果续帧数多于 15 个,那么帧计数器在第 16 个续帧时从 0 重新计数。
SID(Service Identifier)表示了从机节点应完成的服务请求。节点配置服务的 SID 区间为 0xB0~0xB7,诊断 服务的 SID 区间为 0x00~0xAF,0xB8~0xFE。
RSID(Response Service Identifier)表示从机节点应答的内容,它的值是 SID+0x40。
1)节点探测指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要探测的节点地址 | 0x06 | 0xB2 | 0x20 | 产商ID低8位 | 产商ID高8位 | 功能ID低8位 | 功能ID高8位 |
说明:使用0xB2诊断服务进行节点探测,byte4的0x20为自定义的节点探测服务识别ID,产商ID和功能ID根据MCU定义修改 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0xF2 | 0x21 | 0x22 | 0x23 | 0x24 | 0x25 |
说明:如果节点存在,MCU应答byte4~byte8的内容可以自行定义。 | ||||||||
2)开始升级指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要升级的节点地址 | 0x06 | 0x22 | 0xFA | 0x30 | 0x00 | 0x00 | 0x00 |
说明:使用0x22诊断服务发送节点升级指令,byte4的0xFA为自定义的节点升级服务识别ID,byte5的0x30为开始升级指令。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x30 | 错误码 | 0x00 | 0x00 |
说明:byte6字节为错误码定义,参照升级错误码定义。 | ||||||||
3)升级包总数指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要升级的节点地址 | 0x06 | 0x22 | 0xFA | 0x31 | 包总数高8位 | 包总数低8位 | 0x00 |
说明:byte5的0x31为升级包总数指令,byte6和byte7为2字节大小的升级包总数的高8位和低8位。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x31 | 错误码 | 0x00 | 0x00 |
说明:byte6字节为错误码定义,参照升级错误码定义。 | ||||||||
4)升级包ID请求指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要升级的节点地址 | 0x06 | 0x22 | 0xFA | 0x32 | 0x00 | 0x00 | 0x00 |
说明:byte5的0x32为升级ID请求指令,由MCU告知当前需要发送的下一个升级包ID。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x32 | 错误码 | 包ID高8位 | 包ID低8位 |
说明:byte6字节为错误码定义,参照升级错误码定义。Byte7和8为MCU下一个升级包ID。 | ||||||||
5)升级包数据指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(LEN) | byte4(SID) | byte5 | byte6 | byte7 | byte8 |
首帧 | 需要升级的节点地址 | 0x10 | 0x45 | 0x22 | 0xFA | 0x33 | 包ID高8位 | 包ID低8位 |
续帧1 | 需要升级的节点地址 | 0x21 | D1 | D2 | D3 | D4 | D5 | D6 |
续帧2 | 需要升级的节点地址 | 0x22 | D7 | D8 | D9 | D10 | D11 | D12 |
续帧3 | 需要升级的节点地址 | 0x23 | D13 | D14 | D15 | D16 | D17 | D18 |
续帧4 | 需要升级的节点地址 | 0x24 | D19 | D20 | D21 | D22 | D23 | D24 |
续帧5 | 需要升级的节点地址 | 0x25 | D25 | D26 | D27 | D28 | D29 | D30 |
续帧6 | 需要升级的节点地址 | 0x26 | D31 | D32 | D33 | D34 | D35 | D36 |
续帧7 | 需要升级的节点地址 | 0x27 | D37 | D38 | D39 | D40 | D41 | D42 |
续帧8 | 需要升级的节点地址 | 0x28 | D43 | D44 | D45 | D46 | D47 | D48 |
续帧9 | 需要升级的节点地址 | 0x29 | D49 | D50 | D51 | D52 | D53 | D54 |
续帧10 | 需要升级的节点地址 | 0x2A | D55 | D56 | D57 | D58 | D59 | D60 |
续帧11 | 需要升级的节点地址 | 0x2B | D61 | D62 | D63 | D64 | 0xFF | 0xFF |
说明:byte5的0x33为升级包数据发送指令,包数据长度为64字节(D1~D64),最后一包的数据不满64字节则填充0xFF。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x33 | 错误码 | 包ID高8位 | 包ID低8位 |
说明:byte6字节为错误码定义,参照升级错误码定义。Byte7和8为MCU下一个升级包ID。 | ||||||||
6)CRC校验码发送指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要升级的节点地址 | 0x06 | 0x22 | 0xFA | 0x34 | 校验码高8位 | 校验码低8位 | 0x00 |
说明:byte5的0x34为升级包校验码发送指令,由PC计算(累加升级包数据每个字节包括填充的0xFF)并发送计算的CRC校验码值(16字节)。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x34 | 错误码 | 校验码高8位 | 校验码低8位 |
说明:byte6字节为错误码定义,参照升级错误码定义。Byte7和8为MCU计算的CRC校验码数值,算法同PC端。 | ||||||||
7)重启MCU指令: | ||||||||
PC端请求 | byte1(NAD) | byte2(PCI) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 需要升级的节点地址 | 0x06 | 0x22 | 0xFA | 0x35 | 0x00 | 0x00 | 0x00 |
说明:byte5的0x35为升级包接收完整重启MCU指令。 | ||||||||
MCU端应答 | byte1(NAD) | byte2(PCI) | byte3(RSID) | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | MCU节点地址 | 0x06 | 0x62 | 0xFA | 0x35 | 错误码 | 0x00 | 0x00 |
说明:byte6字节为错误码定义,参照升级错误码定义。 |
MCU端flash分区如上图所示,boot为启动分区,app1为A分区,也是app启动运行的分区,app2为B分区,用于升级过程接收保存升级包数据的备份分区,data flash分区为用户数据保存区域和升级标志等信息保存区域。
MCU端接收升级数据包并写入B分区并累加CRC校验码,当升级包接收完成后计算的CRC校验码和PC端发送过来的校验码一致的话说明接收的数据包是完整的,写入升级标志置位、包总数和CRC等信息,并重启MCU。Boot启动时读取升级标志,包总数和CRC校验值,判断升级标志是否置位,如果置位则读取B分区内容并计算CRC校验值,如果计算的CRC校验值和保存的CRC校验值一致说明B分区数据完整无误。擦除A分区数据内容,将B分区数据拷贝到A分区。跳转到A分区运行。
上位机提供了本地OTA升级功能。用户可以在没有烧写工具的情况下通过LIN升级APP软件,升级界面如下图1所示,界面中有“升级节点探测”功能和“升级”功能两块。
其中“升级节点探测”功能是为了探测可升级的节点地址,为后续升级使用。如果探测到可升级的MCU节点,将在“探测节点地址列表”中显示。
探测节点完成后可以在列表中选中要升级的节点,后续对同一个MCU升级已经知道了节点地址就可以直接在“2”所在的位置直接填写节点地址不需要再去走探测流程。然后点击“加载升级文件”按钮选则需要升级的bin文件,Bin文件信息将在旁边的矩形框中显示。点击“开始升级”按钮开始进行升级。如果升级过程中出现错误,比如CRC校验错误等,可以再次点击“开始升级”按钮进行升级,升级过程比较慢,大概需要两三分钟。当所有的升级包都发送成功后会弹出如下图2所示的对话框提示用户是否需要写入升级标志并重启MCU,如果用户点击取“取消”按钮,升级包数据仅仅保存在B分区,并不会更新到A分区,升级过程实际并没有完成。用户点击“确定”按钮才真正重启MCU将B分区数据拷贝到A分区,并从A分区启动运行程序。
图1
图2
通过实现LIN诊断协议OTA升级功能,让我对LIN诊断协议有了更加深刻的理解。本文定义的协议也适用于OTA远程升级,对升级流程中有考虑不足的请指出,谢谢。