细说STM32F407单片机CAN基础知识及其HAL驱动程序

目录

一、CAN总线结构和传输协议

[1、 CAN总线结构](#1、 CAN总线结构)

(1)闭环结构的CAN总线网络

(2)开环结构的CAN总线网络

(3)隐性电平和显性电平

2、CAN总线传输协议

(1)CAN总线传输特点

(2)位时序和波特率

1)同步段(SYNC_SEG)

[2)位段1(Bit Segment 1,BS1)](#2)位段1(Bit Segment 1,BS1))

[3)位段2(Bit Segment 2,BS2)](#3)位段2(Bit Segment 2,BS2))

3.帧的种类

4.标准格式数据帧和遥控帧

5.扩展格式数据帧和遥控帧

6.优先级法则

二、CAN外设工作原理和HAL驱动程序

1、片上CAN外设的功能概述

[2、 CAN模块的基本控制](#2、 CAN模块的基本控制)

[3、 CAN模块的测试模式](#3、 CAN模块的测试模式)

[(1)静默模式(silent mode)](#(1)静默模式(silent mode))

[(2)回环模式(loop back mode)](#(2)回环模式(loop back mode))

[(3)回环与静默组合模式(loop back combined with silent mode)](#(3)回环与静默组合模式(loop back combined with silent mode))

4、消息发送

5、消息接收

6、标识符筛选

(1)标识符筛选原理

1)1个32位筛选器------标识符掩码模式

2)2个32位筛选器------标识符列表模式

3)2个16位筛选器------标识符掩码模式

4)个16位筛选器------标识符列表模式

(2)函数HAL_CAN_ConfigFilter()

7、中断及处理

(1)中断和中断事件

(2)发送中断的事件源和回调函数

(3)FIFO0的中断事件源和回调函数

(4)FIFO1的中断事件源和回调函数

(5)状态改变或错误的中断事件源和回调函数


CAN是控制器区域网络(Controller Area Network)的缩写。CAN总线是一种适用于工业设备的高性能总线网络。STM32F407有2个CAN控制器,开发板上有2个CAN收发器,可以进行CAN总线网络的通信试验。本文使用旺宝红龙STM32F407ZGT6 KIT V1.0开发板。

CAN的高性能和可靠性已被普遍认可,并被广泛应用于船舶、医疗设备、工业设备等方面,特别是在汽车的控制方面,已经成为汽车网络的标准协议。

一、CAN总线结构和传输协议

作为一种串行通信总线,如同I2C总线协议一样,CAN总线也有物理层定义和传输协议定义。

1、 CAN总线结构

CAN总线网络的结构有闭环和开环两种形式。

(1)闭环结构的CAN总线网络

总线两端各连接一个120Ω的电阻,两根信号线形成回路。这种CAN总线网络由ISO 11898标准定义,是高速、短距离的CAN网络,通信速率为125kbit/s到1Mbit/s。在1Mbit/s通信速率时,总线最长达40m。

(2)开环结构的CAN总线网络

两根信号线独立,各自串联一个2.2k的电阻。这种CAN总线网路由ISO11519-2标准定义,是低速、远距离的CAN网络,通信速率最高为125kbit/s。

(3)隐性电平和显性电平

CAN总线只有两根信号线CANH和CANL,没有时钟同步信号。所以CAN是一种异步通信方式,与UART的异步通信方式类似,而SPI、I2C是以时钟信号同步的同步通信方式。

CAN总线的两根信号线通常采用双绞线,传输的是差分信号,通过两根信号线的电压差CANH-CANL来表示总线电平。以差分信号传输信息具有抗干扰能力强,能有效抑制外部电磁干扰等优点,这也是CAN总线在工业上应用广泛的一个原因。使用差分信号表示总线电平的还有RS485网络,也是一种常用的工业现场总线。

两根信号线的电压差CANH-CANL表示CAN总线的电平,与传输的逻辑信号1或0对应。对应于逻辑1的称为隐性(Recessive)电平,对应于逻辑0的称为显性(Dominant)电平。对应于逻辑1和逻辑0,开环结构和闭环结构CAN网络的CANH和CANL的电压值不一样,隐性电平和显性电平的电压值也不一样。

|----------------------------------------------------------------|---------|----------|---------|---------|
| 典型电压 C A N | 闭环(高速) || 开环(低速) ||
| 典型电压 C A N | 隐性(逻辑1) | 显性(逻辑0 ) | 隐性(逻辑1) | 显性(逻辑0) |
| CANH/V | 2.5 | 3.5 | 1.75 | 4.0 |
| CANL/V | 2.5 | 1.5 | 3.25 | 1.0 |
| CANH-CANL/V | 0 | 2.0 | -1.5 | 3.0 |

在CAN总线网络中,CAN总线上的一个终端设备称为一个节点(Node),在CAN网络中,没有主设备和从设备的区别。一个CAN节点的硬件部分一般由CAN控制器和CAN收发器两个部分组成。CAN控制器负责CAN总线的逻辑控制,实现CAN传输协议;CAN收发器主要负责MCU逻辑电平与CAN总线电平之间的转换。

CAN控制器一般是MCU的片上外设,例如,STM32F407有两个CAN控制器。CAN收发器一般是单独的芯片,并且根据CAN总线的结构不同,需要使用不同的CAN收发器芯片,例如,旺宝红龙STM32F407ZGT6 KIT V1.0开发板上使用的CAN收发器芯片是SN65HVD230,++++只能构成闭环网络结构++++。

2、CAN总线传输协议

(1)CAN总线传输特点

CAN总线的数据传输有其自身的特点,主要有以下几点。

  • CAN总线上的节点既可以发送数据又可以接收数据,没有主从之分。但是在同一个时刻,只能有一个节点发送数据,其他节点只能接收数据。
  • CAN总线上的节点没有地址的概念。CAN总线上的数据是以帧为单位传输的,帧又分为数据帧、遥控帧等多种帧类型,帧包含需要传输的数据或控制信息。
  • CAN总线具有"线与"的特性,也就是当有两个节点同时向总线发送信号时,一个发送显性电平(逻辑0),另一个发送隐性电平(逻辑1),则总线呈现为显性电平。这个特性被用于总线仲裁,也就是哪个节点优先占用总线进行发送操作。
  • 每个帧有一个标识符(Identifier,以下简称ID)。ID不是地址,它表示传输数据的类型,也可以用于总线仲裁时确定优先级。例如,在汽车的CAN总线上,假设用于碰撞检测的节点输出数据帧的ID为01,车内温度检测节点发送数据帧的ID为05等。
  • 每个CAN节点都接收数据,但是可以对接收的帧根据ID进行过滤。只有节点需要的数据才会被接收并进一步处理,不需要的数据会被自动舍弃。例如,假设安全气囊控制器只接受碰撞检测节点发出的ID为01的帧,这种ID的过滤是由硬件完成的,以便安全气囊控制器在发生碰撞时能及时响应。
  • CAN总线通信是半双工的,即总线不能同时发送和接收。在多个节点竞争总线进行发送时,通过ID的优先级进行仲裁,竞争胜出的节点继续发送,竞争失败的节点立刻转入接收状态。
  • CAN总线没有用于同步的时钟信号,所以需要规定CAN总线通信的波特率,所有节点都使用相同的波特率进行通信。

(2)位时序和波特率

一个CAN网络需要规定一个通信的波特率,各节点都以相同的波特率进行数据通信。位时序指的是一个节点采集CAN总线上的一个位数据的时序,通过位时序的控制,CAN总线可以进行位同步,以吸收节点时钟差异产生的波特率误差,保证接收数据的准确性。

图中的标称位时间(Nominal Bit Time,NBT)指的是传输一个位数据的时间,用于确定CAN总线的波特率。这个时间被分成了3段。

1)同步段(SYNC_SEG)

在这个时间段内,总线上应该发生一次位信号的跳变。如果节点在同步段检测到总线上的一个跳变沿,就表示节点与总线是同步的。同步段长度固定为1个tq。

tq(time quantum [ˈkwɑ:ntəm])被称为时间片,tq由CAN控制器的时钟频率决定。在STM32F407中,两个CAN控制器在APB1总线上,CAN控制器有预分频器,APB1总线的时钟信号PCLK1经分频后得到

2)位段1(Bit Segment 1,BS1)

