STM32 CAN过滤器

1 前言

STM32F10X的bxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。
在CAN协议里,报文的标识符不代表节点的地址,而是和报文的内容相关的。因此,发送者以广播的形式把报文发送给所有的接收者。节点在接收报文时,根据标识符(CAN ID)的值决定软件是否需要该报文;如果需要,就拷贝到SRAM里;如果不需要,报文就被丢弃且无需软件的干预。
为满足这一需求,在互联型产品中, bxCAN控制器为应用程序提供了28个位宽可变的、可配置的过滤器组(27~0);在其它产品中,bxCAN为应用程序提供了14个位宽可变的、可配置的过滤器组(13~0),以便只接收那些软件需要的报文。硬件过滤的做法节省了CPU开销,否则就必须由软件过滤从而占用一定的CPU开销。每个过滤器组x由2个32位寄存器,CAN_FxR0和CAN_FxR1组成。
[互联型产品是指STM32F105xx和STM32F107xx微控制器。]

为了让大家了解STM32的bxCAN的接收过滤机制,首先大家需要了解几个概念。

2 几个重要的概念

2.1 为什么要过滤器?

在这里,我们可以将CAN总线看成一个广播消息通道,上面传输着各种类型的消息,好比报纸,有体育新闻,财经新闻,政治新闻,还有军事新闻,每个人都有自己的喜好,不一定对所有新闻都感兴趣,因此,在看报纸的时候,一般人都是只看自己感兴趣的那类新闻,而过滤掉其他不感兴趣的内容。那么我们一般是怎么过滤掉那些不感兴趣的内容的呢?下面有两种方法来实现这个目的:

第一种方法:
每次看报纸时,你都看下每篇文章的标题[文章标题就类似CAN ID即标识符],如果感兴趣则继续看下去,如果不感兴趣,则忽略掉。

第二种方法:
你告诉邮递员[邮递员就是过滤器],你只对财经新闻感兴趣,请只将财经类报纸送过来,其他的就不要送过来了,就这样,你看到的内容必定是你感兴趣的财经类新闻。

上面那种方法好呢?很明显,第二种方法是最好的,因为你不用自己每次判断哪些新闻内容是你感兴趣的,可以免受“垃圾”新闻干扰,从而可以节省时间忙其他事。bxCAN的过滤器就是采用上述第二种方法,你只需要设置好你感兴趣的那些CAN报文ID,那么MCU就只能收到这些CAN报文,是从硬件上过滤掉,完全不需要软件参与进来,从而节省了大大节省了MCU的时间,可以更加专注于其他事务,这个就是bxCAN过滤器的意义所在。

2.2 两种过滤模式(列表模式与掩码模式)

假设我们是bxCAN这个IP的设计者,现在由我们来设计过滤器,那么我们该如何设计呢?

首先我们是不是很快就会想到只要准备好一张表,把我们需要关注的所有CAN报文ID写上去,开始过滤的时候只要对比这张表,如果接收到的报文ID与表上的相符,则通过,如果表上没有,则不通过,这个就是简单的过滤方案。恭喜你!bxCAN过滤器的列表模式采用的就是这种方案。

但是,这种列表方案有点缺陷,即如果我们只关注一个报文ID,则需要往列表中写入这个ID,如果需要关注两个,则需要写入两个报文ID,如果需要关注100个,则需要写入100个,如果需要1万个,那么需要写入1万个,可问题是,有这个大的列表供我们使用吗?大家都知道,MCU上的资源是有限的,不可能提供1万个或更多,甚至100个都嫌多。非常明显,这种列表的方式受到列表容量大小的限制,实际上,bxCAN的一个过滤器若工作在列表模式下,scale为32时,每个过滤器的列表只能写入两个报文ID,若scale为16时,每个过滤器的列表最多可写入4个CAN ID,由此可见,MCU的资源是非常非常有限的,并不能任我们随心所欲。因此,我们需要考虑另外一种替代方案,这种方案应该不受到数量限制。

下面假设我们是古时候一座城镇的守卫,城主要求只有1156年出生的人才可以进城,我们又该如何执行呢?假设古时候的人也有类似今天的身份证,大家都知道,身份份证号码中有4位是表示出生年月,如下图:
在这里插入图片描述
如上图,身份证中第7~10这4位数表示的是出生年份,那么,我们可以这么执行:

检查想要进城的所有人的身份证号码的第7~10位数字,如果这个数字依次为1156则可以进入,否则则不可以,至于身份证号码的其他位则完全不关心。假如过几天城主放宽进城条件为只要是1150年~1160前的人都可以进城,那么,我们就可以只关注身份证号码的第7~9这3位数是否为115就可以了,对不对?这样一来,我们就可以非常完美地执行城主的要求了。

