首页 / 资讯中心 / 文章详情

机器人关节CAN协议设计:八个字节如何精准控制电机

机器人关节CAN协议设计:八个字节如何精准控制电机 ★ FEATURED ARTICLE
1. 从八个字节说起为什么CAN协议是机器人关节控制的命脉搞机器人关节控制的人绕不开一个话题上位机怎么把“我要你转到什么位置、用多大力、多快速度”这些指令准确无误地塞进只有八个字节的CAN数据帧里然后让电机驱动器准确理解并执行。这八个字节看起来少得可怜但在机器人关节的实时控制场景里它承载的信息量远超很多人的想象。我最早接触机器人关节控制的时候犯过一个很典型的错误以为CAN协议就是“把数据发出去对面收到就行”。结果调试的时候发现电机要么不动要么乱动要么偶尔动一下然后又停了。排查了半天硬件接线、电源、终端电阻最后发现问题出在协议约定上——我发出去的八个字节和驱动器期望收到的八个字节在字节序、缩放因子、单位定义上完全对不上。驱动器把我的“位置指令”当成了“电流指令”不炸机已经算给面子了。这篇文章就是围绕这个核心问题展开的CAN总线上的八个字节到底怎么约定才能让机器人关节电机准确理解并执行目标指令。我会从协议设计的基本思路讲起拆解字节分配、数据编码、缩放因子、通信周期这些关键环节然后给出可以直接参考的实操方案和排查技巧。不管你是刚入行做关节驱动的嵌入式工程师还是做机器人系统集成需要对接不同品牌驱动器的开发者这些内容应该都能帮你少走一些弯路。需要提前说明的是不同厂商的CAN协议在细节上差异很大有的用CANopen有的用自定义协议有的在CANopen基础上做了私有扩展。我下面讲的内容是基于常见工业实践和主流方案做的合理归纳具体参数和字节定义你需要以实际使用的驱动器手册为准。但底层的设计逻辑和排查思路是通用的理解了这些你拿到任何一份新的协议文档都能快速上手。2. 协议设计的核心逻辑八个字节怎么分才够用2.1 为什么是八个字节而不是更多或更少经典CAN帧的数据场就是八个字节这是协议本身规定的不是谁拍脑袋决定的。CAN FD虽然把数据场扩展到了最多64字节但在机器人关节控制这个领域经典CAN仍然是主流原因有几个一是关节控制对实时性要求极高八个字节的短帧传输时间短、确定性好二是很多电机驱动器的CAN控制器就是经典CAN硬件成本低、生态成熟三是八个字节对于大多数关节控制场景来说已经够用了。那八个字节到底够不够我们算一笔账。一个机器人关节通常需要控制的核心量包括目标位置、目标速度、目标力矩或电流、以及一些使能、模式切换、故障复位之类的控制字。如果每个量用两个字节16位表示四个量就是八个字节刚好用完。如果位置用四个字节32位表示那就只剩四个字节给其他量这时候就需要做取舍或者分帧发送。注意分帧发送会引入延迟和同步问题在高速控制场景下要慎重。如果确实需要传输超过八个字节的数据优先考虑升级到CAN FD或者换用其他通信方式而不是硬拆。2.2 字节分配的基本原则控制字、模式、目标值我见过不少自定义协议字节分配的逻辑大致遵循这样一个框架第一个字节放控制字使能、启动、停止、复位、刹车等第二个字节放运行模式位置模式、速度模式、力矩模式、回零模式等剩下的六个字节放目标值。如果只控制一个量比如纯位置模式那六个字节可以全部用来表示位置精度可以做到很高。如果要同时控制位置和速度那就各分三个字节或者位置四个字节、速度两个字节。这里有一个关键决策点目标值是放在同一帧里还是分不同帧发送。同一帧的好处是同步性好位置和速度同时更新适合插补运动或者力位混合控制。分帧的好处是每个量可以用更多字节表示精度更高但需要处理好帧与帧之间的时间差否则会出现“位置更新了但速度还是旧的”这种不同步问题。我的经验是对于大多数关节控制场景同一帧里放位置和速度或者位置和力矩就足够了。位置用三个字节24位速度用两个字节16位剩下一个字节放控制字和模式这样分配比较均衡。24位的位置分辨率对于旋转关节来说如果减速比是100:1输出端的分辨率可以到0.0001度级别完全够用。2.3 缩放因子与单位约定最容易被忽略的坑字节分配只是第一步真正容易出问题的是缩放因子和单位约定。同样两个字节表示速度有的驱动器约定单位是“转每分钟”有的约定是“弧度每秒”有的约定是“编码器计数每毫秒”。如果你不搞清楚这个发出去的数值和驱动器理解的数值可能差几十倍甚至几百倍。我踩过的一个坑是这样的驱动器手册上写“速度指令单位是0.1转每分钟”我没注意那个“0.1”直接按“转每分钟”发了一个数值100结果驱动器理解成10转每分钟电机转得慢悠悠的我还以为是电机功率不够。后来仔细看手册才发现问题。所以每次拿到新的驱动器我第一件事就是找到手册里关于“单位”和“缩放因子”的章节把每个量的物理单位和数值对应关系搞清楚。缩放因子的计算通常是这样假设速度范围是-3000到3000转每分钟用16位有符号整数表示那缩放因子就是3000除以32767约等于0.0916。也就是说你发出去的数值乘以0.0916就是实际的转每分钟值。反过来你想让电机转1000转每分钟就要发1000除以0.0916约等于10917。这个计算过程看起来简单但在实际调试中因为缩放因子搞错导致电机飞车或者不动的情况非常常见。提示建议在协议文档里明确写出每个量的“物理单位”、“数值范围”、“缩放因子”和“偏移量”并且用表格形式呈现。不要只写“速度指令”要写“速度指令单位0.1转每分钟范围-30000到30000对应-3000到3000转每分钟”。3. 核心细节拆解从字节到物理量的完整映射3.1 字节序问题大端还是小端字节序是另一个高频踩坑点。同样四个字节表示一个32位整数大端模式和小端模式解析出来的数值完全不同。比如字节序列是0x12 0x34 0x56 0x78大端解析出来是0x12345678小端解析出来是0x78563412差了十万八千里。CAN协议里字节序没有统一规定有的厂商用大端有的用小端有的甚至在不同字段上用不同的字节序。我遇到过最离谱的情况是位置指令用大端速度指令用小端手册里还没写清楚只能靠抓包和试错来确认。怎么确认字节序最可靠的方法是看手册如果手册没写就发一个已知数值比如发0x00 0x00 0x00 0x01看驱动器收到的是1还是16777216。如果是1说明是大端如果是16777216说明是小端。这个方法简单粗暴但有效。注意多字节字段的字节序问题在跨平台通信时尤其容易出问题。如果你的上位机是x86架构小端驱动器是ARM架构通常也是小端但可配置两边不一致就会出错。建议在协议设计阶段就统一约定字节序并在文档里明确写出来。3.2 有符号数与无符号数的处理位置、速度、力矩这些量通常是有符号的因为关节可以正转也可以反转。有符号数的表示方法通常是二进制补码但也有一些驱动器用“偏移量”的方式比如0x8000表示零位0x0000表示负满量程0xFFFF表示正满量程。这两种方式在解析时完全不同。二进制补码的好处是运算方便直接按有符号整数解析就行。偏移量方式的好处是可以充分利用无符号数的范围但解析时需要先减去偏移量。我个人的偏好是二进制补码因为大多数编程语言和处理器都原生支持不容易出错。有一个细节需要注意如果你用Python或者MATLAB做上位机解析有符号数的时候要确保数据类型正确。比如用Python的struct模块解析格式字符串要用h有符号16位而不是H无符号16位否则负数会被解析成很大的正数。3.3 控制字与状态字的位定义控制字和状态字通常是按位定义的一个字节的八个位分别代表不同的功能。比如控制字的bit0是使能bit1是启动bit2是停止bit3是复位bit4是刹车等等。状态字的bit0是就绪bit1是运行中bit2是故障bit3是到位等等。这种位定义的方式很高效一个字节可以表达八种不同的控制命令或状态信息。但问题是不同厂商的位定义完全不同甚至同一厂商的不同型号驱动器位定义也可能有差异。我见过一个案例某驱动器的控制字bit3是“复位”另一个型号的bit3是“急停”结果换驱动器的时候没注意一发复位指令电机就急停了。所以每次对接新驱动器我都会做一张位定义对照表把控制字和状态字的每个位都列出来标注清楚功能。这张表在调试和排查问题时非常有用可以快速定位是哪个位出了问题。位控制字功能示例状态字功能示例bit0使能就绪bit1启动运行中bit2停止到位bit3复位故障bit4刹车警告bit5模式切换使能状态bit6保留保留bit7保留保留提示这张表只是示例实际定义以驱动器手册为准。建议在代码里用宏定义或者枚举类型来表示每个位不要直接写魔法数字否则后期维护会很痛苦。3.4 通信周期与超时处理CAN通信是周期性的上位机每隔一定时间发一帧指令驱动器收到后执行并回复状态。通信周期的选择很关键太短了总线负载高太长了控制精度差。对于机器人关节控制常见的通信周期是1毫秒到10毫秒。1毫秒适合高动态场景比如足式机器人的关节控制10毫秒适合对实时性要求不那么高的场景比如机械臂的轨迹跟踪。超时处理是另一个关键点。如果驱动器超过一定时间没收到指令应该自动进入安全状态比如停止运动或者保持当前位置。这个超时时间通常设为通信周期的3到5倍。比如通信周期是1毫秒超时时间设为3到5毫秒。如果上位机因为某种原因卡住了驱动器不会一直执行最后的指令而是会停下来避免发生危险。我遇到过因为超时时间设置不当导致的问题超时时间设得太短总线偶尔丢一帧驱动器就报超时故障停机设得太长上位机已经死机了驱动器还在执行最后的指令结果撞到了限位。所以这个参数需要根据实际场景仔细调整。4. 实操过程从零搭建一套CAN关节控制协议4.1 硬件准备与基础环境搭建先说一下硬件环境。你需要一台上位机可以是工控机、树莓派或者带CAN接口的嵌入式板卡一个CAN分析仪比如常见的USB-CAN盒子用来抓包和调试一个机器人关节驱动器以及配套的电机和电源。接线方面CAN_H接CAN_HCAN_L接CAN_L两端各接一个120欧姆的终端电阻。这个终端电阻很重要不接的话通信会不稳定甚至完全通不了。软件环境方面Linux下通常用SocketCANWindows下用厂商提供的CAN库或者Python的can库。我个人的习惯是用Python做快速验证因为开发效率高配合can库和struct库几行代码就能把协议跑通。等验证完了再把逻辑移植到C或者C里做正式产品。import can import struct # 初始化CAN总线 bus can.interface.Bus(channelcan0, bustypesocketcan) # 构造一帧指令控制字0x01使能模式0x01位置模式位置目标100000 control_word 0x01 mode 0x01 position 100000 # 24位有符号整数 # 打包成八个字节 data struct.pack(BBI, control_word, mode, position) # 注意这里用表示大端B是无符号字节I是无符号32位整数 # 实际使用时要根据协议调整格式字符串 msg can.Message(arbitration_id0x100, datadata, is_extended_idFalse) bus.send(msg)上面这段代码是一个最简单的示例实际使用时要根据你的协议调整字节序、数据长度和字段顺序。比如位置是24位而不是32位就需要手动处理字节拼接。4.2 协议文档的解读与参数提取拿到一份驱动器CAN协议文档我通常会按以下步骤提取关键信息第一步找到“通信帧格式”章节确认使用的是标准帧还是扩展帧仲裁ID是多少数据长度是八个字节还是可变。第二步找到“数据场定义”章节把每个字节对应的字段列出来。通常会有控制字、模式、目标值、实际值等字段。第三步找到“单位与缩放”章节把每个字段的物理单位、数值范围、缩放因子、偏移量记录下来。第四步找到“状态字与故障码”章节把状态字的位定义和故障码的含义整理成表格。第五步找到“通信周期与超时”章节确认推荐的通信周期和超时时间。这五步做完你手里就有一份完整的协议摘要了。我习惯把这份摘要写成一个Markdown文档放在项目仓库里方便团队其他人查阅。4.3 发送第一帧指令从使能到运动第一次调试的时候不要一上来就发运动指令。正确的顺序是先发使能指令确认驱动器状态字变成“就绪”再发模式设置指令确认驱动器进入正确的模式最后发运动指令从小数值开始逐步加大。我通常会用这样一个调试流程发送控制字0x00确认驱动器上电后状态字正常。发送控制字0x01使能确认状态字的“使能状态”位变成1。发送模式设置指令比如位置模式确认状态字的“模式”字段正确。发送一个很小的位置指令比如让电机转1度观察电机是否动作。逐步加大位置指令观察电机运动是否平滑是否有异响。发送停止指令确认电机停止。发送复位指令确认故障被清除。这个流程看起来简单但每一步都可能出问题。比如使能指令发了但驱动器没反应可能是控制字的位定义搞错了电机动作了但方向反了可能是位置指令的符号搞反了电机运动不平滑可能是缩放因子不对或者通信周期太长。提示调试的时候一定要用CAN分析仪抓包把发送和接收的每一帧都记录下来。这样出问题的时候可以回放分析比凭空猜测高效得多。4.4 参数计算实例位置指令的缩放与打包假设你的关节驱动器位置指令范围是-180度到180度用24位有符号整数表示缩放因子是180除以8388607约等于0.00002146度。你想让电机转到90度计算过程如下90除以0.00002146约等于4193840。把这个数转换成24位有符号整数的十六进制表示就是0x3FFFB0。然后按大端字节序打包成三个字节0x3F 0xFF 0xB0。如果协议规定位置指令放在第3、4、5字节从0开始计数那完整的八个字节就是控制字、模式、0x3F、0xFF、0xB0、保留、保留、保留。这个计算过程看起来简单但实际调试中因为缩放因子搞错、字节序搞错、有符号数处理错误导致的问题非常多。我的建议是写一个专门的函数来处理打包和解包把所有的转换逻辑集中在一处方便测试和修改。def pack_position(position_deg, scale180.0/8388607): 将角度值打包成24位有符号整数的三个字节 raw int(position_deg / scale) # 限制范围 raw max(-8388608, min(8388607, raw)) # 转换成二进制补码 if raw 0: raw (1 24) raw # 按大端打包 return bytes([(raw 16) 0xFF, (raw 8) 0xFF, raw 0xFF]) def unpack_position(data_bytes, scale180.0/8388607): 将三个字节解包成角度值 raw (data_bytes[0] 16) | (data_bytes[1] 8) | data_bytes[2] # 处理二进制补码 if raw 0x800000: raw raw - (1 24) return raw * scale上面这两个函数可以直接用在你的项目里只需要根据实际协议调整scale参数和字节序即可。5. 常见问题与排查技巧实录5.1 电机不动或乱动从协议层面排查电机不动或者乱动是最常见的问题。排查思路可以按以下顺序进行首先确认硬件连接。CAN_H和CAN_L有没有接反终端电阻有没有接电源电压是否正常。这些问题看起来低级但实际调试中占比很高。然后确认通信是否正常。用CAN分析仪抓包看上位机发出的帧驱动器有没有回复。如果没有回复可能是仲裁ID不对或者驱动器没有进入CAN通信模式。如果通信正常但电机不动检查控制字和模式设置。使能位有没有置1模式设置是否正确目标值是否在有效范围内。如果电机乱动检查缩放因子和字节序。发一个已知数值看驱动器收到的实际值是多少和预期差多少反推缩放因子或字节序哪里出了问题。我遇到过一个案例电机偶尔动一下然后停排查发现是通信周期不稳定上位机有时候1毫秒发一帧有时候10毫秒才发一帧驱动器超时保护触发了。后来把上位机的发送逻辑改成定时器驱动问题就解决了。5.2 通信丢帧与总线负载CAN总线虽然可靠性高但在负载高的时候也会丢帧。总线负载率的计算公式是负载率等于所有帧的传输时间之和除以总时间。一帧标准CAN帧大约需要100到130微秒取决于波特率和帧长度如果通信周期是1毫秒那一帧的负载率大约是10%到13%。如果总线上有多个关节每个关节都发一帧负载率就会成倍增加。一般来说总线负载率建议控制在50%以下留出余量应对突发情况。如果负载率太高可以采取以下措施提高波特率从500k提高到1M减少不必要的帧合并多个关节的指令到一帧里如果协议支持或者升级到CAN FD。丢帧的另一个原因是终端电阻不匹配或者线缆太长。CAN总线的最大通信距离和波特率有关500k波特率下最大距离约100米1M波特率下约40米。如果线缆太长信号反射会导致通信错误。这时候需要加终端电阻或者使用CAN中继器。5.3 状态字反馈异常的处理状态字反馈异常通常表现为状态字一直是0或者状态字的某些位一直不变或者状态字跳变频繁。排查思路如下先确认驱动器是否真的在发送状态帧。用CAN分析仪抓包看有没有来自驱动器的帧。如果没有可能是驱动器没有配置为主动上报状态或者仲裁ID不对。如果驱动器有发送状态帧但状态字一直是0可能是状态字的位定义搞错了。对照手册确认每个位的含义看是不是解析错了。如果状态字跳变频繁可能是通信干扰或者电源不稳定。检查线缆屏蔽、接地、电源纹波。我遇到过因为电机电源和CAN电源共地导致状态字跳变的情况后来加了隔离模块就稳定了。提示状态字的解析建议做成可配置的把位定义放在配置文件里这样换驱动器的时候只需要改配置不用改代码。5.4 常见问题速查表问题现象可能原因排查方法解决方案电机不动使能未置位检查控制字bit0发送使能指令电机不动模式设置错误检查模式字段设置为正确模式电机乱动缩放因子错误发已知值反推修正缩放因子电机乱动字节序错误发0x00000001测试调整字节序通信丢帧总线负载高计算负载率提高波特率或减少帧通信丢帧终端电阻缺失检查两端电阻加120欧姆电阻状态字异常位定义错误对照手册修正解析逻辑状态字异常电源干扰检查接地和屏蔽加隔离模块超时故障通信周期不稳抓包看时间间隔改用定时器发送超时故障超时时间太短检查超时参数调整为周期3到5倍5.5 几个容易被忽略的实操心得第一个心得每次修改协议参数后一定要重新抓包确认。我见过太多因为改了代码但忘了重新烧录或者改了配置但没重启导致调试方向完全错误的情况。抓包是最可靠的验证手段。第二个心得保留一份“已知正确”的抓包记录。当系统工作正常的时候把发送和接收的帧都保存下来。以后出问题的时候和这份记录对比能快速定位差异。第三个心得不要迷信手册要动手验证。手册上写的和实际驱动器行为不一致的情况并不少见尤其是小厂商的产品。手册说缩放因子是0.1实际可能是0.01这种时候只能靠实测。第四个心得协议文档要版本化管理。驱动器的固件升级后协议可能发生变化。如果没有版本管理升级后出现的问题很难排查。建议在代码里记录协议版本号和驱动器固件版本对应起来。6. 协议扩展与多关节协同的思考单个关节的协议跑通之后下一步通常是多关节协同。这时候会遇到新的问题多个关节的指令怎么同步总线仲裁怎么分配状态反馈怎么汇总。一个常见的做法是给每个关节分配不同的仲裁ID上位机按顺序发送指令。但这样会有时间差第一个关节和最后一个关节的指令接收时间可能差几百微秒。对于低速场景问题不大对于高速协同场景这个时间差会导致运动不同步。更好的做法是使用CAN FD的广播帧或者用支持时间戳的协议让所有关节在同一时刻执行指令。有些驱动器支持“同步帧”功能上位机先发一帧同步信号所有关节收到后同时执行之前收到的指令。这种方式可以做到微秒级的同步精度。另一个思路是把多个关节的指令合并到一帧里发送。比如一帧八个字节可以放两个关节的位置指令每个三字节剩下两个字节放控制字。这样一帧就能控制两个关节同步性更好。但缺点是每个关节的指令精度降低了而且关节数量多了之后还是需要分帧。我在实际项目中用过的一种方案是用CAN FD一帧64字节可以放八个关节的位置和速度指令同步性很好。但前提是驱动器和上位机都支持CAN FD硬件成本会高一些。协议设计这件事没有绝对的最优解只有最适合当前场景的方案。关键是理解底层逻辑知道每个设计决策的 trade-off 在哪里然后根据实际需求做取舍。八个字节也好六十四个字节也好核心都是把物理量的语义准确地映射到字节序列上让发送方和接收方达成一致。这个一致性就是协议的本质。
阅读完成 · 觉得有帮助?
咨询建站