STM32 CAN控制器
一、CAN基础知识
CAN协议 简介
CAN (Controller Area Network )是 ISO 国际标准化的串行半双工异步(位时序同步)通信协议。
(1)由于系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需要。
(2)CAN 控制器根据两根线(CAN_H、CAN_L)上的电位差,产生的差分信号
来判断总线电平。
CAN协议- 电平特性
隐性电平(Recessiveness -简称 R):逻辑值0,CAN_H 和 CAN_L 之差为 2.5V 左右。 显性电平(Dominance -简称 D):逻辑值1,CAN_H 和 CAN_L 之差为 0V。
在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。
协议规范,这么设计是有一定的技巧的:
显性或者隐性的优先级可以看成是每个 CAN单元unit 与 CAN总线Bus 通信时,电平逻辑关系 相与&的关系
。
(1)显性电平(电平 = 0)优先级高是因为 CAN独立单元 和 CAN总线相与&后,把CAN总线的电平,也同时拉低了。只要有一个 CAN独立单元为显性电平,则整个CAN总线就被拉高。(2)但是CAN独立单元隐形电平(电平 = 1)和CAN总线相与&后,CAN总线电平状态保持不变。因此只有所有的CAN独立单元全为隐形电平时,CAN总线电平才为隐性电平。
另外,在 CAN 总线的起止端都有一个 120Ω终端电阻来做阻抗匹配,以减少回波反射。
注意:120Ω的终端电阻,在CAN通信中是必不可少的。
因为很多情况下,CAN通信在正常模式调试时,MCU片上CAN设备接收不到消息,有一种失败原因是CAN分析仪上面的 120Ω终端电阻拨片没有打开ON。
二、STM32Fxxx CAN控制器
设计目标是:以最小的 CPU 负荷来高效处理大量收到的报文。
支持报文发送的优先级要求(优先级特性可软件配置)。对于安全紧要的应用,bxCAN 提供所有支持时间触发通信模式所需的硬件功能。
(1)STM32F4 bxCAN 的主要特点
- 支持 CAN 协议 2.0A 和 2.0B 主动模式
各个行业的协议遵循的规范 - 波特率最高达 1Mbps
相对于其它通信协议的通信速度,已经算很高了 - 支持时间触发通信
暂时没用过 - 具有 3 个发送邮箱
主CAN1和从CAN2都各自有自己的3个发送邮箱,发送消息时,哪个邮箱空闲idle,就选择哪个邮箱发送消息。 - 具有 2 个 3 级深度的接收 FIFO
相当于主CAN1和从CAN2各有自己的2个接收邮箱:FIFO0,FIFO1。而每个接收邮箱又都可以细分3块FIFO缓存区,每接收一个消息可以挂载在指定的接收邮箱上,逐次填充3个FIFO缓存区,也就是3级深度的意思。 - 可变的过滤器组(CAN1 和 CAN2 共享 28组过滤器,一组过滤器又分为含有2个32位的寄存器)
过滤器的意思就是MCU的片上外设-CAN设备从CAN_BUS总线接收到消息(也就是报文),但是我也不能什么消息都接收过来对吧,因此需要一个东西去过滤掉那些对我无用的消息,选择提炼接收那些对我有用的消息,那么这个过滤的东西就是过滤器。但是过滤的过程不仅仅有一个器件就行了,他还需要其他器件的配合,因此一个过滤器组含有2个32位寄存器。那么到底按照什么条件去筛选呢,这里引入标识符ID,也就是消息报文中仲裁段id,通过设置过滤时只允许通过我们指定的id而过滤掉别的id,从而达到过滤消息的作用。过滤器(筛选器)的作用是:减少了 CPU 处理 CAN 通信的开销
。
从框图中,总结出CAN通信主要分为三大部分:
- CAN 控制和状态:控制CAN通信过程中的发送和接收,中断,其它各种状态以及工作的各种模式。
- CAN 邮箱:发送和接收的用来放消息(报文)的邮箱,其实也就是缓存存储区。
- CAN 过滤器(筛选器):过滤消息(报文)中仲裁段ID的寄存器组。
(2)过滤器作用 详细分析
为什么要使用 过滤器(filter)?
此部分内容参考文章: 过滤器详解.
此文章讲的透彻、浅显易懂
bxCAN 工作模式
bxCAN 主要有三种工作模式: 初始化(Initialize)、 正常(Normal)和睡眠(Sleeping,低功耗)
测试模式:环回模式,静默模式,环回与静默组合模式
(1)环回模式
(2)静默模式
(3)环回与静默组合模式
- 调试模式
bxCAN 发送消息
CAN邮箱发送时需设置的寄存器
(1)CAN 发送状态寄存器 (CAN_TSR)
-
RQCPx : 邮箱 x 请求完成 (Request completed mailbox x)
最后一个请求(发送或中止)执行完毕时,由硬件置 1。由软件通过写入“1”清零,或是在发生发送请求(CAN_TI1R 寄存器中的 TXRQx位)时由硬件清零。
如果将此位清零,邮箱 x 的所有状态位(TXOK1、ALST1 和 TERR1)都将清零。 -
TXOKx :邮箱 x 发送成功 (Transmission OK of mailbox x)
每次发送成功后,硬件都将更新此位。
0:上一次发送失败
1:上一次发送成功
当邮箱 x 的发送请求成功完成时,此位由硬件置 1。 -
TMEx : 发送邮箱 x 为空状态 (Transmit mailbox x empty)
当 x 邮箱为空,即邮箱没有挂起发送请求时,硬件自动置1。 -
ABRQx :邮箱 x 中止请求 (Abort request for mailbox x)
由软件置 1,用于中止相应邮箱的发送请求。
邮箱变为空后,此位由硬件清零。邮箱未挂起等待发送时,将此位置 1 没有任何作用。
(2)CAN 发送邮箱标识符寄存器(CAN_TIxR)
- TXRQ : 发送邮箱请求 (Transmit mailbox request)
由软件置 1,用于请求发送相应邮箱的内容。
邮箱变为空后,此位由硬件清零。
最开始发送报文时,x邮箱TMPx = 1 表示邮箱内容为空,此时需要把报文(数据帧等)填充 x发送邮箱,紧接着将 TXRQ = 1,请求发送x邮箱中的内容,此时 x邮箱 进入挂号(挂起 pending)状态,等待成为优先级最高的邮箱,相应的状态寄存器的TMPx位清零,表明x邮箱有数据不为空。
此过程可以被中止ABRQx = 1,中止相应邮箱的发送请求,进入发送失败状态,邮箱置空 TMPx = 1;RQCPx = 1;TXOKx = 0.
此后由调度器统一调度选择具有最高优先级的邮箱,安排发送。当x邮箱成为优先级最高的邮箱时,会将邮箱内的数据进行打包(报文),待CAN 总线变为空闲后,被安排好的邮箱将开始发送报文(进入发送态)。邮箱一旦发送成功,则邮箱立即恢复 空状态TMPx = 1;RQCPx = 1;TXOKx = 1.
如果发送失败,失败原因将由 CAN_TSR 寄存器的 ALST位( 邮箱 x 仲裁丢失)和 TERR位 (邮箱x 发送错误)提示,进入发送失败状态,邮箱置空 TMPx = 1;RQCPx = 1;TXOKx = 0.
如果设置了自动重发送模式,则会重新回到等待调度器再次调度重发。
bxCAN 接收消息
过滤器总结
通过CAN_TOOL和CAN分析仪测试发现:
(一)列表模式
0、0x7FF 、 0x1FFFFFFF 为独立单一的can_id
(二)掩码模式
(1)配置情形1
配置一 配置二 配置三
filter_id 0 0 0x7FF / 0x1FFFFFFF
mask_id 0 0x7FF / 0x1FFFFFFF 0
只要 filter_id / mask_id 有一个配置为0,则过滤器组将会使所有id都可以通过过滤,不屏蔽任何id,任何id都可以通过筛选。
(2)配置情形2
配置
filter_id 0x7FF / 0x1FFFFFFF
mask_id 0x7FF / 0x1FFFFFFF
此时过滤器可以屏蔽所有id,不让任何id通过筛选(包括 0 和 0x7FF / 0x1FFFFFFF)。这种模式可以用于过滤器不想使用(即关闭过滤器)时,全部以 0x7FF / 0x1FFFFFFF 填充filter_id 和 mask_id 对应的2组寄存器。
三、CAN问题总结
(一)CAN初始化失败原因
(1)Check acknowledge error
CAN收发器在CAN初始化之前,CAN收发器要处于正常供电状态。
如果CAN收发器没有正常供电(5V和3.3V供电),任何一路没有正常供电都会造成CAN收发器无法正常工作,从而导致软件程序CAN初始化的失败。
/* Wait the acknowledge */
wait_ack = 0;
while (((CANx->MSR & CAN_MSR_INAK) == CAN_MSR_INAK) && (wait_ack != INAK_TIMEOUT))
{
wait_ack++;
}
/* ...and check acknowledged */
if ((CANx->MSR & CAN_MSR_INAK) == CAN_MSR_INAK)
{
InitStatus = CAN_InitStatus_Failed;
printf("Check acknowledge error 2\n");
}
else
{
InitStatus = CAN_InitStatus_Success ;
}
CAN收发器在CAN初始化之前,没有供电,会造成INAK位,CAN_Rx引脚无法检测到连续11位的隐形电平,使得硬件无法清零,导致CAN初始化失败。
(2)boot0引脚是否默认接地,启动模式为:选择主 Flash 作为自举空间
(3)CAN因波特率不同导致初始化失败
CAN因波特率不同导致初始化失败
(4)CAN的Rx和Tx引脚外接上拉电阻导致初始化失败
外接上拉电阻初始化失败
(5) 检查CAN收发器的STB引脚是否正常接地或者程序里面是否将STB引脚拉为低电平
STB引脚低电平有效或默认接地:此时为正常工作状态
STB引脚拉高时:此时为低功耗工作状态,CAN收发器无法正常工作:即无法正常接收/发送数据帧。
(6)电源供电电压不足:BAT-12V,电源电压不足或者电源电压不稳定,导致CAN初始化失败,内部晶振无法工作。
(7)CAN引脚复用重映射,导致GPIO引脚的初始化参数不对
重映射CAN引脚需要打开RCC_AFIO时钟:以stm32f10x为例
注意:复用为默认引脚时,不需要开启此RCC_AFIO时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, EABLE);
(8)如果以上问题都没有,且硬件确保无问题。请仔细检查程序,对应引脚和配置参数是否正确,有时候很可能是因为某一个变量的名称在拷贝时,忘记更改正确变量名导致的。
(9)CAN波特率:两个设备必须设置相同,一般500KHZ
(二)CAN2 (Slave CAN)通信失败原因
在初始化CAN2之前,没有先初始化和打开主CAN1的RCC时钟。因为CAN2通信依赖于主CAN1片上器件的使能。
经测试,在使用CAN2之前,一般只需要打开主CAN1的RCC时钟,CAN2即可正常工作。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1 | RCC_APB1Periph_CAN2, ENABLE);
// void CAN_SlaveStartBank(uint8_t CAN_BankNumber);
CAN_SlaveStartBank(14); // 此时需要初始化过滤器,并打开CAN2
(三)CAN收发器供电不正常,造成的初始化问题
(四)过滤器初始化过程中,出现初始化卡死,停止不动问题
一般是开启了CAN中断,但是中断服务函数并没有写或者写的有问题。
(1)设备刚上电,CAN过滤器初始化进行中程序卡住阻塞,现象:
(2)设备刚上电,CAN和过滤器初始化也都完成,但是在CAN_TEST_TOOL测试上位机发送 id 时,主程序继续卡住阻塞,CAN_TEST_TOOL测试上位机停止发送id时,主进程仍然退不出来。添加打印信息也无法检测出问题所在的地方。
原因分析:经过检查发现,CAN初始化过程中,打开了CAN中断,但是并没有写CAN的中断处理函数,导致程序一直: B . (跳转到当前位置,查看汇编时发现),即在当时现场一直跳转到当前位置,无法退出。
(五)发送邮箱出现 No Mailbox
这个问题也是最常见的问题:
(1)接线:检查硬件连接是否正确,一般是板子的CAN_H和CAN_L两条线,没有接入CAN_BUS总线,即没有连接外设(PC端的CAN分析仪),CAN_H和CAN_L不接外部设备会导致邮箱满或溢出,导致新的消息无法获得邮箱。
(2)CAN收发器是否正常供电和STB引脚是否拉低:如果软件配置没有问题,则需要用示波器检查CAN收发器的Rx和Tx引脚是否有数据波形,其次还需要检查CAN收发器的VCC供电电压是否开启,以及不同CAN收发器芯片的VCC供电电压不同(一般是3.3V,但是也有5V供电的),需要查看一下数据手册是否满足其要求。
(3)仔细检查程序
- 配置引脚的参数是否正确,三个引脚:Tx、Rx、STB
- 是否开启时钟:RCC_GPIO时钟,RCC_CAN的时钟,复用重映射还需开启RCC_AFIO
(六)CAN时钟分析
STM32系统时钟 CAN时钟配置分析:参考 CAN时钟配置.
(七)关闭(不激活)不想使用的CAN过滤器
使已经激活的CAN过滤器失效:禁止已经打开的过滤器的范围。
前提是:先打开CAN1的RCC时钟
void close_filter(int can_dev)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
u8 f_i = 0;
if (can_dev == CAN_DEV1) {
for (f_i = 0; f_i < 13; f_i++) {
CAN_FilterInitStructure.CAN_FilterNumber = f_i;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = DISABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
} else if (can_dev == CAN_DEV2) {
for (f_i = CAN2_FILTER_S; f_i < 27; f_i++) {
CAN_FilterInitStructure.CAN_FilterNumber = f_i;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = FIFO1;
CAN_FilterInitStructure.CAN_FilterActivation = DISABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
} else {
printf("close_filter can_dev is error \n");
}
}
(八)CAN接收的数据出现帧丢失问题
前提:已经开启CAN的NVIC接收中断CAN1_RX0_IRQHandler,在中断服务函数里接收CAN_BUS上的数据帧。整个系统,开启了串口打印调试。
void CAN1_RX0_IRQHandler(void)
{
can_msg_t can_msg;
p_can_read(CAN_DEV1, &can_msg);
}
分析:Keil5调试发现,由于串口打印调试cpu处理占用的时间大于下一次接收到can数据帧的时间,因此会导致接收到的最新can数据帧会覆盖掉之前的数据帧,导致 old frame 来不及被处理就被最新的数据替换掉,因此会导致CAN接收的数据出现帧丢失。
(九)CAN总线中节点ID相同会怎样?
问题描述:在写CAN测试程序的时候,内部程序向CAN总线上发送数据帧的同时也接受CAN总线上的数据帧。当用CAN-Test上位机工具模拟发送 连续帧,单帧,首帧,设置发送间隔和帧间隔都为0发送时,会出现发送失败。
在线调试时,发现出现硬件CAN总线离线错误,LEC = 5;导致CAN功能无法接收数据,也无法发送数据。
后面经过几天的分段调试,发现如果同一时段,CAN发送数据帧同时也接收CAN总线上的数据,如果接收和发送can_id相同则会造成CAN总线离线。
详细可参考:链接 CAN总线中节点ID相同会怎样?.
总结:当两节点同时发送ID相同数据不同的报文时,将发生数据场填充错误;当两节点同时发送ID相同数据也相同的报文时,若有其他节点应答则不发生错误,若无其他节点应答则发生应答错误。因此,我们在设计CAN总线时应避免ID段相同的情况出现。
与此同时,CAN初始化时,设置参数最好是使能ABOM,同时开启自动重发可降低丢帧概率。