跳转至

UAVCAN通信协议

本文为官方《UAVCAN通信协议》文档的转录。各系列内存表见 内存表参数,UAVCAN 系列内存表见 UAVCAN 内存表。

1 UAVCAN协议概述

UAVCAN是一种轻量级协议,基于CAN2.0B通信协议,传输速率高达1Mb/s,通过CAN总线可以为航空航天和机器人应用提供高度可靠的通信方法。UAVCAN是一个去中心化的对等网络,其中每个对等(节点)都有一个唯一的数字标识符NODE_ID。UAVCAN的节点可以使用以下任何通信方法进行通信:

  • 消息广播:具有发布/订阅语义的数据交换的主要方法。
  • 服务调用:对等请求/响应交互的通信方法。

UAVCAN通过串行化的消息和服务数据结构可把长传输数据自动分解为多个CAN帧,允许节点交换任意大小的数据结构。

使用CAN2.0B标准数据帧通信,出厂CAN默认波特率为1M,波特率可配置范围 10K~1M,通信地址,CAN波特率、通信站点地址可通过上位机软件进行设置。

2 UAVCAN指令

信息帧指令:

UAVCAN指令 UAVCAN签名 说明
341(0x0155) 无 节点状态指令 舵机心跳指令
1020(0x03FC) 无 扭矩开关指令
2011(0x07DB) 无 单电机位置控制指令
2012(0x07DC) 56 D7 8A D5 6C 8A 65 3A 多电机位置控制指令
2013(0x07DD) E4 81 9D 8E 5B 7B 80 65 数据反馈指令 自动上报指令
2014(0x07DE) 无 自动上报启停指令

服务帧指令:

UAVCAN指令 UAVCAN签名 说明
250(0xFA) 4F A9 E7 BE A3 6E B3 EC 参数读指令,参数为大端先行
251(0xFB) 8C E7 80 A1 F9 E4 C7 68 参数写指令,参数为大端先行
252(0xFC) 无 舵机重启指令

备注:当用多帧格式传输数据时,把签名加入到数据前面用于计算CRC

2.1 节点状态指令

CAN-ID DLC 数据
Rx 18015500+NODE_ID 8 帧计数(4字节),故障码(1字节),状态码(2字节),帧尾(1字节)
  • CAN-ID:默认值0x18015564,NODE_ID舵机节点ID默认值100,取值范围1~127,用于识别不同舵机的节点状态
  • 帧计数(4字节):0 ~ (2^32-1)的计数值,每发送一帧计数值加一
  • 故障码(1字节):取值如下
    • 致命故障:0xC0,磁编码出错、欠压
    • 重大故障:0x40,过载、过热
    • 小故障:0x80,过压
    • 无故障:0x00
  • 状态码(2字节):参考舵机状态码
  • 帧尾(1字节):C0+Transfer_ID,Transfer_ID范围0 ~ 31,每发送一帧Transfer_ID自动加一

备注¹:节点状态指令默认自动上报频率为1Hz,频率可通过FC软件修改 备注²:总线上的舵机NODE_ID必需保证是唯一,可通过FC软件修改

2.2 扭矩开关指令

CAN-ID DLC 数据
Tx 1003FC01 3 舵机通道(1字节),扭矩指令(1字节),帧尾(1字节)
  • 舵机通道(1字节):范围0 ~ 17
  • 扭矩指令(1字节):0表示关闭扭矩,1表示打开扭矩
  • 帧尾(1字节):C0

备注:位置指令会自动打开舵机扭矩,可通过此指令关闭舵机扭矩,使舵机进入自由状态

2.3 单电机位置控制指令

CAN-ID DLC 数据
Tx 1007DB01 4 舵机通道(1字节),位置数据(2字节),帧尾(1字节)
  • 舵机通道(1字节):范围0 ~ 17
  • 位置数据(2字节):范围-8192 ~ 8191,对应360度角度,1LSB:传感器精度,小端格式负数为补码
  • 帧尾(1字节):C0

备注:舵机的默认通道为0,可通过FC软件修改

2.4 多电机位置控制指令

2.4.1 控制少于3个舵机

CAN-ID DLC 数据
Tx 1007DC01 7 通道0位置(2字节),通道1位置(2字节),通道2位置(2字节),帧尾(1字节)
  • 通道x位置(2字节):x范围0 ~ 2,位置范围-8192 ~ 8191,对应360度角度,1LSB:传感器精度,小端格式负数为补码
  • 帧尾(1字节):C0

备注:指令中的舵机通道只能是0 ~ 2

2.4.2 控制多于3个舵机

