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请求