一、目标
在上篇文章实现基于UDS LIN诊断协议的本地OTA升级-CSDN博客博客中已经基于LIN UDS诊断协议实现了通过PC端上位机对MCU进行本地的OTA升级。本篇将在上篇文章的基础上实现基于UDS 诊断协议的CAN本地OTA升级。本篇文章对实现的目的、需要用到的第三方工具请查看之前的博客相应章节,本文不再赘述。本文对CAN诊断帧和LIN诊断帧区别、升级协议、MCU端升级过程以及PC端升级过程做详细说明。
二、LIN和CAN诊断的区别
LIN诊断是通过节点地址(NAD)进行寻址,而CAN诊断是通过物理地址进行寻址。LIN诊断响应地址即为节点地址。而CAN响应的物理地址需要约定好。比如定义CAN诊断物理地址为0x700,MCU响应地址为0x701。
从发送格式上,LIN诊断帧(单帧)的PDU 单元包括:NAD+PCI+SID+D1~D5
而CAN诊断帧(单帧)的PDU单元包括:PCI+SID+D1~D6
可以比较直观的看出CAN诊断帧比LIN诊断帧在PDU单元数据上少了NAD的定义,因为物理地址可作为ID发送,不需要单独的节点地址定义。
三、升级协议
3.1 升级时序如下图所示
3.2 升级协议诊断帧数据定义
(1)切换扩展会话: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x10 | 0x03 | \ | \ | \ | \ | \ |
切换到扩展会话:0x10(会话控制) ; 0x03(扩展会话) | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x50 | 0x03 | \ | \ | \ | \ | \ |
(2)读取软件版本: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x03 | 0x22 | 0xFB | 0x88 | \ | \ | \ | \ |
通过ID读取数据: 0x22(通过ID读取); 0xFB88(自定义读取软件版本号的DID) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x06 | 0x62 | 0xFB | 0x88 | XX | YY | ZZ | \ |
软件版本号:XX.YY.ZZ | ||||||||
(3)停用DTC存储功能: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x85 | 0x02 | \ | \ | \ | \ | \ |
停用DTC存储功能: 0x85(故障码控制设置);0x02(停止故障码存储) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0xC5 | 0x02 | \ | \ | \ | \ | \ |
(4)切换到编程会话: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x10 | 0x02 | \ | \ | \ | \ | \ |
切换到编程会话:0x10(会话控制) ; 0x02(编程会话)。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x50 | 0x02 | \ | \ | \ | \ | \ |
(5)安全访问请求种子: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x27 | 0x01 | \ | \ | \ | \ | \ |
安全访问请求种子: 0x27(安全访问);0x01(请求种子) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x67 | 0x01 | XX | XX | XX | XX | \ |
XX XX XX XX为获取的安全种子 | ||||||||
(6)安全访问验证秘钥: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x06 | 0x27 | 0x02 | YY | YY | YY | YY | \ |
安全访问验证key: 0x27(安全访问);0x02(验证key) YY YY YY YY:为获取的安全访问种子经过算法转换后的key 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x67 | 0x02 | \ | \ | \ | \ | \ |
(7)检查刷写条件: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x04 | 0x31 | 0x01 | 0x02 | 0x03 | \ | \ | \ |
检查刷写前条件: 0x31(例程控制);0x01(启动例程);0x0203(自定义RID表示检查电压状态) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x04 | 0x71 | 0x01 | 0x02 | 0x03 | \ | \ | \ |
(8)擦除升级分区: | ||||||||
PC端请求 | byte1(PCI) | byte2(LEN) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
首帧 | 0x10 | 0x0d | 0x31 | 0xff | 0x00 | 0x00 | 0x44 | XX |
续帧1 | 0x21 | XX | XX | XX | YY | YY | YY | YY |
擦除升级分区: 0x31(例程控制);0x01(启动例程);0xff00(自定义RID表示擦除升级分区);0x44(地址4字节,大小4字节);XX XX XX XX(4字节擦除起始地址);YY YY YY YY(4字节擦除分区大小) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x04 | 0x71 | 0x01 | 0xff | 0x00 | \ | \ | \ |
(9)请求下载: | ||||||||
PC端请求 | byte1(PCI) | byte2(LEN) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
首帧 | 0x10 | 0x0b | 0x34 | 0x00 | 0x44 | XX | XX | XX |
续帧1 | 0x21 | XX | YY | YY | YY | YY | \ | \ |
请求数据: 0x34(请求下载);0x00(没有压缩和加密算法);0x44(地址4字节,大小4字节);XX XX XX XX(4字节擦除起始地址);YY YY YY YY(4字节擦除分区大小) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x04 | 0x74 | 0x20 | 0x00 | 0x42 | \ | \ | \ |
0x20(包长度2字节表示);0x0042(一包66字节,包括SID+包序号,也就是实际一包数据64字节) | ||||||||
(10)升级包数据指令: | ||||||||
PC端请求 | byte1(PCI) | byte2(LEN) | byte3(SID) | byte4 | byte5 | byte6 | byte7 | byte8 |
首帧 | 0x10 | 0x42 | 0x36 | 0x01 | D1 | D2 | D3 | D4 |
续帧1 | 0x21 | D5 | D6 | D7 | D8 | D9 | D10 | D11 |
续帧2 | 0x22 | D12 | D13 | D14 | D15 | D16 | D17 | D18 |
续帧3 | 0x23 | D19 | D20 | D21 | D22 | D23 | D24 | D25 |
续帧4 | 0x24 | D26 | D27 | D28 | D29 | D30 | D31 | D32 |
续帧5 | 0x25 | D33 | D34 | D35 | D36 | D37 | D38 | D39 |
续帧6 | 0x26 | D40 | D41 | D42 | D43 | D44 | D45 | D46 |
续帧7 | 0x27 | D47 | D48 | D49 | D50 | D51 | D52 | D53 |
续帧8 | 0x28 | D54 | D55 | D56 | D57 | D58 | D59 | D60 |
续帧9 | 0x29 | D61 | D62 | D63 | D64 | \ | \ | \ |
传输第1包数据: 0x36(传输数据);0x01(数据包序列号);D1~D64(64字节升级包数据),最后一包数据如果没有64字节则用0xFF填充 | ||||||||
MCU端应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x76 | 0x01 | \ | \ | \ | \ | \ |
(11)传输退出: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x01 | 0x37 | \ | \ | \ | \ | \ | \ |
请求传输退出: 0x37(传输退出) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x01 | 0x77 | \ | \ | \ | \ | \ | \ |
(12)检查数据包完整性: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x06 | 0x31 | 0x01 | 0x02 | 0x02 | 校验码高8位 | 校验码低8位 | \ |
检查升级数据包完整性: 0x31(例程控制);0x01(启动例程);0x0202(自定义RID表示检查数据包是否完整) ; crc_h/l(2字节CRC校验值)。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x04 | 0x71 | 0x01 | 0x02 | 0x02 | \ | \ | \ |
(13)重启MCU指令: | ||||||||
PC端请求 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x11 | 0x03 | \ | \ | \ | \ | \ |
ECU复位: 0x11(复位); 0x03(软件复位) 。 | ||||||||
MCU端肯定应答 | byte1(PCI) | byte2(SID) | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
单帧 | 0x02 | 0x51 | 0x03 | \ | \ | \ | \ | \ |
说明:byte6字节为错误码定义,参照升级错误码定义。 |
四、MCU端升级流程
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分区运行。
五、PC端升级过程
上位机提供了本地OTA升级功能。用户可以在没有烧写工具的情况下通过CAN升级APP软件,升级界面如下图1所示。
其中“升级节点探测”功能是为了探测可升级的节点地址,为后续升级使用。如果探测到可升级的MCU节点,将在“探测节点地址列表”中显示。
在“节点地址”所在的编辑框直接填写节点地址。然后点击“加载升级文件”按钮选则需要升级的bin文件,Bin文件信息将在旁边的矩形框中显示。点击“开始升级”按钮开始进行升级。如果升级过程中出现错误,比如CRC校验错误等,可以再次点击“开始升级”按钮进行升级,升级过程比较慢,大概需要两三分钟。当所有的升级包都发送成功后会弹出对话框提示用户是否需要写入升级标志并重启MCU,如果用户点击取“取消”按钮,升级包数据仅仅保存在B分区,并不会更新到A分区,升级过程实际并没有完成。用户点击“确定”按钮才真正重启MCU将B分区数据拷贝到A分区,并从A分区启动运行程序。
注:有需要整套代码,包括MCU UDS LIN协议栈及上位机程序欢迎咨询。