CAN-ID DLC 数据
Tx1 1007DC01 8 校验码(2字节),通道0位置(2字节),通道1位置(2字节),通道2位置(低位字节),帧尾(1字节)
Tx2 1007DC01 8 通道2位置(高位字节),通道3位置(2字节),通道4位置(2字节),通道5位置(2字节),帧尾(1字节)
Tx3 1007DC01 8 通道6位置(2字节),通道7位置(2字节),通道8位置(2字节),通道9位置(低位字节),帧尾(1字节)
Tx4 1007DC01 8 通道9位置(高位字节),通道10位置(2字节),通道11位置(2字节),通道12位置(2字节),帧尾(1字节)
Tx5 1007DC01 8 通道13位置(2字节),通道14位置(2字节),通道15位置(2字节),通道16位置(低位字节),帧尾(1字节)
Tx6 1007DC01 4 通道16位置(高位字节),通道17位置(2字节),帧尾(1字节)
  • 校验码(2字节):参考校验码章节内容
  • 通道x位置(2字节):x范围0 ~ 17,位置范围-8192 ~ 8191,对应360度角度,1LSB:传感器精度,小端格式负数为补码
  • 帧尾(1字节):
    • Tx1:80,表示开始帧,BIT5 = 0
    • Tx2:20,表示中间帧,BIT5相对上一帧取反
    • Tx3:00,表示中间帧,BIT5相对上一帧取反
    • Tx4:20,表示中间帧,BIT5相对上一帧取反
    • Tx5:00,表示中间帧,BIT5相对上一帧取反
    • Tx6:60,表示结束帧,BIT5相对上一帧取反

备注:以上指令可控制18个舵机,实际应用中可根据舵机通道数量对指令进行增减

2.5 数据反馈自动上报指令

CAN-ID DLC 数据
Rx1 1807DD00+NODE_ID 8 校验码(2字节),舵机通道(1字节),目标位置(2字节),当前位置(2字节),帧尾(1字节)
Rx2 1807DD00+NODE_ID 8 舵机电压(2字节),舵机电流(2字节),舵机PCB温度(1字节),舵机电机温度(1字节),舵机状态(1字节),帧尾(1字节)
  • CAN-ID:默认值0x1807DD64,NODE_ID舵机节点ID默认值100,取值范围1~127,可通过FC软件修改,用于识别不同舵机的节点反馈数据
  • 舵机通道(1字节):范围0 ~ 17,舵机默认通道为0,可通过FC软件修改
  • 目标位置(2字节):范围-8192 ~ 8191,对应360度角度,1LSB:传感器精度,小端格式负数为补码
  • 当前位置(2字节):范围-8192 ~ 8191,对应360度角度,1LSB:传感器精度,小端格式负数为补码
  • 舵机电压(2字节):1LSB:0.1V
  • 舵机电流(2字节):1LSB:电流采样电路精度
  • 舵机PCB温度(1字节):舵机PCB板MOS温度,1LSB:1度
  • 舵机电机温度(1字节):1LSB:1度,舵机电机如不带温度传感器此数据无效
  • 舵机状态(1字节):
    • Bit0: 0表示正常,1表示驱动器工作异常,磁编码出错等,正常自动恢复
    • Bit1: 0表示正常,1表示uavcan指令错误,CRC出错、非法指令、非法帧,接收到正常指令自动恢复
    • Bit2: 0表示正常,1表示电机失力
    • Bit3: 0表示正常,1表示电机处于堵转/过载状态,扭矩正常自动恢复
    • Bit4: 0表示正常,1表示驱动器MOS处于过热状态,温度正常自动恢复
    • Bit5: 0表不正常,1表示电机处于过热状态,温度正常自动恢复
    • Bit6: 0表示正常,1表示欠压/过压,电压正常自动恢复
  • 帧尾(1字节):
    • Rx1:80+Transfer_ID,表示开始帧,BIT5 = 0
    • Rx2:60+Transfer_ID,表示结束帧,BIT5相对上一帧取反
    • Transfer_ID范围0 ~ 31,以上帧尾Transfer_ID相同,每接收完整的两帧后Transfer_ID自动加一

备注¹:数据反馈指令默认自动上报频率为10Hz,频率可通过FC软件修改 备注²:总线上的舵机NODE_ID必需保证是唯一,舵机通道可相同,相同通道一般是指执行相同动作的舵机;舵机NODE_ID与舵机通道可通过FC软件修改

2.6 自动上报启动/暂停指令

CAN-ID DLC 数据
Tx 1007DE01 3 舵机NODE_ID(1字节),开关指令(1字节),帧尾(1字节)
  • 舵机NODE_ID(1字节):范围0~127,0表示所有舵机
  • 开关指令(1字节):0表示暂停,5表示启动
  • 帧尾(1字节):C0

2.7 参数写指令

