基于设备唯一标识符的CAN网络临时编址方法
1. 案例概述
在制作在线升级软件的CAN通信协议时,为了能够对多个MCU进行同时升级,并且可靠地获取每个MCU的升级状态,需要对MCU的CAN节点进行编址。假如我们只想实现每次仅仅升级一个MCU,那就没必要对其进行编址了。
假如我们想要每次升级多个MCU,并且采用对正确结果不回复、只对错误结果回复的方式,也不必编址。只有升级失败的MCU回复失败标志,但这样统计的结果却是不可靠的。
2. 问题原因分析
在过去,曾经使用拨码开关,对电池均衡装置的24个模块手工编址。但是拨码开关并不是标准配置,退一步说,作为通用Boot loader当然也不应该依赖拨码开关,所以要考虑一种不需要拨码开关的自动编址方法。我们使用的STM系列芯片,每个芯片有一个全球唯一的“设备唯一标识符”(Unique Device ID),简称UDID。这是能够可靠地区分各个芯片的数据。
接下来考虑CAN通信的特点。CAN通信是一种不分主机从机的通信,所有节点都可以主动发送报文,这是其他通信方式,比如以485为例的串口等,所不具备的。为了防止多个节点同时发送不同报文产生的错误,CAN通信采用仲裁机制对报文的ID进行仲裁。因为CAN通信的仲裁机制不对DLC和DATA进行仲裁,所以我们不可以通过DATA传递UDID信息的。上位机要想获取所有MCU的UDID信息,MCU就必须将UDID信息放置在CAN报文的ID中。
3. 解决方案
我们分析一下STM系列芯片的UDID的特点,以及CAN报文ID的特点。UDID是96位的整数,而CAN报文ID分两种情况,标准帧有11位,扩展帧有29位。为了每条报文能够容纳尽可能多的信息,我们采用扩展帧格式。将96位的UDID分成若干组,分别放置在CAN报文ID中,只要简单的算术就能算出,至少要拆成4组,每组24位。这样,ID剩余了5位,这5位能够表达32种命令,对一个简单的Boot loader来说,已经足够了。我们通过4条不同的召唤报文,将4组UDID片段分别召唤上来。上位机要做的事情,当然就是压缩了,将每个24位的片段压缩成n位,然后将压缩前的片段和压缩后的片段,它们的对应关系广播给全体MCU。MCU收到所有的对应关系,对照自身的UDID,算出属于自己的4组压缩后的片段。MCU将4组压缩后的片段,再次拼接起来,得到4n位的压缩UDID,作为自己的地址,传送给上位机。上位机收到地址,便可知道对应MCU的存在。
首先考虑压缩的办法,这里使用最单纯的占座法。将收到的片段,按照时间顺序,从1开始对号入座。
接下来考虑n的取值,要想将4n位全部放置在CAN的29位ID中,n能取到的最大值是7,这样理论上可以区分128个不同的MCU。但这样会对CAN报文的ID空间造成很大破坏。退而求其次,n取值为6,这样理论上可以区分64个不同的MCU,4组压缩后的地址片段共占用24位,这和压缩前的每个地址片段的位数恰好是一样的,也便于制作协议。考虑到地址中不宜出现全0和全1,以及保留一些情况以备扩展,我们编号时,从1到60进行编号,这样就避开了全0的0号和全1的63号,同时保留了61号和62号备用。目前我们的产品,同时连接60个已经足够了。
上位机收到24位的压缩UDID后,将其拆成4组,每组6位,并各自解压缩,便可还原出真实的UDID。
这里其实涉及一个说法问题。两片MCU的UDID一定不同,但是其分成4份之后,1片MCU的某1份很可能和其他MCU的对应位置的1份相同。考虑最极端的情况,我们连接了60 × 60 × 60 × 60个MCU(暂不考虑CAN网络本身容纳节点的能力),而它们的UDID在压缩后,每个UDID片段,在去除重复后都只剩60个不同的。这样的结果,使得所有MCU都正常识别了。但我们不能因此而宣称我们支持60 × 60 × 60 × 60个MCU的同时升级,我们只能宣称我们支持60个MCU可以同时升级。为了避免多于60个的MCU的成功编址,上位机要最后加一个确认报文,认可60个以内的MCU的地址,否决第60个以后的MCU的地址。
4. 实践情况
定义上位机与MCU的通信协议。上位机不必给自己编址,只要注意避免发生ID冲突即可。下面是协议中有关编址的部分。
4.1. 请求UDID
上位机向MCU请求各自的UDID。每次召唤其中的1组。
表 4-1 由PC发给MCU的UDID请求
ID[28:24] |
0x13 |
BYTE0 |
UDID_NO |
请求ID的组号 |
ID[23:16] |
0x00 |
|
|
|
ID[15:8] |
0x00 |
|
|
|
ID[7:0] |
0x13 |
|
|
|
DLC |
1 |
|
|
|
请求UDID的组号的有效取值及其含义如下表:
表 4-2 请求UDID的组号与片段的关系
UDID_NO |
0x14 |
0x15 |
0x16 |
0x17 |
UDID片段 |
UDID[95:72] |
UDID[71:48] |
UDID[47:24] |
UDID[23:0] |
MCU收到请求后,回送对应的UDID片段。
表 4-3 由MCU发给PC的UDID回应
UDID_NO |
0x14 |
0x15 |
0x16 |
0x17 |
ID[28:24] |
0x14 |
0x15 |
0x16 |
0x17 |
ID[23:16] |
UDID[95:88] |
UDID[71:64] |
UDID[47:40] |
UDID[23:16] |
ID[15:8] |
UDID[87:80] |
UDID[63:56] |
UDID[39:32] |
UDID[15:8] |
ID[7:0] |
UDID[79:72] |
UDID[55:48] |
UDID[31:24] |
UDID[7:0] |
DLC |
1 |
1 |
1 |
1 |
BYTE0 |
0x00 |
0x00 |
0x00 |
0x00 |
4.2. 分配地址
上位机通过收集UDID的报文,统计每个UDID片段并各自独立编号:
1°统计UDID[95:72]的数据,去掉重复数据后,以报文中的时间标识为准,取不超过60个数据,从1开始分配编号,这个编号记做ADDR_I;
2°统计UDID[71:48]的数据,去掉重复数据后,以报文中的时间标识为准,取不超过60个数据,从1开始分配编号,这个编号记做ADDR_J;
3°统计UDID[47:24]的数据,去掉重复数据后,以报文中的时间标识为准,取不超过60个数据,从1开始分配编号,这个编号记做ADDR_K;
4°统计UDID[23:0]的数据,去掉重复数据后,以报文中的时间标识为准,取不超过60个数据,从1开始分配编号,这个编号记做ADDR_L。
表 4-4 由PC发给MCU的分配ADDR_I报文
ID[28:24] |
0x13 |
BYTE0 |
UDID[95:88] |
UDID[95:72] |
ID[23:16] |
0x00 |
BYTE1 |
UDID[87:80] |
|
ID[15:8] |
0x00 |
BYTE2 |
UDID[79:72] |
|
ID[7:0] |
0x14 |
BYTE3 |
ADDR_I |
地址片段I |
DLC |
4 |
|
|
|
表 4-5 由PC发给MCU的分配ADDR_J报文
ID[28:24] |
0x13 |
BYTE0 |
UDID[71:64] |
UDID[71:48] |
ID[23:16] |
0x00 |
BYTE1 |
UDID[63:56] |
|
ID[15:8] |
0x00 |
BYTE2 |
UDID[55:48] |
|
ID[7:0] |
0x15 |
BYTE3 |
ADDR_J |
地址片段J |
DLC |
4 |
|
|
|
表 4-6 由PC发给MCU的分配ADDR_K报文
ID[28:24] |
0x13 |
BYTE0 |
UDID[47:40] |
UDID[47:24] |
ID[23:16] |
0x00 |
BYTE1 |
UDID[39:32] |
|
ID[15:8] |
0x00 |
BYTE2 |
UDID[31:24] |
|
ID[7:0] |
0x16 |
BYTE3 |
ADDR_K |
地址片段K |
DLC |
4 |
|
|
|
表 4-7 由PC发给MCU的分配ADDR_L报文
ID[28:24] |
0x13 |
BYTE0 |
UDID[23:16] |
UDID[23:0] |
ID[23:16] |
0x00 |
BYTE1 |
UDID[15:8] |
|
ID[15:8] |
0x00 |
BYTE2 |
UDID[7:0] |
|
ID[7:0] |
0x17 |
BYTE3 |
ADDR_L |
地址片段L |
DLC |
4 |
|
|
|
地址片段与地址的拼接关系如下表:
表 4-8 地址片段与地址的拼接关系
ADDR |
ADDR_I |
ADDR_J |
ADDR_K |
ADDR_L |
DEV |
DEV[23:18] |
DEV[17:12] |
DEV[11:6] |
DEV[5:0] |
MCU得到完整的装置地址后,将自身的内置Flash容量回复给上位机。
表 4-9 由MCU发给PC的设备信息
ID[28:24] |
0x0C |
BYTE0 |
FLASH_KB[15:8] |
Flash容量/KB |
ID[23:16] |
DEV[23:16] |
BYTE1 |
FLASH_KB[7:0] |
|
ID[15:8] |
DEV[15:8] |
BYTE2 |
USER_VER[15:8] |
User版本 |
ID[7:0] |
DEV[7:0] |
BYTE3 |
USER_VER[7:0] |
|
DLC |
8 |
BYTE4 |
0xFF |
保留备用 不做验证 |
|
|
BYTE5 |
0xFF |
|
|
|
BYTE6 |
0xFF |
|
|
|
BYTE7 |
0xFF |
User版本:STM32F107版本是0xF107,STM32F439版本是0xF439,……
上位机据此报文,按照时间顺序取60个有效地址,发送允许升级报文。
表 4-10 由PC发给MCU的允许升级报文
ID[28:24] |
0x13 |
BYTE0 |
DEV[23:16] |
装置地址 |
ID[23:16] |
0x00 |
BYTE1 |
DEV[15:8] |
|
ID[15:8] |
0x00 |
BYTE2 |
DEV[7:0] |
|
ID[7:0] |
0x0C |
BYTE3 |
UP_ALLOW |
升级允许标志 |
DLC |
4 |
|
|
|
其中:DEV是允许升级的装置地址;
UP_ALLOW是升级允许标志,0x55表示允许升级,0xAA表示禁止升级。5. 效果评价
效果和我们预期的一致。
拿12个MCU进行测试,截取上位机记录的编址部分的报文如下:
TX: 13000013 14
RX: 14431337 00
RX: 14431337 00
RX: 14431339 00
RX: 14431556 00
RX: 14431557 00
RX: 14431757 00
RX: 14432042 00
TX: 13000013 15
RX: 15253232 00
RX: 15343135 00
RX: 15403135 00
RX: 15413135 00
RX: 15433135 00
RX: 15483135 00
RX: 15503135 00
RX: 15513135 00
RX: 15523135 00
RX: 15533135 00
RX: 15593135 00
RX: 15613135 00
TX: 13000013 16
RX: 164E3405 00
RX: 16524105 00
TX: 13000013 17
RX: 17D2FF32 00
RX: 17D5FF32 00
RX: 17D6FF32 00
RX: 17D6FF34 00
RX: 17D7FF32 00
RX: 17DAFF32 00
TX: 13000014 43 13 37 01
TX: 13000014 43 13 39 02
TX: 13000014 43 15 56 03
TX: 13000014 43 15 57 04
TX: 13000014 43 17 57 05
TX: 13000014 43 20 42 06
TX: 13000015 25 32 32 01
TX: 13000015 34 31 35 02
TX: 13000015 40 31 35 03
TX: 13000015 41 31 35 04
TX: 13000015 43 31 35 05
TX: 13000015 48 31 35 06
TX: 13000015 50 31 35 07
TX: 13000015 51 31 35 08
TX: 13000015 52 31 35 09
TX: 13000015 53 31 35 0A
TX: 13000015 59 31 35 0B
TX: 13000015 61 31 35 0C
TX: 13000016 4E 34 05 01
TX: 13000016 52 41 05 02
TX: 13000017 D2 FF 32 01
RX: 0C045081 01 00 F1 07
TX: 13000017 D5 FF 32 02
RX: 0C046082 01 00 F1 07
RX: 0C048082 01 00 F1 07
RX: 0C049082 01 00 F1 07
RX: 0C0CB082 01 00 F1 07
RX: 0C10C082 01 00 F1 07
TX: 13000017 D6 FF 32 03
RX: 0C043083 01 00 F1 07
RX: 0C14A083 01 00 F1 07
TX: 13000017 D6 FF 34 04
RX: 0C181044 01 00 F1 07
TX: 13000017 D7 FF 32 05
RX: 0C082085 01 00 F1 07
RX: 0C047085 01 00 F1 07
TX: 13000017 DA FF 32 06
RX: 0C044086 01 00 F1 07
TX: 1300000C 04 50 81 55
TX: 1300000C 04 60 82 55
TX: 1300000C 04 80 82 55
TX: 1300000C 04 90 82 55
TX: 1300000C 0C B0 82 55
TX: 1300000C 10 C0 82 55
TX: 1300000C 04 30 83 55
TX: 1300000C 14 A0 83 55
TX: 1300000C 18 10 44 55
TX: 1300000C 08 20 85 55
TX: 1300000C 04 70 85 55
TX: 1300000C 04 40 86 55
上位机成功整理出这12片MCU的UDID,如下(分4组以十六进制显示):
431337.433135.524105.D2FF32
431337.483135.524105.D5FF32
431337.513135.524105.D5FF32
431337.523135.524105.D5FF32
431556.593135.524105.D5FF32
431557.613135.524105.D5FF32
431337.403135.524105.D6FF32
431757.533135.524105.D6FF32
432042.253232.4E3405.D6FF34
431339.343135.524105.D7FF32
431337.503135.524105.D7FF32
431337.413135.524105.DAFF32
我们看一看上位机发出的第一条报文,即TX: 13000013 14。
理论上应该收到12条报文回复,但实际上只收到了7条,可以看出,其中有很多条,由于报文内容完全一致,在报文仲裁时同时通过仲裁,使得上位机只收到了1条。但也并非所有的相同报文都这样合并了,实际上我们收到了两条RX: 14431337 00。
经过多次调整和测试,为了能够可靠认出全部的MCU,我们需要将报文的等待时间放得很长。采用1000kbps的比特率,每条召唤报文需要等200毫秒。6. 推广建议
这种自动编址方案,对于自动升级这类一次性使用的场合,非常合适。
但是,对于需要长久运行的场合,这种自动编址方案并不适合。首先这需要一个扮演上位机的节点,进行地址分配;这个地址分配会很耗时;由于所有地址都是临时地址,所以任何一个节点,一旦因为任何原因而丢失地址,就必须令整个网络重新进行地址分配,否则这个节点便再也无法回到网络中。
要想将这种自动编址方案应用到产品中,还需要改良。
参考资料
《RM0008 Reference manual STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced ARM?-based 32-bit MCUs》
《RM0090 Reference manual STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced ARM?-based 32-bit MCUs》