STM32-CAN配置与库函数解析
CAN总线介绍:https://blog.csdn.net/weixin_46251230/article/details/129147612
STM32-CAN控制器介绍:https://blog.csdn.net/weixin_46251230/article/details/129150872
STM32CubeMx配置
因为bxCAN是挂载在APB1总线上的,所以设置APB1总线的时钟为36M
选择CAN接口进行配置
勾选主CAN模式,这里并不是主机的意思
配置位时间参数
根据STM32参考手册里位时间特性的介绍,来配置最小时间单位Tq
在位时间参数里可以配置分频系数,这个系数没有下拉列表,需要自己写,APB1 36MHz时钟来到这里经过分频再给后面使用,根据实际通信速度来配置,例如可以设置为4分频,那36MHz/4 = 9MHz
同步段因为固定为1个时间单元,所以不用配置
根据采样点最好在一个位的50% ~ 80%位置采样,所以时间段1可以配置长一点,其范围为1到16个时间单元,这里可根据下拉列表选择11个时间单元
时间段2的设置就要注意不要让总和超过最大Tq数,因为同步段+时间段1+时间段2的Tq数范围是8 ~ 25个,不过配置工具已经把参数规定好了,所以不用担心超出的问题,这里可以设置为6个时间单元,则会自动计算出一个位占用的时间(Time for one Bit)为2000ns
计算过程:
36MHz/4 = 9MHz,因为同步段(1个Tq)+时间段1(11个Tq)+时间段2(6个Tq)=18个Tq
所以 9MHz/18 = 0.5MHz,即每一个Tq的频率就是0.5MHz,转为时间就为 1/0.5MHz = 1/500000Hz = 0.000002s = 2us = 2000ns
而2000ns的速率就是500KHz
重新同步跳跃宽度(SJW)可设置范围是1 ~ 4个时间单元,这里可以选择2
配置基础参数
接收FIFO锁定模式:选择Enable(锁定)时,当接收FIFO满时,新接收到的报文就丢弃,软件可以读到FIFO中最早收到的3个报文。
选择Disable(不锁定)时,那么FIFO中最后收到的报文就被新报文所覆盖。这样,最新收到的报文不会被丢弃掉。
发送FIFO优先级:未使能就按邮箱序号进行发送
配置工作模式
正常模式就需要两个或更多的实验板来进行通信
环回模式就只使用一个实验板就可以测试通信
本次实验使用环回模式
NVIC中断配置
CAN发送使用轮询的方式,接收就用RX0中断方式
GPIO配置
因为CAN收发器的STB接到了单片机的PC13引脚,所以将PC13配置为推挽输出模式
上面配置完就可以生成Keil工程进行代码编写
生成的CAN初始化函数如下:
void MX_CAN_Init(void)
{hcan.Instance = CAN1;hcan.Init.Prescaler = 4;hcan.Init.Mode = CAN_MODE_LOOPBACK;hcan.Init.SyncJumpWidth = CAN_SJW_2TQ;hcan.Init.TimeSeg1 = CAN_BS1_11TQ;hcan.Init.TimeSeg2 = CAN_BS2_6TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = DISABLE;hcan.Init.AutoRetransmission = DISABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}
}
CAN使用到的库函数介绍
1、配置过滤器
CAN_FilterTypeDef结构体就是过滤器的一些参数设置
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig);
2、发送数据
CAN_TxHeaderTypeDef结构体是对发送报文进行组帧,aData数组存放着要发送的数据,pTxMailbox指针是返回控制器使用了哪个邮箱进行发送
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox);
3、中止发送请求
HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes);
4、获取空邮箱的个数
uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan);
5、接收数据
RxFifo指定用于接收报文的FIFO缓存,CAN_RxHeaderTypeDef结构体定义接收报文的格式,aData数组存放接收到的报文
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);
6、获取接收FIFO满的个数
uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo);
CAN通信实现(环回模式)
CAN.h
进行宏定义,定义结构体类型
//发送接收状态宏定义
#define CAN_SEND_OK 0
#define CAN_SEND_FAIL 1#define CAN_REC_OK 0
#define CAN_REC_FAIL 1//定义结构体类型
typedef struct
{uint32_t uiOperate_Mode; //操作模式uint8_t ucRec_Flag; //接收标志位uint8_t ucSend_Buf[8]; //发送缓存uint8_t ucRec_Buf[8]; //接收缓存void (*CAN_Init)(void);void (*CAN_Config)(void);uint8_t (*CAN_Send_Msg)(uint8_t* pSend_Buf,uint8_t LEN);uint8_t (*CAN_Rec_Msg)(uint8_t* pRec_Buf);}CAN_Test_T;/* extern variables-----------------------------------------------------------*/
extern CAN_Test_T CAN_Test;
CAN.c
配置过滤器并启动CAN
/*** @name CAN_Config* @brief CAN配置* @param None* @retval None */
static void CAN_Config(void)
{//CAN过滤器参数配置CAN_FilterTypeDef CAN_FilterTypeDefSture;CAN_FilterTypeDefSture.FilterBank = 0; //配置过滤器0(F1共有14个,0-13)CAN_FilterTypeDefSture.FilterScale = CAN_FILTERSCALE_16BIT; //配置为16位过滤器CAN_FilterTypeDefSture.FilterMode = CAN_FILTERMODE_IDMASK; //屏蔽位模式//ID号为0x00,屏蔽位为0x00,说明任何ID都接收CAN_FilterTypeDefSture.FilterIdLow = 0x00; //FR1CAN_FilterTypeDefSture.FilterMaskIdLow = 0x00;CAN_FilterTypeDefSture.FilterIdHigh = 0x00; //FR2CAN_FilterTypeDefSture.FilterMaskIdHigh = 0x00; CAN_FilterTypeDefSture.FilterFIFOAssignment = CAN_FILTER_FIFO0; //过滤器0关联到FIFO0CAN_FilterTypeDefSture.FilterActivation = ENABLE; //激活过滤器0CAN_FilterTypeDefSture.SlaveStartFilterBank = 14;//启动过滤器if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterTypeDefSture) != HAL_OK){printf("CAN配置过滤器成功!");System.Error_Handler();}//使能FIFO0接收到一个新报文中断,具体为FIFO0的挂起中断if(HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){printf("CAN使能FIFO0接收到一个新报文中断");System.Error_Handler();}//启动CANif(HAL_CAN_Start(&hcan) != HAL_OK){printf("CAN启动失败!");System.Error_Handler();}printf("配置成功,CAN成功启动!\r\n");
}
CAN发送报文,可在主函数中调用
/*** @name CAN_Send_Msg* @brief CAN发送信息* @param pSend_Buf:发送缓存指针* LEN:发送报文长度* @retval CAN_SEND_OK:发送成功* CAN_SEND_FAIL:发送失败*/
static uint8_t CAN_Send_Msg(uint8_t* pSend_Buf,uint8_t LEN)
{uint8_t i = 0;static uint8_t ucTestData = 0;uint32_t uiTxMailBox; //接收CAN发送数据成功时返回的邮箱号(0-2)//定义CAN TX消息头参数CAN_TxHeaderTypeDef CAN_TxHeaderTypeDefStrue = {0x88, //标准标识符-11位0x00, //拓展标识符-29位CAN_ID_STD, //设置为标准格式CAN_RTR_DATA, //设置为数据帧8, //发送数据的长度 0 ~ 8DISABLE //不使用捕获时间戳计数器};//判断工作模式if(CAN_Test.uiOperate_Mode == CAN_MODE_LOOPBACK){printf("\r\nCAN工作在环回模式,使用一块实验板来测试\r\n");}else{printf("\r\nCAN工作在正常模式,需要两块以上的实验板才能测试\r\n");}//设置要发送的报文printf("CAN要发送的报文如下:\r\n");for(i=0;i<8;i++){printf("%#.2x ",ucTestData);CAN_Test.ucSend_Buf[i] = ucTestData++;}printf("\r\n");//将消息添加到第一个空闲的Tx邮箱并激活相应的传输要求if(HAL_CAN_AddTxMessage(&hcan,&CAN_TxHeaderTypeDefStrue,pSend_Buf,&uiTxMailBox) != HAL_OK){return CAN_SEND_FAIL;}//通过检查空闲邮箱个数确认是否发送完成Timer6.usDelay_Timer = 0;do{//超时处理if(Timer6.usDelay_Timer >= TIMER_1s){printf("CAN发送超时\r\n");return CAN_SEND_FAIL;}} while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3); //如果3个发送邮箱都不是空闲的话,就说明数据还在发送//发送成功return CAN_SEND_OK;
}
CallBack.c
重写FIFO0挂起中断,接收CAN消息
因为是环回模式,所以主函数中发送的数据会被中断接收,通过HAL_CAN_GetRxMessage函数放到CAN_Test.ucRec_Buf缓存中
/*** @name HAL_CAN_RxFifo0MsgPendingCallback* @brief CAN接收FIFO0 挂起中断* @param *_hcan:CAN结构体指针* @retval None */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_)
{//定义CAN Rx消息头参数CAN_RxHeaderTypeDef CAN_RxHeaderTypeDefStrue;//CAN接收消息if(HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&CAN_RxHeaderTypeDefStrue,CAN_Test.ucRec_Buf) == HAL_OK){CAN_Test.ucRec_Flag = TRUE;}LED.LED_Fun(LED3,LED_Flip);
}
System.c
主函数中,判断触摸按键1是否按下,是则发送一次数据,通过接收标志位判断中断是否已经完成数据接收,是则打印出数据
/*** @name Run* @brief 系统运行* @param None* @retval None */
static void Run()
{//计数static uint16_t usCAN_Send_OK_Cnt = 0;static uint16_t usCAN_Send_Fail_Cnt = 0;static uint16_t usCAN_Rec_OK_Cnt = 0;//发送数据if(KEY1.KEY_Flag == TRUE){KEY1.KEY_Flag = FALSE;//发送if(CAN_Test.CAN_Send_Msg(CAN_Test.ucSend_Buf,8) == CAN_SEND_OK){printf("CAN发送数据成功次数:%u\r\n",++usCAN_Send_OK_Cnt);}else{printf("CAN发送数据失败次数:%u\r\n",++usCAN_Send_Fail_Cnt);}}//接收数据if(CAN_Test.ucRec_Flag == TRUE){CAN_Test.ucRec_Flag = FALSE;printf("CAN接收数据成功次数:%u\r\n",++usCAN_Rec_OK_Cnt);//打印接收到的数据CAN_Test.CAN_Rec_Msg(CAN_Test.ucRec_Buf); }
}
实验结果
每触摸一次按键,就成功发送一次数据,并成功接收一次数据