定义了采样点的位置。在BS1结束的时间点对总线采样,得到的电平就是这个位的电平。BS1的初始长度是1到16个tq,但它的长度可以在再同步(resynchronization)的时候被自动加长,以补偿各节点频率差异导致的正相位漂移。

3)位段2(Bit Segment 2,BS2)

定义了发送点的位置。BS2的初始长度是1到8个tq,再同步时可以被自动缩短,以补偿负相位漂移。

CAN控制器可以自动对位时序进行再同步,再同步时自动调整BS1和BS2的长度,位段加长或缩短的上限称为再同步跳转宽度(Resynchronization Jump Width,SJW),SJW的取值是1到4个tq。

CAN总线的波特率就由标称位时间长度NBT决定,而NBT是位时序3个段的时间长度和,即

NBT=(1+m+n)×tq。
Baudrate=1/NBT

3.帧的种类

CAN网络通信是通过5种类型的帧(frame)进行的,这5种帧及其用途如表:

|-----------------------------|--------------------------------------------------|
| 帧类型 | 帧用途 |
| 数据帧(Data frame) | 节点发送的包含ID和数据的帧 |
| 遥控帧(Remote frame) | 节点向网络上的其他节点发出的某个ID的数据请求,发送节点收 到遥控帧后就可以发送相应ID的数据帧 |
| 错误帧(Error frame) | 节点检测出错误时,向其他节点发送的通知错误的帧 |
| 过载帧(Overload frame) | 接收单元未做好接收数据的准备时发送的帧,发送节点收到过载帧 后可以暂缓发送数据帧 |
| 帧间空间(Inter-frame space) | 用于将数据帧、遥控帧与前后的帧分隔开的帧 |

其中,数据帧和遥控帧有ID,并且有标准格式和扩展格式两种格式,标准格式的ID是11位,扩展格式的ID是29位。

4.标准格式数据帧和遥控帧

