- STM32内置bxCAN外设(CAN控制器、拓展CAN),支持CAN2.0A和2.0B(全部的CAN),可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节
- 波特率最高可达1兆位/秒,高速CAN波特率为125K~1MBps,可支持高速CAN
- 意思就是发送的时候有3个缓存区,可以存入3个待发报文,
- 2个3级深度的接收FIFO,这是接收的缓存区,总共可以缓存2*3=6个报文
14个过滤器组(互联型28个),看芯片是不是互联型,这个过滤器,就是用来过滤接收报文ID的,所有挂载在CAN总线的报文,都是广播发出来的,每个设备都能收到,各种报文都混杂在 起,都在总线上传输,但是对于某个特定的设备而言,它往往只需要接收部分ID的报文,这样我们就可以配置这个过滤器,让CAN外设,只接收我想要的报文,无关报文直接就过滤掉,这样就非常方便
STM32 CAN外设额外的一些特色功能:时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式
一、CAN网拓扑结构
这里每个CAN节点都挂载在CAN总线上,对于其中一个CAN节点,都由CAN控制器和CAN收发器组成,CAN控制器一般集成在MCU,也就是单片机里面,然后STM32引出CAN_RX和CAN_TX引脚,与CAN收发器连接,之后CAN收发器引出CAN_High和CAN_LOW,与CAN总线相连
stm32的can就两个引脚CAN_RX、CAN_TX
二、CAN收发器电路
VCC供电必须接5V,不能接3.3V,因为协议规定,CAN_H引脚在显性电平时电压为3.5V,要是供电只有3.3V,那么CAN总线的电压,就没法符合规范了。
TXD和RXD,和左边的CAN控制器相连,T接T,R接R,不用交叉,这一点别和串口搞混了
SJA1000这个芯片,是一个独立的CAN控制器,比如如果你用一些低端的单片机,,它没有内置CAN控制器,那么就可以通过外置这个SJA1000,实现CAN通信
模块中间的R3是120Ω电阻,这个电阻就是终端电阻了,可以看到,这里每个收发器模块,自带的就有一个终端电阻,所以当两个模块相连时,正好是两个终端电阻,但是如果三个或更多模块相连,那么终端电阻就也会有3个或更多,这一点,不符合协议的要求,所以,当3个或更多收发器模块相连时,我们得把中间模块的终端电阻去掉,这才是符合协议的做法
三、STM32的CAN基本结构
发送邮箱有3个,每个邮箱可以存入一个CAN报文,如果我们想发出一个报文,那我们就把这个报文写入到其中一个空置邮箱,之后设置寄存器请求发送,就完事了,然后剩下的所有步骤,比如等待总线空闲,操作引脚输出波形,进行位同步和仲裁等等,这些步骤,全都由硬件电路自动执行,所以使用起来,其实非常简单。
接收部分的接收过滤器和2个FIFO,当CAN总线上出现一个数据帧或者遥控帧时,CAN硬件电路都会把这个报文缓存下来,至于是不是要保留这个报文,那得看它能不能通过过滤器,过滤器内,我们可以设置过滤规则,告诉硬件,我们想要什么ID的报文,如果硬件收到了这些旧的报文,就可以把它存入FIFO,如果收到的报文,无法通过任何一个过滤器,说明这个D的报文,我们并不需要,那硬件就直接就把它扔掉,别来烦我们,这样可以减轻软件的负担。然后,通过过滤器的报文会自动存入主接收FIFO 0或者主接收FIFO 1,FIFO的意思是先进先出寄存器,你也可以把它称为队列,通过过滤器的报文,要进队伍0或者队伍1里面排队,等待CPU读取,这里设计了两个队伍,每个队伍有3个邮箱,也就是最大存入3个报文,如果接收报文很快,CPU无法及时读走,那报文就可以在FIFO 0或者FIFO1里面排队,这样在一定程度上,可以避免报文丢失。
波特率计算
STM32中的位时序
计算出来的波特率一定要在高速或低速CAN总线的范围内,如果不在其范围内,则要调整。
这里的波特率就是表中的400000 bit/s
下面设置的几种模式:
- 时间触发模式
- 自动总线关闭管理
- 使能唤醒
- 自动重发模式
- 接收FIFO锁定模式:接收的FIFO满了,新获取的数据是覆盖呢还是丢弃呢,不使能就是锁定模式:新获取数据直接丢弃
- 发送FIFO优先级:不使能就是依次使用发送邮箱,使能就根据标识符ID进行优先发送
Test MODE 测试模式:
- Normal:正常模式,多主机之间通信
- Loopback:回环模式,单主机通信测试
- Silent
- Loopback combined with Silent
标识符ID过滤器:
某个CAN设备节点向总线发出的数据,到底是否是另一个CAN设备节点需要的数据呢?CAN外设节点使用过滤器判定是否是自己使用的数据。
stm32f103的CAN外设(互联型)有28个过滤器,非互联型有14个过滤器,所以接下来介绍一下过滤器的原理。
标识符过滤器框图:
- 每个过滤器的核心由两个32位寄存器组成:R1[31:0]和R2[31:0]
- FSCx:位宽设置,置0为16位, 置1为32位
- FBMx:模式设置,置0为屏蔽模式,置1为列表模式。
- FFAx:关联设置,置0为FIFO0, 置1为FIFO1
- FACTx:激活设置,置0为禁用,置1为启用。
屏蔽模式就是设置某些位为1,然后传输过程中进行匹配,如果发现为1的位置但是传输的ID的相应位不是1,而是0,则直接舍弃。也就是对某几个bit位进行匹配
列表模式是要求必须完全匹配,每个bit位都要一样
屏蔽模式主要用于连续的ID的多个报文,列表模式主要用于精确某个几个报文。
扩展格式的有29位ID,所以必须使用32位模式过滤
#include "CAN_Driver.h"
#include "stm32f1xx_hal_can.h"
#include "driver_oled.h"
#include <stdio.h>
#include <string.h>
//1.配置CAN模块的接收过滤器:奇数可以通过,偶数不能能过。
int setCAN_Filter(void)
{//设置过滤器规则:CAN_FilterTypeDef canFilter;//使用哪个过滤器,stm32f103的CAN外设(互联型)有28个过滤器,非互联型有14个过滤器canFilter.FilterBank = 0;//过滤器的模式,置0为屏蔽模式,置1为列表模式。CAN_FILTERMODE_IDMASK就是0屏蔽模式canFilter.FilterMode = CAN_FILTERMODE_IDMASK;//can过滤器的长度:32位或者16位的canFilter.FilterScale = CAN_FILTERSCALE_32BIT;//设置规则://这里设置过滤规则是只要奇数,STID[0]位是高16位的第5位(从0开始算),所以这里设置0x20,看下面的设置标识符屏蔽的图片canFilter.FilterIdHigh = 0x0020;canFilter.FilterIdLow = 0x0000;//设置掩码屏蔽,高16位肯定和上面的标识符屏蔽一样都是0x20,到那时掩码要看IDE(ID扩展标志位)和RTR位(远程请求标志位),我们这个程序只是作为环回模式,自收自发,所以这两位设为1,不需要canFilter.FilterMaskIdHigh = 0x0020;canFilter.FilterMaskIdLow = 0x0006;//通过过滤器的报文放置在哪一个FIFO中:canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;//使能FIFOcanFilter.FilterActivation = CAN_FILTER_ENABLE;//把配置好的过滤器参数设置到can模块中:HAL_CAN_ConfigFilter(&hcan,&canFilter);//启动CAN模块:HAL_CAN_Start(&hcan);vTaskDelay(10);return 0;
}//2.发送CAN报文
int CAN_sendMsg(uint32_t msgID, uint8_t* pdata, uint8_t datalen)
{//配置发送报文的格式:stdid + 数据 + RTR + IDE + DLCCAN_TxHeaderTypeDef TxHeader;TxHeader.StdId = msgID;TxHeader.RTR = 0;//数据帧TxHeader.IDE = 0; //标准帧TxHeader.DLC = datalen;//查看有没有空闲邮箱:while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);//如果有空闲邮箱:uint32_t TxMailbox;HAL_CAN_AddTxMessage(&hcan, &TxHeader, pdata, &TxMailbox);//在OLED屏上显示一下结果:char buf[32] = {0};sprintf(buf, "sendok msgid=%d",TxHeader.StdId);OLED_PrintString(0,0, buf);vTaskDelay(100);return 0;
}//3.接收CAN报文
int CAN_recvMsg(uint8_t* pdata)
{CAN_RxHeaderTypeDef RxHeader;//判断一下接收的FIFO0中有没有数据:if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){//没有接收到OLED_PrintString(0,2, "msg not recv");}else{//接收到了:OLED_PrintString(0,2, "msg is recved");HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, pdata);char buf[32] = {0};sprintf(buf, "ID=%d,data=%d",RxHeader.StdId, *pdata);OLED_PrintString(0,4,buf);}vTaskDelay(100);return 0;
}
设置标识符屏蔽
STID[0]位是高16位的第6位
设置掩码