STM32利用CUBEMX配置CAN实现数据传输:Normal模式连接两块板子详解
使用工具:keil,stm32cubemx
芯片型号:stm32F407zgt6(主板),stm32F103c8t6(从板)
1、CAN简介
1.1、CAN简介
CAN:控制器区域网络(Controller Area Network)的缩写。CAN总线是一种适用于工业设备的高性能总线网络。STM32F407有两个CAN控制器,可以进行CAN总线网络的通信试验。
1.2、CAN协议的特点
① 多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符(Identifier以下称为ID)决定优先级。ID并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续 发送消息,仲裁失利的单元则立刻停止发送而进行接收工作(0的优先级高于1)。
② 系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元 时,连接在总线上的其它单元的软硬件及应用层都不需要改变(ID号表示优先级,并不是地址)。
③ 通信速度较快,通信距离远。最高1Mbps(距离小于40M),最远可达10KM(速率低于 5Kbps)。
④ 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能), 检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
⑤ 故障封闭功能。CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等) 还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
⑥ 连接节点多。CAN总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速 度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
1.3、CAN的拓扑图
总线电平分为显性电平和隐性电平,二者必居其一,显性电平对应逻辑0,CAN_H和CAN_L之差为2 V左右。而隐性电 平对应逻辑1,CAN_H和CAN_L之差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出 隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)—–解释了为什么ID中0的优先级高于1。
2、CAN协议
主要介绍数据帧和遥控帧
数据帧:用于发送单元向接收单元传送数据的帧(主要)
遥控帧:用于接收单元向具有相同ID的发送单元请求数据的帧
数据帧可分为:
1、起始段(SOF):只有一位,显性电平0表示帧开始
2、仲裁帧(ID+RTR):11位的ID和一位RTR,共12位,①哪一个ID先显示显性电平0,那个ID就先占用主线(ID数值越小优先级越高)。② RTR用于:区分是数据帧和远程帧(0 = 数据帧,1 = 远程帧),由于0的优先级更高即;具有相同ID的数据帧和遥控帧竞争总线时,数据帧优先级更高
3、控制段(IDE+RB0+DLC):① IDE是标识符扩展位(Identifier Extension Bit),用于表示帧是扩展格式还是标准格式(1位),②RB0是保留位,默认为显性电平(1位)③ DLC是数据长度编码(4位),编码数值为0到8,表示后面数据段的字节(0~8个字节:0~64bit),遥控帧的DLC编码数值为0,因为遥控帧不传输数据。
4、数据段:数据段里面是数据帧需要传输的数据,可以是0到8字节,数据的字节个数由DLC编码确定。遥控帧没有数据段
5、CRC段。检查帧的传输错误的段。CRC共16位,其中前15位是CRC校验码,最后一位总是隐形电平,是CRC段的界定符
6、ACK段。ACK段包括一个ACK位(Acknowledge Bit)和一个ACK段界定符。发送节点发送的ACK位是隐形电平,接收节点接收的ACK位是显性电平。
7、帧结束(End Of Frame,EOF)。帧结束是帧结束段,由7个隐性位表示EOF。
数据帧或遥控帧结束后,后面一般是帧空间或过载帧,用于分隔开数据帧和遥控帧。
扩展格式数据帧和遥控帧的结构如图所示。扩展格式的ID总是29位,扩展格式帧与标准格式帧的差异在于仲裁段和控制段。
(1)仲裁段。扩展格式数据帧的仲裁段总共32位,包括11位标准ID、SRR位、IDE位(1)、18位扩展ID、RTR位。(标准格式的仲裁段是12位)
SRR位(Substitue Remote Request Bit)只存在于拓展格式帧中,用于替代标准格式帧中的RTR位。SRR位总是隐形电平,相当于一个占位符,真正的RTR位在仲裁段的最后一位。RTR位还是用于区分数据帧和遥控帧。
扩展帧格式帧的IDE位总是隐形电平,表示这是扩展格式的帧。
(2)控制段。控制段由RB1位、RB0位和4位DLC组成。RB1位和RB0位是保留位,总是显性电平(0)。4位的DLC编码表示数据的长度,从0到8字节。
3、CAN的四种模式
前三种是CAN的测试模式
3.1、静默模式(slient mode)
CAN模块可以接收有效的数据帧和遥控帧,不能发送数据到CAN总线—-用于检测总线数据
3.2、环回模式(loop back mode)
CAN模块可以正常的向总线发送数据,但是不能接收总线上的数据,只能接收自己发送的数据(需要通过筛选规则)。 这种模式可用于自检测试,为了不受外部事件的影响,CAN内核在此模式下不会对数据或遥控帧的ACK采样,这样可以忽略ACK错误。
3.3、环回与静默组合模式(loop back mode with silent mode)
这是环回与静默模式的组合,可用于“热自检”—-自发自收(检测自己数据,不会影响到CAN数据总线)
3.4、正常模式(normal mode)
这里介绍一下主要函数:
注意:使用cubemx配置的CAN工程不会自己启动CAN,需要自己调用HAL_CAN_Start()函数
3.5、CAN片上外设
STM32F4的CAN外设的主要特点如下:
(1)波特率最高为1Mbit/s。
(2)每个CAN模块有3个发送邮箱,可自动重发。
(3)具有16位自主运行的定时器,可以定时触发通信,可以在最后两个数据字节发送时间戳。
(4) 每个CAN模块有两个FIFO单元,每个FIFO有3个接收邮箱,每个FIFO有独立的中断地址。
(5)两个CAN模块共用28个筛选器组,筛选器用于配置可接收ID列表或掩码。数据帧和遥控帧根据ID被筛选,只有通过筛选的帧才能进入接收邮箱。帧的筛选完全由硬件完成,减少处理器的负担。——重点:cubemx配置的CAN工程没有过滤配置,需要自己去配置,否则接收不到报文信息
需要保证两个模块的CAN波特率相同才能正常通信
波特率计算:
BS1 和 BS2 总和 +1 不超过 25(通常 8~20 比较合适);
同步段始终为 1 TQ,不用你配置;
F407中CAN挂载APB1上
4、HAL配置
其余的基础配置不再赘述,下面仅展示关于CAN方面的配置
4.1、CAN配置
开启CAN中断:
我是将接收和中断的相关中断都开启了
将其他你需要的外设配置好,点击生成工程
4.2、代码详解
再次说明:使用cubemx配置的CAN工程,缺少过滤器组的配置,我的过滤器配置如下(接收所有报文)
过滤器配置:
void CAN_Filter_config(void)
{
CAN_FilterTypeDef sFilterConfig;
/* 配置CAN过滤器 */
sFilterConfig.FilterBank = 0; /* 过滤器0 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000; /* 32位ID */
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000; /* 32位MASK */
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; /* 过滤器0关联到FIFO0 */
sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; /* 激活滤波器0 */
sFilterConfig.SlaveStartFilterBank = 14;
/* 过滤器配置 */
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
HAL_CAN_Start(&hcan1);//开启CAN
HAL_CAN_ActivateNotification(&hcan1 ,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN的中断
}
我是在过滤器中开启了CAN和CAN的中断
CAN接收数据函数:
/**
* @brief CAN 接收数据查询
* @note 接收数据格式固定为: 标准ID, 数据帧
* @param id : 要查询的 标准ID(11位)
* @param buf : 数据缓存区
* @retval 接收结果
* @arg 0 , 无数据被接收到;
* @arg 其他, 接收的数据长度
*/
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{
if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) == 0) /* 没有接收到数据 */
{
return 0;
}
if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &can_rx_handle, buf) != HAL_OK) /* 读取数据 */
{
return 0;
}
if (can_rx_handle.StdId!= id || can_rx_handle.IDE != CAN_ID_STD || can_rx_handle.RTR != CAN_RTR_DATA) /* 接收到的ID不对 / 不是标准帧 / 不是数据帧 */
{
return 0;
}
return can_rx_handle.DLC;
}
CAN发送数据函数:
/**
* @brief CAN 发送一组数据
* @note 发送格式固定为: 标准ID, 数据帧
* @param id : 标准ID(11位)
* @param msg : 数据指针
* @param len : 数据长度
* @retval 发送状态 0, 成功; 1, 失败;
*/
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len)
{
uint16_t t = 0;
uint32_t TxMailbox = CAN_TX_MAILBOX0;
can_tx_handle.StdId = id; /* 标准标识符 */
can_tx_handle.ExtId = id; /* 扩展标识符(29位) */
can_tx_handle.IDE = CAN_ID_STD; /* 使用标准帧 */
can_tx_handle.RTR = CAN_RTR_DATA; /* 数据帧 */
can_tx_handle.DLC = len;
if (HAL_CAN_AddTxMessage(&hcan1 , &can_tx_handle, msg, &TxMailbox) != HAL_OK) /* 发送消息 */
{
return 1;
}
while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) != 3) /* 等待发送完成,所有邮箱为空 */
{
t++;
if (t > 0xFFF)
{
HAL_CAN_AbortTxRequest(&hcan1, TxMailbox); /* 超时,直接中止邮箱的发送请求 */
return 1;
}
}
return 0;
}
CAN的中断函数:
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){
if(hcan->Instance == CAN1){
uint8_t buf[8] = {0};
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &can_rx_handle, buf);
// 如果是我们关心的 ID
if (can_rx_handle.StdId == 0x102 && can_rx_handle.IDE == CAN_ID_STD) {
memcpy(canbuf_copy, buf, 8); // 复制数据到全局缓冲区
can_rx_flag = 1; // 设置接收标志
}
}
}
中断函数中不宜做耗时操作(printf,逻辑处理),中断函数的处理方法:中断中设置标志位+缓存数据,然后再去主函数中去处理数据和打印输出
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin (GPIOF,GPIO_PIN_10);
HAL_Delay(100);
if (can_rx_flag) {
can_rx_flag = 0; // 清除标志
short ultrasonic_E_raw = (short)((canbuf_copy[0] << 8) | canbuf_copy[1]);
float ultrasonic_E = ultrasonic_E_raw / 1000.0f;
printf("*******************************\r\n");
printf("Recv via STM32F407 Main Loop\r\n");
printf("StdId ID: 0x102\r\n");
printf("Ultrasonic E = %.3f m\r\n", ultrasonic_E);
printf("*******************************\r\n");
}
}
这是主函数中的循环
上面是工程的具体实现,具体的看文件吧
如有错误,请指正
5、参考文章
1、HAL库STM32常用外设—— CAN通信(一)_stm32 can hal-CSDN博客
作者:小咖大_