客户反映这样一个现象:使用FlexCAN ,使能发送中断和接收中断,在中断服务函数中如果同时有发送中断标志和接收中断标志,在清除一个标志位的同时会自动把另外一个标志位也清除掉。
本博客将此case复现,并提供解决办法。
以下应用层代码:
#include "gpio.h"
#include "uart.h"
#include "can.h"
/* 实验名称:CAN 通讯测试
实验平台:渡鸦开发板
板载芯片:MK60DN512
实验效果:使用CAN1模块的3号邮箱采用中断方式接收来自0x56的数据,使用2号邮箱向0x10地址发送数据,使能发送中断 */
extern CAN_Type * const CAN_InstanceTable[];
// CAN1 中断服务函数
void CAN_ISR(void)
{
printf("Go to CAN_ISR\r\n");
// 判断2号邮箱发送中断标志位是否置位
if((CAN_InstanceTable[HW_CAN1]->IFLAG1 & 0x04) != 0x00)
{
printf("Tx interrupt\r\n");
/* make sure clear IT pending bit according to IT enable reg */
CAN_InstanceTable[HW_CAN1]->IFLAG1 |= 0x04; // 通常方式清除标志位
// CAN_InstanceTable[HW_CAN1]->IFLAG1 = 0x04;
}
// 判断接收3号邮箱中断标志位是否置位
else if((CAN_InstanceTable[HW_CAN1]->IFLAG1 & 0x08)!= 0x00)
{
printf("Rx interrupt\r\n");
/* make sure clear IT pending bit according to IT enable reg */
CAN_InstanceTable[HW_CAN1]->IFLAG1 |= 0x08; // 通常方式清除标志位
// CAN_InstanceTable[HW_CAN1]->IFLAG1 = CAN_IFLAG1_BUF4TO0I(0x08);
}
}
int main(void)
{
DelayInit();
GPIO_QuickInit(HW_GPIOE, 6, kGPIO_Mode_OPP);
UART_QuickInit(UART0_RX_PD06_TX_PD07, 115200);
printf("CAN test\r\n");
CAN_QuickInit(CAN1_TX_PE24_RX_PE25, kCAN_Baudrate_125K); // 初始化
CAN_CallbackInstall(HW_CAN1, CAN_ISR); // 安装中断回调函数
CAN_ITDMAConfig(HW_CAN1,3, kCAN_IT_RX);// 打开3号邮箱中断接收功能
CAN_SetReceiveMB(HW_CAN1, 3, 0x56);// 配置3号邮箱接收
CAN_ITDMAConfig(HW_CAN1,2, kCAN_IT_Tx);// 打开2号邮箱中断发送功能
// while(1)
// {
CAN_WriteData(HW_CAN1, 2, 0x10, (uint8_t *)"CAN TEST", 8); // 使用2号邮箱发送数据
DelayMs(1000);
CAN_WriteData(HW_CAN1, 2, 0x10, (uint8_t *)"Hello !!", 8); // 使用2号邮箱发送数据
DelayMs(1000);
// }
while(1);
}
在数据发送完成后,进去中断服务函数,IFLAG1 发送中断标志位置1
执行完清标志位语句后:
CAN_InstanceTable[HW_CAN1]->IFLAG1 |= 0x04;
如果将清标志语句改为:
CAN_InstanceTable[HW_CAN1]->IFLAG1 = 0x04;
这样之后会再一次进入中断服务函数,这次完成中断接收功能,并清楚标志位
总结起来:
清标志这里
change from: can_reg_ptr->IFLAG1 |= tmp_reg;
change to: can_reg_ptr->IFLAG1 = tmp_reg;
解决方法参考的是:https://community.freescale.com/message/346689#346689
一开始还没想明白原因是什么,现在终于明白了原因。
我们通常操作寄存器的某位都是:
reg_ptr->IFLAG1 |= tmp_reg1;// 置位,tem_reg1 默位为1,其他全为0
reg_ptr->IFLAG1 &= tmp_reg2;// 清零,tem_reg1 默位为0,其他全为1
这种形式来修改某一寄存器中的某位,目的是只修改需要改的位,避免影响其它位。
它其实等同于,是一个read-modify-write的过程
reg_ptr->IFLAG1 =reg_ptr->IFLAG1 | tmp_reg1;
reg_ptr->IFLAG1 =reg_ptr->IFLAG1 & tmp_reg2;
因为它是写1清0,
CAN_InstanceTable[HW_CAN1]->IFLAG1 |= 0x04;
这条语句本义是给IFLAG1[2]写1清0,其他位保持不变。但是实际效是:
IFLAG1[3]为1,又写了一次1,结果就清0了。
所以根本原因就是IFLAG1的位是写w1c,而一般的寄存器写什么就是什么。