再变下,假设现在使用机器来当守卫,不再是人来执行这个“筛选”工作。机器是死的,没有人那么灵活,那么机器又该如何执行呢?

对于机器来说,每一步都得细化到机器可以理解的程度,于是我们可以作如下细化:

第一步:获取想进城的人的身份证号码

第二步:只看获取到身份证的第7~9位,其他位忽略

第三步:将忽略后的结果与1156进行比较

第四步:比较结果相同则通过,不同则不能通过

这种方式,我们称之为掩码模式。

2.3 验证码与屏蔽码

对于机器来说,我们要为它准备好两张纸片,一片写上屏蔽码,另一片纸片写上验证码,屏蔽码上相应位为1时,表示此位需要与验证码对应位进行比较,反之,则表示不需要。机器在执行任务的时候先将获取的身份证号码与屏蔽码进行“与”操作,再将结果与验证码的进行比较,根据判断是否相同来决定是否通过。整个判别流程如下所示:
在这里插入图片描述
从上图可以很容易地理解屏蔽码与验证码的含义,这样一来,能通过的结果数量就完全取决于屏蔽码,设得宽,则可以通过的多(所有位为0,则不过任何过滤操作,则谁都可以通过),设得窄,则通过的少(所有位设为1,则只有一个能通过)。那么知道这个有什么用呢?因为bxCAN的过滤器的掩码模式就是采用这种方式,在bxCAN中,分别采用了两个寄存器(CAN_FiR1,CAN_FiR2)来存储屏蔽码与验证码,从而实现掩码模式的工作流程的。这样,我们就知道了bxCAN过滤器的掩码模式的大概工作原理。

但是,我们得注意到,采用掩码模式的方式并不能精确的对每一个ID进行过滤,打个比方,还是采用之前的守卫的例子,假如城主要求只有1150~1158年出生的人能通过,那么,若我们还是才用掩码模式,那么掩码就设为第7~9位为”1”,对应的,验证码的7~9位分别为”115”,这样就可以了。但是,仔细一想,出生于1159的人还是可以通过,是不是?但总体来说,虽然没有做到精确过滤,但我们还是能做到大体过滤的,而这个就是掩码模式的缺点了。在实际应用时,取决于需求,有时我们会同时使用到列表模式和掩码模式,这都是可能的。

2.4 列表模式与掩码模式的对比

综合之前所述,下面我们来对比一下列表模式与掩码模式这两种模式的优缺点。

模式优点缺点
列表模式能精确地过滤每个指定的CAN ID有数量限制
掩码模式取决于屏蔽码,有时无法完全精确到每一个CAN ID,部分不期望的CAN ID有时也会收到数量取决于屏蔽码,最多无上限

2.5 标准CAN ID与扩展CAN ID

1986 年德国电气商BOSCH公司开发出面向汽车的CAN 通信协议,刚开始的时候,CAN ID定义为11位,我们称之为标准格式,ISO11898-1标准中CAN的基本格式如下图所示:
在这里插入图片描述
如上图所示,标准CAN ID存放在上图ID18~ID28中,共11位。随着工业发展,后来发现11位的CAN ID已经不够用,于是就增加了18位,扩展CAN ID到29位,如下图所示:
在这里插入图片描述
从上图对比扩展CAN报文与标准CAN报文,发现在仲裁域部分,扩展CAN报文的CAN ID包含了base Identifier与extension Identifier,即基本ID与扩展ID,而标准CAN报文的CAN ID部分只包含基本ID,扩展ID(ID0~ID17)被放在基本ID的右方,也就是说,属于低位。知道这些有什么用呢?至少我们可以得到这两条信息:

  • 标准ID一般小于或等于<=0x7FF(11位),只包含基本ID。
  • 对于扩展CAN的低18位为扩展ID,高11位为基本ID。

例如标准CAN ID 0x7E1,二进制展开为111 1110 0001 ,其全部是基本ID。

再例如扩展CAN ID 0x1835f107,二进制展开为[1 1000 0011 10][01 11110001 0000 0111],总共29位,左边红色中括号中的11位为基本ID,右边绿色中括号内的18位为扩展ID,请记住这个信息!知道这个之后,我们可以很方便地将一个CANID拆分成基本ID和扩展ID,这个也将在后续的内容中多次用到,再次留意一下,扩展ID是位于基本ID的右方,在扩展CAN ID的构成中,扩展ID位于低18位,而基本ID位于高11位,于是要获取一个扩展CANID的基本ID,就只需要将这个CANID右移18位。

3 设置过滤器模式

下图来自STM32F10X参考手册:
在这里插入图片描述
上图中可以看出,过滤器一共有4种工作模式:32位宽的掩码模式,32位宽的列表模式, 16位宽的列表模式,16位宽的掩码模式
下面将分别介绍过滤器的4种工作模式:

3.1 32位宽的掩码模式

在这里插入图片描述
如上图所示,当寄存器FSC0 = 1; FBM0 = 0; 则0号过滤器处于32位宽的掩码模式,这时F0R1需配置ID即前面说的验证码,F0R2需配置掩码即前面说的屏蔽码;其余几个滤波器只是x从1变到13,对应不同的寄存器分别进行配置即可。最右边的num表示这两个32位寄存器都是配置同一个滤波器。
最下边显示的是与CAN ID各位对应的映射关系。上图最下边的映射关系恰好等于扩展CAN ID左移3位再补上IDE(扩展帧标识),RTR(远程帧标志)。注意看上图最底下的各位定义,可以看出,从右到左,首先,最低位是没有用的,然后是RTR,表示是否为远程帧,接着IDE,扩展帧标志,然后才是EXID[0:17]这18位扩展ID,最后才是STID[0:10]这11位标准ID,也就是前面所说的基本ID。在进行配置的时候,即将希望通过的CAN ID写入的时候,要注意各个位对号入座,即基本ID放到对应的STD[0:10],扩展ID对应放到EXID[0:17],若是扩展帧,则需要将IDE设为“1”,标准帧则为“0”,数据帧设RTR为“0”,远程帧设RTR为“1”。


static void CANFilterConfig_Scale32_IdMask(void)
{
	CAN_FilterInitTypeDef  CAN_FilterInitStructure;
	U16 std_id =0x7e9;
	U32 ext_id =0x1800f001;
	U32 mask =0;
	 
	CAN_FilterInit(&CAN_FilterInitStructure);     //初始化CAN_FilterInitStructrue结构体变量
	CAN_FilterInitStructure.CAN_FilterNumber=0;      //设置过滤器组0,范围为0~13
	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;    //设置过滤器组0为屏蔽模式
	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;   //设置过滤器组0位宽为32位
	 
	//标识位寄存器的设置
	//ext_id<<3对齐,见上图9,再>>16取高16位
	CAN_FilterInitStructure.CAN_FilterIdHigh=((ext_id<<3) >>16) &0xffff;  //设置标识符寄存器高字节。
	CAN_FilterInitStructure.CAN_FilterIdLow=(U16)(ext_id<<3) | CAN_ID_EXT;   //设置标识符寄存器低字节
	//如果要过滤标准帧,这里也可以这样设置
	//CAN_FilterInitStructure.CAN_FilterIdHigh=std_id<<5;  //设置标识符寄存器高字节.这里为什么是左移5位呢?从上图可以看出,CAN_FilterIdHigh包含的是STD[0~10]和EXID[13~17],标准CAN ID本身是不包含扩展ID数据,因此为了要将标准CAN ID放入此寄存器,标准CAN ID首先	应左移5位后才能对齐.EXID[13~17]是5位
	//CAN_FilterInitStructure.CAN_FilterIdLow=0|CAN_ID_STD;   //设置标识符寄存器低字节
	 
	//屏蔽寄存器的设置
	//这里的思路是先将标准CAN ID和扩展CAN ID对应的ID值先异或后取反,为什么?异或是为了找出两个CAN ID有哪些位是相同的,是相同的位则说明需要关心,需要关心的位对应的屏蔽码位应该设置为1,因此需要取反一下。最后再整体左移3位。
	mask =(std_id<<18);//这里为什么左移18位?因为从ISO11898中可以看出,标准CAN ID占ID18~ID28,为了与CAN_FilterIdHigh对齐,应左移2位,接着为了与扩展CAN对应,还应该再左移16位,因此,总共应左移2+16=18位。也可以用另一个方式来理解:直接看Mapping的内容,发现STDID相对EXID[0]偏移了18位,因此左移18位.
	mask ^=ext_id;//将对齐后的标准CAN与扩展CAN异或后取反
	mask =~mask;
	mask <<=3;//再整体左移3位
	mask |=0x02; //只接收数据帧,不接收远程帧
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=(mask>>16)&0xffff; //设置屏蔽寄存器高字节
	CAN_FilterInitStructure.CAN_FilterMaskIdLow=mask&0xffff;   //设置屏蔽寄存器低字节
	 
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0;  //此过滤器组关联到接收FIFO0
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活此过滤器组
	CAN_FilterInit(&CAN_FilterInitStructure); //设置过滤器
}

上面的CAN_FilterIdHigh,CAN_FilterIdLow,CAN_FilterMaskIdHigh,CAN_FilterMaskIdLow四个参数是如何对应到CAN_FxR1和CAN_FxR2的配置,需要看库里面的CAN_FilterInit函数
贴出CAN_FilterInit函数即可以理解上面的代码注释中多次提到的移位对齐。

