本帖最后由 dukedz 于 2018-6-6 22:48 编辑
之前公司的七轴机械臂内部使用 RS485 通讯,每个关节都是一个 RS485 节点,当时有个命令可以用来修改设备地址,同时目标地址为 255 的命令为广播命令,所有节点都会响应, 然后,我不小心发送了一个广播改地址的命令,结果所有关节地址都相同了,也就没办法再次通过命令单独修改各关节的地址,最终要把每个关节拆开重新配置,差点把手臂毁掉,因为关节拆开后,编码器的校準也失效了,所谓牵一发动全身,悔的肠子都青了,那以后,我都会为设备加上自动分配地址的功能。 传统很多设备都用硬件拨码开关来设置地址,然而很多产品譬如机械臂关节空间很小,没有地方放置拨码开关,而且很多产品增加拨码开关后外壳处理会十分麻烦,没有软件配置灵活。
为实现地址自动分配,首先要来选择基本的 RS485 通讯协议,多个节点通讯时,建议每条通讯命令都包含源地址和目的地址,这样做不易出错,也比较简洁和统一。 建议使用 CDBUS 格式,你可能没有听过这个名字,但你可能曾经或正在使用相似的协议,它的组成包含 3 个部分: - 3 个字节的头:「源地址,目标地址,用户数据长度」
- 0~255 字节的用户数据(因为数据长度用 1 个字节表示)
- 2 个字节的 CRC 校验,涵盖整个数据包,校验算法同 ModBus.
数据包与数据包之间要有一定的空闲时间,来隔开不同的数据包,详细请参见 CDBUS 的协议定义:https://github.com/dukelec/cdbus_ip (CDBUS 最大的好处是支持硬件控制器增强,主动避让冲突,支持并发读写、多主、对等通讯、数据主动上报等。)
譬如地址 0x00 为主机,0x01 为 1 号从机,那么主机发送两个字节数据 0x10 0x11 给 1 号从机的完整数据为:
[00 01 02 10 11 49 f0] 然后 1 号从机回覆单个 0x10 给主机:
[01 00 01 10 04 b8] 注:CDBUS 中地址 255 为广播地址;总线用户数据长度通常限制在 253 字节,方便用于硬件控制器及串口转发数据。
然而 CDBUS 只是最底层的协议,接下来我们要定义上述用户数据的格式,最简单常用的方式就是首字节为命令号,然后后面跟可选命令参数; 回覆数据第一个字节通常为状态,然后是返回的数据。 这种方式完善之后也有一个名字,叫 CDNET, 它除了上面说的最基本的形式之外,还支持类似电脑的网络端口形式、支持多网络、确保数据完整性、大数据拆包等功能, 有兴趣可以详见:https://github.com/dukelec/cdnet 而这篇**只涉及 CDNET 最基本的部分,举个例子,譬如命令 0x01 的定义是查询设备信息:
[00 01 01 01 91 b4] 返回设备信息的字符串:
[01 00 0f 40 4d 3a 20 … 34 crc_l crc_h] - 40 表示当前数据包为回覆(区分请求,详见 CDNET 定义);
- 4d 到 34 对应的字符串为 "M: c1; S: 1234"(M 后跟设备型号,S 后面跟设备序列号,也就是唯一码),用字符串比较方便,可一次返回所有信息,也方便扩充,譬如增加版本号,设备端实现也很简便。
接下来开始进入主题,我们需要定义两个命令,一个用来查询设备信息,一个用来设置设备地址, 查询设备信息的 0x01 命令上面已经说了一半,也就是没有跟参数的情况下,直接返回设备信息, 它还可以跟以下 4 个参数来配合地址自动分配:
[max_time, mac_start, mac_end, "filter string"] - max_time 是两个字节,单位毫秒的时间长度,设备生成一个不大于此数的随机数,等待相应随机时间才能回覆;
- mac_start 和 mac_end 是需要当前设备地址介于这两个数之间时,才能回覆;
- "filter string" 是需要当前设备信息包含此字串的情况下,才能回覆。(方便用于查询特定设备当前的地址)
跟参数与不跟参数都是返回相同格式的设备信息字符串。
另一个命令是设置地址,命令号为 0x03, 它有 3 个参数,第 3 个为可选:
[0x00, new_mac, "filter string"] - 0x00 是代表 mac 地址设置,因为 CDNET 可以跨网,所以还有其它值代表设置网络号等;
- new_mac 为设置的新地址;
- "filter string" 同样是需要当前设备信息包含此字串的情况下,才能执行命令,否则忽略且不返回。
返回空数据表示成功(40 表示当前数据包为回覆这个还是要的;返回之后才改变地址)。
具体的地址自动分配流程是这样的: - 先用带参数的 0x01 命令来查询总线上有哪些设备,过滤字串可以为空,地址范围可以默认为 1 ~ 254. 如果设备比较多,需要指定相对较大的等待时间范围,减少冲突的可能性,因为正常通讯时每个节点在发送数据前会先确保总线空闲,所以冲突的概率会比较低(不检测总线空闲也完全可以;另外也可以使用 CDBUS 的硬件控制器 CDCTL 进一步降低空闲检测的死区时间)(如果发送前检测到总线忙,那就等空闲立刻发送,虽然重新生成新的等待时间可以降低冲突概率,但会增加流程复杂度,没有必要);
- 扫描到的设备信息都会包含唯一码,那么把目标设备的唯一码字串当作设置地址的第 3 个过滤参数,那么便可以成功修改目标设备的地址,即使当前地址被多个设备佔用。(主机不是收到回复立马分配地址,而是等最大等待时间到了之后,确保大家都回复完了之后,再慢慢整理信息、分配地址。)
- 指定地址范围不仅可以减少冲突,同时也可以用来忽略已经分配好地址的设备,譬如 1~9 的地址已经分配好了,那么接下来就只扫描 10 以后的地址范围。
- 最后还要记得改变主机地址到一个非 0x00 的地址,然后反过来测试一下是否有其它设备佔用了 0x00 地址。当然,如果你的代码规定设备地址永远不为 0x00 也就可以直接忽略此步骤。
扫描过程一般要多扫描几次,确保不会因为冲突等因素漏掉一些回覆,通常要连续扫描 3 次得到相同结果才行。 以上命令的具体定义可以参见上面的 CDNET 连接,里面有包含;
而具体的实现代码可以参考 CDBUS Bridge 的固件:https://github.com/dukelec/cdbus_bridge
(相关代码在 fw/usr/common_services.c 路径)
This work is licensed under a Creative Commons Attribution 4.0 International License.
原文地址:http://blog.dukelec.com/rs485-auto-addressing-zh
|