基于STM32F429HAL库的CAN通信代码*
最近在学正点原子阿波罗STM32F429开发板的CAN通信章节,例程里提供了查询方式实现的CAN通信实验。昨天又复习了一遍UART串口实验的中断例程,原子哥手把手地讲了串口接收中断的实现。基于此,在正点原子代码基础上,编写了CAN通信接收中断处理方式的部分代码。
首先是CAN发送接收初始化部分。定义全局句柄,设置发送波特率,接收FIFO。用return返回值,来检测初始化是否成功(一般用在主函数里判断)。
`
```c
``
CAN_HandleTypeDef CAN1_Handler; //CAN1句柄
CanTxMsgTypeDef TxMessage;//发送
CanRxMsgTypeDef RxMessage;//接收
uint32_t std_id=0x00;
CAN初始化
//则波特率为:45M/((6+8+1)*6)=500Kbps
//返回值:0,初始化OK;
// 其他,初始化失败;
u8 CAN1_Mode_Init(u32 tsjw,u32 tbs2,u32 tbs1,u16 brp,u32 mode)
{
CAN_FilterConfTypeDef CAN1_FilerConf;
CAN1_Handler.Instance=CAN1;
CAN1_Handler.pTxMsg=&TxMessage; //发送消息
CAN1_Handler.pRxMsg=&RxMessage; //接收消息
CAN1_Handler.Init.Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN1_Handler.Init.Mode=mode; //模式设置
CAN1_Handler.Init.SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
CAN1_Handler.Init.BS1=tbs1; //tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
CAN1_Handler.Init.BS2=tbs2; //tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
CAN1_Handler.Init.TTCM=DISABLE; //非时间触发通信模式
CAN1_Handler.Init.ABOM=DISABLE; //软件自动离线管理
CAN1_Handler.Init.AWUM=DISABLE; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
CAN1_Handler.Init.NART=ENABLE; //禁止报文自动传送
CAN1_Handler.Init.RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN1_Handler.Init.TXFP=DISABLE; //优先级由报文标识符决定
if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK) return 1; //初始化
//接收FIFO设置
CAN1_FilerConf.FilterIdHigh=0X0000; //32位ID
CAN1_FilerConf.FilterIdLow=0X0000;
CAN1_FilerConf.FilterMaskIdHigh=0X0000; //32位MASK
CAN1_FilerConf.FilterMaskIdLow=0X0000;
CAN1_FilerConf.FilterFIFOAssignment=CAN_FILTER_FIFO0;//过滤器0关联到FIFO0
CAN1_FilerConf.FilterNumber=0; //过滤器0
CAN1_FilerConf.FilterMode=CAN_FILTERMODE_IDMASK;
CAN1_FilerConf.FilterScale=CAN_FILTERSCALE_32BIT;
CAN1_FilerConf.FilterActivation=ENABLE; //激活滤波器0
CAN1_FilerConf.BankNumber=14;
if(HAL_CAN_ConfigFilter(&CAN1_Handler,&CAN1_FilerConf)!=HAL_OK) return 2;//滤波器初始化
else return 0;
}
接下来是编写HAL_CAN_Init()函数的回调函数,主要是GPIO引脚复用,时钟使能,设置中断优先级。
``c
```c
```c
```c
```c
```c
//CAN底层驱动,引脚配置,时钟配置,中断配置
//此函数会被HAL_CAN_Init()调用
//hcan:CAN句柄
``
void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan)
{
GPIO_InitTypeDef GPIO_Initure;`
__HAL_RCC_CAN1_CLK_ENABLE(); //使能CAN1时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12; //PA11,12
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速
GPIO_Initure.Alternate=GPIO_AF9_CAN1; //复用为CAN1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化
__HAL_CAN_ENABLE_IT(&CAN1_Handler,CAN_IT_FMP0);//FIFO0消息挂起中断允许
HAL_NVIC_SetPriority(CAN1_RX0_IRQn,1,2); //抢占优先级1,子优先级2
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); //使能中断
}
``
然后是编写CAN发送函数和CAN接收中断处理函数。CAN发送函数CAN1_Send_Msg()相对简单,即在内部先设置发送ID、字长等信息,然后利用数组循环发送数据。
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
u8 CAN1_Send_Msg(u8* msg,u8 len)
{
u16 i=0;
TxMessage.RTR=CAN_RTR_DATA;
TxMessage.IDE=CAN_ID_STD;
TxMessage.ExtId=0x00;
TxMessage.StdId=std_id;
TxMessage.DLC=len;
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
// CAN1_Handler.pTxMsg->Data[i]=msg[i];
if(HAL_CAN_Transmit(&CAN1_Handler,10)!=HAL_OK) return 1; //发送
else return 0;
}
`最重要的,即是CAN接收中断函数的编写。HAL库提供了名为CAN1_RX0_IRQHandler()的CAN中断处理函数。在其内部调用 HAL_CAN_IRQHandler()函数,而该函数会调用CAN_Receive_IT()函数,CAN_Receive_IT()函数,最终调用_weak回调函数HAL_CAN_RxCpltCallback()来处理。因此,CAN接收代码写在该回调函数中。其主要实现逻辑为,CAN每发送一个数据,都传到TxMessage.Data[i]数组中,在中断回调函数里将数据传递,即 RxMessage.Data[i]=TxMessage.Data[i]语句,实现了CAN接收功能。另外,在其内部,还有LCD屏幕显示功能,可在CAN回环模式下,显示发送接收是否正常。
//CAN中断处理过程
//此函数会被CAN_Receive_IT()调用
//hcan:CAN句柄
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* hcan)
{
int i=0;
//CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
__HAL_CAN_ENABLE_IT(&CAN1_Handler,CAN_IT_FMP0);//重新开启FIF00消息挂号中断
for(i=0;i<8;i++)
RxMessage.Data[i]=TxMessage.Data[i];
LCD_Fill(30,270,160,310,WHITE);//清除之前的显示
for(i=0;i<RxMessage.DLC;i++)
{
if(i<4)LCD_ShowxNum(30+i*32,270,RxMessage.Data[i],3,16,0X80); //显示数据
else LCD_ShowxNum(30+(i-4)*32,290,RxMessage.Data[i],3,16,0X80); //显示数据
}
}
``
最后贴一下,can.c内的完整代码,如下,
``
#include "can.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "lcd.h"
#include "sdram.h"
//
CAN_HandleTypeDef CAN1_Handler; //CAN1句柄
CanTxMsgTypeDef TxMessage;//发送
CanRxMsgTypeDef RxMessage;//接收
uint32_t std_id=0x00;
CAN初始化
//则波特率为:45M/((6+8+1)*6)=500Kbps
//返回值:0,初始化OK;
// 其他,初始化失败;
u8 CAN1_Mode_Init(u32 tsjw,u32 tbs2,u32 tbs1,u16 brp,u32 mode)
{
CAN_FilterConfTypeDef CAN1_FilerConf;
CAN1_Handler.Instance=CAN1;
CAN1_Handler.pTxMsg=&TxMessage; //发送消息
CAN1_Handler.pRxMsg=&RxMessage; //接收消息
CAN1_Handler.Init.Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN1_Handler.Init.Mode=mode; //模式设置
CAN1_Handler.Init.SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
CAN1_Handler.Init.BS1=tbs1; //tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
CAN1_Handler.Init.BS2=tbs2; //tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
CAN1_Handler.Init.TTCM=DISABLE; //非时间触发通信模式
CAN1_Handler.Init.ABOM=DISABLE; //软件自动离线管理
CAN1_Handler.Init.AWUM=DISABLE; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
CAN1_Handler.Init.NART=ENABLE; //禁止报文自动传送
CAN1_Handler.Init.RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN1_Handler.Init.TXFP=DISABLE; //优先级由报文标识符决定
if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK) return 1; //初始化
//接收FIFO设置
CAN1_FilerConf.FilterIdHigh=0X0000; //32位ID
CAN1_FilerConf.FilterIdLow=0X0000;
CAN1_FilerConf.FilterMaskIdHigh=0X0000; //32位MASK
CAN1_FilerConf.FilterMaskIdLow=0X0000;
CAN1_FilerConf.FilterFIFOAssignment=CAN_FILTER_FIFO0;//过滤器0关联到FIFO0
CAN1_FilerConf.FilterNumber=0; //过滤器0
CAN1_FilerConf.FilterMode=CAN_FILTERMODE_IDMASK;
CAN1_FilerConf.FilterScale=CAN_FILTERSCALE_32BIT;
CAN1_FilerConf.FilterActivation=ENABLE; //激活滤波器0
CAN1_FilerConf.BankNumber=14;
if(HAL_CAN_ConfigFilter(&CAN1_Handler,&CAN1_FilerConf)!=HAL_OK) return 2;//滤波器初始化
else return 0;
}
//CAN底层驱动,引脚配置,时钟配置,中断配置
//此函数会被HAL_CAN_Init()调用
//hcan:CAN句柄
void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_CAN1_CLK_ENABLE(); //使能CAN1时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12; //PA11,12
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速
GPIO_Initure.Alternate=GPIO_AF9_CAN1; //复用为CAN1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化
__HAL_CAN_ENABLE_IT(&CAN1_Handler,CAN_IT_FMP0);//FIFO0消息挂起中断允许
HAL_NVIC_SetPriority(CAN1_RX0_IRQn,1,2); //抢占优先级1,子优先级2
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); //使能中断
}
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
u8 CAN1_Send_Msg(u8* msg,u8 len)
{
u16 i=0;
TxMessage.RTR=CAN_RTR_DATA;
TxMessage.IDE=CAN_ID_STD;
TxMessage.ExtId=0x00;
TxMessage.StdId=std_id;
TxMessage.DLC=len;
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
// CAN1_Handler.pTxMsg->Data[i]=msg[i];
if(HAL_CAN_Transmit(&CAN1_Handler,10)!=HAL_OK) return 1; //发送
else return 0;
}
//CAN接收中断
void CAN1_RX0_IRQHandler(void)
{
HAL_CAN_IRQHandler(&CAN1_Handler);//此函数会调用CAN_Receive_IT()接收数据
}
//CAN中断处理过程
//此函数会被CAN_Receive_IT()调用
//hcan:CAN句柄
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* hcan)
{
int i=0;
//CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
__HAL_CAN_ENABLE_IT(&CAN1_Handler,CAN_IT_FMP0);//重新开启FIF00消息挂号中断
for(i=0;i<8;i++)
RxMessage.Data[i]=TxMessage.Data[i];
LCD_Fill(30,270,160,310,WHITE);//清除之前的显示
for(i=0;i<RxMessage.DLC;i++)
{
if(i<4)LCD_ShowxNum(30+i*32,270,RxMessage.Data[i],3,16,0X80); //显示数据
else LCD_ShowxNum(30+(i-4)*32,290,RxMessage.Data[i],3,16,0X80); //显示数据
}
}
`
其对应的头文件等,正点原子官方均有定义。此外,本例只实现了CAN通信的最基本功能(接收发送),代码逻辑、代码完成度,尚有很多不足之处,请大家能够积极讨论,互相进步。