- 前言
在RoboMaster比赛中,无论是电机驱动还是板间通信,CAN通信都发挥了极其重要的作用,但是相比于其它基本知识,CAN是一个相对而言比较复杂的通讯协议,有着更多的特性需要去记忆,也因此成为了很多RMer电控入门阶段的美好回忆,本篇文章将从理论出发,结合实践,讲解如何使用CAN通信。 - 什么是CAN通信?
CAN 是控制器域网 (Controller Area Network, CAN) 的简称,是由研发和生产汽车电子产 品著称的德国 BOACH 公司开发,并最终成为国际标准(ISO11898),CAN 是国际上应用 最广泛的现场总线之一。 在北美和西欧,CAN 总线协议已经成为汽车计算机控制系统和嵌 入式工业控制局域网的标准总线,并且拥有以 CAN 为底层协议专为大型货车和重工机械车 辆设计的 J1939 协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。 - 理论部分
- 硬件层面
-
信息传递方式——差分信号
CAN控制器通过组成总线的2根线(CAN-H和CAN-L)的电位差来确定总线的电平,信号是以两线之间的“差分”电压形式出现,总线电平分为显性电平和隐性电平。
这样,通过采用两种互补的逻辑数值"显性"和"隐性"的方法,用"显性"数值表示逻辑"0",而"隐性"表示逻辑"1"。从而,CAN总线仅通过CAN_H与CAN_L两条线即可实现信息的传递。
这时我们可以联想到之前学过的另一种通信方式UARTCAN UART 比赛中应用 板间通信,大多数电机驱动 与操作手通信 总线结构 多主机、多从机的总线结构,其中多个节点可以同时发送和接收消息 点对点连接,只有一个发送器和一个接收器 通信速率 支持高速传输,可以实现较高的数据速率,1 Mbps或更高 低速通信,通常在几十 kbps 到几 Mbps 的范围内 数据帧格式 使用帧格式来发送和接收数据,其中包括标识符、数据和检验位 异步通信,使用起始位、数据位、校验位和停止位来传输数据 可靠性和冲突处理 具有较高的可靠性和抗干扰能力,可以检测和处理冲突,确保数据的完整性和正确性 较为简单,没有内置的错误检测和冲突处理机制 部分内容将会在下面详细讲解
-
帧仲裁
在编写控制程序时,我们往往需要处理来自多个传感器或上位机的数据,那么当我们用CAN传递信息时,如果多个模块同时向我们反馈信息,如果不对顺序进行安排,数据将会混在一起无法解算。这种情况下就需要进行仲裁,判断哪个设备可以占用总线,而其他设备要转变为接收或者等待。CAN的仲裁机制巧妙地利用了差分信号的特性,即显性电平覆盖隐形电平的特性,如果出现多个设备同时发送的情况,则先输出隐形电平的设备会失去对总线的占有权。下图中D为显性电平,R为隐形电平,通过该图可以很容易地理解CAN的仲裁机制。
-
波特率
CAN有着很高的通讯速率,通过查阅手册可知,一般RM系列电调的通讯速率为1Mbps,只有波特率一致的情况下,主控才能成功与电调进行通讯,CAN的通讯速率的决定因素包括以下四点:
• 同步段 (SYNC_SEG) :位变化应该在此时间段内发生。只有一个时间片的固定长度 (1 x tq )
• 位段 1(BS1) :定义采样点的位置。其持续长度可以在 1 到 16 个时间片之间调整
• 位段 2(BS2) :定义发送点的位置。其持续长度可以在 1 到 8 个时间片之间调整
• 同步跳转宽度( SJW ):定义位段加长或缩短的上限。它可以在 1 到 4 个时间片之间调整
CAN 总线波特率和 tq (Time Quantum),tBS1 (Time Quanta in Bit Segment 1)和 tBS2 (Time Quanta in Bit Segment 1) 的值直接相关,tq 通过总线分频后直接得到,tBS1和 tBS2 则通过 TS1 和 TS2 放大为 tq 的若干倍,需要注意的是下图中 tBS1 和 tBS2 的计 算是通过 tq(TS1+1)和 tq(TS2+1)得到的,而在 CubeMX 中,我们配置的 Time Quanta in Bit Segment 的值对应的就是 (TS1+1)和(TS2+1)。至于CAN究竟挂在哪个总线上就需要查阅数据手册了
-
- 软件层面
-
过滤器
把CAN总线看作是一个兵种组,每天会为组员安排不同任务,但是队员们并不是每个人都要做全部工作的,不加区分地把数据传给每一个人只会导致工作的混乱。因此我们可以在任务前面加上标识,如电控任务,机械任务,电路任务,视觉任务让每个人只接收自己技术组的任务,而直接过滤掉与自己无关的工作事实上,bxCAN的过滤器就是采用上述方法,你只需要设置好你感兴趣的那些CAN报文ID,那么MCU就只能收到这些CAN报文,是从硬件上过滤掉,完全不需要软件参与进来,从而节省了大大节省了MCU的时间,可以更加专注于其他事务,这个就是bxCAN过滤器的意义所在。
CAN的过滤器模式分为掩码模式和列表模式。
-
列表模式
简单来说就是制作一张ID表,如果来的数据的ID在这张表中则接收,否则不收。
-
掩码模式
假设某学校的学工号是 入学年份+学生序号,那么,我想要确定学生的年级的话只需要看学工号就可以了,但是为了省事,其实我们完全可以把学工号后面的序号忽略,只看前面的入学年份部分。这就是掩码模式。
-
-
标准数据帧
CAN的一个标准数据帧包括以下几个部分
- 仲裁场
仲裁场包括12位标识符,作用在前面帧仲裁处已进行讲解 - 控制场
仲裁场后跟随的是控制场,存放数据长度DLC,数据场中要填写CAN发送的数据
- 仲裁场
-
- 硬件层面
- 实践部分
-
CUBE配置
1.选择外部高速晶振
2.Debug选Serial Wire
3.配置时钟树
接下来结合时钟树讲解一下波特率的计算配置
经查阅,我们知道STM32F407IGHX的CAN挂在APB1总线上,因此我们在时钟树上找到APB1,外设频率为42MHz。打开CAN
此时我们设的预分频(Prescaler)为3,所以$$t_q=\frac{1}{f}=\frac{1}{\frac{42MHz}{3}}=\frac{1}{14}ns$$因而,由理论部分的公式求得:波特率(Baud Rate)Rb=1000000bit/s.
选取工作模式为 Normal
关于各种工作模式,介绍如下:工作模式 模式说明 详细说明 Normal 正常模式 可发可收 Loopback 回环模式 自发自收 Silent 静默模式 只收不发 Loopback combine with Silent 回环静默模式 略 记得打开中断以接收。 -
代码部分
-
发送初始化
-
变量含义
StdId:CAN总线消息的标准ID
IDE:指定CAN总线消息的标识符扩展位
RTR:指定CAN总线消息的远程传输请求位
DLC:指定CAN总线消息的数据长度码
chassis_tx_message.StdId = 0x222; //标准ID chassis_tx_message.StdId = 0x222; //标准ID chassis_tx_message.RTR = CAN_RTR_DATA;//设置了远程传输请求位 chassis_tx_message.IDE = CAN_ID_STD;//使用了标准的11位标识符 chassis_tx_message.DLC = 0x08; //8字节长
-
-
接收初始化
CAN_FilterTypeDef CAN_FilterType; CAN_FilterType.FilterBank=0; //使用0号过滤器 CAN_FilterType.FilterIdHigh=0x0000; //设置验证码高低各4字节 CAN_FilterType.FilterIdLow=0x0000; CAN_FilterType.FilterMaskIdHigh=0x0000; //设置屏蔽码高低各4字节 CAN_FilterType.FilterMaskIdLow=0x0000; CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0; //通过CAN的信息放入0号FIFO CAN_FilterType.FilterMode=CAN_FILTERMODE_IDMASK; //采用掩码模式 CAN_FilterType.FilterScale=CAN_FILTERSCALE_32BIT; //设置32位宽 CAN_FilterType.FilterActivation=ENABLE; //激活滤波器 CAN_FilterType.SlaveStartFilterBank=14; HAL_CAN_ConfigFilter(hcan, &CAN_FilterType); //配置过滤器 HAL_CAN_Start(hcan); //开启CAN总线 HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//激活CAN接收
-
发送函数
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
函数名 HAL_CAN_AddTXMessage 函数功能 将一段数据通过 CAN 总线发送 返回值 HAL_StatusTypeDef,HAL 库定义的几种状态,如果本次 CAN 发送成功, 则返回 HAL_OK 参数1 CAN_HandleTypeDef *hcan,即 can 的句柄指针,如果是 can1 就输入&hcan1,can2 就输入&hcan2 参数2 CAN_TxHeaderTypeDef *pHeader,待发送的 CAN 数据帧信息的结构体指 针,包含了 CAN 的 ID,格式等重要信息 参数3 uint8_t aData[],装载了待发送的数据的数组名称 参数4 uint32_t *pTxMailbox,用于存储 CAN 发送所使用的邮箱号 实例:队内CAN发送函数
/** * @brief 向CAN总线发送信息 * @param phcan: 指向CAN句柄的指针 * @param txdata: 要发送的信息 * @retval 无 */ void Can_SendMessage(CAN_HandleTypeDef* phcan, CAN_TxHeaderTypeDef* pheader, uint8_t txdata[]) { uint32_t mailbox; /* Start the Transmission process */ uint32_t ret = HAL_CAN_AddTxMessage(phcan, pheader, txdata, &mailbox); if (ret != HAL_OK) { /* Transmission request Error */ Can_Error_Source = 5; Can_Error_Message_Header = *pheader; Can_ErrorHandler(ret); } }
-
接收函数
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
函数名 HAL_CAN_GetRxMessage 函数功能 接收 CAN 总线上发送来的数据 返回值 HAL_StatusTypeDef,HAL 库定义的几种状态,如果本次 CAN 接收成功, 则返回 HAL_OK 参数1 CAN_HandleTypeDef *hcan,即 can 的句柄指针,如果是 can1 就输入&hcan1,can2 就输入&hcan2 参数2 uint32_t RxFifo,接收时使用的 CAN 接收 FIFO 号,一般为 CAN_RX_FIFO0 参数3 CAN_RxHeaderTypeDef *pHeader,存储接收到的 CAN 数据帧信息的结构 体指针,包含了 CAN 的 ID,格式等重要信息 参数4 uint8_t aData[],存储接收到的数据的数组名称 实例:CAN接收电机反馈值
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &chassis_rx_message, rxData); int i = (int)chassis_rx_message.StdId - 0x204 - 1; wheelrxdata.rxangle[i] = ((rxData[0]<<8) | rxData[1]); wheelrxdata.rxspeed[i] = ((rxData[2]<<8) | rxData[3]); wheelrxdata.rxI[i] = ((rxData[4]<<8) | rxData[5]); wheelrxdata.rxT[i] = rxData[6]; return; }
-
总结:
使用CAN通信的基本思路:
- 按需求初始化发送/接收(一般是两者兼有)
- 开启发送函数
- 书写中断回调函数
- 得到数据,对数据进行处理以进行进一步操作
-
-