1.接线&信号电压及其逻辑规范
CAN_H&CAN_L是CAN的通讯线,基于差分信号组成,以确保数据的准确性。其中通讯的电缆线最好双绞。
CAN_H&CAN_L的低电平时逻辑1,高电平时逻辑0,该信号标准和串口有所区别。
电压差为2V时,显性电平,表示逻辑0
电压差为0V时,隐性电平,表示逻辑1
差分信号的好处:
数据在传输过程中,如果受到外界强影响,该影响同时作用在CAN_H&CAN_L两条线上,那么电压差不变。所以为了保证电缆中的信号同时受到影响,还需要让CAN_H&CAN_L两线双绞传输。
2.通讯数据帧
2.1通讯帧格式
起始位:1位bit
默认无通讯时,通讯逻辑位是1,当设备需要进行CAN通讯时,起始位被数据被置零
识别码:11位bit/29位bit
对应11位标准帧格式和29位扩展帧格式。识别码中包含设备优先级。
RTR位:1位bit
RTR=1时,该数据帧为数据类型帧
RTR=0时,该数据针为远程请求帧
控制码:6位bit
bit(0),IDE位用来区分标准帧格式和扩展帧格式,bit(0)=0 对应标准帧格式,bit(0)=1 对应扩展帧格式
bit(1),空闲位,目前固定逻辑0.
bit(2)~~bit(5),DLC位,即Date Link Control,二进制编码对应者0~~8,即后面跟着的据码长度,该长度最大8个字节。bit(2)~~bit(5)=8时,数据码长度就是8个字节长度,8*8=64bit。
CRC码:16位bit
这是为了确保数据准确性设置的
bit(0)~~bit(14)区域是CRC校验码,设备接收端根据数据计算得出CRC位,计算的CRC和接收的CRC不一致,说明数据存在问题,需要重新发送数据帧
bit(15) CRC界定符,固定逻辑1.目的是把CRC前的数据和CRC后面的数据分隔开。
ACK码:2位bit
bit(0) ACK确认槽,发送端发送的是逻辑1,接收端发送0作为应答。
bit(1) ACK界定位,固定逻辑1.隔开后方数据。
结束位:7位
bit(0)~~bit(6)数据全部为 1 ,表示数据帧传输结束。
2.2标准帧结构
2.3扩展帧结构
2.4数据帧汇总
该数据帧中核心的数据分成两大类,一类是硬件CAN接口自动处理部分数据,另一类是用户自定义数据
自动处理数据:
- 起始位
- CRC码
- ACK码
- 结束位
用户自定义数据:
- 识别码
- RTR位
- 控制码
其中CAN的报文中自动处理数据因为受CAN的接口控制,不需要调整。用户自定义数据则无需严格按照标准去做。
自定义的格式可以依据业务需求进行修改。
3.用户自定义数据
自定义数据类型以小米微电机为例
3.1小米微电机基础报文格式
数据域 | 29位ID | 29位ID | 29位ID | 8Byte数据区 |
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 通信类型 | 数据区2 | 目标地址 | 数据区1 |
摘抄自手册
3.2通讯协议类型说明
3.2.1获取设备ID(通信类型0)
获取设备ID和64位MCU唯一标识符
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 0 | bit15~8:标识主机 CAN_ID | 目标电机 CAN_ID | 0 |
应答帧:
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 0 | 目标电机 CAN_ID | 0xFE | 64位MCU标识 |
3.2.2运动模式电机控制指令(通信类型1)
用来向电机发送控制指令
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 1 | Byte2:力矩(0~65535) 对应(-12Nm~12Nm) | 目标电机 CAN_ID | Byte0~1: 目标角度[0~65535]对应(-4π~4π) Byte2~3: 目标角速度[0~65535]对应(-30rad/s~30rad/s) Byte4~5:Kp [0~65535]对应(0.0~500.0) Byte6~7:Kd [0~65535]对应(0.0~5.0) |
应答帧:应答电机反馈帧(见通信类型2)
3.2.3电机反馈数据(通信类型2)
用来向主机反馈点击运行状态
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 2 | Bit8~Bit15:当前电机CAN ID bit21~16:故障信息(0无 1有) bit21: 未标定 bit20: HALL编码故障 bit19: 磁编码故障 bit18: 过温 bit17: 过流 bit16: 欠压故障 bit22~23:模式状态 0 : Reset模式[复位] 1 : Cali 模式[标定] 2 : Motor模式[运行] | 主机CAN _ID Byte0 | Byte0~1: 当前角度 [0~65535]对应(-4π~4π) Byte2~3: 当前角速度 [0~65535]对应(-30rad/s~30rad/s) Byte4~5:当前力矩 [0~65535]对应(-12Nm~12Nm) Byte6~7:当前温度:Temp(摄氏度)*10 |
3.2.4电机使能运行 (通信类型3)
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 3 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID |
应答帧:应答电机反馈帧(见通信类型2)
3.2.5电机停止运行 (通信类型4)
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 4 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID | 正常运行时,data区需清0; Byte[0]=1时:清故障; |
应答帧:应答电机反馈帧(见通信类型2)
3.2.6设置电机机械零位(通信类型6)
会把当前电机位置设为机械零位(掉电丢失)
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 6 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID | Byte[0]=1 |
应答帧:应答电机反馈帧(见通信类型2)
3.2.7设置电机CAN_ID(通信类型7)
更改当前电机CAN_ID , 立即生效。
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 7 | bit15~8:用来标识主CAN_ID Bit16~23: 预设置CAN_ID | 目标电机 CAN_ID |
应答帧:应答电机反馈帧(见通信类型2)
3.2.8单个参数读取(通信类型17)
更改当前电机CAN_ID , 立即生效。
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 17 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID | Byte0~1: index,参数列表详见4.1.11 Byte2~3: 00 Byte4~7: 00 |
应答帧:
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 17 | bit15~8:目标电机CAN_ID | 主机CAN_ID | Byte0~1: index,参数列表详见4.1.11 Byte2~3: 00 Byte4~7: 参数数据,1字节数据在Byte4 |
3.2.9单个参数写入(通信类型18) (掉电丢失)
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 18 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID | Byte0~1: index,参数列表详见4.1.11 Byte2~3: 00 Byte4~7: 参数数据 |
应答帧:应答电机反馈帧(见通信类型2)
3.2.10故障反馈帧(通信类型21)
数据域 | 29位ID | 8Byte数据区 | ||
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 21 | bit15~8:用来标识主CAN_ID | 目标电机 CAN_ID | Byte0~3: fault值(非0:有故障,0:正常) bit16:A相电流采样过流 bit15~bit8:过载故障 bit7:编码器未标定 bit5:C相电流采样过流 bit4:B相电流采样过流 bit3:过压故障 bit2:欠压故障 bit1:驱动芯片故障 bit0:电机过温故障,默认80度 Byte4~7: warning值 bit0:电机过温预警,默认75度 |
应答帧:应答电机反馈帧(见通信类型2)
4.通讯代码实现
4.1数据定义
#define P_MIN -12.5f
#define P_MAX 12.5f
#define V_MIN -30.0f
#define V_MAX 30.0f
#define KP_MIN 0.0f
#define KP_MAX 500.0f
#define KD_MIN 0.0f
#define KD_MAX 5.0f
#define T_MIN -12.0f
#define T_MAX 12.0f
struct exCanIdInfo{
uint32_t id:8;
uint32_t data:16;
uint32_t mode:5;
uint32_t res:3;
};
can_receive_message_struct rxMsg;
can_trasnmit_message_struct txMsg={
.tx_sfid = 0,
.tx_efid = 0xff,
.tx_ft = CAN_FT_DATA,
.tx_ff = CAN_FF_EXTENDED,
.tx_dlen = 8,
};
#define txCanIdEx (((struct exCanIdInfo)&(txMsg.tx_efid)))
#define rxCanIdEx (((struct exCanIdInfo)&(rxMsg.rx_efid))) //将扩展帧id解析为自定义数据结构
int float_to_uint(float x, float x_min, float x_max, int bits){
float span = x_max - x_min;
float offset = x_min;
if(x > x_max) x=x_max;
else if(x < x_min) x= x_min;
return (int) ((x-offset)*((float)((1<<bits)-1))/span);
}
#define can_txd() can_message_transmit(CAN0, &txMsg)
#define can_rxd() can_message_receive(CAN0, CAN_FIFO1, &rxMsg)
4.2常见数据类型代码
4.2.1电机使能运行帧(通信类型3)
void motor_enable(uint8_t id, uint16_t master_id)
{
txCanIdEx.mode = 3;
txCanIdEx.id = id;
txCanIdEx.res = 0;
txCanIdEx.data = master_id;
txMsg.tx_dlen = 8;
txCanIdEx.data = 0;
can_txd();
}
4.2.2运控模式电机控制指令(通信类型1)
void motor_controlmode(uint8_t id, float torque, float MechPosition, float speed, float kp, float kd)
{
txCanIdEx.mode = 1;
txCanIdEx.id = id;
txCanIdEx.res = 0;
txCanIdEx.data = float_to_uint(torque,T_MIN,T_MAX,16);
txMsg.tx_dlen = 8;
txMsg.tx_data[0]=float_to_uint(MechPosition,P_MIN,P_MAX,16)>>8;
txMsg.tx_data[1]=float_to_uint(MechPosition,P_MIN,P_MAX,16);
txMsg.tx_data[2]=float_to_uint(speed,V_MIN,V_MAX,16)>>8;
txMsg.tx_data[3]=float_to_uint(speed,V_MIN,V_MAX,16);
txMsg.tx_data[4]=float_to_uint(kp,KP_MIN,KP_MAX,16)>>8;
txMsg.tx_data[5]=float_to_uint(kp,KP_MIN,KP_MAX,16);
txMsg.tx_data[6]=float_to_uint(kd,KD_MIN,KD_MAX,16)>>8;
txMsg.tx_data[7]=float_to_uint(kd,KD_MIN,KD_MAX,16);
can_txd();
}
4.2.3电机停止运行帧(通信类型4)
void motor_reset(uint8_t id, uint16_t master_id)
{
txCanIdEx.mode = 4;
txCanIdEx.id = id;
txCanIdEx.res = 0;
txCanIdEx.data = master_id;
txMsg.tx_dlen = 8;
for(uint8_t i=0;i<8;i++)
{
txMsg.tx_data[i]=0;
}
can_txd();
}
4.2.4电机模式参数写入命令(通信类型18,运行模式切换)
uint8_t runmode;
uint16_t index;
void motor_modechange(uint8_t id, uint16_t master_id)
{
txCanIdEx.mode = 0x12;
txCanIdEx.id = id;
txCanIdEx.res = 0;
txCanIdEx.data = master_id;
txMsg.tx_dlen = 8;
for(uint8_t i=0;i<8;i++)
{
txMsg.tx_data[i]=0;
}
memcpy(&txMsg.tx_data[0],&index,2);
memcpy(&txMsg.tx_data[4],&runmode, 1);
can_txd();
}
4.2.5电机模式参数写入命令(通信类型18,控制参数写入)
uint16_t index;
float ref;
void motor_write(uint8_t id, uint16_t master_id)
{
txCanIdEx.mode = 0x12;
txCanIdEx.id = id;
txCanIdEx.res = 0;
txCanIdEx.data = master_id;
txMsg.tx_dlen = 8;
for(uint8_t i=0;i<8;i++)
{
txMsg.tx_data[i]=0;
}
memcpy(&txMsg.tx_data[0],&index,2);
memcpy(&txMsg.tx_data[4],&ref,4);
can_txd();
}
4.3通讯帧解析
上文的数据定义中,定义了一个扩展ID信息结构体,并将发送的数据结合组装成一个一个整体的数据帧。
细节摘录:
//扩展帧结构体
struct exCanIdInfo{
uint32_t id:8;
uint32_t data:16;
uint32_t mode:5;
uint32_t res:3;
};
//接收数据
can_receive_message_struct rxMsg;
//发送数据
can_trasnmit_message_struct txMsg={
.tx_sfid = 0,
.tx_efid = 0xff,
.tx_ft = CAN_FT_DATA,
.tx_ff = CAN_FF_EXTENDED,
.tx_dlen = 8,
};
//扩展帧结构和数据结合组成完整的数据
#define txCanIdEx (((struct exCanIdInfo)&(txMsg.tx_efid)))
#define rxCanIdEx (((struct exCanIdInfo)&(rxMsg.rx_efid)))
struct exCanIdInfo{
uint32_t mode:5;
uint32_t id:8;
uint32_t res:3;
uint32_t data:16;
};
上诉写法是位域写法,定义了 uint32_t id:8; 的数据,但是只把32位数据存放在前8个bit中。
8+16+5+3=32符合扩展帧中仲裁字段的长度,也就是说这个结构体exCanIdInfo是为了构建仲裁字段的。
扩展帧中数据字段只控制数据长度,IDE功能别移除。
电机使能运行帧(通信类型3)
void motor_enable(uint8_t id, uint16_t master_id)
{
txCanIdEx.mode = 3;//选择控制模式
txCanIdEx.id = id;//8位ID,对应目标设备的ID
txCanIdEx.res = 0;
//发送端主机ID,同时包含一个扩展8位的数据
//16位数据
txCanIdEx.data = master_id;
txMsg.tx_dlen = 8;//发送数据的长度
txCanIdEx.data = 0;
can_txd();
}
数据域 | 29位ID | 29位ID | 29位ID | 8Byte数据区 |
大小 | bit28~24 | bit23~8 | bit7~0 | Byte0~Byte7 |
描述 | 通信类型 | 数据区2 | 目标地址 | 数据区1 |