标准格式数据帧和遥控帧的结构如下图所示,它们都有11位的ID。数据帧传输带有ID的0到8字节的数据;遥控帧只有ID,没有数据,用于请求数据。

数据帧可以分为以下几段。

  • 帧起始(Start Of Frame,SOF)。帧起始只有一个位,是一个显性电平(逻辑0),表示一个帧的开始。
  • 仲裁段(Arbitration Field)。仲裁段包括11位的ID和RTR位,共12位。多个节点竞争总线发送数据时,根据仲裁段的数据决定哪个节点优先占用总线。哪个ID先出现显性电平(逻辑0),对应的节点就占用总线。所以,ID数值小的优先级更高。如果两个节点发送数据帧的ID相同,再根据仲裁段最后的RTR位裁决。RTR(Remote Transmit Request)是远程传输请求,RTR位用于区分数据帧和遥控帧。数据帧的RTR位是显性电平(逻辑0),遥控帧的RTR位是隐性电平(逻辑1)。所以,具有相同ID的数据帧和遥控帧竞争总线时,数据帧优先级更高。
  • 控制段。控制段包括IDE位、RB0位和4位的DLC,共6位。IDE是标识符扩展位(Identifier Extension Bit),用于表示帧是标准格式,还是扩展格式。标准格式帧的IDE是显性电平(逻辑0),扩展格式帧的IDE是隐性电平(逻辑1)。RB0是保留位,默认为显性电平。DLC是4个位的数据长度编码(Data Length Code),编码数值为0到8,表示后面数据段的字节数。遥控帧的DLC编码数值总是0,因为遥控帧不传输数据。
  • 数据段。数据段里是数据帧需要传输的数据,可以是0到8字节,数据的字节个数由DLC编码确定。遥控帧没有数据段。
  • CRC段。CRC段共16位,其中前15位是CRC校验码,最后一位总是隐性电平,是CRC段的界定符(Delimiter)。
  • ACK段。ACK段包括一个ACK位(Acknowledge Bit)和一个ACK段界定符。发送节点发送的ACK位是隐性电平,接收节点接收的ACK位是显性电平。
  • 帧结束(End Of Frame,EOF)。帧结束是帧结束段,由7个隐性位表示EOF。数据帧或遥控帧结束后,后面一般是帧间空间或过载帧,用于分隔开数据帧或遥控帧。

5.扩展格式数据帧和遥控帧

扩展格式数据帧和遥控帧的结构如下图。扩展格式的ID总共是29位,扩展格式帧与标准格式帧的差异在于仲裁段和控制段。

  • 仲裁段。扩展格式数据帧的仲裁段总共32位,包括11位标准ID、SRR位、IDE位、18位扩展ID、RTR位。SRR位(Substitute Remote Request Bit)只存在于扩展格式帧中,用于替代标准格式帧中的RTR位。SRR位总是隐性电平,相当于是一个占位符,真正的RTR位在仲裁段的最后一位。RTR位还是用于区分数据帧和遥控帧。扩展格式帧中的IDE位总是隐性电平,表示这是扩展格式的帧。
  • 控制段。控制段由RB1位、RB0位和4位DLC组成。RB1位和RB0位是保留位,总是显性电平。4位的DLC编码表示数据的长度,从0到8字节。

6.优先级法则

数据帧和遥控帧的仲裁段用于多个节点竞争总线时进行仲裁,优先级高的帧获得在总线上发送数据的权利。优先级的确认总结为以下几条法则。

  • 在总线空闲时,最先开始发送消息的节点获得发送权。
  • 多个节点同时开始发送时,从仲裁段的第一位开始进行仲裁,第一次出现各节点的位电平互异时,输出显性电平的节点获得发送权。
  • 相同ID和格式的数据帧和遥控帧,数据帧具有更高优先级,因为数据帧的RTR位是显性电平,而遥控帧的RTR位是隐性电平。
  • 对于11位标准ID相同的标准数据帧和扩展数据帧,标准数据帧具有更高的优先级,因为标准数据帧的IDE位是显性电平,而扩展数据帧的IDE位是隐性电平。

二、CAN外设工作原理和HAL驱动程序

1、片上CAN外设的功能概述

STM32F4系列器件上有两个基本扩展CAN,支持2.0A和2.0B的CAN协议。两个CAN外设是CAN1和CAN2,称它们为CAN模块。

STM32F4系列器件的两个CAN模块的结构如图所示。CAN1是带有512字节SRAM的主CAN控制器,CAN2无法直接访问SRAM存储器,是从CAN控制器。两个CAN控制器共享512字节SRAM。

STM32F4的CAN外设的主要特点如下:

  • 波特率最高为1Mbit/s。
  • 每个CAN模块有3个发送邮箱,可自动重发。
  • 具有16位自由运行的定时器,可以定时触发通信,可以在最后两个数据字节发送时间戳。
  • 每个CAN模块有两个FIFO单元,每个FIFO有3个接收邮箱,每个FIFO有独立的中断地址。
  • 两个CAN模块共用28个筛选器组,筛选器用于配置可接收ID列表或掩码。数据帧和遥控帧根据ID被筛选,只有通过筛选的帧才进入接收邮箱。帧的筛选完全由硬件完成,减少处理器的负担。

