文章目录
- CAN总线简介
- CAN物理层
- CAN总线结构
- CAN时序
- CAN的帧
- 仲裁
- STM32的CAN外设
- STM32的bxCAN结构
- STM32的CAN时序
- CAN1给CAN2发送数据(实验)
- Cube配置
- 代码编写
- 参考资料
CAN总线简介
- 控制器局域网络,Controller Area Network
- 异步半双工通信协议(没有时钟线,不能同时收发数据)
- 总线制
- CAN由一对差分对(两条线)CANH和CANL组成
- CANH电压比CANL高视为逻辑0(显性电平),否则为逻辑1(隐性电平)
- 多个设备可以连接到同一条CAN上
- 总线上的设备通过ID号区分
- ID号长度可以为11位(标准长度)或29位(拓展长度)
- CAN2.0b标准(大部分设备支持)
- 最大速率1Mbps
- 一组数据称作一帧,分为数据帧和远程帧(不常用,不传输数据,告诉设备自己即将发送数据)
- 数据帧最多传输8个字节的数据
- 每一帧都有CRC校验
- CAN FD标准(支持设备比较少)
- 最大速率可达8Mbps
- 数据帧最多可传输64字节数据
CAN物理层
- 单片机一般使用类似串口的CAN RX和CAN TX接口驱动CAN
- CAN RX/CAN TX需要经过CAN收发器(Transceiver)才能被转换成对应的差分电平
- 一般CAN差分信号的电压为0-5V,也可以是0~3.3V(不建议,兼容性差)
CAN总线结构
- CAN有两个120Ω终端电阻,位于CAN布线的两端(主控板集成了这个电阻)
- 每个设备挂载在总线上形成数个支路
CAN时序
- CAN每一位会被分成数个Time Quanta(Tq),一般在10个以上
- CAN通信以Tq为基本时间单位
- 每一位的时间长度可以调整以保证同步
- 每一位被分成为3段:同步段,BS1和BS2
- BS1和BS2中间会进行接收的采样(BS1,即Prop+Phase1;BS2,即Phase2)
- CAN的实际工作频率会比通信频率更高
- 保证了时钟不同步的情况下,通信保持同步
- 设备可以通过插入和删去TQ来保证接收和发送同步
CAN的帧
- ID:ID号,11位(标准长度)或29位(拓展长度)
- 用于表示接收对象
- RTR:区别远程帧与数据帧
- 一般是数据帧,为0
- IDE:区别ID号长度
- 0,即为11位ID号;1,即29位ID号
- DLC:数据长度(发送数据的字节长),大致为0~8
- 在FDK中可以最高支持64位64个字节
- CRC:校验部分
- 连续的5个以上相同位中间会插入一个相反位(除结尾外)
仲裁
- 由于CAN定义2条信号线的电压差代表逻辑1或0,CAN的两条信号线实质等效于串口的一条线
- 发送和接收不能同时进行
- 当总线上连续3位的时间为1且没有正在传输的帧时,视为空闲状态,此时设备才能进行发送
- 如果有2个或以上设备同时启动了发送则会进入仲裁(Arbitration)
- 发送0更多的设备会赢得仲裁并继续发送
- 由于CAN时序的规定,仲裁是很难进入的,只有2个设备在同一Tq内启动了发送才会进入仲裁
- 仲裁保证了在半双工总线上不会产生冲突
STM32的CAN外设
- FDCAN:仅限H系列和G系列(第三代STM32)支持
- 支持FDCAN和CAN2.0b
- bxCAN:标准CAN外设,仅支持CAN2.0b(最高速度只有1Mbps)
- 一般有1~3个CAN
- CAN1和CAN3是主CAN,CAN2是从CAN
- CAN2仅在CAN1打开之后才能打开
- 推荐CAN1和CAN2同时打开使用
STM32的bxCAN结构
- 每个CAN具有3个发送邮箱和6个接收邮箱
- 邮箱用于暂存数据(可以当成串口的缓冲区)
- 接收邮箱分为2组FIFO,可触发不同的中断(向量不同)
- 外设会自动管理这些邮箱的发送和接收队列
- 2个CAN共享28个过滤器,用于过滤接收的ID号
STM32的CAN时序
-
CAN的频率计算公式如下
-
PSC决定了Tq的大小
-
一般BS1约为BS2的2倍,且BS2不宜过低
-
常用参数:对于STM32F407,主频168MHz时,APB1为42MHz,设置BS1=9,BS2=4,PSC=3,此时得到1MHz的CAN速率(常用配置)
CAN1给CAN2发送数据(实验)
-
将CAN1和CAN2直线连在一起
-
c板的2个CAN都内置了终端电阻,可以直接互相连接
-
CAN总线是需要共地的,但是c板的CAN1没有引出接地线,这里可以不接;实际使用中必须接地(电调一般通过电源线接地)
-
设置CAN速率为1Mbps(c板CAN总线接口最大支持1M传输速度)
Cube配置
打开Cube,一般情况下连接CAN需要GND接地,配置基础设置后
在connectivity下CAN1和CAN2都激活
然后根据芯片手册配置对应的引脚
配置好了之后就可以在CAN1下面配置时序
在Bit Timings Parameters中配置BS1,BS2 以及PSC
必须先设置BS1和BS2才能设置PSC
按照基本配置,BS1设为9,BS2设为4,PSC设为3
然后设置中断,一般CAN中的中断只会用到RX1和RX0,但我们也可以直接全部打开
再把按键外设打开,让它来触发发送
然后就可以生成代码
代码编写
在启动CAN之前要先配置过滤器
在main.c中USER CODE BEGIN 4/END 4之间先新建一个函数
/* USER CODE BEGIN 4 */
void CAN_Configfilter()
{
}
/* USER CODE END 4 */
在配置过滤器之前先定义一下过滤器的配置结构体
打开HAL库手册,找到CAN_FilterTypedef
可以看到在该结构体下面有如下的类型
这样我们可以先激活过滤器,即初始化结构体中的FilterActivation
sFiterconfig.FilterActivation = CAN_FILTER_ENABLE;//CAN_FILTER_ENABLE是一个宏定义,表示过滤器处于开启状态
然后设置过滤器编号,即FilterBank
可以看到编号从0到13或者0到27,我们这里选择0
sFiterconfig.FilterBank=0;
然后设置FilterFIFOAssignment,即代表中断给到RX0还是RX1,它有两个值
sFiterconfig.FilterFIFOAssignment=CAN_FilterFIFO0;//CAN_FilterFIFO0是宏定义
//表示我们把这个过滤器给到了RX0
接下来设置FilterMode,在HAL库手册中可以看到有两种模式
我们可以选择常用模式CAN_FILTERMODE_IDMASK
sFiterconfig.FilterMode=CAN_FILTERMODE_IDMASK;
还有一个FilterScale,同样有两种设置
我们这里配置为32bit
sFiterconfig.FilterScale=CAN_FILTERSCALE_32BIT;
后面就可以设置filter里面的一些数据
FilterIdHigh,FilterIdLow,FilterMaskIdHigh以及FilterMaskIdLow
我们全部设置为0x0000
sFiterconfig.FilterIdHigh=0x0000;
sFiterconfig.FilterIdLow=0x0000;
sFiterconfig.FilterMaskIdHigh=0x0000;
sFiterconfig.FilterMaskIdLow=0x0000;
最后该结构体中还有一个SlaveStartFilterBank未设置
该参数是一个定值,一般设置为14
sFilterconfig.SlaveStartFilterBank=14;
最终代码如下
/* USER CODE BEGIN 4 */
void CAN_Configfilter()
{
CAN_FilterTypeDef sFiterconfig;
sFiterconfig.FilterActivation = CAN_FILTER_ENABLE;
sFiterconfig.FilterBank=0;
sFiterconfig.FilterFIFOAssignment=CAN_FILTER_FIFO0;
sFiterconfig.FilterMode=CAN_FILTERMODE_IDMASK;
sFiterconfig.FilterScale=CAN_FILTERSCALE_32BIT;
sFiterconfig.FilterIdHigh=0x0000;
sFiterconfig.FilterIdLow=0x0000;
sFiterconfig.FilterMaskIdHigh=0x0000;
sFiterconfig.FilterMaskIdLow=0x0000;
sFiterconfig.SlaveStartFilterBank=14;
}
/* USER CODE END 4 */
然后可以在HAL库手册中查找到如何使用该过滤器
由于是全英文的我们可以使用翻译网站来提供参考,翻译如下:
1.通过实现HAL_CAN_MspInit()来初始化CAN低层资源:
使用__HAL_RCC_CANx_CLK_ENABLE()使能CAN接口时钟
—配置CAN管脚
◦为CAN gpio启用时钟
◦配置CAN引脚作为备用功能open-drain
-如果使用中断(例如HAL_CAN_ActivateNotification())
◦配置CAN中断优先级使用HAL_NVIC_SetPriority()
◦启用CAN IRQ处理程序使用HAL_NVIC_EnableIRQ()
◦在CAN IRQ处理程序中,调用HAL_CAN_IRQHandler()
2. 使用HAL_CAN_Init()函数初始化CAN外围设备。该函数使用HAL_CAN_MspInit() for
低级的初始化。
3.使用以下配置功能配置接收过滤器:
- HAL_CAN_ConfigFilter ()
4. 使用HAL_CAN_Start()函数启动CAN模块。在这个级别上,节点在总线上是活动的:它接收
消息,并可以发送消息。
5. 为了管理消息传输,可以使用以下Tx控制函数:
- HAL_CAN_AddTxMessage()请求传输新消息。
- HAL_CAN_AbortTxRequest()终止挂起消息的传输。
- HAL_CAN_GetTxMailboxesFreeLevel()获取空闲的Tx邮箱的数量。
- HAL_CAN_IsTxMessagePending()用于检查Tx邮箱中是否有消息挂起。
- HAL_CAN_GetTxTimestamp()获取发送的Tx消息的时间戳,如果时间触发
已启用通信模式。
6. 当消息被接收到CAN Rx FIFOs中时,可以使用
HAL_CAN_GetRxMessage()函数。函数HAL_CAN_GetRxFifoFillLevel()允许知道如何
许多Rx消息存储在Rx Fifo中。
7. 调用HAL_CAN_Stop()函数将停止CAN模块。
8. 去初始化是通过HAL_CAN_DeInit()函数实现的。
我们可以看到前两条是系统的初始化,第三条是配置过滤器,第四条启动CAN
因此我们可以先配置过滤器
HAL_CAN_ConfigFilter(&hcan1,&sFiterconfig);//第一个参数是CAN的地址,第二个参数是配置的结构体
后面我们还需要设置CAN2的过滤器,不过在配置之前我们需要修改结构体中的一个参数
sFilterconfig.FilterBank=14;
然后配置CAN2的过滤器
HAL_CAN_ConfigFilter(&hcan2,&sFiterconfig);
但是我们发现HAL_CAN_ConfigFilter()这个函数有返回值(int),该返回值可以反馈该配置是否成功
因此我们需要把该函数作为一个判断条件,判断是否配置成功
if( HAL_CAN_ConfigFilter(&hcan1,&sFiterconfig)!=HAL_OK)
{
Error_Handler();//该函数是HAL库里面自带的一个用于处理错误的函数,该函数可以由我们来编写
}//表示如果过滤器配置不成功,就会进行错误处理
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}//配置失败的情况下会把程序卡死
对CAN2进行同样的过滤器配置
如果不进行过滤器配置会导致收不到信息
配置完第三点的过滤器后,我们可以进行第四点启动CAN
先启动CAN1再启动CAN2
if(HAL_CAN_Start(&hcan1)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_Start(&hcan2)!=HAL_OK)
{
Error_Handler();
}
然后需要启动中断我们使用HAL_CAN_ActivateNotification()这个函数,该函数功能如下
它有两个参数即CAN_HandleTypeDef * hcan以及uint32_t ActiveITs
其中的ActiveITs是一个宏定义组
可以找到其中有如下宏定义
其中
- CAN_IT_TX_MAILBOX_EMPTY表示发送的邮箱变为空,即发送完成
- CAN_IT_RX_FIFO0_MSG_PENDING代表在RX0的中断里面收到了数据
- CAN_IT_RX_FIFO1_MSG_PENDING代表在RX1的中断里面收到了数据
if(HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING|CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_ActivateNotification(&hcan2,CAN_IT_RX_FIFO0_MSG_PENDING|CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
这样就完成了CAN初始化的函数编写,完整代码如下
/* USER CODE BEGIN 4 */
//定义can过滤器
void CAN_Configfilter()
{
CAN_FilterTypeDef sFiterconfig;
sFiterconfig.FilterActivation = CAN_FILTER_ENABLE;
sFiterconfig.FilterBank=0;
sFiterconfig.FilterFIFOAssignment=CAN_FILTER_FIFO0;
sFiterconfig.FilterMode=CAN_FILTERMODE_IDMASK;
sFiterconfig.FilterScale=CAN_FILTERSCALE_32BIT;
sFiterconfig.FilterIdHigh=0x0000;
sFiterconfig.FilterIdLow=0x0000;
sFiterconfig.FilterMaskIdHigh=0x0000;
sFiterconfig.FilterMaskIdLow=0x0000;
sFiterconfig.SlaveStartFilterBank=14;
//启动can1过滤器
if( HAL_CAN_ConfigFilter(&hcan1,&sFiterconfig)!=HAL_OK)
{
Error_Handler();
}
//更改过滤器编号
sFilterconfig.FilterBank=14;
//启动can2过滤器
if( HAL_CAN_ConfigFilter(&hcan2,&sFiterconfig)!=HAL_OK)
{
Error_Handler();
}
//启动can1
if(HAL_CAN_Start(&hcan1)!=HAL_OK)
{
Error_Handler();
}
//启动can2
if(HAL_CAN_Start(&hcan2)!=HAL_OK)
{
Error_Handler();
}
//启动can1的rx0和rx1中断
if(HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING|CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
//启动can2的rx0和rx1中断
if(HAL_CAN_ActivateNotification(&hcan2,CAN_IT_RX_FIFO0_MSG_PENDING|CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
}
/* USER CODE END 4 */
编写好了之后要先进行该函数的声明
/* USER CODE BEGIN PFP */
void CAN_Configfilter();//声明can过滤器函数
/* USER CODE END PFP */
然后可以在USER CODE BEGIN 2/END 2之间调用
/* USER CODE BEGIN 2 */
void CAN_Configfilter();//调用can过滤器函数
/* USER CODE END 2 */
这样就完成了CAN的启动
然后就可以创建要发送数据的数组以及接受数据的数组
/*USER CODE BEGIN PV*/
uitnt8_t txDateBuffer[8],rxDateBuffer[8]
/*USER CODE END PV*/
CAN有两个header的问题,因此我们需要给两个header的结构体
/*USER CODE BEGIN PV*/
uitnt8_t txDateBuffer[8],rxDateBuffer[8];
CAN_TxHeaderTypeDef txHeader;//主要存储了我们要发送的ID号,帧的类型(数据帧还是远程帧),ID号长度(11位还是29位)等信息
CAN_RxHeaderTypeDef rxHeader;
/*USER CODE END PV*/
我们使用了key按键来触发,因此需要编写相应的代码
在c中使用布尔变量我们需要添加对应的头文件,
/*USER CODE BEGIN Includes*/
#include"stdbool.h"
/*USER CODE END Includes*/
这样就可以使用布尔的函数
在之前编写的USER CODE BEGIN PV/END PV中间加入bool类型的pa0Pressed
修改后代码如下
/*USER CODE BEGIN PV*/
uitnt8_t txDateBuffer[8],rxDateBuffer[8];
CAN_TxHeaderTypeDef txHeader;//主要存储了我们要发送的ID号,帧的类型(数据帧还是远程帧),ID号长度(11位还是29位)等信息
CAN_RxHeaderTypeDef rxHeader;
bool pa0Pressed = false;//通过这个布尔以及读取的gpio的电平来判断gpio的上升沿和下降沿
/*USER CODE END PV*/
在while(1)中补齐代码
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET && !pa0Pressed)
{
pa0Pressed=true;//按键的上升沿
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET && !pa0Pressed)
{
pa0Pressed=false;//按键的下降沿
}
HAL_Delay(100);
//这个布尔变量pa0Pressed,在我们按下按键时会从SET变为RESET
//这样就进行了一个边沿的判定
然后在上升沿判断里进行一个发送数据的操作
设置txHeader结构体的初始化
可以在HAL库里面看到该结构体
我们可以把txHeader里面的StdId设置为0x200,但为了调试我们后面可能会修改这个数值
因此我们可以再设置一个uint16_t txId,让它等于0x200,让txHeader.StdId等于该数据
配置IDE可以看到需要CAN_identifier_type
可以配置成两种CAN_ID_STD和CAN_ID_EXT
这里我们配置标准的这个
然后是RTR,即设置为远程帧还是标准帧,需要CAN_remote_transmission_request
同样是两种
这里我们使用数据帧
最后配置DLC,即数据长度,这里我们给8位
txHeader.StdId=txId;
txHeader.IDE=CAN_ID_STD;
txHeader.RTR=CAN_RTR_DATA;
txHeader.DLC=8;
这样我们就配置好了txHeader
然后我们还是让它传送一个系统运行时间
*((uint32_t*)(txDateBuffer))=HAL_GetTick();
//这里我们把txDateBuffer(原来是uint8_t类型)强制类型转换成一个uint32_t类型
然后我们执行发送函数
HAL_CAN_AddTxMessage(&hcan1,&txHeader,txDateBuffer,)
/*发送需要的Header,以及数据txDateBuffer
最后的参数是一个ptxMailbox,STM32的发送邮箱有三个,ptxMailbox会把实际发送的这个邮箱丢进这个指针指向的这个地址
该参数的函数有两个返回值,第一个返回值是hal ok即表示发送是否完成,第二个返回值表示用了哪个Mailbox发送,我们可以直接写一个NULL即往0的地址去写数据,可能会出现问题
因此我们可以再写一个变量,让它把数据写到这个地址里*/
HAL_CAN_AddTxMessage(&hcan1,&txHeader,txDateBuffer,&TXmailbox);
//这样在使用这个函数时,可以发送数据,同时把该函数的返回值丢进TXmailbox这个地址里
我们还需要判断该Mailbox是否被占用
if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)!=0)
{
HAL_CAN_AddTxMessage(&hcan1,&txHeader,txDateBuffer,&TXmailbox);
}
至此我们就完成了发送的函数
我们的接收是中断接收,因此需要编写中断的回调函数
RX0和RX1对应两个不同的中断向量,因此对应两个不同的中断函数
void HAL_can_RxFifo0MsgPendingCallback(CAN_HanderTypeDef*hcan)//接收数据的回调函数
{
}//RX0
void HAL_can_RxFifo1MsgPendingCallback(CAN_HanderTypeDef*hcan)//接收数据的回调函数
{
}//RX1
在回调函数中要先确认是CAN2接收数据
然后将接收到的数据存储到数据缓冲区中
我们需要用到HAL_CAN_GetRxMessage这个函数
HAL_CAN_GetRxMessage(&hcan2,CAN_RX_FIFo0,&rxDateBuffer);
但是还会存在有两个接收中断的问题,因此我们可以给他们分配两个不同的Header,和两个不同的Buffer
这样就需要我们把原来的rx Header与rxDateBuffer改为两个
这样代码修改如下
/* USER CODE BEGIN PV */
uint8_t txDateBuffer[8], rxDateBuffer0[8],rxDateBuffer1[8];
CAN_TxHeaderTypeDef txHeader;//主要存储了我们要发送的ID号,帧的类型(数据帧还是远程帧),ID号长度(11位还是29位)等信息
CAN_RxHeaderTypeDef rxHeader0,rxHeader1;
bool pa0Pressed = false;//通过这个布尔以及读取的gpio的电平来判断gpio的上升沿和下降沿
uint16_t txId=0x200;//方便调试
uint32_t TXmailbox;
/* USER CODE END PV */
这样RX0和RX1这两个中断向量接收到的数据就会进入不同的缓存中
这样代码编写就完成了,可以进行编译与烧录了
全部代码如下:
/* USER CODE BEGIN PV */
uint8_t txDateBuffer[8], rxDateBuffer0[8],rxDateBuffer1[8];
CAN_TxHeaderTypeDef txHeader;//主要存储了我们要发送的ID号,帧的类型(数据帧还是远程帧),ID号长度(11位还是29位)等信息
CAN_RxHeaderTypeDef rxHeader0,rxHeader1;
bool pa0Pressed = false;//通过这个布尔以及读取的gpio的电平来判断gpio的上升沿和下降沿
uint16_t txId=0x200;//方便调试
uint32_t TXmailbox;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
void CAN_Configfilter();
/* USER CODE END PFP */
/* USER CODE BEGIN 2 */
void CAN_Configfilter();
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET && !pa0Pressed)
{
pa0Pressed=true;//按键的上升沿
txHeader.StdId=txId;
txHeader.IDE=CAN_ID_STD;
txHeader.RTR=CAN_RTR_DATA;
txHeader.DLC=8;
*((uint32_t*)(txDateBuffer)) = HAL_GetTick();
if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)!=0)
{
HAL_CAN_AddTxMessage(&hcan1,&txHeader,txDateBuffer,&TXmailbox);
}
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET && !pa0Pressed)
{
pa0Pressed=false;//按键的下降沿
}
HAL_Delay(100);
//这个布尔变量pa0Pressed,在我们按下按键时会从SET变为RESET
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void CAN_Configfilter()
{
CAN_FilterTypeDef sFiterconfig;
sFiterconfig.FilterActivation = CAN_FILTER_ENABLE;
sFiterconfig.FilterBank=0;
sFiterconfig.FilterFIFOAssignment=CAN_FILTER_FIFO0;
sFiterconfig.FilterMode=CAN_FILTERMODE_IDMASK;
sFiterconfig.FilterScale=CAN_FILTERSCALE_32BIT;
sFiterconfig.FilterIdHigh=0x0000;
sFiterconfig.FilterIdLow=0x0000;
sFiterconfig.FilterMaskIdHigh=0x0000;
sFiterconfig.FilterMaskIdLow=0x0000;
sFiterconfig.SlaveStartFilterBank=14;
if( HAL_CAN_ConfigFilter(&hcan1,&sFiterconfig)!=HAL_OK)
{
Error_Handler();
}
sFiterconfig.FilterBank=14;
if( HAL_CAN_ConfigFilter(&hcan2,&sFiterconfig)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_Start(&hcan1)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_Start(&hcan2)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
if(HAL_CAN_ActivateNotification(&hcan2,CAN_IT_RX_FIFO0_MSG_PENDING|CAN_IT_RX_FIFO1_MSG_PENDING)!=HAL_OK)
{
Error_Handler();
}
}
void HAL_can_RxFifo0MsgPendingCallback(CAN_HandleTypeDef*hcan)//接收数据的回调函数
{
if(hcan==&hcan2)
{
HAL_CAN_GetRxMessage(&hcan2,CAN_RX_FIFO0,&rxHeader0,rxDateBuffer0);
}
}//RX0
void HAL_can_RxFifo1MsgPendingCallback(CAN_HandleTypeDef*hcan)//接收数据的回调函数
{
if(hcan==&hcan2)
{
HAL_CAN_GetRxMessage(&hcan2,CAN_RX_FIFO1,&rxHeader1,rxDateBuffer1);
}
}//RX1
/* USER CODE END 4 */