CAN学习笔记20(中断配置)
- 2026-11-07 00:29:33
CAN学习笔记20(中断配置)中断程序任务设定: 当FIFO0中有数据,就触发中断,然后在中断程序里进行数据的接收,接收到的数据存放在数据结构体里面,名称设定为:CAN_Rxmsg。然后再置一个标志位,表示有数据收到,然后再对数据进行处理。 在主循环里面判断CAN_Rxmsg这个结构体是否收到数据,如果收到数据了,就显示这些数据。 在实际项目中一般是多个帧数据同时发送,对于重要的数据,才会需要用到中断触发,可以充分利用FIFO中的3个缓存区,或者接收多个数据组成的数据包使用中断。而线路中只有一个数据来源的时候就没必要用到中断,只需要用查询功能就可以。 本任务是练习中断功能的配置。 程序沿用前面的程序构架进行修改 程序整体设计思路如下: 上电初始化外设,主循环里扫描按键,按一下按键循环轮流发送 4 条预定义 CAN 报文;收到 CAN 报文(中断置标志)后,在 OLED 上实时显示接收报文类型(标准帧 / 扩展帧、数据帧 / 远程帧)、ID、长度、数据。 程序修改如下: 1. 配置中断的步骤 (1)使用CAN_ITConfig,打开外设的中断信号输出。 (2)配置NVIC,接收外设的中断信号,进入NVIC。 (3)在程序中写中断函数。 CAN外设占用4个专用的中断向量,如下: 

在程序代码库中选择合适的中断函数进行调用,配置程序代码。 2. 在MyCAN.c中配置中断初始化 
上面程序中 来源于系统配置参数,通过如下步骤实现系统查询和配置调取 先在本工程文件搜索IRQChannel, 
找到系统功能定义 
然后根据系统文件提示,继续搜索 
找到与自己当前STM32C8T6型号一致的单片机 然后查找相关参数配置。 
然后选择 USB_LP_CAN1_RX0_IRQn 进行配置 
注意:这个函数只是把报文放进邮箱,不代表已经发到 CAN 总线上去了。 如果 3 个邮箱全满, 初始化超时计数器 Timeout = 0 while 循环:轮询这个邮箱的发送状态 触发条件:CAN 总线上收到报文,存入 FIFO0,并且 FMP0 中断使能打开,进入这个中断; 
依次初始化 OLED 屏幕、按键模块、CAN1 外设; 在 OLED 固定位置打印固定标签,预留显示区域: :读取按键,返回按键编号,没有按键按下返回 0; 如果检测到按键 1 按下: :说明中断已经收到一帧报文, 第一步立刻 第一层判断 第二层判断
发送:主循环按键触发、阻塞式发送 接收:中断接收 + 主循环轮询标志解析刷新 OLED(中断只管收报文打标记,业务显示全部放主循环)
发送中断:发送邮箱空时产生
FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生
FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生
状态改变错误中断:出错/唤醒/进入睡眠时产生


/*** @brief Clears the CANx's interrupt pending bits.* @param CANx: where x can be 1 or 2 to to select the CAN peripheral.* @param CAN_IT: specifies the interrupt pending bit to clear.* - CAN_IT_TME // 发送邮箱空中断,邮箱空闲可发送新报文* - CAN_IT_FF0 // FIFO0满中断,FIFO0已经存满3帧报文* - CAN_IT_FOV0 // FIFO0溢出中断,FIFO0满后又收到新帧,报文丢失* - CAN_IT_FF1 // FIFO1满中断,FIFO1已经存满3帧报文* - CAN_IT_FOV1 // FIFO1溢出中断,FIFO1满后又收到新帧,报文丢失* - CAN_IT_WKU // 唤醒中断,CAN从休眠状态被总线信号唤醒* - CAN_IT_SLK // 休眠确认中断,CAN成功进入休眠模式* - CAN_IT_EWG // 错误警告中断,错误计数达到警告阈值(≥96)* - CAN_IT_EPV // 被动错误中断,错误计数达到被动阈值(≥128)* - CAN_IT_BOF // 总线关闭中断,错误计数溢出,脱离总线停止收发* - CAN_IT_LEC // 最后错误码中断,记录本次总线错误类型* - CAN_IT_ERR // 通用错误总中断,EWG/EPV/BOF/LEC任意错误触发* @retval None.*/
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // 使能FIFO0收到报文中断(FMP0:FIFO0消息挂起中断)3. 在MyCAN.c中配置NVIC
配置结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // NVIC分组2:2位抢占优先级,2位子优先级把结构体展开,配置结构体参数
NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; // 选择CAN1_RX0中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级1NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; 



