前言
本文从“使用RM开发板A型控制M3508电机”的角度来讲解CAN通信,对于底层原理的阐述较少,读者可以很容易地从网络上查询到相关的资料,本文不做赘述。
DrinkCat
1. CAN通信的硬件部分
1.1 所需硬件
要实现CAN通信,需要有
- CAN控制器
- CAN收发器
STM32都有CAN控制器,但并非所有开发板都有CAN收发器,此处需要各位注意。
1.2 硬件连线
CAN有两种网络架构,其一是闭环总线网络,其二是开环总线网络。
其中开环总线用在低速通信上,适用于远距离通讯。闭环总线用在高速通信上,适用于近距离通信。
2. CAN通信的软件部分
2.1 CAN报文的种类
帧 | 用途 |
数据帧 | 用于节点向外传输数据 |
遥控帧 | 用于向其他节点请求数据 |
错误帧 | 用于告知其他节点数据校验出错 |
过载帧 | 用于通知其他节点本节点并未做好接收准备 |
帧间隔 | 用于分隔 |
实际使用中,数据帧的使用最为普遍,下文会详细介绍。
2.2 数据帧的结构
帧起始 | 仲裁段 | 控制段 | 数据段 | CRC段 | ACK段 | 帧结束 |
在实际的数据发送实践中,我们主要关注仲裁段、控制段、数据段。
2.2.1 仲裁段
仲裁段的作用:当两个报文同时发送时,根据报文的优先级,决定哪个数据包能被优先传输。
仲裁段内包含一个数据帧的ID,分为标准ID和扩展ID两种。ID越小,报文的优先级越高。
仲裁段的物理层原理可以自行查阅网络资料。
2.2.2 控制段
控制段用来表示本数据帧中数据段含有多少个字节。控制段可以表示的范围为0~8。
2.2.3 数据段
节点要发送的原始数据,大端序,由0~8字节组成
2.3 过滤器
在CAN通信网络中,对一个节点来说,并非所有的报文都是有用的,因此通过过滤器来筛选所需的报文可以有效节省系统资源的不必要浪费。
CAN中,过滤器有两种模式:
- 列表模式:把所有有用的报文ID存储在列表中,精确接收所需数据。
- 掩码模式:可以筛选出一定范围的数据,原理见下方。
2.3.1 掩码模式原理
在掩码模式中,我们需要设置掩码和校验码。
假设我们要筛选出开头为10的ID,一个ID如下:
1 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
我们设计掩码如下
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
将原始数据与掩码相乘,得到的结果中只会保留掩码中“1”所对应位置的数据,其他位均为0。
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
此时将得到的结果与校验码:
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
比较,如果一致,则说明是所需数据。
3. CAN通信实战
使用STM32F427IIH6(RoboMaster开发板A型)控制M3508电机。
3.1 首先在CubeMX中进行配置
注意时钟配置,千万不要出错。
打开CAN1,按图所示配置。注意:CubeMX中自动生成的Rx、Tx管脚并非开发板中CAN1接口所对应的管脚,需要自行调整至PD0和PD1!!!
3.2 C620电调通信协议
3.2.1 发送
在发送时,为了节约通信资源,可以在一个数据包中同时控制四个电调(ID1-4/5-8),只需将数据包的ID设置为0x200或0x1FF即可。
3.2.2 接收
接收时,电调反馈的数据帧ID为0x200+电调ID。(例:ID为1的电调反馈的数据帧ID为0x)
3.3 代码实现
3.3.1 数据发送
void m3508_control(int16_t motor1, int16_t motor2, int16_t motor3, int16_t motor4)
{
uint32_t send_mail_box; //定义发送邮箱
can_tx_message.StdId = M3508_GROUP_1; //数据帧的ID,此处设置为第一组,即0x200
can_tx_message.IDE = CAN_ID_STD; //ID为标准ID
can_tx_message.RTR = CAN_RTR_DATA; //设置为数据帧
can_tx_message.DLC = 0x08; //数据长度为8字节
can_send_data[0] = motor1 >> 8; //电机1电流高八位
can_send_data[1] = motor1; //电机1电流低八位
can_send_data[2] = motor2 >> 8;
can_send_data[3] = motor2;
can_send_data[4] = motor3 >> 8;
can_send_data[5] = motor3;
can_send_data[6] = motor4 >> 8;
can_send_data[7] = motor4;
//CAN发送数据
HAL_CAN_AddTxMessage(&hcan1, &can_tx_message, can_send_data, &send_mail_box);
}
发送环节只需注意ID、数据长度、以及类型的设置,发送邮箱等为固定操作,照写即可。
3.3.2 数据接收
在开始数据接收前,我们要先配置过滤器。
void can_filter_init(void)
{
CAN_FilterTypeDef can_filter_st;
can_filter_st.FilterActivation = ENABLE; //开启过滤器
can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; //过滤器模式,此处设置为掩码模式
can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; //过滤器大小设置
can_filter_st.FilterIdHigh = 0x0000; //校验码高八位
can_filter_st.FilterIdLow = 0x0000; //校验码低八位
can_filter_st.FilterMaskIdHigh = 0x0000; //掩码高八位
can_filter_st.FilterMaskIdLow = 0x0000; //掩码低八位
can_filter_st.FilterBank = 0; //过滤器编号,CAN1是0~13
can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0; //设置FIFO
HAL_CAN_ConfigFilter(&hcan1, &can_filter_st); //设置过滤器
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); //开启中断接收
}
上述代码配置了一个所有ID都能通过的过滤器(任何ID经MASK处理后都为00000000,与校验码相同)。
此代码可以作为模板使用。
3.3.3 电机反馈数据接收
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) //CAN接收中断
{
CAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);
switch (rx_header.StdId)
{
case M3508_ID_1:
case M3508_ID_2:
case M3508_ID_3:
case M3508_ID_4:
{
//todo: 进行处理
break;
}
default:
{
break;
}
}
}