STM32F4系列MCU上的CAN模块只是CAN控制器,要构成一个CAN节点,MCU还需要外接一个CAN收发器芯片,实现MCU逻辑电平到CAN总线物理层的电平转换和控制。

2、 CAN模块的基本控制

CAN模块有3种主要的工作模式:初始化、正常和睡眠。硬件复位后,CAN模块处于睡眠模式;在初始化模式下,可以对CAN模块进行初始化设置;在正常模式下,可以进行数据的接收与发送。通过配置CAN主控制寄存器CAN_MCR的SLEEP、INRQ等位,用户可以实现在3种工作模式之间的转换。

HAL驱动程序中用于CAN模块初始化、工作模式转换、启动和停止的函数如下表:

|-----------------------------|-------------------------------------------------------------------|
| 函数名 | 功能描述 |
| HAL_CAN_Init() | CAN模块初始化,主要是配置CAN总线通信参数 |
| HAL_CAN_MspInit() | CAN模块初始化MSP弱函数,在HAL_CAN_Init()里被调用。需要用户 程序重新实现,用于引脚GPIO配置,中断优先级配置 |
| HAL_CAN_Start( ) | 启动CAN模块 |
| HAL_CAN_Stop() | 停止CAN模块,允许重新访问配置寄存器 |
| HAL_CAN_RequestSleep() | 使CAN模块在完成当前操作后进入睡眠模式 |
| HAL_CAN_WakeUp () | 将CAN模块从睡眠模式唤醒 |
| HAL_CAN_IsSleepActive() | 查询CAN模块是否处于睡眠模式,返回值为1表示模块处于睡眠模式 |

CAN模块的初始化函数是HAL_CAN_Init(),其原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef *hcan);

其中,hcan是CAN_HandleTypeDef结构体类型指针,是CAN模块对象指针。

CAN_HandleTypeDef的成员变量Init是结构体类型CAN_InitTypeDef,用于存储CAN通信参数。在CubeMX生成的代码中,会为启用的CAN模块定义外设对象变量,例如:

cpp 复制代码
CAN_HandleTypeDef hcan1;	//表示CAN1的外设对象变量

其他函数的原型定义如下:

cpp 复制代码
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan);				//MSP初始化函数
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan);	//启动CAN模块
HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan);	//停止CAN模块
HAL_StatusTypeDef HAL_CAN_Requestsleep(CAN_HandleTypeDef *hcan);	//进入睡眠模式
HAL_StatusTypeDef HAL_CAN_WakeUp(CAN_HandleTypeDef *hcan);	//从睡眠模式唤醒
uint32_t HAL_CAN_IsSleepActive(CAN_HandleTypeDef *hcan);	//返回1表示模块处于睡眠模式

一个CAN模块需要先用函数HAL_CAN_Init()进行外设初始化,模块处于初始化模式,可以进行筛选器组的配置。执行函数HAL_CAN_Start()启动CAN模块进入正常模式,模块可以在正常模式和睡眠模式之间切换。执行HAL_CAN_Stop()将停止CAN模块。

3、 CAN模块的测试模式

在对CAN模块进行初始化设置时,通过设置位时序寄存器CAN_BTR的SILM和LBKM位,可以使CAN模块进入测试模式。在测试模式下,我们将主控制寄存器CAN_MCR中的INRQ位复位,可以进入正常模式。要进入测试模式,必须在CAN模块初始化时进行设置。在测试模式下,CAN模块可以自发自收,以测试CAN模块的功能是否正常。CAN模块的3种测试模式如图:

(1)静默模式(silent mode)

在静默模式下,CAN模块可以接收有效的数据帧和遥控帧,但是只能向总线发送隐性位,发送的显性位都被自己接收,所以在静默模式下,CAN模块无法启动发送操作。这种模式一般用于监测总线流量。

(2)回环模式(loop back mode)

在回环模式下,CAN模块可以正常地向总线发送数据,但不能接收总线上的数据,只能接收自己发送的数据(需要通过筛选规则)。这种模式可用于自检测试。为了不受外部事件的影响,CAN内核在此模式下不会对数据帧或遥控帧的ACK段采样,这样可以忽略ACK错误。

(3)回环与静默组合模式(loop back combined with silent mode)

这是回环与静默模式的组合,可用于"热自检"。在这种模式下,CAN模块不能接收总线上的数据,只能接收自己发送的数据;只能向总线上发送隐性位,因而不会影响CAN总线。

使CAN模块进入某种测试模式是在初始化函数HAL_CAN_Init()中,通过设置CAN模块的属性实现的,在示例代码里会具体介绍。

4、消息发送

一个CAN模块有3个发送邮箱。发送数据时,用户需要选择一个空闲的发送邮箱,将标识符ID、数据长度和数据(最多8字节)写入邮箱,然后CAN模块会自动控制将邮箱内的数据发送出去。

用户可以设置自动重发,也就是在出现错误后自动重发,直到成功发送出去。如果禁止自动重发,则发送失败后不再重发,会通过发送状态寄存器CAN_TSR相应的位指示错误原因,如仲裁丢失或发送错误。