/* Filter Scale */
  if (CAN_FilterInitStruct->CAN_FilterScale == CAN_FilterScale_16bit)
  {
    /* 16-bit scale for the filter */
    CAN1->FS1R &= ~(uint32_t)filter_number_bit_pos;

    /* First 16-bit identifier and First 16-bit mask */
    /* Or First 16-bit identifier and Second 16-bit identifier */
    CAN1->sFilterRegister[CAN_FilterInitStruct->CAN_FilterNumber].FR1 = 
    ((0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterMaskIdLow) << 16) |
        (0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterIdLow);

    /* Second 16-bit identifier and Second 16-bit mask */
    /* Or Third 16-bit identifier and Fourth 16-bit identifier */
    CAN1->sFilterRegister[CAN_FilterInitStruct->CAN_FilterNumber].FR2 = 
    ((0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterMaskIdHigh) << 16) |
        (0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterIdHigh);
  }

  if (CAN_FilterInitStruct->CAN_FilterScale == CAN_FilterScale_32bit)
  {
    /* 32-bit scale for the filter */
    CAN1->FS1R |= filter_number_bit_pos;
    /* 32-bit identifier or First 32-bit identifier */
    CAN1->sFilterRegister[CAN_FilterInitStruct->CAN_FilterNumber].FR1 = 
    ((0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterIdHigh) << 16) |
        (0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterIdLow);
    /* 32-bit mask or Second 32-bit identifier */
    CAN1->sFilterRegister[CAN_FilterInitStruct->CAN_FilterNumber].FR2 = 
    ((0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterMaskIdHigh) << 16) |
        (0x0000FFFF & (uint32_t)CAN_FilterInitStruct->CAN_FilterMaskIdLow);
  }

以上的标准库底层代码总结就是:
如果是16位宽,则CAN_FilterMaskIdLow是配置FR1的高16位,CAN_FilterIdLow是配置FR1的低16位,CAN_FilterMaskIdHigh是配置FR2的高16位,CAN_FilterIdHigh是配置FR2的低16位。
如果是32位宽,则CAN_FilterIdHigh是配置FR1的高16位,CAN_FilterIdLow是配置FR1的低16位,CAN_FilterMaskIdHigh是配置FR2的高16位,CAN_FilterMaskIdLow是配置FR2的低16位。

注意:初始化结构体参数中CAN_FilterMaskIdLow和CAN_FilterMaskIdHigh有可能是作ID,有可能是作掩码,具体要看情况(看处于哪种模式,对照前面的模式图表)。

关于验证码与屏蔽码的概念前面已经明确说明了,不清楚的可以回过去看看前面的内容。在32位宽的掩码模式下,既可以过滤标准CAN ID,也可以过滤扩展CAN ID,甚至两者混合着来也是可以的,下面我们就这3种情况分别给出示例。

【1、只针对标准CAN ID】
如下代码示例:
注:下面使用HAL库