CAN-ID DLC 数据
Tx 10FB8081+(NODE_ID<<8) 4+L*2 参数地址(2字节),参数长度L(1字节),参数数据1(2字节) ... 参数数据L(2字节),帧尾(1字节)
Rx 10FB0180+NODE_ID 2 写入状态(1字节),帧尾(1字节)
  • CAN-ID:
    • Tx:0x10FB8081+(NODE_ID<<8),NODE_ID默认为100,CAN-ID默认值为0x10FBE481
    • Rx:0x10FB0180+NODE_ID,NODE_ID默认为100,CAN-ID默认值为0x10FB01E4
  • 参数地址(2字节):参考内存表地址
  • 参数长度L(1字节):参数长度L
  • 参数数据1(2字节):参考内存表数据
  • 参数数据L(2字节):参考内存表数据,L<=2
  • 写入状态(1字节):0表示正常,1表示地址无效,2表示参数无效
  • 帧尾(1字节):C0

2.8 参数读指令

CAN-ID DLC 数据
Tx 10FA8081+(NODE_ID<<8) 4 参数地址(2字节),参数长度L(1字节),帧尾(1字节)
Rx 10FA0180+NODE_ID 3+L*2 读取状态(1字节),参数长度L(1字节),参数数据1(2字节) ... 参数数据L(2字节),帧尾(1字节)
  • CAN-ID:
    • Tx:0x10FA8081+(NODE_ID<<8),NODE_ID默认为100,CAN-ID默认值为0x10FAE481
    • Rx:0x10FA0180+NODE_ID,NODE_ID默认为100,CAN-ID默认值为0x10FA01E4
  • 参数地址(2字节):参考内存表地址
  • 参数长度L(1字节):参数长度L
  • 参数数据1(2字节):参考内存表数据
  • 参数数据L(2字节):参考内存表数据,L<=2
  • 写入状态(1字节):0表示正常,1表示地址无效,2表示参数无效
  • 帧尾(1字节):C0

3 UAVCAN协议CRC校验

备注:本章节针对多帧指令中的校验码计算,单帧指令可跳过本节

多帧数据组成如下:

UAVCAN数据帧1 UAVCAN数据帧2 ... UAVCAN数据帧N
Byte0 ~ Byte7 Byte0 ~ Byte7 ... Byte0 ~ ByteX
  • UAVCAN数据帧1:Byte0与Byte1为CRC校验码(小端格式),Byte2 ~ Byte6有效数据,Byte7帧尾
  • UAVCAN数据帧2:Byte0 ~ Byte6有效数据,Byte7帧尾
  • UAVCAN数据帧N:Byte0 ~ Byte(X-1)有效数据,ByteX帧尾,X<8

参与CRC计算的数据组成如下:

UAVCAN签名 UAVCAN数据帧1 UAVCAN数据帧2 ... UAVCAN数据帧N
签名(8字节) Byte2 ~ Byte6 Byte0 ~ Byte6 ... Byte0 ~ Byte(X-1)
  • UAVCAN签名:不同指令签名不一样,签名编码详细查阅UAVCAN指令
  • UAVCAN数据帧1:Byte2 ~ Byte6有效数据参与CRC计算
  • UAVCAN数据帧2:Byte0 ~ Byte6有效数据参与CRC计算
  • UAVCAN数据帧N:Byte0 ~ Byte(X-1)有效数据参与CRC计算

CRC校验算法如下:

  • 校验格式:CRC-16-CCITT-FALSE
  • 参考资料:http://reveng.sourceforge.net/crc-catalogue/16.htm#crc.cat.crc-16-ccitt-false
  • 初始值:0xFFFF
  • 校验式:0x1021
  • 取反:否
  • 异或:0
/*
 * License: CC0, no copyright reserved.
 */

#include <iostream>
#include <cstdint>
#include <cassert>

class TransferCRC
{
    std::uint16_t value_;

public:
    TransferCRC()
      : value_(0xFFFFU)
    { }

    void add(std::uint8_t byte)
    {
        value_ ^= static_cast<std::uint16_t>(byte) << 8;
        for (std::uint8_t bit = 8; bit > 0; --bit)
        {
            if (value_ & 0x8000U)
            {
                value_ = (value_ << 1) ^ 0x1021U;
            }
            else
            {
                value_ = (value_ << 1);
            }
        }
    }

    void add(const std::uint8_t* bytes, unsigned len)
    {
        assert(bytes);
        while (len--)
        {
            add(*bytes++);
        }
    }

    std::uint16_t get() const { return value_; }
};

int main()
{
    TransferCRC crc;
    crc.add(reinterpret_cast<const std::uint8_t*>("123456789"), 9);
    std::cout << std::hex << "0x" << crc.get() << std::endl;
}