用户可以终止邮箱数据的发送,终止发送后邮箱会变成空闲状态。

用户可以设置时间触发通信模式(time triggered communication mode)。在此模式下,会激活CAN模块内部的一个硬件计数器,CAN总线每收发一个位数据,计数器都会递增。在发送或接收时,在帧的起始位时刻捕获计数值,作为发送或接收数据帧的时间戳数据。在CAN的HAL驱动程序中,与发送消息相关的函数如表:

|--------------------------------------------|---------------------------------|
| 函数名 | 功能描述 |
| HAL_CAN_GetTxMailboxesFreeLevel () | 查询空闲的发送邮箱个数,空闲邮箱个数大于0时就可以发送 |
| HAL_CAN_AddTxMessage() | 向一个邮箱写入一条消息,由CAN模块自动控制邮箱内消息 的发送 |
| HAL_CAN_AbortTxRequest() | 中止发送一个被挂起(等待发送)的消息 |
| HAL_CAN_IsTxMessagePending() | 判断一个消息是否在等待发送 |
| HAL_CAN_GetTxTimestamp() | 如果使用了时间触发通信模式,此函数读取发送消息的时间戳 |

函数HAL_CAN_GetTxMailboxesFreeLevel()用于查询一个CAN模块空闲的发送邮箱个数,如果有空闲的发送邮箱,就可以使用函数HAL_CAN_AddTxMessage()向发送邮箱写入一条消息,然后由CAN模块启动发送过程。这个函数只能发送数据帧或遥控帧,其函数原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan,CAN_TxHeaderTypeDef*pHeader,uint8_t aData[],uint32_t *pTxMailbox)

其中,参数hcan是CAN模块外设对象指针;参数pHeader是CAN_TxHeaderTypeDef结构体类型指针,定义了消息的一些参数;aData是发送数据的数组,最多8字节的数据;参数pTxMailbox用于返回实际使用的发送邮箱号。

结构体CAN_TxHeaderTypeDef用于定义消息的一些参数,用于CAN模块组装成数据帧,该结构体完整定义如下:

cpp 复制代码
typedef struct
{
    uint32_t StdId;	//11位的标准标识符,设置范围是0~0x7FF
    uint32_t ExtId;	//29位的扩展标识符,设置范围是0~0x1FFFFFFF
    uint32_t IDE;	//帧格式类型,标准ID(CAN_ID_STD)或扩展ID(CAN_ID_EXT)
    uint32_t RTR;	//RTR位,消息类型:数据帧(CAN_RTR_DATA)或遥控帧(CAN_RTR_REMOTE)
    uint32_t DLC;	//数据字节数,最多8字节,设置范围是0~8
    FunctionalState TransmitGlobalTime;	//是否使用时间戳,取值ENABLE或DISABLE
}CAN_TxHeaderTypeDef;

其中,成员变量IDE表示帧格式类型,有两个宏定义表示标准ID和扩展ID。

cpp 复制代码
#define CAN_ID_STD (0x00000000U) //标准ID
#define CAN_ID_EXT (0x00000004U) //扩展ID

成员变量RTR表示消息类型,只能是数据帧或遥控帧,有两个宏定义用于此变量的取值。

cpp 复制代码
#define CAN_RTR_DATA     (0x00000000U) //数据帧
#define CAN_RTR_REMOTE   (0x00000002U) //遥控帧

CAN模块发送数据是将消息写入模块的发送邮箱,然后由CAN控制器将邮箱内的消息发送出去。CAN模块发送消息只有HAL_CAN_AddTxMessage()这一个函数,不像串口、SPI等其他外设有中断模式、DMA方式的专用函数。

将消息写入邮箱后,可以用函数HAL_CAN_IsTxMessagePending(查询邮箱里的消息是否发送出去了,这个函数的原型定义是:

cpp 复制代码
uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *hcan,uint32_t TxMailboxes);

其中,参数TxMailboxes是发送邮箱号。函数返回值如果是0,则表示没有等待发送的消息,也就是消息已经被发送出去了;如果返回值为1,则表示邮箱里的消息仍然在等待发送。CAN总线上可能有很多个节点,需要通过总线仲裁获得CAN总线使用权之后,节点才能将邮箱里的消息发送出去。

CAN模块也有表示消息发送出去的中断事件,如果打开了相应的中断事件使能控制位,也可以在中断里做出响应。在后面会专门介绍CAN的中断。

5、消息接收

每个CAN模块有两个接收FIFO(Receive FIFO),每个FIFO有3个邮箱。FIFO完全由硬件管理,当有邮箱接收到有效消息时,就会产生相应的事件中断标志,可以产生CAN RX硬件中断。FIFO0和FIFO1有各自的中断地址。从邮箱中读出消息后,邮箱就自动释放。如果一个FIFO的3个邮箱都接收到消息而没有及时读出,再有消息进入时就会产生上溢。根据是否设置FIFO锁定,有两种处理情况。

  • 如果禁止FIFO锁定,则新传入的消息会覆盖FIFO中存储的最后一条消息。
  • 如果启用FIFO锁定,则新传入的消息会被舍弃。

