[485通信]

我设计的 485 地址自动分配方案,大家给点意见?

[复制链接]
31635|58
手机看帖
扫描二维码
随时随地手机跟帖
dukedz|  楼主 | 2018-6-3 23:39 | 显示全部楼层 |阅读模式
本帖最后由 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_ipCDBUS 最大的好处是支持硬件控制器增强,主动避让冲突,支持并发读写、多主、对等通讯、数据主动上报等。

譬如地址 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

相关帖子

dukedz|  楼主 | 2018-6-4 00:13 | 显示全部楼层
之前设计了好几个方案,需要加专门的命令来实现地址自动分配,实现起来要写比较多代码,麻烦;
这一版只需借用两个已有的命令就可以实现,无论是命令定义还是代码实现都相对简洁很多。

使用特权

评论回复
jeekstudio| | 2018-6-4 07:57 | 显示全部楼层
学习了

使用特权

评论回复
Leeone| | 2018-6-4 09:14 | 显示全部楼层
这个还是在板子已经有ID的情况下进行自动分配吧,在什么情况下需要重新分配ID

使用特权

评论回复
dukedz|  楼主 | 2018-6-4 14:47 | 显示全部楼层
Leeone 发表于 2018-6-4 09:14
这个还是在板子已经有ID的情况下进行自动分配吧,在什么情况下需要重新分配ID ...

没有 ID、ID 错乱、ID 冲突、新接入设备不知道 ID 是多少的各种情况都可以用。

譬如很多软件可设置 ID 的设备,接入总线之前要单独接电脑,去配置 ID,
除非接电脑用独立的 RS232、USB 等接口,否则就可能会出现我说的那种不小心把所有设备 ID 改成同一个了的状况。

而且单独接电脑配置好 ID 放在一边,装配的时候万一不小心装错顺序也很麻烦,我之前就遇到过很多次。

支持总线分配地址的话,就简单高效很多,省去单独接电脑配置的步骤,随便装配,无论是出厂默认 ID 还是不知道当前 ID 改为多少了,都没关系。

需要改变已经分配好的 ID 也很方便,譬如在已经安装的 10 台设备的中间加入一台新的设备,依然可以调整为连续的顺序,而不用把 ID 顺序搞的很乱。

使用特权

评论回复
Leeone| | 2018-6-4 17:51 | 显示全部楼层
dukedz 发表于 2018-6-4 14:47
没有 ID、ID 错乱、ID 冲突、新接入设备不知道 ID 是多少的各种情况都可以用。

譬如很多软件可设置 ID  ...

比如总线上新装了10个设备,刷过程序初始ID都是0,能支持同时给10个设备重新分配ID 1-10 ??

使用特权

评论回复
dukedz|  楼主 | 2018-6-4 18:31 | 显示全部楼层
Leeone 发表于 2018-6-4 17:51
比如总线上新装了10个设备,刷过程序初始ID都是0,能支持同时给10个设备重新分配ID 1-10 ?? ...

没错,就是这个功能。

使用特权

评论回复
Leeone| | 2018-6-5 11:12 | 显示全部楼层
"filter string" 是需要当前设备信息包含此字串的情况下,才能回覆。 这个是指10个设备里面都有特定的字符信息,假如10个设备里面的程序都是一模一样的广播出去不就不行了

使用特权

评论回复
dukedz|  楼主 | 2018-6-5 12:53 | 显示全部楼层
本帖最后由 dukedz 于 2018-6-5 13:59 编辑
Leeone 发表于 2018-6-5 11:12
"filter string" 是需要当前设备信息包含此字串的情况下,才能回覆。 这个是指10个设备里面都有特定的字符 ...

解决方法很多种,通常做法是取 CPU ID 信息,每个片子都不同。

如果片子没有 CPU ID 唯一码,也可以取周边设备,譬如蓝牙、WiFi 等 MAC 地址。

如果这些都没有,可以代码里面放一个特定的数据,烧录工具每次烧录时动态搜寻并替换 BIN 文件中对应的数据,这样程序运行的时候取到的数据就是不同的了。也可以用烧录工具单独写 FLASH、写 EEPROM、写 OTP 区域等等。(通常是烧录工具配合条码扫描枪来录入唯一码。)

还有一个不需要唯一码的做法,就是每次开机随机生成一个序例码,譬如 6 或 8 个字节,那么重复的可能性也是很小的了。就算重复我们还有随机时间回复的机制,所以多扫描几次也能确保检测出来,万一真遇到重复的情况,发一个重启命令让部分或全部设备重新生成序例码即可。

要知道,唯一码不仅仅只是用来做地址分配,同时可以实现产品售后管理等需求,也可以用来做一些防抄保护,也让你的产品看起来更专业。

使用特权

评论回复
dukedz|  楼主 | 2018-6-5 13:06 | 显示全部楼层
本帖最后由 dukedz 于 2018-6-5 13:20 编辑

譬如一个 STM32F105 的产品,它正常工作情况下返回的产品信息为:
'M: cdbus bridge; S: 43ff8d50d475535314334234; SW: 85163ab'

烧录模式下返回(开机默认烧录模式,3 秒钟无操作跳转用户代码):
'M: cdbus bridge (bl); S: 43ff8d50d475535314334234; SW: 85163ab'

STM32 的 CPU ID 是 12 字节,看起来稍微有点长,不过好用就好。顺便说一下,如果总线不同设备使用的 CPU 不同,唯一码长度不同也没关系,只要能过滤字符串就可以。

