CAN 通信的应用非常广泛,本文不涉及CAN通信的基础配置,重点分析一下STM32和GD32的CAN通信两种ID过滤方式。
首先,不管是STM32还是GD32,实现CAN通信ID过滤的机制和原理一定是一样的,只是用到的寄存器有差别。
1. ID过滤原理:
在CAN协议里,报文的标识符不代表节点的地址,而是跟报文的内容相关的。因此,发送者以广播的形式把报文发送给所有的接收者。节点在接收报文时,根据标识符的值决定软件是否需要该报文。
为满足这一需求,在互联型产品中,bxCAN控制器为应用程序提供了28个位宽可变的、可配置的过滤器组(270);在其它产品中,bxCAN控制器为应用程序提供了14个位宽可变的、可配置的过滤器组(130),以便只接收那些软件需要的报文。硬件过滤的做法节省了CPU开销,否则就必须由软件过滤从而占用一定的CPU开销。
每个过滤器组x由2个32位寄存器,在STM32中是由CAN_FxR0和CAN_FxR1组成。在GD32中是由器CAN_FxDATA0和CAN_ FxDATA1组成。
2. ID过滤方式:
掩码模式、列表模式。
不管是掩码模式还是列表模式,目的肯定都是为了让数据接收方接收特定ID的数据,但是既然分了两种模式,肯定有他们的不同的目的。
3.ID过滤方式1——掩码模式:
在掩码模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理。
举个例子:
假如是标准帧模式,以16位宽度为例,预设的ID=0x0001,当期望接收所有ID尾号为1的数据,那么设置对应的掩码为MASK=0x0001即可。
为什么呢?将ID的0x0001和MASK的0x0001解析成2进制:
预设ID:0000 0000 0000 0001;
MASK:0000 0000 0000 0001;
MSAK值的1的位表示:接收的ID必须与预设ID的位一致;
MASK值为0的位表示:接收的ID可以不与预设ID的位一致;
因此示例的MASK仅限制了ID的最后1位与预设ID的最后1为一致即可,其余的位不做限制,只要满足这样形式的ID数据,都能通过示例的过滤机制,从而被接收方正确接收,此处可知,掩码的作用就是要以预设ID为模板,决定过滤掉哪些类型的ID;
也就是说:
为了过滤出一组标识符,应该设置过滤器组工作在掩码模式。
注意,示例选用了标准帧模式,以16位宽度进行过滤,因此原本一个过滤器组的2个32位寄存器就分成了4个16位的空间,分别去保存预设的ID和相应的掩码:
格式如下:
预设ID1:【占用寄存器1的低16位】;
ID1的掩码:【占用寄存器1的高16位】;
同理:
预设ID2:【占用寄存器2的低16位】;
ID2的掩码:【占用寄存器2的高16位】;
由此可知,此种方式下,最多可预设2种ID,可过滤出多个ID;
STM32与GD32的过滤器机制一模一样:
(1)STM32参考手册的16位掩码模式过滤器如下:
(2)GD32参考手册的16位掩码模式过滤器如下:
注意,此种模式下,在配置时需要将对应的ID和掩码数据分别写进寄存器的STID区域,如上图所示,在16位空间中,STID区域处于高11位,因此需要将预设ID和掩码数据左移5位后写进寄存器。代码如下:
/**
* @brief can外设配置
* @param None
* @retval 返回值
*/
void can_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
/*
CAN1_RX -- PA11
CAN1_TX -- PA12
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
CAN_InitStructure.CAN_TTCM = DISABLE; //不生成时间戳
CAN_InitStructure.CAN_ABOM = ENABLE; //自动总线关闭管理
CAN_InitStructure.CAN_AWUM = DISABLE; //自动唤醒模式
CAN_InitStructure.CAN_NART = DISABLE; //仲裁丢失或出错后的自动重传功能
CAN_InitStructure.CAN_RFLM = DISABLE; //接收FIFO加锁模式
CAN_InitStructure.CAN_TXFP = ENABLE; //传输FIFO优先级
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq;
CAN_InitStructure.CAN_BS2 = CAN_BS1_8tq;
CAN_InitStructure.CAN_Prescaler = 40; // 波特率36000000/18/40 = 50KHz
CAN_Init(CAN1, &CAN_InitStructure);
CAN_FilterInit(&CAN_FilterInitStructure);
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;
if (can.device_mode == DEVICE_MASTER) //识别所有0x0xx类型的从设备地址
{
CAN_FilterInitStructure.CAN_FilterIdHigh = CAN_ID_BASE_SLAVE << 5; //ID1
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x700 << 5; //ID1_MASK
CAN_FilterInitStructure.CAN_FilterIdLow = CAN_ID_DEFAULT << 5; //ID2
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x7FF << 5; //ID2_MASK
}
else //仅识别与本设备地址匹配的地址,保证主设备对从设备实现一对一通讯,也识别0x0000广播
{
CAN_FilterInitStructure.CAN_FilterIdHigh = can.std_id << 5; //ID1
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x7FF << 5; //ID1_MASK
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000 << 5; //ID2
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x7FF << 5; //ID2_MASK
}
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); //FIFO0消息挂号中断
nvic_set(USB_LP_CAN1_RX0_IRQn, IRQ_PRIO_CAN1);
}
4. ID过滤方式2——列表模式:
在标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟过滤器标识符相同。通俗一点讲就是:接收的ID必须与预设的ID一模一样才能通过过滤,最终被接收到。
举个例子:
假如是标准帧模式,以16位宽度为例,预设的ID=0x0001,当期望仅接收与预设ID一致的数据,那么只要发送的ID与预设ID一样就可以被接收到,其余类型的ID数据都被过滤掉,接收方将接收不到此类数据。
也就是说:
为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。
注意,示例选用了标准帧模式,以16位宽度进行过滤,因此原本一个过滤器组的2个32位寄存器就分成了4个16位的空间,分别去保存预设的ID:
格式如下:
预设ID1:【占用寄存器1的低16位】;
预设ID2:【占用寄存器1的高16位】;
预设ID3:【占用寄存器2的低16位】;
预设ID4:【占用寄存器2的高16位】;
由此可知,此种方式下,最多可预设4种ID,可过滤出4个ID;
STM32与GD32的过滤器机制一模一样:
(1)STM32参考手册的16位列表模式过滤器如下:
(2)GD32参考手册的16位列表模式过滤器如下:
注意,此种模式下,在配置时需要将对应的4个ID分别写进寄存器的STID区域,如上图所示,在16位空间中,STID区域处于高11位,因此需要将预设ID数据左移5位后写进寄存器。示例代码如下:
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList; //??????
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit; //?????16?
CAN_FilterInitStructure.CAN_FilterIdHigh = std_id<<5; //4???CAN ID?????4????
CAN_FilterInitStructure.CAN_FilterIdLow = std_id1<<5;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = std_id<<5;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = std_id1<<5;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //?????????FIFO0?
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//激活过滤器0
5. 总结一下:
两种过滤方式各有千秋,需要过滤出一组ID,使用掩码模式;
需要过滤出特定的几个ID,使用列表模式。
实际使用中可巧妙地调整预设ID和掩码,甚至组合使用掩码模式和列表模式实现自己想要的过滤效果。