用户可以通过轮询方式或中断方式读取接收邮箱中的消息。CAN模块接收消息的相关函数如表:

|---------------------------------------|----------------------|
| 函数名 | 功能描述 |
| HAL_CAN_GetRxFifoFillLevel () | 查询一个FIFO中存在未读消息的邮箱个数 |
| HAL_CAN_GetRxMessage() | 读取一个接收邮箱中的消息 |

函数HAL_CAN_GetRxFifoFillLevel()用于查询某个FIF()存在未读消息的邮箱个数,函数原型定义如下:

cpp 复制代码
uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan,uint32_t RxFifo)

其中,参数RxFifo是FIFO编号,一个CAN模块有两个FIFO,可使用如下的两个宏作为此参数的取值。

cpp 复制代码
#define CAN_RX_FIFO0(0x00000000U) //CAN模块FIFO0
#define CAN_RX_FIFO1(0x00000001U) //CAN模块FIFO1

如果查询到有未读取的消息,就用函数HAL_CAN_GetRxMessage(读取接收的消息,此函数的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan,uint32_t RxFifo,CAN_RxHeaderTypeDef *pHeader,uint8_t aData[])

其中,参数RxFifo是FIFO编号,用宏CAN_RX_FIFO0和CAN_RX_FIFO1分别表示FIFO0和FIFO1;参数pHeader是CAN_RxHeaderTypeDef结构体类型指针,记录了帧的一些信息;aData[]是接收数据的数组,最多8字节。

记录帧信息的结构体CAN_RxHeaderTypeDef的定义如下:

cpp 复制代码
typedef struct
{
    uint32_t StdId;		//11位的标准标识符,范围是0~0x7FF
    uint32_t ExtId;		//29位的扩展标识符,范围是0~0x1FFFFFFF
    uint32_t IDE;		//帧格式类型,标准ID(CAN_ID_STD)或扩展ID(CAN_ID_EXT)
    uint32_t RTR;		//RTR位,消息类型:数据帧或遥控帧
    uint32_t DLC;		//数据字节数,最多8字节
    uint32_t Timestamp;	//时间戳数据,数值范围是0~0xFFFE
    uint32_t FilterMatchIndex;	//匹配的筛选器索引
}CAN_RxHeaderTypeDef;

结构体CAN_RxHeaderTypeDef的部分成员变量与结构CAN_TxHeaderTypeDef的相同,只有后面两个成员变量CAN_RxHeaderTypeDef特有的。

6、标识符筛选

(1)标识符筛选原理

在CAN网络中,发送节点是以广播方式发送消息的,所有CAN节点都可以收到消息。数据帧和遥控帧带有标识符,标识符一般表示了消息的类型。一个CAN节点一般只对特定的消息感兴趣,如果用软件对接收的帧ID进行判别,将消耗接收节点的大量CPU时间。STM32F4的两个CAN控制器有28个共用的标识符筛选器组(Filter Bank),可以完全用硬件方式对接收的帧ID进行筛选,只允许符合条件的帧进入接收邮箱,自动放弃不符合条件的帧。

每个筛选器组包含两个32位寄存器,分别是CAN_FxR1和CAN_FxR2。这两个寄存器可以被配置为两个32位长度筛选器或4个16位长度筛选器,筛选器可以是掩码模式或列表模式,所以一个筛选器组有4种配置模式:

1)1个32位筛选器------标识符掩码模式

在这种模式下,寄存器CAN_FxR1存储一个32位ID,这个ID与11位标准ID(STID[10:0])、18位扩展ID(EXID[17:0])、IDE位、RTR位的位置对应关系如图中所示。IDE为0时表示标准格式帧,否则表示扩展格式帧。

CAN_FxR2存储一个32位掩码,如果掩码为1,则表示该位必须与ID中的位一致,如果为0,则表示不用一致。

例如,如果让一个CAN节点只接收标准ID为奇数的标准格式数据帧,则设置寄存器CAN_FxR1表示的ID时,STID[0]位必须设置为1,IDE位必须设置为0(表示标准格式帧),RTR位必须设置为0(表示数据帧)。设置寄存器CAN_FxR2表示的掩码时,对应的这些位必须设置为1,其他位设置为0。

|--------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|-------------------|-------------------|-------|
| 映射 | STID[10:3] |||||||| STID[2:0] ||| EXID[17:13] ||||| EXID[12:5] |||||||| EXID[4:0] ||||| I D E | R T R | 0 |
| ID | × | × | × | × | × | × | × | × | × | × | 1 | × | × | × | × | × | × | × | × | × | × | × | × | × | × | × | × | × | × | 0 | 0 | 0 |
| 掩码 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |

2)2个32位筛选器------标识符列表模式

在这种模式下,寄存器CAN_FxR1和CAN_FxR2各存储一个32位ID,ID的组成与模式(1)相同。只有匹配这两个ID的帧才能通过筛选。

3)2个16位筛选器------标识符掩码模式

在这种模式下,寄存器CAN_FxR1的高16位组成一个ID,低16位组成一个掩码;寄存器CAN_FxR2的高16位组成一个ID,低16位组成一个掩码。