SW 是软件版本的缩写,每次编译自动根据 Git 信息生成,因为还没有打 tag 所以是 hash 值,打了 tag 之后就可以是 v1.0 这种形式了。
很多同行都是手动改代码里面定义的版本号,每次修改代码都要手动改,麻烦不说还容易忘记、改错。
而使用 Git 信息自动生成,譬如本地有临时修改没有提交,都会自动标记出来以提醒,方便日后追踪。

使用特权

评论回复
兰天白云| | 2018-6-5 13:17 | 显示全部楼层
2.扫描到的设备信息都会包含唯一码,那么把目标设备的唯一码字串当作设置地址的第 3 个过滤参数,那么便可以成功修改目标设备的地址,即使当前地址被多个设备佔用
楼主这样做前提是设备有自己的唯一码,如果没有就很难区分很相似的两个设备,最终导致分配地址失败

使用特权

评论回复
评论
dukedz 2018-6-5 13:21 回复TA
有方法的,见 9 楼给出的解决方案。 
兰天白云| | 2018-6-5 13:22 | 显示全部楼层
如果设备有重复地址,楼主用“max_time 是两个字节,单位毫秒的时间长度,设备生成一个小于此数的随机数,等待相应随机时间才能回覆”根据时间差,让先回复的设备被分配地址,后回复的设备始终在侦测总线的空闲状态,这是先回复的设备和主机不能让总线出现空闲,否则后回复的设备会发送数据导致主机和先回复的设备通讯被搞乱

使用特权

评论回复
dukedz|  楼主 | 2018-6-5 14:02 | 显示全部楼层
本帖最后由 dukedz 于 2018-6-5 14:04 编辑
兰天白云 发表于 2018-6-5 13:22
如果设备有重复地址,楼主用“max_time 是两个字节,单位毫秒的时间长度,设备生成一个小于此数的随机数, ...

不着急收到回复立马分配地址,而是等最大等待时间到了之后,确保大家都回复完了之后,再慢慢整理信息、分配地址。

使用特权

评论回复
Leeone| | 2018-6-5 15:10 | 显示全部楼层
dukedz 发表于 2018-6-5 12:53
解决方法很多种,通常做法是取 CPU ID 信息,每个片子都不同。

如果片子没有 CPU ID 唯一码,也可以取周 ...

明白你的做法,一般场合是使用的

使用特权

评论回复
mohanwei| | 2018-6-5 16:54 | 显示全部楼层
这种距离不长的场合,最好是串口菊花链+自动地址递增。

使用特权

评论回复
dukedz|  楼主 | 2018-6-5 18:19 | 显示全部楼层
本帖最后由 dukedz 于 2018-6-5 18:22 编辑
mohanwei 发表于 2018-6-5 16:54
这种距离不长的场合,最好是串口菊花链+自动地址递增。

你说的这种很早之前用过,转发效率很低,同步性也很差,最要命的是抗干扰差,哪怕半米不到,电机电流的干扰会导致很多错包。。。

使用特权

评论回复
mohanwei| | 2018-6-5 19:40 | 显示全部楼层
dukedz 发表于 2018-6-5 18:19
你说的这种很早之前用过,转发效率很低,同步性也很差,最要命的是抗干扰差,哪怕半米不到,电机电流的干 ...

这个要看个人软硬件水平……
中断+状态机,每个节点延迟1字节,可忽略不计
电机电流能干扰到丢包就太扯了……不敢想象硬件是怎么设计的

使用特权

评论回复
dukedz|  楼主 | 2018-6-5 19:54 | 显示全部楼层
mohanwei 发表于 2018-6-5 19:40
这个要看个人软硬件水平……
中断+状态机,每个节点延迟1字节,可忽略不计
电机电流能干扰到丢包就太扯了 ...

就市面很多桌面机械臂用的那种坨机,电源线比较细,而且一串就是 4、5 个,叠加的电流比较大,串口信号是 3.3V 的电平,突发的大电流会瞬间抬高地的电平,导致串口的参考地变化,进而引发数据出错。记得他们测试的时候 10000 个数据包会错掉近 100 个包。(坨机独立供电就不会有错包,所以基本可以排除软件问题。)

理论上,差分就是防止这种干扰导致的影响,如果你说那只是用于远距离,那为何 USB 特别是非高速的年代,那么近距离为何要用差分?

使用特权

评论回复
mohanwei| | 2018-6-5 21:29 | 显示全部楼层
dukedz 发表于 2018-6-5 19:54
就市面很多桌面机械臂用的那种坨机,电源线比较细,而且一串就是 4、5 个,叠加的电流比较大,串口信号是 ...

地线内阻(截面、长度、材质),最大电流,信号电平,噪声容限……起码要有这些基本概念并且会计算,要不谈何设计

使用特权

评论回复
icecut| | 2018-6-5 22:50 | 显示全部楼层
用广播包修改设备地址这种设计是个坑.lz 不去把坑堵上....反而搞了一个比较繁琐的 id 自动分配.
我想说 lz 等设计出来就知道现在的想法离着商用还有多远....

使用特权

评论回复
评论
mycomputer0000 2018-6-21 15:58 回复TA
正点 
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:支持仲裁的高速 RS485: https://github.com/dukelec/cdbus_ip

10

主题

165

帖子

5

粉丝