static void CANFilterConfig_Scale32_IdMask_StandardIdOnly(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  uint16_t StdIdArray[10] ={0x7e0,0x7e1,0x7e2,0x7e3,0x7e4,
                                0x7e5,0x7e6,0x7e7,0x7e8,0x7e9}; //定义一组标准CAN ID
  uint16_t      mask,num,tmp,i;
  
  sFilterConfig.FilterNumber = 2;				//使用过滤器2
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;		//配置为掩码模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;	//设置为32位宽
  sFilterConfig.FilterIdHigh =(StdIdArray[0]<<5);		//验证码可以设置为StdIdArray[]数组中任意一个,这里使用StdIdArray[0]作为验证码
  sFilterConfig.FilterIdLow =0;
  
  mask =0x7ff;						//下面开始计算屏蔽码
  num =sizeof(StdIdArray)/sizeof(StdIdArray[0]);
  for(i =0; i<num; i++)		//屏蔽码位StdIdArray[]数组中所有成员的同或结果
  {
    tmp =StdIdArray[i] ^ (~StdIdArray[0]);	//所有数组成员与第0个成员进行同或操作
    mask &=tmp;
  }
  sFilterConfig.FilterMaskIdHigh =(mask<<5);
  sFilterConfig.FilterMaskIdLow =0|0x02; 		//只接收数据帧
  
  sFilterConfig.FilterFIFOAssignment = 0;		//设置通过的数据帧进入到FIFO0中
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

如上代码所示,对于验证码,任意一个期望通过的CAN ID都是可以设为验证码的,但屏蔽码,却是所有期望通过的CAN ID相互同或后的最终结果。
注:对于移位不明白的参考前面模式图中的的Mapping映射加以理解。

【2、只针对扩展CAN ID】
如下代码示例:

static void CANFilterConfig_Scale32_IdMask_ExtendIdOnly(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  //定义一组扩展CAN ID用来测试
uint32_t ExtIdArray[10] ={0x1839f101,0x1835f102,0x1835f113,0x1835f124,0x1835f105,
                            0x1835f106,0x1835f107,0x1835f108,0x1835f109,0x1835f10A};
  uint32_t      mask,num,tmp,i;
  
  sFilterConfig.FilterNumber = 3;					//使用过滤器3
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;			//配置为掩码模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;		//设为32位宽
  sFilterConfig.FilterIdHigh =((ExtIdArray[0]<<3) >>16) &0xffff;//数组任意一个成员都可以作为验证码
  sFilterConfig.FilterIdLow =((ExtIdArray[0]<<3)&0xffff) | CAN_ID_EXT;
  
  mask =0x1fffffff;
  num =sizeof(ExtIdArray)/sizeof(ExtIdArray[0]);
  for(i =0; i<num; i++)				//屏蔽码位数组各成员相互同或的结果
  {
    tmp =ExtIdArray[i] ^ (~ExtIdArray[0]);	//都与第一个数据成员进行同或操作
    mask &=tmp;
  }
  mask <<=3;    								//对齐寄存器
  sFilterConfig.FilterMaskIdHigh = (mask>>16)&0xffff;
  sFilterConfig.FilterMaskIdLow = (mask&0xffff)|0x02; 		//只接收数据帧
  sFilterConfig.FilterFIFOAssignment = 0;
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

如上代码所示,与之前的标准CAN ID相比,扩展CAN ID的验证码与屏蔽码放入到相对应的寄存器时所移动的位数与标准CAN ID时有所差别,其他的都一样。
注:对于移位不明白的参考前面模式图中的的Mapping映射加以理解。

【3、标准CAN ID与扩展CAN ID混合过滤】

static void CANFilterConfig_Scale32_IdMask_StandardId_ExtendId_Mix(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  //定义一组标准CAN ID
uint32_t StdIdArray[10] ={0x711,0x712,0x713,0x714,0x715,
                          0x716,0x717,0x718,0x719,0x71a};
  //定义另外一组扩展CAN ID
uint32_t ExtIdArray[10] ={0x1900fAB1,0x1900fAB2,0x1900fAB3,0x1900fAB4,0x1900fAB5,
                            0x1900fAB6,0x1900fAB7,0x1900fAB8,0x1900fAB9,0x1900fABA};
  uint32_t      mask,num,tmp,i,standard_mask,extend_mask,mix_mask;
  
  sFilterConfig.FilterNumber = 4;				//使用过滤器4
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;		//配置为掩码模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;	//设为32位宽
  sFilterConfig.FilterIdHigh =((ExtIdArray[0]<<3) >>16) &0xffff;	//使用第一个扩展CAN  ID作为验证码
  sFilterConfig.FilterIdLow =((ExtIdArray[0]<<3)&0xffff);
  
  standard_mask =0x7ff;		//下面是计算屏蔽码
  num =sizeof(StdIdArray)/sizeof(StdIdArray[0]);
  for(i =0; i<num; i++)			//首先计算出所有标准CAN ID的屏蔽码
  {
    tmp =StdIdArray[i] ^ (~StdIdArray[0]);
    standard_mask &=tmp;
  }
  
  extend_mask =0x1fffffff;
  num =sizeof(ExtIdArray)/sizeof(ExtIdArray[0]);
  for(i =0; i<num; i++)			//接着计算出所有扩展CAN ID的屏蔽码
  {
    tmp =ExtIdArray[i] ^ (~ExtIdArray[0]);
    extend_mask &=tmp;
  }
  mix_mask =(StdIdArray[0]<<18)^ (~ExtIdArray[0]);	//再计算标准CAN ID与扩展CAN ID混合的屏蔽码
  mask =(standard_mask<<18)& extend_mask &mix_mask;	//最后计算最终的屏蔽码
  mask <<=3;    						//对齐寄存器
 
  sFilterConfig.FilterMaskIdHigh = (mask>>16)&0xffff;
  sFilterConfig.FilterMaskIdLow = (mask&0xffff);
  sFilterConfig.FilterFIFOAssignment = 0;
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

如上代码所示,在混合的情况下,只需稍微修改下屏蔽码的计算方式就可以了,其他的基本没有什么变化。
注:对于移位不明白的参考前面模式图中的的Mapping映射加以理解。

3.2 32位宽的列表模式

在这里插入图片描述
如上图所示,当寄存器FSC0 = 1; FBM0 = 1; 则0号过滤器处于32位宽的列表模式,这个过滤器一分为2可以保存两个CAN ID; 这时F0R1和F0R2都是配置ID即前面说的验证码;其余几个滤波器只是x从1变到13,一共14个,对应不同的寄存器分别进行配置即可。最右边的num表示这两个32位寄存器分别配置2个滤波器。
注意看上图最底下的Mapping各位定义,可以看出,从右到左,首先,最低位是没有用的,然后是RTR,表示是否为远程帧,接着IDE,扩展帧标志,然后才是EXID[0:17]这18位扩展ID,最后才是STID[0:10]这11位标准ID,也就是前面所说的基本ID。在进行配置的时候,即将希望通过的CAN ID写入的时候,要注意各个位对号入座,即基本ID放到对应的STD[0:10],扩展ID对应放到EXID[0:17],若是扩展帧,则需要将IDE设为“1”,标准帧则为“0”,数据帧设RTR为“0”,远程帧设RTR为“1”。示例代码如下:
使用标准库:

static void CANFilterConfig_Scale32_IdList(void)
{
	CAN_FilterInitTypeDef  CAN_FilterInitStructure;
	U16 std_id =0x7e9;
	U32 ext_id =0x1800f001;
	CAN_FilterInit(&CAN_FilterInitStructure); //初始化CAN_FilterInitStructrue结构体变量
	CAN_FilterInitStructure.CAN_FilterNumber=0;     //设置过滤器组0,范围为0~13
	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdList;    //设置过滤器组0为标识符列表模式
	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;   //设置过滤器组0位宽为32位
	 
	//设置屏蔽寄存器,这里当标识符寄存器用
	CAN_FilterInitStructure.CAN_FilterIdHigh=std_id<<5) ;  //为什么左移5位?与上面相同道理,这里不再重复解释
	CAN_FilterInitStructure.CAN_FilterIdLow=0|CAN_ID_STD; //设置标识符寄存器低字节,CAN_FilterIdLow的ID位可以随意设置,在此模式下不会有效。
	 
	//设置标识符寄存器
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=((ext_id<<3)>>16) & 0xffff; //设置屏蔽寄存器高字节
	CAN_FilterInitStructure.CAN_FilterMaskIdLow=((ext_id<<3)& 0xffff) | CAN_ID_EXT;   //设置屏蔽寄存器低字节
	 
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0;  //此过滤器组关联到接收FIFO0
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活此过滤器组
	CAN_FilterInit(&CAN_FilterInitStructure); //设置过滤器
}

使用HAL库:

static void CANFilterConfig_Scale32_IdList(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  uint32_t StdId =0x321;				//这里写入两个CAN ID,一个位标准CAN ID
  uint32_t ExtId =0x1800f001;			//一个位扩展CAN ID
  
  sFilterConfig.FilterNumber = 0;				//使用过滤器0
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST;		//设为列表模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;	//配置为32位宽
  sFilterConfig.FilterIdHigh = StdId<<5;			//基本ID放入到STID中
  sFilterConfig.FilterIdLow = 0|CAN_ID_STD;			//设置IDE位为0
  sFilterConfig.FilterMaskIdHigh = ((ExtId<<3)>>16)&0xffff;
  sFilterConfig.FilterMaskIdLow = (ExtId<<3)&0xffff|CAN_ID_EXT;	//设置IDE位为1
  sFilterConfig.FilterFIFOAssignment = 0;			//接收到的报文放入到FIFO0中
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

上面对两种库的配置可以看到都差不多。
这里需要说明一下,由于我们使用的是HAL库,在HAL库中,CAN_FxR1与CAN_FxR2寄存器分别被拆成两段,CAN_FxR1寄存器的高16位对应着上面代码中的FilterIdHigh,低16位对应着FilterIdLow,而CAN_FxR2寄存器的高16位对应着FilterMaskIdHigh,低16位对应着FilterMaskIdLow,这个CAN_FilterConfTypeDef的的4个成员FilterIdHigh,FilterIdLow,FilterMaskIdHigh,FilterMaskIdLow,不应该单纯看其名字,被其名字误导,而应该就单纯地将这4个成员看成4个uint_16类型的变量x,y,m,n而已,后续其他示例也是同样理解,不再重复解释。这4个16位的变量其具体含义取决于当前过滤器工作与何种模式,比如当前32位宽的列表模式下,FilterIdHigh与FilterIdLow一起用来存放一个CAN ID,FilterMaskIdHigh与FilterMaskIdLow用来存放另一个CAN ID,不再表示其字面所示的mask含义,这点我们需要特别注意。

在上述代码示例中,我们分别将标准CAN ID和扩展CAN ID放入到CAN_FxR1与CAN_FxR2寄存器中。对于标准CAN ID,对比 图11,由于标准CAN ID只拥有标准ID,所以,只需要将标准ID放入到高16位的STID[0:10]中,高16位最右边被EXID[13:17]占着,因此,需要将StdId左移5位才能刚好放入到CAN_FxR1的高16位中,于是有了:
sFilterConfig.FilterIdHigh = StdId<<5;

另一个扩展CAN ID ExtId类型,将其基本ID放入到STID中,扩展ID放入到EXID中,最后设置IDE位为1。就这样配置好了。

3.3 16位宽的掩码模式

在这里插入图片描述
如上图所示,当寄存器FSC0 = 0; FBM0 = 0; 则0号过滤器处于16位宽的掩码模式,这个过滤器一分为2可以保存两个CAN ID和两个掩码;
CAN_FxR1的低16位是作为验证码,对应的16位屏蔽码为CAN_FxR1的高16位,同样的,CAN_FxR2的低16位是作为验证码,对应与CAN_FxR2的高16位为屏蔽码。
其示例代码如下:

static void CANFilterConfig_Scale16_IdMask(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  uint16_t StdIdArray1[10] ={0x7D1,0x7D2,0x7D3,0x7D4,0x7D5,	//定义第一组标准CAN ID
                          0x7D6,0x7D7,0x7D8,0x7D9,0x7DA};
  uint16_t StdIdArray2[10] ={0x751,0x752,0x753,0x754,0x755,	//定义第二组标准CAN ID
                          0x756,0x757,0x758,0x759,0x75A};
  uint16_t      mask,tmp,i,num;
  
  sFilterConfig.FilterNumber = 5;					//使用过滤器5
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;			//配置为掩码模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT;		//设为16位宽
  
  //配置第一个过滤对
  sFilterConfig.FilterIdLow =StdIdArray1[0]<<5;			//设置第一个验证码
  mask =0x7ff;
  num =sizeof(StdIdArray1)/sizeof(StdIdArray1[0]);
  for(i =0; i<num; i++)							//计算第一个屏蔽码
  {
    tmp =StdIdArray1[i] ^ (~StdIdArray1[0]);
    mask &=tmp;
  }
  sFilterConfig.FilterMaskIdLow =(mask<<5)|0x10;    //只接收数据帧
  
  //配置第二个过滤对
  sFilterConfig.FilterIdHigh = StdIdArray2[0]<<5;	//设置第二个验证码
  mask =0x7ff;
  num =sizeof(StdIdArray2)/sizeof(StdIdArray2[0]);
  for(i =0; i<num; i++)					//计算第二个屏蔽码
  {
    tmp =StdIdArray2[i] ^ (~StdIdArray2[0]);
    mask &=tmp;
  }
  sFilterConfig.FilterMaskIdHigh = (mask<<5)|0x10;  //只接收数据帧
  
 
  sFilterConfig.FilterFIFOAssignment = 0;		//通过的CAN 消息放入到FIFO0中
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

如上代码所示,在这种模式下,其特殊之处就是可以配置两套验证码,屏蔽码组合,可以分别相对独立地对标准CAN ID进行过滤。

3.4 16位宽的列表模式

在这里插入图片描述
如上图所示,当寄存器FSC0 = 0; FBM0 = 1; 则0号过滤器处于16位宽的列表模式,这个过滤器一分为4可以保存4个CAN ID;
在16位宽的列表模式下,FilterIdHigh,FilterIdLow,FilterMaskIdHigh,FilterMaskIdLow这4个16位变量都是用来存储一个标准CAN ID,这样,就可以存放4个标准CAN ID了,需要注意地是,此种模式下,不能处理扩展CAN ID,凡是需要过滤扩展CAN ID的,都是需要用到32位宽的模式。代码示例:

static void CANFilterConfig_Scale16_IdList(void)
{
  CAN_FilterConfTypeDef  sFilterConfig;
  uint32_t StdId1 =0x123;						//这里采用4个标准CAN ID作为例子
  uint32_t StdId2 =0x124;
  uint32_t StdId3 =0x125;
  uint32_t StdId4 =0x126;
  
  sFilterConfig.FilterNumber = 1;				//使用过滤器1
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST;		//设为列表模式
  sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT;	//位宽设置为16位
  sFilterConfig.FilterIdHigh = StdId1<<5;	 //4个标准CAN ID分别放入到4个存储中
  sFilterConfig.FilterIdLow = StdId2<<5;
  sFilterConfig.FilterMaskIdHigh = StdId3<<5;
  sFilterConfig.FilterMaskIdLow = StdId4<<5;
  sFilterConfig.FilterFIFOAssignment = 0;			//接收到的报文放入到FIFO0中
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;
  
  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

4 过滤器匹配序号

一旦收到的报文被存入FIFO,就可被应用程序访问。通常情况下,报文中的数据被拷贝到SRAM中;为了把数据拷贝到合适的位置,应用程序需要根据报文的标识符来辨别不同的数据。
bxCAN提供了过滤器匹配序号,以简化这一辨别过程。
根据过滤器优先级规则,过滤器匹配序号和报文一起,被存入邮箱中。因此每个收到的报文,都有与它相关联的过滤器匹配序号。过滤器匹配序号可以通过下面两种方式来使用:
● 把过滤器匹配序号跟一系列所期望的值进行比较
● 把过滤器匹配序号当作一个索引来访问目标地址
对于标识符列表模式下的过滤器(非屏蔽方式的过滤器),软件不需要直接跟标识符进行比较。
对于屏蔽位模式下的过滤器,软件只须对需要的那些屏蔽位(必须匹配的位)进行比较即可。
在给过滤器编号时,并不考虑过滤器组是否为激活状态。另外,每个FIFO各自对其关联的过滤 器进行编号。请参考下图的例子。
在这里插入图片描述

从上图可以看出,两个FIFO右边的滤波器序号是顺序累加的,与其关联的滤波器是哪个Bank无关。但由于是顺序累加的,所以与其关联的滤波器的工作模式有关系,因为决定了这个滤波器要分配几个序号。

小结:
当有一条报文通过过滤器匹配OK,过滤器匹配序号和报文一起,被存入邮箱中。应用程序可以读取该条报文的匹配序号快速确认对应的是哪个滤波器,比如是个列表模式滤波器则直接可以确定其ID值,通过匹配序号用得好可以简化对报文ID的比较过程。
具体如何使用过滤器匹配序号:
● 把过滤器匹配序号跟一系列所期望的值进行比较
● 把过滤器匹配序号当作一个索引来访问目标地址

5 过滤器优先级规则

根据过滤器的不同配置,有可能一个报文标识符能通过多个过滤器的过滤;在这种情况下,存
放在接收邮箱中的过滤器匹配序号,根据下列优先级规则来确定:
● 位宽为32位的过滤器,优先级高于位宽为16位的过滤器
● 对于位宽相同的过滤器,标识符列表模式的优先级高于屏蔽位模式
● 位宽和模式都相同的过滤器,优先级由过滤器号决定,过滤器号小的优先级高
在这里插入图片描述

上面的例子说明了bxCAN的过滤器规则:在接收一个报文时,其标识符首先与配置在标识符列表模式下的过滤器相比较;如果匹配上,报文就被存放到相关联的FIFO中,并且所匹配的过滤器的序号被存入过滤器匹配序号中。如同例子中所显示,报文标识符跟#4标识符匹配,因此报文内容和FMI4被存入FIFO。
如果没有匹配,报文标识符接着与配置在屏蔽位模式下的过滤器进行比较。
如果报文标识符没有跟过滤器中的任何标识符相匹配,那么硬件就丢弃该报文,且不会对软件有任何打扰。

小结:
在接收一个报文时,其标识符首先与配置在标识符列表模式下的过滤器相比较,顺序是根据Filer Num从小到大且32位滤波器优先,如果都没有匹配,报文标识符接着与配置在屏蔽位模式下的过滤器进行比较。

6 总结

在实际的应用中,我们需要根据需求的实际情况来决定使用何种过滤配置,STM32互联型的bxCAN提供了28个过滤器,在配置之前,我们需要先将那些需要通过的CAN ID进行整理,若数量少,则使用列表模式,精准,不会放进任何无关的报文;若只有标准CAN ID,则可以考虑使用16位宽模式,若需求中的CAN ID过多,则可以考虑使用多个过滤器,部分使用列表模式,部分使用掩码模式,CAN ID值相近的可以归纳成一组,使用掩码模式进行过滤。但使用掩码模式的同时,我们也需要意识到,也有可能部分不期望的CAN ID也会通过过滤器,掩码放得越宽,带进其他CAN ID的几率就越大,这点我们需要格外注意,视情况进行应用判断和处理。另外,对于相近的CAN ID,我们可以提前计算好屏蔽码,直接在代码中填入,而不是在代码中临时计算,这样可以提高软件效率。

【如果我们想读取总线上所有CAN ID的数据,即不过滤任何CAN ID,所有CAN ID的数据都可以进入,那过滤器应该怎么设置?】
掩码模式,掩码设为全0

【注意STM32 CAN初始化之后还必须初始化过滤器才能接收到数据】

参考鸣谢:
https://blog.csdn.net/flydream0/article/details/8148791

https://blog.csdn.net/flydream0/article/details/52317532

  • 39
    点赞
  • 145
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾爱技术圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值