【STM32CubeMX】F103 & BxCAN

2023-12-14 22:34:11

F103&BxCAN

bxCAN总体描述

  • 有一个增强的过滤机制来处理各种类型的报文此外,应用层任务需要更多CPU时间,因此报文接收所需的实时响应程度需要减轻。

  • 接收FIFO的方案允许,CPU花很长时间处理应用层任务而不会丢失报文。 构筑在底层CAN驱动程序上的高层协议软件,跟CAN控制器之间有高效的接口。

BxCAN与CAN的区别?

  1. 硬件结构:BxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。而CAN通常只有一个主控制器。
  2. 功能: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

文章来源:https://blog.csdn.net/m0_60313295/article/details/135004798
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。