F103&BxCAN
bxCAN总体描述
有一个增强的过滤机制来处理各种类型的报文此外,应用层任务需要更多CPU时间,因此报文接收所需的实时响应程度需要减轻。
接收FIFO的方案允许,CPU花很长时间处理应用层任务而不会丢失报文。 构筑在底层CAN驱动程序上的高层协议软件,跟CAN控制器之间有高效的接口。
BxCAN与CAN的区别?
- 硬件结构:BxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。而CAN通常只有一个主控制器。
- 功能:BxCAN具有更多的功能,例如时间触发通信模式。在该模式下,CAN硬件的内部定时器被激活,并且被用于产生(发送与接收邮箱的)时间戳,分别存储在CAN_RDTxR/CAN_TDTxR寄存器中。内部定时器在每个CAN位时间累加。内部定时器在接收和发送的帧起始位的采样点位置被采样,并生成时间戳。
功能名词
标识符过滤:在CAN协议里,报文的标识符不代表节点的地址,而是跟报文的内容相关的。因此,发送者乙 广播的形式把报文发送给所有的接收者。节点在接收报文时-根据标识符的值-决定软件是否 需要该报文;如果需要,就拷贝到SRAM里;如果不需要,报文就被丢弃且无需软件的干预。
数据结构
-
CAN过滤器配置结构定义
typedef struct { uint32_t FilterIdHigh; // 指定过滤器标识号的高16位,范围0x0000~0xFFFF uint32_t FilterIdLow; // 指定过滤器标识号的低16位,范围0x0000~0xFFFF uint32_t FilterMaskIdHigh; // 用于设置过滤器掩码高16位,范围0x0000~0xFFFF uint32_t FilterMaskIdLow; // 用于设置过滤器掩码低16位,范围0x0000~0xFFFF uint32_t FilterFIFOAssignment; // 指定将分配给过滤器的FIFO(0或1U),该参数可以是@ref CAN_filter_FIFO的值 uint32_t FilterBank; // 指定要初始化的筛选器组,对于单个CAN实例(14个专用过滤器组),该参数必须是0~13之间。对于双CAN实例(28个过滤器组共享),该参数必须是0~27之间。 uint32_t FilterMode; // 指定要初始化的筛选器模式,列表模式和掩码模式,该参数可以是@ref CAN_filter_mode的值 uint32_t FilterScale; // 指定过滤器的规模,该参数可以是@ref CAN_filter_scale的值 uint32_t FilterActivation; // 启用或禁用过滤器,该参数可以是@ref CAN_filter_activation的值 uint32_t SlaveStartFilterBank; // 为从CAN实例选择启动过滤器组,对于单个CAN实例,此参数没有意义。对于双CAN实例,索引较低的滤波器组分配给主CAN实例,索引较大的滤波器组分配给从CAN实例。该参数必须为0~27之间。 } CAN_FilterTypeDef;
-
CAN Tx消息头结构定义
typedef struct { uint32_t StdId; // 指定标准标识符,范围0~0x7FF uint32_t ExtId; // 指定扩展标识符,范围0~0x1FFFFFFF uint32_t IDE; // 指定要传输的消息的标识符类型。该参数可以是@ref CAN_identifier_type uint32_t RTR; // 指定要传输的消息的帧类型。该参数可以是@ref CAN_remote_transmission_request uint32_t DLC; // 指定要传输的帧的长度,范围0~8 FunctionalState TransmitGlobalTime; //指定时间戳计数器值是否在开始时捕获在帧传输中,以DATA6和DATA7代替pData[6]和pData[7]发送。@注:必须启用“时间触发通信模式”。@注意:DLC必须被编程为8字节,为了这2字节被发送。可设置为“ENABLE”或“DISABLE”。 } CAN_TxHeaderTypeDef;
-
CAN Rx消息头结构定义
typedef struct { uint32_t StdId; // 指定标准标识符,范围0~0x7FF uint32_t ExtId; // 指定扩展标识符,范围0~0x1FFFFFFF uint32_t IDE; // 指定要传输的消息的标识符类型。该参数可以是@ref CAN_identifier_type uint32_t RTR; // 指定要传输的消息的帧类型。该参数可以是@ref CAN_remote_transmission_request uint32_t DLC; // 指定要传输的帧的长度,范围0~8 uint32_t Timestamp; //指定在帧接收开始时捕获的时间戳计数器值。@注:必须启用“时间触发通信模式”。该参数必须为0~0xFFFF。 uint32_t FilterMatchIndex; // 指定匹配接受筛选元素的索引。该参数必须为0~0xFF } CAN_RxHeaderTypeDef;
相关资料
下面图片摘录于
STM32F10xxx参考手册(中文)
-
注意手册里也提醒了,要是想跑CAN总线的化,还需要
CAN收发器
,MCU只能提供CAN_Rx,CAN_Tx,不要搞混了。 -
这里是过滤器的寄存器的含义图,因为我没找到其它的关于解释过滤器关于CAN中标准的ID的位置到底在哪里,下面两张图就有,比如STID的含义就是CAN协议中的标准ID,而且它也是通过程序也知道它是对应着过滤器结构中FilterIdHigh,FilterIdLow之类的,方便理解下面的程序,为什么要移位。
-
这个说实话,没看懂这样的定义是啥,32位的仲裁域?还是标准标识符,怪怪的。但是是手册中唯一,一张有介绍CAN帧的图,所以就顺便截出来了,万一大家有自己的理解。
前置配置
-
STM32CubeMX中配置
在STM32CubeMX中配置,RCC配置使用高速外部晶振,开启USART1,配置PC13为输出模式(点灯),CAN配置(下面附图)其它的省略配置过程。
-
配置完后生成,Keil5打开。
相关程序
以下函数都可以写在main.c中,只不过分块写更容易理解。
初始化相关的
// 开启CAN
HAL_CAN_Start(&hcan);
发送相关的
/*
* @描述:CAN发送函数
* @参数:id(CAN发送的ID号)
* @参数:idMode(帧ID号类型选择)
CAN_ID_STD:标准ID号
CAN_ID_EXT:扩展ID号
* @参数:*pData(要发送的数据)
* @参数:len(发送的数据长度)
* 范围 0 ~ 8
* @返回值: 返回0:正常 返回1:异常
*/
uint8_t CAN_SendData(uint32_t id,uint8_t idMode,uint8_t *pData,uint8_t len)
{
CAN_TxHeaderTypeDef pHeader;
uint32_t pTxmailbox;
uint8_t result = 0;
if(idMode == CAN_ID_STD)
{
pHeader.StdId = id; //标准ID号
pHeader.IDE = CAN_ID_STD; //标准ID
}
else
{
pHeader.ExtId = id; //扩展ID号
pHeader.IDE = CAN_ID_STD; //扩展ID
}
pHeader.RTR = CAN_RTR_DATA; //传输数据的帧类型为数据帧
pHeader.DLC = len>8?8:len; //发送数据的字节长度,最大为8个字节
pHeader.TransmitGlobalTime = DISABLE; //是否要发送时间戳
// CAN发送函数
if(HAL_CAN_AddTxMessage(&hcan, &pHeader, pData, &pTxmailbox) != HAL_OK)
{
return 1;
}
return 0;
}
接收相关的
过滤模式:部分ID位数相同也能接收 列表模式:必须是指定ID才能接收
两种模式的程序都在下面,测试时,二选一验证就行了。
- 列表模式
/*
* @描述:CAN过滤器配置,接收模式采用的是列表模式,
* 该id不管是标准帧还有扩展帧都适用,因为两个
* 筛选id的寄存器分别写入了它的标准帧和扩展帧
* @参数:id(筛选的ID号)
*/
void CAN_Fliter_Config_IDLIST(uint32_t id)
{
CAN_FilterTypeDef hcan_filterconfig;
hcan_filterconfig.FilterActivation = CAN_FILTER_ENABLE; //过滤器使能
hcan_filterconfig.FilterBank = 0; //使用过滤器0
hcan_filterconfig.FilterMode = CAN_FILTERMODE_IDLIST; //采用列表模式
hcan_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT; //采用32位掩码模式
hcan_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0; //使用FIFO0
hcan_filterconfig.FilterIdHigh = id << 5; //过滤器ID高16位
hcan_filterconfig.FilterIdLow = 0 | CAN_ID_STD | CAN_RTR_DATA; //过滤器ID低16位,CAN_ID_STD(标准ID)、CAN_RTR_DATA(帧类型为数据帧)
hcan_filterconfig.FilterMaskIdHigh = ((id<<3)>>16)&0xffff; //过滤器掩码高16位
hcan_filterconfig.FilterMaskIdLow = ((id<<3) & 0xffff) | CAN_ID_EXT | CAN_RTR_DATA; //过滤器掩码低16位,CAN_ID_EXT(扩展ID)、CAN_RTR_DATA(帧类型为数据帧)
hcan_filterconfig.SlaveStartFilterBank = 14; //过滤器数量14
HAL_CAN_ConfigFilter(&hcan,&hcan_filterconfig); //初始化过滤器
HAL_CAN_Start(&hcan); //开启CAN
//开启CAN-FIFO0中断
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);
}
- 掩码模式
/*
* @描述:CAN过滤器配置,接收模式采用的是掩码模式。
* 掩码模式,对'1'位,不感兴趣,对'0'位必须匹配。
* @参数:id(筛选的ID号)
* @阐述:mask_if(掩码)
*/
void CAN_Fliter_Config_IDMASK(uint32_t id,uint32_t mask_id)
{
CAN_FilterTypeDef hcan_filterconfig;
hcan_filterconfig.FilterActivation = CAN_FILTER_ENABLE; //过滤器使能
hcan_filterconfig.FilterBank = 0; //使用过滤器0
hcan_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK; //采用掩码模式
hcan_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT; //采用32位掩码模式
hcan_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0; //使用FIFO0
// hcan_filterconfig.FilterIdHigh = (((((uint32_t)id)<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xffff0000)>>16;//过滤器ID高16位,CAN_ID_EXT(扩展ID)、CAN_RTR_DATA(帧类型为数据帧)
// hcan_filterconfig.FilterIdLow = (((((uint32_t)id)<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0x0000ffff); //过滤器ID低16位,CAN_ID_EXT(扩展ID)、CAN_RTR_DATA(帧类型为数据帧)
// hcan_filterconfig.FilterMaskIdHigh = (mask_id & 0xffff0000)>>16; //过滤器掩码高16位
// hcan_filterconfig.FilterMaskIdLow = mask_id & 0x0000ffff; //过滤器掩码低16位
hcan_filterconfig.FilterIdHigh = id << 5; //过滤器ID高16位,CAN_ID_EXT(扩展ID)、CAN_RTR_DATA(帧类型为数据帧)
hcan_filterconfig.FilterIdLow = 0 | CAN_ID_STD | CAN_RTR_DATA; //过滤器ID低16位,CAN_ID_EXT(扩展ID)、CAN_RTR_DATA(帧类型为数据帧)
hcan_filterconfig.FilterMaskIdHigh = 0x00; //过滤器掩码高16位
hcan_filterconfig.FilterMaskIdLow = 0x00; //过滤器掩码低16位
hcan_filterconfig.SlaveStartFilterBank = 14; //过滤器数量14
HAL_CAN_ConfigFilter(&hcan,&hcan_filterconfig); //初始化过滤器
HAL_CAN_Start(&hcan); //开启CAN
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN-FIFO0中断
}
中断回调函数
/*CAN-FIFO0中断回调函数*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8]; // 存储接收数据
uint32_t RxDataLength; // 数据长度
if(hcan->Instance == CAN1)
{
// 从FIFO中读取消息
if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
{
// 处理读取错误
}
// 获取数据长度
RxDataLength = RxHeader.DLC;
// 判断是标准帧还是扩展帧,并打印相应的ID和简单的验证下前四个字节的数据
if(RxHeader.IDE == CAN_ID_STD)
{
printf("接收到标准ID为:0x%X\r\n", RxHeader.StdId);
printf("接收到的数据为:%d %d %d %d\r\n",RxData[0],RxData[1],RxData[2],RxData[3]);
}
else if(RxHeader.IDE == CAN_ID_EXT)
{
printf("接收到扩展ID为:0x%X\r\n", RxHeader.ExtId);
printf("接收到的数据为:%d %d %d %d\r\n",RxData[0],RxData[1],RxData[2],RxData[3]);
}
}
}
main.c中主函数片段,该片段中只包含用户需要测试编写的,不包括STM32CubeMX生成的其它初始化。
int main()
{
uint8_t CAN_Send_Data[] = {8,8,8,8,8,8,8,8};
int Count = 0;
HAL_CAN_Start(&hcan);//开启CAN
CAN_Fliter_Config_IDMASK(0x123,0x000);
while(1)
{
// CAN发送函数
if(CAN_SendData(0x0123,CAN_ID_STD,CAN_Send_Data,8))
{
printf("Send Lose!\r\n");
}
else
{
printf("SendOK:%d\r\n",Count++);
}
// 闪烁LED-运行状态灯
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
HAL_Delay(500);
}
}
串口配置(可选),方便观察到调试现象,不一定要用
// 引入该库为了可以使用 printf 函数
#include "stdio.h"
//避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//标准库需要支持的函数
struct __FILE
{
int handle;
};
FILE __stdout;
//重定向print
int fputc(int ch, FILE *f)//printf
{
//HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET); //485发送使能端口 没有可去掉
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff); //发送一个字节的数据到你希望的串口
//HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET); //485发送使能端口 没有可去掉
return (ch);
}
实验现象
材料:STM32C6T6两块,CAN收发器两块,若干杜邦线,串口模块,ST-LINK下载器,面包板若干块
程序:配置,闪烁LED观察程序运行状态,调用CAN发送程序并用变量计数程序是否发送成功,并用串口打印出来。
程序资料
文中演示的程序
链接:https://pan.baidu.com/s/18lqiq-thWXHbma8eLD9ODw 提取码:7qkw