4. 在MyCAN.c中写中断函数
(1)自定义2个函数模块
CanRxMsg MyCAN_RxMsg; // 定义CAN接收消息结构体,存放接收到的CAN报文uint8_t MyCAN_RxFlag; // CAN接收标志位,1代表收到一帧CAN数据,0代表无新数据
然后在MyCAN.h中声明外部可调用

(2)在MyCAN.c中写中断函数模块的程序
// CAN发送函数,带超时等待,等待发送完成void MyCAN_Transmit(CanTxMsg *TxMessage){uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage); // 把报文写入发送邮箱,返回邮箱编号uint32_t Timeout = 0;// 循环等待发送状态为发送成功while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok){Timeout ++;if (Timeout > 100000) // 超时判断,防止卡死{break;}}}
上面程序运行流程
CAN_Transmit(CAN1, TxMessage)作用:把传入的 Tx 报文(ID、数据长度、8 字节数据等)提交给 CAN 外设,申请一个空闲发送邮箱; 返回值:0 / 1 / 2,代表选中的邮箱编号;
CAN_Transmit 返回 CAN_TxStatus_NoMailBox(0xFF),后续 CAN_TransmitStatus 判断就会一直不满足,进入 while 循环等待。CAN_TransmitStatus(CAN1, TransmitMailbox) 查询该邮箱状态:CAN_TxStatus_Ok:报文已经成功发送到总线,邮箱发送完成;其他状态:发送中 / 邮箱挂起(还没轮到发) / 发送失败(仲裁丢失、报错) 每次循环 Timeout++,做软件计时(空循环延时,不是精准定时器)当 Timeout > 100000,直接跳出循环,退出等待,防止程序死锁卡死
// CAN1_RX0中断服务函数,FIFO0收到报文触发void USB_LP_CAN1_RX0_IRQHandler(void){// 判断是否是FIFO0收到报文中断标志if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) == SET){CAN_Receive(CAN1, CAN_FIFO0, &MyCAN_RxMsg); // 从FIFO0读取报文到接收结构体MyCAN_RxFlag = 1; // 置接收标志,通知主循环读取报文}}
中断名 USB_LP_CAN1_RX0_IRQHandler 是 STM32F1 系列的固定向量名:USB 低优先级 和 CAN1_RX0 共用同一个中断入口。
CAN_GetITStatus(CAN1, CAN_IT_FMP0):检查是不是 FIFO0 报文挂起中断标志。FMP0 = FIFO0 Message Pending,代表 FIFO 里面有新报文。
CAN_Receive(CAN1, CAN_FIFO0, &MyCAN_RxMsg):把 FIFO0 里的一帧 CAN 报文(ID、DLC、数据、IDE/RTR 等)拷贝到全局结构体 MyCAN_RxMsg;- 硬件自动清除 FMP 标志
(标准库 CAN_Receive 内部会处理 FIFO 弹出)
MyCAN_RxFlag = 1:置全局接收标志位。中断里面只做最快的底层读取 + 打标记,不做复杂解析、打印、运算; 复杂解析交给主 while 循环里判断 if(MyCAN_RxFlag ==1)去处理,这是嵌入式标准写法:中断快进快出。
5. 修改main.c 主程序
(1)模拟发送数组报文
标准 ID + 数据帧 扩展 ID + 数据帧 标准 ID + 远程请求帧 扩展 ID + 远程请求帧