4)个16位筛选器------标识符列表模式

在这种模式下,寄存器CAN_FxR1表示2个16位ID,寄存器CAN_FxR2表示2个16位ID。用户可以为一个FIFO设置多个筛选器组,但是一个筛选器组只能配置给一个FIFO。如果为FIFO设置了筛选器,并且接收的帧与所有筛选器都不匹配,那么该帧会被丢弃。只要通过了一个筛选器,帧就会被存入接收邮箱。

(2)函数HAL_CAN_ConfigFilter()

函数HAL_CAN_ConfigFilter()用于设置CAN模块的标识符筛选器,应该在执行HAL_CAN_Start()启动一个CAN模块之前调用这个函数。其原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan,CAN_FilterTypeDef *sFilterConfig)

其中,参数sFilterConfig是结构体CAN_FilterTypeDef类型指针,它保存了筛选器的设置。这个结构体定义如下:

cpp 复制代码
typedef struct
{
    uint32_t FilterIdHigh;		//CAN_FxR1寄存器的高16位,取值范围为0~0xFFFF
    uint32_t FilterIdLow;		//CAN_FxR1寄存器的低16位,取值范围为0~0xFFFE
    uint32_t FilterMaskIdHigh;	//CAN_FxR2寄存器的高16位,取值范围为0~0xFFFF
    uint32_t FilterMaskIdLow;	//CAN_FxR2寄存器的低16位,取值范围为0~0xFFFF
    /*筛选器应用于哪个FIFO,使用宏CAN_FILTER_FIFO0或CAN_FILTER_FIFO1 */
    uint32_t FilterFIFOAssignment;
    /*筛选器组编号,具有双CAN模块的MCU有28个筛选器组,编号范围为0~27*/
    uint32_t FilterBank;
    /*筛选器模式,ID掩码模式(CAN_FILTERMODE_IDMASK)或ID列表模式(CAN_FILTERMODE_IDLIST)*/
    uint32_t FilterMode;
    /*筛选器长度,即32位(CAN_FILTERSCALE_32BIT)或16位(CAN_FILTERSCALE_16BIT)*/
    uint32_t FilterScale;
    uint32_t FilterActivation;		//是否启用此筛选器,ENABLE或者DISABLE
    uint32_t SlaveStartFilterBank;	//设置应用于从CAN控制器的筛选器的起始编号
}CAN_FilterTypeDef;

某些变量的取值具有相应的宏定义,例如,FilterMode是筛选器模式,有两个宏定义可用于此变量的取值,宏定义如下:

cpp 复制代码
#define CAN_FILTERMODE_IDMASK	(0x00000000U)	//ID掩码模式
#define CAN_FILTERMODE_IDLIST	(0×00000001U)	//ID列表模式

总之,筛选器的设置是CAN模块使用中比较复杂的环节。

7、中断及处理

(1)中断和中断事件

一个CAN模块有4个中断,对应4个ISR。例如,CAN1的4个中断及其ISR。

|------------------------|----------------|----------------------|-----------------------|
| 中断名称 | 中断中文名称 | 说明 | ISR名称 |
| CAN1_TX | 发送中断 | 任何一个发送邮箱发送完成时产生的中断 | CAN1_TX_IRQHandler() |
| CAN1 _ RX0 | FIFO0接收中断 | FIFO0接收消息、满或上溢时产生的中断 | CAN1_RX0_IRQHandler() |
| CAN1_RX1 | FIFO1接收中断 | FIFO1接收消息、满或上溢时产生的中断 | CAN1_RX1_IRQHandler() |
| CAN1_SCE | 状态改变和错误 中断 | 状态改变或发生错误时产生的中断 | CAN1_SCE_IRQHandler() |

每个中断又有1个或多个中断事件源,HAL驱动程序中为每个中断事件源定义了中断类型宏定义,也就是中断事件使能控制位的宏定义。例如,CAN1_TX只有一个中断事件源,为其定义中断事件类型的宏定义如下:

cpp 复制代码
#define CAN_IT_TX_MAILBOX_EMPTY  ((uint32_t)CAN_IER_TMEIE)

HAL驱动程序中有两个宏函数可以开启或禁止某个具体的中断事件源。

cpp 复制代码
__HAL_CAN_ENABLE_IT(__HANDLE__,__INTERRUPT__) //开启某个中断事件源

__HAL_CAN_DISABLE_IT(__HANDLE__,__INTERRUPT__) //禁用某个中断事件源

其中,_HANDLE_是CAN模块对象指针,__INTERRUPT__是表示中断事件类型的宏,例如CAN_IT_TX_MAILBOX_EMPTY。

在CubeMX为CAN模块的4个硬件中断生成的ISR中,都调用了函数HAL_CAN_IRQHandler(),这是CAN中断处理通用函数。函数HAL_CAN_IRQHandler()会根据中断使能寄存器、中断标志寄存器的内容判断具体发生了哪个中断事件,再调用相应的回调函数。CAN的HAL驱动程序中为常用的中断事件定义了回调函数,只要搞清楚中断事件与回调函数的对应关系,编程时重新实现关联的回调函数,就可以对某个中断事件做出处理。

(2)发送中断的事件源和回调函数

发送中断(CAN1_TX)只有一个中断事件源CAN_IT_TX_MAILBOX_EMPTY,在3个发送邮箱中任何一个发送完成时都产生该事件中断,但是3个邮箱有各自的回调函数:

|-----------------------------|----------------|--------------------------------------|
| 中断事件类型宏 | 中断事件说明 | 回调函数 |
| CAN_IT_TX_MAILBOX_EMPTY | 邮箱0发送完成 | HAL_CAN_TxMailbox0CompleteCallback() |
| CAN_IT_TX_MAILBOX_EMPTY | 邮箱1发送完成 | HAL_CAN_TxMailbox1CompleteCallback() |
| CAN_IT_TX_MAILBOX_EMPTY | 邮箱2发送完成 | HAL_CAN_TxMailbox2CompleteCallback() |

另外,调用函数HAL_CAN_AbortTxRequestO中止某个邮箱的发送后,也会调用相应的回调函数,只是这几个回调函数不是由中断引起的,而是由函数HAL_CAN_AbortTxRequest()引起的。

(3)FIFO0的中断事件源和回调函数

FIFO0接收中断(CAN1_RX0)是在FIFO0接收消息、满或上溢时触发的中断。这个中断有3个中断事件源,对应的回调函数如表所示:

|---------------------------------|----------------|-------------------------------------|
| 中断事件类型宏 | 中断事件说明 | 回调函数 |
| CAN_IT_RX_FIFO0_MSG_PENDING | FIFO0接收新消息 | HAL_CAN_RxFifo0MsgPendingCallback() |
| CAN_IT_RX_FIFO0_FULL | FIFO0满 | HAL_CAN_RxFifo0FullCallback() |
| CAN_IT_RX_FIFO0_OVERRUN | FIFO0发生上溢 | _ |

其中,接收新消息的中断事件是比较有用的,因为CAN模块接收消息一般是使用中断方式。

(4)FIFO1的中断事件源和回调函数

FIFO1接收中断(CAN1_RX1)是在FIFO1接收消息、满或上溢时触发的中断。这个中断也有3个中断事件源,对应的回调函数如表所示:

|---------------------------------|----------------|-------------------------------------|
| 中断事件类型宏 | 中断事件说明 | 回调函数 |
| CAN_IT_RX_FIFO1_MSG_PENDING | FIFO1接收新消息 | HAL_CAN_RxFifo1MsgPendingCallback() |
| CAN_IT_RX_FIFO1_FULL | FIFO1满 | HAL_CAN_RxFifolFullCallback() |
| CAN_IT_RX_FIFO1_OVERRUN | FIFO1发生上溢 | ------ |

(5)状态改变或错误的中断事件源和回调函数

状态改变或错误中断(CAN1_SCE)在CAN模块发生状态改变或错误时触发,例如,CAN模块进入睡眠状态或从睡眠状态被唤醒,或出现总线错误等。CAN1_SCE的中断事件源和回调函数如表所示:

|---------------------------------------|----------------------------------------|-----------------------------------|
| 中断事件宏定义 | 中断事件说明 | 回调函数 |
| CAN_IT_SLEEP_ACK | CAN模块进入睡眠状态 | HAL_CAN_SleepCallback() |
| CAN_IT_WAKEUP | 监测到消息,被唤醒 | HAL_CAN_WakeUpFromRxMsgCallback() |
| CAN_IT_ERROR CAN_IT_BUSOFF等多种 | 有多种错误事件源,通过错 误状态寄存器CAN_ESR的 内容判断具体错误类型 | HAL_CAN_ErrorCallback() |

相关推荐
Zevalin爱灰灰1 小时前
FreeRTOS从入门到精通 第七章(FreeRTOS列表和列表项)
stm32·操作系统·freertos
2301_805962933 小时前
NRF24L01模块STM32通信-通信初始化
stm32·单片机·嵌入式硬件
阳光宅男@李光熠5 小时前
【电子通识】PWM驱动让有刷直流电机恒流工作
单片机·嵌入式硬件
半个番茄5 小时前
STM32 : 奈奎斯特-香农采样定理
网络·stm32·单片机
IT信息技术学习圈7 小时前
CSP初赛知识学习计划(第二天)
单片机·嵌入式硬件·学习
硬件技术我知道11 小时前
产品 防尘防水IP等级 划分与实验方法
网络·人工智能·嵌入式硬件·物联网·计算机视觉·硬件工程·智慧城市
JoneMaster12 小时前
[读书日志]从零开始学习Chisel 第三篇:Scala面向对象编程——类和对象(敏捷硬件开发语言Chisel与数字系统设计)
开发语言·嵌入式硬件·学习·硬件架构·scala
分享大师12 小时前
【小制作】米家模拟手指点击
单片机·gitlab
qq_5719572613 小时前
通过串口通信控制led灯的亮灭
c语言·stm32·单片机
hfffhfh14 小时前
STM32CUBEMX+PLS_D1000激光测距模块+MT6701角度传感器,获取三角形第三边角度
stm32·单片机·嵌入式硬件