(3)初始化
int main(void){OLED_Init();Key_Init();MyCAN_Init();OLED_ShowString(1, 1, " Rx :");OLED_ShowString(2, 1, "RxID:");OLED_ShowString(3, 1, "Leng:");OLED_ShowString(4, 1, "Data:");while (1){// 主循环轮询}}
第 1 行:Rx:后面用来显示 Std / Ext + Data/Remote 第 2 行:RxID:后面显示报文 ID 第 3 行:Leng:后面显示 DLC 数据长度 第 4 行:Data:后面显示 4 个字节数据
(3)按键扫描,CAN发送逻辑
KeyNum = Key_GetNum();if (KeyNum == 1){MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);pTxMsgArray ++;if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg)){pTxMsgArray = 0;}}
Key_GetNum()调用之前的阻塞发送函数 MyCAN_Transmit,发送数组里当前索引对应的报文;索引 pTxMsgArray自增;判断索引是否超出数组总个数(4 个),到达上限就清零,实现循环发包;
时序特点: 按下按键 → 进入
MyCAN_Transmit,程序卡在里面阻塞等待发送完成或者超时,发送函数返回后,才继续跑主循环剩下代码。
(4)接收标志判断、报文解析 + OLED 刷新
if (MyCAN_RxFlag == 1){MyCAN_RxFlag = 0;if (MyCAN_RxMsg.IDE == CAN_Id_Standard){OLED_ShowString(1, 6, "Std");OLED_ShowHexNum(2, 6, MyCAN_RxMsg.StdId, 8);}else if (MyCAN_RxMsg.IDE == CAN_Id_Extended){OLED_ShowString(1, 6, "Ext");OLED_ShowHexNum(2, 6, MyCAN_RxMsg.ExtId, 8);}if (MyCAN_RxMsg.RTR == CAN_RTR_Data){OLED_ShowString(1, 10, "Data ");OLED_ShowHexNum(3, 6, MyCAN_RxMsg.DLC, 1);OLED_ShowHexNum(4, 6, MyCAN_RxMsg.Data[0], 2);OLED_ShowHexNum(4, 9, MyCAN_RxMsg.Data[1], 2);OLED_ShowHexNum(4, 12, MyCAN_RxMsg.Data[2], 2);OLED_ShowHexNum(4, 15, MyCAN_RxMsg.Data[3], 2);}else if (MyCAN_RxMsg.RTR == CAN_RTR_Remote){OLED_ShowString(1, 10, "Remote");OLED_ShowHexNum(3, 6, MyCAN_RxMsg.DLC, 1);OLED_ShowHexNum(4, 6, 0x00, 2);OLED_ShowHexNum(4, 9, 0x00, 2);OLED_ShowHexNum(4, 12, 0x00, 2);OLED_ShowHexNum(4, 15, 0x00, 2);}}
MyCAN_RxFlag == 1MyCAN_RxMsg 里面已经存好了报文;MyCAN_RxFlag = 0; 清标志,代表当前这帧报文开始处理;IDE:区分标准帧 / 扩展帧,OLED 打印标识,同时打印对应 ID;RTR:区分数据帧 / 远程帧:数据帧:打印 DLC 长度,依次打印前 4 个数据字节; 远程帧:标记 Remote,数据区统一打印 00(远程帧本身不带有效数据);
关键:接收报文是异步触发的,和按键发送互不干扰。总线随时可能收到报文,中断置标志,主循环跑到这里就刷新屏幕。
编译,下载,测试
关注
重播 分享 赞
示例程序运行逻辑如下:
上电 → OLED、按键、CAN 初始化,屏幕打印固定表头; 主循环不断扫描按键,同时轮询 MyCAN_RxFlag;按下按键 1: 进入 MyCAN_Transmit,报文写入发送邮箱,循环等待发送完成 / 超时;发送函数返回,索引 + 1,下次按键发下一条; CAN 总线上收到别的设备发来报文: 硬件触发 CAN1_RX0 中断; 中断服务函数读取报文到 MyCAN_RxMsg,置MyCAN_RxFlag=1;退出中断,回到主循环; 主循环检测到 flag=1,清 flag,解析报文,刷新 OLED 显示;
触发中断的方式有多种,邮箱满可以触发中断,数据错误触发中断,前面表格中已有介绍。
通过网盘分享的文件:CAN学习笔记20(中断配置).zip
链接: https://pan.baidu.com/s/1pdtqykhizqAJNF75dSVBfg?pwd=frrf 提取码: frrf
本文来自网友投稿或网络内容,如有侵犯您的权益请联系我们删除,联系邮箱:wyl860211@qq.com 。