赞
踩
5.2.1 任务分析
本任务要求设计一个基于 CAN 总线的多机通信系统,该系统须具备交互多种类型数据(如
环境温度、环境湿度和光照强度等)的能力。
发送单元对待发数据进行编码,组建 CAN 数据帧,并将其通过 CAN 总线发送出去。
CAN总线上的每个接收单元收到CAN数据帧之后,对数据帧进行筛选并接收感兴趣的数据,
然后通过串行通信的方式将数据传至上位机显示。
图 5-2-1 展示了基于 CAN 总线的多机通信系统接线,从图中可以看到,CAN 总线上各
通信终端之间的连接方式较为简单,只须将各自的 CAN 接线端子 CANH 与 CANL 分别相连
即可。
根据本任务的要求,在应用程序开发之前,我们须明确以下几个问题:CAN 总线上数据的
发送模式、发送单元对不同数据的编码方式以及接收单元的数据筛选方式。
1.CAN 总线上数据的发送模式
CAN 总线上数据的发送模式为“广播”模式,同一时刻总线上只能有一个终端作为发送单
元,其他终端都为接收单元。发送单元发送的数据帧可被总线上所有的接收单元接收。
当总线空闲时,判断哪个终端可成为发送单元是以终端提交发送请求的先后顺序作为依据
的,即谁先提交谁就具有优先权。
当出现有多个终端同时提交发送请求时,CAN 总线会根据制定好的规则进行优先级仲
裁,在仲裁中胜出的终端可继续发送数据,成为发送单元,其他终端则返回接收模式,成为
接收单元。
2.发送单元对不同数据的编码方式
发送单元在待发送的数据中加入“标识符(ID)”段,用于区分不同的数据。“标识符”段同
时也是 CAN 总线进行发送单元优先级仲裁的依据。
3.接收单元的数据筛选方式
对接收单元而言,它们也使用数据帧中“标识符”段作为判别不同数据的依据。在 CAN 技
术规范中,这项判别工作并不是由用户通过软件编程来实现的,而是通过 CAN 控制器中一个名
为“筛选器”的硬件来完成的,用户只须对“筛选器”进行配置即可。
根据以上的任务分析,本任务涉及的知识点如下:
CAN 总线的基础知识;
STM32F4 系列微控制器内部集成的 CAN 控制器的功能特性;
STM32F4 标准外设库中的 CAN 相关内容;
利用 STM32F4 标准外设库进行 CAN 控制器的配置与编程方法。
5.2.2 知识链接
1.CAN 总线的基础知识
(1)CAN 总线简介
CAN 由德国 BOSCH 公司于 1986 年 2 月开发出来并发布,最早被应用于汽车内部控制系统
的监测与执行机构间的数据通信,目前是国际上应用最广泛的现场总线之一。
近年来,由于 CAN 总线具备高可靠性、高性能、功能完善和成本较低等优势,其应用领域
已从最初的汽车工业领域慢慢渗透进航空工业、安防监控、楼宇自动化、工业控制、工程机械、
医疗器械等领域。例如,当今的酒店客房管理系统集成了门禁、照明、通风、加热和各种报警安
全监测等设备,这些设备通过 CAN 总线连接在一起,形成各种执行器和传感器的联动,这样的
系统架构为用户实时监测各单元运行状态提供了可能性。
CAN 总线具有以下主要特性:
数据传输距离远(最远 10 km);
数据传输速率高(最高数据传输速率 1Mbit/s);
具备优秀的仲裁机制;
使用筛选器实现多地址的数据帧传递;
借助遥控帧实现远程数据请求;
具备错误检测与处理功能;
具备数据自动重发功能;
故障节点可自动脱离总线且不影响总线上其他节点的正常工作。
(2)CAN 技术规范与标准
1991 年 9 月,Philips 半导体制定并发布了 CAN 技术规范 2.0 版本。这个版本的 CAN 技术
规范包括 A 和 B 两部分,其中 2.0A 版本技术规范只定义了 CAN 报文的标准格式,而 2.0B 版本
则同时定义了 CAN 报文的标准与扩展两种格式。1993 年 11 月,ISO(国际标准化组织)正式
颁布了 CAN 国际标准 ISO 11898 与 ISO 11519。ISO 11898 标准的 CAN 通信数据传输速率为
125kbit/s~1Mbit/s,适合高速通信应用场景;而 ISO 11519 标准的 CAN 通信数据传输速率为
125kbit/s 以下,适合低速通信应用场景。
CAN 技术规范主要对 OSI(开放式系统互联参考模式)中的物理层(部分)、数据链路层和
传输层(部分)进行了定义。ISO 11898 与 ISO 11519 标准则对数据链路层及物理层的一部分
进行了标准化,如图 5-2-2 所示。
ISO 并未对 CAN 技术规范的网络层、传输层、会话层、表示层和应用层等部分进行标准化,
而美国汽车工程师学会(Society of Automotive Engineers,SAE)等其他组织、团体和企业则
针对不同的应用领域对 CAN 技术规范进行了标准化。这些标准对 ISO 标准未涉及的部分进行了
定义,它们属于 CAN 应用层协议。常见的 CAN 标准及其详情见表 5-2-1。
(3)CAN 总线的网络拓扑与节点硬件构成
CAN 总线的网络拓扑结构如图 5-2-3 所示。
图 5-2-3 展示的 CAN 总线网络拓扑包括两个网络:一个是遵循 ISO 11898 标准的高速
CAN 总线网络(传输速率为 500 kbit/s),另一个是遵循 ISO 11519 标准的低速 CAN 总线网
络(传输速率 125 kbit/s)。高速 CAN 总线网络被应用在汽车动力与传动系统,它是闭环网
络,总线最大长度为 40m,要求两端各有一个 120Ω的电阻。低速 CAN 总线网络被应用在
汽车车身系统,它的两根总线是独立的,不形成闭环,要求每根总线上各串联一个 2.2kΩ 的
电阻。
CAN 总线上节点的硬件架构如图 5-2-4 所示。
从图 5-2-4 可以看到,CAN 总线上节点的硬件架构有两种方案。
第一种方案的硬件架构由 MCU、CAN 控制器和 CAN 收发器组成。这种方案采用了独立的
CAN 控制器,优点是可以方便地移植程序到其他使用相同 CAN 控制器芯片的系统,缺点是需要
占用 MCU 的 I/O 资源且硬件电路更复杂一些。
第二种方案的硬件架构由集成了 CAN 控制器的 MCU 和 CAN 收发器组成。这种方案的硬件
电路简单,缺点是用户编写的 CAN 驱动程序只适用某个系列的 MCU(如 STM32F4、MSP430
或 C8051 等),可移植性较差。
(4)CAN 总线的报文信号电平与帧类型
总线上传输的信息被称为报文,总线规范不同,其报文信号电平标准也不同。ISO 11898 和
ISO 11519 标准在物理层的定义有所不同,两者的信号电平标准也不尽相同。CAN 总线上的报
文信号使用差分电压传送。图 5-2-5 展示了 ISO 11898 标准的 CAN 总线信号电平标准。
图 5-2-5 中的实线与虚线分别表示 CAN 总线的两条信号线 CAN_H 和 CAN_L。静态时两
条信号线上的电平电压均为 2.5 V 左右(电位差为 0),此时的状态表示逻辑 1(或称“隐性电平”
状态)。当 CAN_H 上的电压值为 3.5 V 且 CAN_L 上的电压值为 1.5 V 时,两线的电位差为 2 V,
此时的状态表示逻辑 0(或称“显性电平”状态)。
CAN 总线上的数据通信基于以下 5 种类型的通信帧,它们的名称与用途如表 5-2-2 所示。
(5)数据帧
数据帧由 7 个段构成,如图 5-2-6 所示。图中深灰色底的位为“显性电平”,浅灰色底的位
为“显性或隐性电平”,白色底的位为“隐性电平”(下同)。
① 帧起始
帧起始(Start of Frame)表示数据帧和远程帧的起始,它仅由一个“显性电平”位组成。
CAN 总线的同步规则规定,只有当总线处于空闲状态(总线电平呈隐性状态)时,才允许站点
开始发送信号。
② 仲裁段
仲裁段(Arbitration Field)是表示帧优先级的段。标准帧与扩展帧的仲裁段格式有所不同:
标准帧的仲裁段由 11bit 的标识符和远程发送请求(Remote Transmission Request,RTR)位
构成;扩展帧的仲裁段由 29bit 的标识符、替代远程请求(Substitute Remote Request,SRR)
位、标识符扩展(Identifier Extension,IDE)位和 RTR 位构成。
RTR 位用于指示帧类型,数据帧的 RTR 位为“显性电平”,而遥控帧的 RTR 位为“隐性
电平”。
SRR 位只存在于扩展帧中,与 RTR 位对齐,为“隐性电平”。因此当 CAN 总线对标准帧和
扩展帧进行优先级仲裁时,在两者的标识符部分完全相同的情况下,扩展帧相对标准帧而言处于
失利状态,具体原理见本节的“优先级仲裁”部分。
③ 控制段
控制段(Control Field)是表示数据的字节数和保留位的段,标准帧与扩展帧的控制段格式
不同。标准帧的控制段由 IDE 位、保留位 r0 和 4bit 的数据长度码(DLC)构成。扩展帧的控制
段由保留位 r1、r0 和 4bit 的数据长度码构成。IDE 位用于指示数据帧为标准帧还是扩展帧,标
准帧的 IDE 位为“显性电平”。数据长度码与字节数的关系如表 5-2-3 所示。
④ 数据段
数据段(Data Field)用于承载数据的内容,它可包含 0~8B 的数据,从 MSB(最高有效位)
开始输出。
⑤ CRC 段
CRC 段(CRC Field)是用于检查帧传输是否错误的段,它由 15bit 的 CRC 序列和 1bit 的
CRC 界定符(用于分隔)构成。CRC 序列是根据多项式生成的 CRC 值,其计算范围包括帧起
始、仲裁段、控制段和数据段。
⑥ ACK 段
ACK 段(Acknowledge Field)是用于确认接收是否正常的段,它由 ACK 槽(ACK Slot)
和 ACK 界定符(用于分隔)构成,长度为 2bit。
⑦ 帧结束
帧结束(End of Frame)用于表示数据帧的结束,它由 7bit 的隐性位构成。
(6)遥控帧
遥控帧的构成如图 5-2-7 所示。
从图 5-2-7 中可以看到,遥控帧与数据帧相比,除了没有数据段外,其他段的构成与数据
帧完全相同。如前所述,RTR 位的极性指明了该帧是数据帧还是遥控帧,遥控帧中的 RTR 位为
“隐性电平”。
(7)错误帧
错误帧用于在接收和发送消息时检测出错误并通知系统,它的构成如图 5-2-8 所示。
从图 5-2-8 可知,错误帧由错误标志和错误界定符构成。错误标志包括主动错误标志和被
动错误标志,前者由 6bit 的显性位构成,后者由 6bit 的隐性位构成。错误界定符由 8bit 的隐性
位构成。
(8)过载帧
过载帧是接收单元用于通知发送单元其尚未完成接收准备的帧,它的构成如图 5-2-9
所示。
从图 5-2-9 可知,过载帧由过载标志和过载界定符构成。过载标志的构成与主动错误标志
的构成相同,由 6bit 的显性位构成。过载界定符的构成与错误界定符的构成相同,由 8bit 的隐
性位构成。
(9)帧间隔
帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的
任何帧(数据帧、遥控帧、错误帧或过载帧等)
隔开,但错误帧和过载帧前不允许插入帧间隔。
帧间隔的构成如图 5-2-10 所示。
从图 5-2-10 可知,帧间隔的构成元素有3 个。一是间隔,它由 3bit 的隐性位构成。二是总线空闲,它由隐性电平构成,且无长度限制。需要注意的是,只有在总线处于空闲状态下,要发送的单元才可以开始访问总线。三是延迟传送,它由 8bit 的隐性位构成。
(10)优先级仲裁
CAN 总线上可以挂载多个 CAN 控制器单元,每个 CAN 控制器单元都可以作为主机进行数
据的发送与接收。CAN 技术规范规定在总线空闲时,仅有一个单元可以占有总线并发送数据。
但如果有多个单元同时准备发送数据,当它们检测到信道空闲时,会在同一时刻将数据发送出来,
这就产生了发送冲撞。
为了解决上述数据发送的冲撞问题,CAN 技术规范提出了优先级的概念。数据帧与遥控帧
中的仲裁段即标明了帧的优先级。在多个单元同时发送数据时,优先级较高的帧先发,优先级较
低的帧则等待 CAN 总线空闲后再发送。
CAN 技术规范规定:“显性电平(逻辑 0)”的优先级高于“隐性电平(逻辑 1)”。CAN 通
信的帧优先级是根据仲裁段的信号物理特性来判定的。多个单元同时发送数据时,CAN 总线对
图5-2-10 帧间隔的构成
284
开发全案例实践
STM32 嵌入式技术应用
通信帧进行仲裁,从仲裁段的第一位开始,连续输出显性电平最多的单元可继续发送。若某个单
元仲裁失利,则从下一个位开始转为接收状态。
图 5-2-11 展示了具有相同标识符的数据帧与遥控帧的仲裁过程。
从图 5-2-11 可知,具有相同标识符的数据帧与遥控帧在 CAN 总线上竞争时,数据帧仲裁
段的最后一位(RTR)为显性电平,而遥控帧中相应的位为隐性电平,因此数据帧具有优先权,
可继续发送。
图 5-2-12 展示了具有相同标识符的标准数据帧与扩展数据帧的仲裁过程。
从图 5-2-12 可知,具有相同标识符的标准数据帧与扩展数据帧在 CAN 总线上竞争时,标
准数据帧的 RTR 位为显性电平,而扩展数据帧中相同位置的 SRR 位为隐性电平,因此标准数据
帧具有优先权,可继续发送。
(11)位时序
CAN 通信属于异步通信,收发单元之间没有同步信号。发送单元与接收单元之间无法做到
完全同步,即收发单元存在时钟频率误差,或者传输路径(电缆、驱动器等)上存在相位延迟。
因此接收单元方面必须采取相应的措施调整接收时序,以确保接收数据的准确性。
CAN 总线上的收发单元使用约定好的波特率进行通信,为了实现收发单元之间的同步,CAN
技术规范把每个数据位分解成如图 5-2-13 所示的 4 段。
从图 5-2-13 可知,每个数据位由同步段(Synchronization Segment,SS)、传播时间段
(Propagation Time Segment,PTS)、相位缓冲段 1(Phase Buffer Segment 1,PBS1)和相位缓冲段 2(Phase Buffer Segment 2,PBS2)构成,每个段又由若干个被称为“Time Quantum(简称 T q)”的最小时间片构成。最小时间片长度的计算方法将在后文中给出。
① SS 它用于收发单元之间的时序同步。若接收单元检测到总线上的信号跳变沿包含在 SS 内,则表示接收单元当前的时序与 CAN 总线是同步的。SS 的时间长度固定为 1 T q。
② PTS 用于补偿网络上的物理延迟(即发送单元的输出延迟、信号传播延迟、接收单元的输入延迟等)。PTS 的时间长度为上述延迟时间之和的两倍以上,一般为 1~8 T q。
③ PBS1 和 PBS2 一起用于补偿收发单元由于时钟不同步引起的误差,通过加长 PBS1 来补偿误差。PBS1 的时间长度一般为 1~8 T q。
④ PBS2 和 PBS1 一起用于补偿收发单元由于时钟不同步引起的误差,通过缩短 PBS2 来补
偿误差。PBS2 的时间长度为 2~8 T q。
上述介绍中提到了通过“加长 PBS1”或“缩短 PBS2”补偿收发单元之间的传输误差,而
CAN 技术规范规定了误差补偿的最大值,我们将其称为“再同步补偿宽度(reSynchronization
Jump Width,SJW)”,SJW 的时间长度范围是 1~4 T q。在实际应用中,调整 PBS1 和 PBS2 的
时间长度不可超过 SJW。
另外,从图 5-2-13 中可以看到,数据位的“采样点”位于 PBS1 与 PBS2 的交界处。PBS1
与 PBS2 的时间长度是可变的,因此“采样点”的位置也是可偏移的。当接收单元的时序与总线
时序同步后,即可确保在“采样点”上采集到的电平为该位的准确电平。
(12)收发同步的方式
前文中提到了接收单元为了确保接收数据的准确性,必须采取相应的措施进行收发同步。
CAN 技术规范规定了两种收发同步方式:硬同步方式与重新同步方式。
通过前面的学习,我们知道了 CAN 总线空闲时呈“隐性电平”状态,而帧起始(SOF)信
号仅由一个“显性电平”位组成。因此 CAN 总线由空闲状态转换为传输状态时,会产生“隐性
电平”到“显性电平”的跳变信号。
硬同步发生在 CAN 通信的初
始阶段,它的工作过程如下:接收单元在 CAN 总线空闲时
若检测到“隐性电平”到“显性电平”的跳变信号,则调整当前数据位的 SS,使其落在帧起始
(SOF)信号内部。硬同步的工作过程示意如图 5-2-14 所示。
重新同步发生在 CAN 通信的过程当中。当接收单元检测到“隐性电平”到“显性电平”的
跳变未落在自身信号时序的 SS 内部时,将启动重新同步。重新同步的目的是确保采样点位置的
准确性,从而确保采样数据的正确性。
在实际应用中,若收发单元的信号相位不同步,则接收单元需要进行重新同步。第一种情形
是发送单元的信号相位落后于接收单元,具体示例如图 5-2-15 所示。第二种情形是接收单元的
信号相位落后于发送单元,具体示例如图 5-2-16 所示。
从图 5-2-15 可以看到,发送单元的信号相位落后于接收单元。即接收单元当前数据位的
SS 产生后,经过 2 个 T q 时长之后,发送单元当前数据位的 SS 才产生。在这种情形下,接收单
元将 PBS1 延伸 2 个 T q 时间片长度,以确保收发单元的采样点同步。
从图 5-2-16 可以看到,接收单元的信号相位落后于发送单元。即发送单元当前数据位
的 SS 产生后,经过 2 个 T q 时长之后,接收单元当前数据位的 SS 才产生。在这种情形下,
接收单元将 PBS2 缩短 2 个 T q 时间片长度,以确保它的下一个数据位的采样点可以与发送
单元同步。
2.STM32F4 系列微控制器的 CAN 控制器介绍
STM32F4 系列微控制器内部集成了 CAN 控制器 — bxCAN(Basic Extended CAN)。
(1)bxCAN 的主要特性
bxCAN 支持 CAN 技术规范 2.0A 和 2.0B,通信比特率高达 1 Mbit/s,支持时间触发通信
方案。
在数据发送方面的特性:bxCAN 含 3 个发送邮箱,其发送优先级可配置,帧起始段支持发
送时间戳。
在数据接收方面的特性:bxCAN 含两个具有三级深度的接收 FIFO,其上溢参数可配置,并
具有可调整的筛选器组,帧起始段支持接收时间戳。
(2)bxCAN 的工作模式与测试模式
bxCAN 有 3 种主要的工作模式:初始化模式、正常模式和睡眠模式。硬件复位后,bxCAN
进入睡眠模式以降低功耗。当硬件处于初始化模式时,可以进行软件初始化操作。一旦初始化完
成,软件必须向硬件请求进入正常模式,这样才能在 CAN 总线上进行同步,并开始接收和发送
数据。
同时为了方便用户调试,bxCAN 提供了 3 种测试模式,包括静默模式、环回模式、环回
与静默组合模式。用户通过配置位时序寄存器(CAN_BTR)的“SILM”与“LBKM”位段可
以控制 bxCAN 在正常模式与 3 种测试模式之间进行切换。各种模式的工作示意如图 5-2-17
所示。
正常模式:可正常地向 CAN 总线发送数据或从总线上接收数据。
静默模式:只能向 CAN 总线发送数据 1(隐性电平),不能发送数据 0(显性电平),但可
以正常地从总线上接收数据。由于这种模式发送的隐性电平不会影响总线的电平状态,故称之为
静默模式。
环回模式:向 CAN 总线发送的所有数据同时会直接传到接收端,但无法接收总线上的任何
数据。这种模式一般用于自检。
环回与静默组合模式:这种模式是静默模式与环回模式的组合,同时具有两种模式的特点。
(3)bxCAN 的组成
bxCAN 有两组 CAN 控制器:CAN1(主)和 CAN2(从),它的组成框图如图 5-2-18
所示。
下面分别对 bxCAN 各个组成部分进行介绍。
① CAN 控制核心
CAN 控制核心包括 CAN 2.0B 活动内核与各种控制寄存器、状态寄存器和配置寄存器,应
用程序使用这些寄存器可完成以下操作:
配置 CAN 参数,如波特率等;
请求发送;
处理接收;
管理中断;
获取诊断信息。
在所有的 CAN 控制核心寄存器中,CAN 主控制寄存器(CAN Master Control Register,
CAN_MCR)与 CAN 位时序寄存器(CAN Bit Timing Register,CAN_BTR)是比较重要的两个
寄存器,接下来分别对它们进行介绍。
从图 5-2-19 中可以看到,CAN_MCR 负责 bxCAN 的工作模式的配置,它主要完成以下功
能配置。
调试冻结(DBF):此位配置调试期间 CAN 处于工作状态或接收/发送冻结状态。
时间触发通信模式(T TCM):此位配置使能或禁止时间触发通信模式。
自动总线关闭管理(ABOM):此位控制 CAN 硬件在退出总线关闭状态时的行为。
自动唤醒模式(AWUM):此位控制 CAN 硬件在睡眠模式下接收到消息的行为。
禁止自动重发送(NART):此位控制 CAN 硬件是否自动重发送消息。
接收 FIFO 锁定模式(RFLM):此位配置接收 FIFO 上溢后是否锁定。
发送 FIFO 优先级(TXFP):此位用于控制在几个邮箱同时挂起时的发送顺序。
CAN_BTR 各位段的定义如图 5-2-20 所示。
CAN_BTR 主要负责两个功能的配置:一是正常模式与各测试模式之间的切换,由“SILM”
与“LBKM”位控制;二是位时序与波特率的配置,这项功能由“SJW[1:0]”“TS2[2:0]”“TS1[3:0]”
和“BRP[9:0]”等位段共同完成。
bxCAN 定义的位时序与 CAN 技术规范定义的位时序有所区别:前者由 3 段构成
(SYNC_SEG、BS1、BS2),后者由 4 段构成(SS、PTS、PBS1、PBS2)。bxCAN 定义的位时
序构成示意如图 5-2-21 所示。
从图 5-2-21 中可以看到,bxCAN 定义的位时序由 3 段构成。
同步段(SYNC_SEG):对应 CAN 技术规范位时序的同步段(SS),时间长度固定为 1 T q。
位段1(BS1):对应CAN技术规范位时序的传播时间段(PTS)和相位缓冲段1(PBS1),
其时间长度为1~16 T q,可自动加长以补偿不同网络节点的频率差异所导致的正相位漂移。
位段 2(BS2):对应 CAN 标准位时序的相位缓冲段 2(PBS2),其时间长度为 1~8 T q,
可自动缩短以补偿负相位漂移。
bxCAN 的波特率配置步骤与计算过程如下。
计算 BS1 的时间长度: T BS1 = T q×(TS1[3:0] + 1)。
计算 BS2 的时间长度: T BS2 = T q×(TS2[2:0] + 1)。
计算一个数据位的时间长度: T 1bit = T q + T BS1 + T BS2 = N × T q。
计算时间片长度: T q = T PCLK ×(BRP[9:0] + 1)。
计算 bxCAN 的波特率:BaudRate =
1
×q N T
( N = 1 + TS1[3:0] + TS2[2:0])。
注:PCLK 为 APB1 总线的时钟频率,对于 STM32F407 微控制器来说,其值默认为 42 MHz。
表 5-2-4 以一个具体的 bxCAN 波特率配置实例(波特率配置为 512 kbit/s)解析了上述配
置步骤与计算过程。
② CAN 发送邮箱
bxCAN 有3 个发送邮箱,可缓存3 个待发送的报文,并由发送调度程序决定先发送哪个邮箱的。
每个发送邮箱都包含 4 个与数据发送功能相关的寄存器,它们的具体名称与功能如下所示。
标识符寄存器(CAN_TIxR):用于存储待发送报文的标准 ID、扩展 ID 等信息。
数据长度控制寄存器(CAN_TDTxR):用于存储待发送报文的 DLC 段信息。
低位数据寄存器(CAN_TDLxR):用于存储待发送报文数据段的低 4 个字节内容。
高位数据寄存器(CAN_TDHxR):用于存储待发送报文数据段的高 4 个字节内容。
用户使用 STM32F4 标准外设库编写 bxCAN 数据发送函数时,先将报文的各段内容分离出
出,然后分别存入相应的寄存器中,最后使能发送即可将数据通过 CAN 总线发送出去。
③ CAN 接收 FIFO
bxCAN 有两个接收FIFO,分别具有3 级深度。即每个FIFO 中有3 个接收邮箱,共可缓存6 个
接收到的报文。为了减少 CPU 负载、简化软件设计并保证数据的一致性,FIFO 完全由硬件进行管
理。接收到报文时,FIFO 的报文计数器自增。反之,FIFO 中缓存的数据被取走后,报文计数器自减。
应用程序通过查询CAN 接收FIFO 寄存器(CAN_RFxR)可以获知当前FIFO 中挂起的消息数。
根据 CAN 主控制寄存器(CAN_MCR)的相关介绍,用户配置该寄存器的“RFLM”位可以
控制接收 FIFO 上溢后是否锁定。FIFO 工作在锁定模式时,溢出后会丢弃新报文。反之,在非锁
定模式下,FIFO 溢出后新报文将覆盖旧报文。
与发送邮箱类似,每个接收 FIFO 也包含 4 个与数据接收功能相关的寄存器,它们的具体名
称和功能如下所示。
标识符寄存器(CAN_RIxR):用于存储接收报文的标准 ID、扩展 ID 等信息。
数据长度控制寄存器(CAN_RDTxR):用于存储接收报文的 DLC 段信息。
低位数据寄存器(CAN_RDLxR):用于存储接收报文数据段的低 4 个字节内容。
高位数据寄存器(CAN_RDHxR):用于存储接收报文数据段的高 4 个字节内容。
④ 筛选器
根据 CAN 技术规范,报文消息的标识符与节点地址无关,它是消息内容的一部分。在 CAN 总
线上,发送单元将消息广播给所有接收单元,接收单元根据标识符的值来判断是否需要该消息。若需
要,则存储该消息;反之,则丢弃该消息。接收单元方面的整个流程应在无软件干预的情况下完成。
为了满足这一要求,STM32F4 系列微控制器的 bxCAN 为应用程序提供了 28 个可配置、可
调整的硬件筛选器组(编号 0~27)。进而节省软件筛选所需的 CPU 资源。每个筛选器组包含两
个 32bit 寄存器,分别是 CAN_FxR0 和 CAN_FxR1。
筛选器参数配置涉及的寄存器有:CAN 筛选器主寄存器(CAN_FMR)、模式寄存器
(CAN_FM1R)、尺度寄存器(CAN_FS1R)、FIFO 分配寄存器(CAN_FFA1R)和激活寄存器
(CAN_FA1R)。在使用过程中,需要对筛选器作以下配置。
一是配置筛选器的模式(Filter Mode)。用户通过配置模式寄存器(CAN_FM1R)可将筛选
器配置成“标识符掩码”模式或“标识符列表”模式。
标识符掩码模式将允许接收的报文标识符的某几位作为掩码。筛选时,只须将掩码与待收报
文的标识符中相应的位进行比较,若相同则筛选器接收该报文。标识符掩码模式也可以理解成“关
键字搜索”。
标识符列表模式将所有允许接收的报文标识符制作成一个列表。筛选时,如果待收报文的标识
符与列表中的某一项完全相同,则筛选器接收该报文。标识符列表模式也可以理解成“白名单管理”。
二是配置筛选器的尺度(Filter Scale Configuration)。用户通过配置尺度寄存器(CAN_FS1R)
可将筛选器尺度配置为“双 16 位”或“单 32 位”。
三是配置筛选器的 FIFO 关联情况(FIFO Assignment for Filterx)。用户通过配置 FIFO 分配
寄存器(CAN_FFA1R)可将筛选器与“FIFO0”或 “FIFO1”相关联。
不同的筛选器模式与尺度的组合构成了筛选器的 4 种工作状态,如图 5-2-22 所示。
表 5-2-5 对图 5-2-22 中筛选器的 4 种工作状态进行了说明。
根据 ISO 11898 标准定义,标准 ID 的长度为 11bit,扩展 ID 的长度为 29bit,因此筛选器的
16bit 尺度只能适用于标准 ID 的筛选,32bit 尺度则可适用于标准 ID 或扩展 ID 的筛选。表 5-2-6
对标识符列表和标识符掩码模式的优缺点及其适用场景进行了分析。
3.STM32F4 标准外设库中的 CAN 相关内容介绍
通过前面的学习,我们了解了 STM32F4 系列微控制器的 bxCAN 组成架构及其在编程应用
中需要配置的参数。bxCAN 的功能丰富且其在使用过程中所涉及的寄存器繁多,因此为了减少
工作人员的编程工作量,STM32F4 标准外设库提供了 CAN 初始化结构体及其配置函数、CAN
发送结构体、CAN 接收结构体与 CAN 筛选器模式配置结构体,用于 bxCAN 工作参数的配置,
并配套了一系列 CAN 数据收发功能函数。上述结构体与函数的声明定义位于库文件
“stm32f4xx_can.c”和“stm32f4xx_can.h”中,接下来分别对它们进行介绍。
(1)CAN 初始化结构体
CAN 初始化结构体用于配置 bxCAN 的工作模式、工作参数与通信波特率,该结构体所配置
的参数经 CAN_Init()函数部署后生效,其原型定义如下:
- typedef struct {
- uint16_t CAN_Prescaler; /* 配置 CAN 时钟分频系数,范围 1~1024*/
- uint8_t CAN_Mode; /* 配置 CAN 的工作模式,正常模式或测试模式 */
- uint8_t CAN_SJW; /* 配置 SJW 最大值 */
- uint8_t CAN_BS1; /* 配置 BS1 段的时间长度 */
- uint8_t CAN_BS2; /* 配置 BS2 段的时间长度 */
- FunctionalState CAN_TTCM; /* 是否使能 TTCM 时间触发功能 */
- FunctionalState CAN_ABOM; /* 是否使能 ABOM 自动离线管理功能 */
- FunctionalState CAN_AWUM; /* 是否使能 AWUM 自动唤醒功能 */
- FunctionalState CAN_NART; /* 是否使能 NART 自动重传功能 */
- FunctionalState CAN_RFLM; /* 是否使能 RFLM 锁定 FIFO 功能 */
- FunctionalState CAN_TXFP; /* 配置 TXFP 报文优先级的判定方法 */
- } CAN_InitTypeDef;
对各结构体成员变量的作用介绍如下。
① CAN_Prescaler
bxCAN 的时钟分频系数配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 BRP[9:0]
位段的值。CAN_Prescaler 值的变化直接影响时间片长度 T q,其配置范围为 1~1024。
② CAN_Mode
bxCAN 工作模式配置,它实际配置 CAN 位时序寄存器(CAN_BTR)的 SILM 位和 LBKM
位的值。用户配置 CAN_Mode 为不同的值可将 bxCAN 置为正常模式或 3 种测试模式中的一种。
CAN_Mode 可供配置的选项如下:
正常模式(CAN_Mode_Normal);
环回模式(CAN_Mode_LoopBack);
静默模式(CAN_Mode_Silent);
环回与静默组合模式(CAN_Mode_Silent_LoopBack)。
③ CAN_SJW
重新同步跳转宽度配置,它实际配置 CAN 位时序寄存器(CAN_BTR)的 SJW[1:0]位段的
值,范围 1~4 T q,可供配置的选项如下:
1 个时间片长度(CAN_SJW_1tq);
2 个时间片长度(CAN_SJW_2tq);
3 个时间片长度(CAN_SJW_3tq);
4 个时间片长度(CAN_SJW_4tq)。
④ CAN_BS1
CAN 位时序中 BS1 的时间长度配置,它实际配置 CAN 位时序寄存器(CAN_BTR)的
TS1[3:0]位段的值,范围 1~16 T q。如配置选项“CAN_BS1_4tq”表示将 BS1 的时间长度配置
为 4 T q。
⑤ CAN_BS2
CAN 位时序中 BS2 的时间长度配置,它实际配置 CAN 位时序寄存器(CAN_BTR)的
TS2[2:0]位段的值,范围 1~8 T q。如配置选项“CAN_BS2_2tq”表示将 BS2 的时间长度配置
为 2 T q。
⑥ CAN_TTCM
是否使能时间触发功能配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 TTCM 位的
值,可供配置的选项有 ENABLE(使能)和 DISABLE(禁用)。
⑦ CAN_ABOM
是否使能自动离线管理功能配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 ABOM
位的值。自动离线管理功能被使能后,CAN 收发单元可以在出错离线后适时自动恢复。
CAN_ABOM 可供配置的选项有 ENABLE 和 DISABLE。
⑧ CAN_AWUM
是否使能自动唤醒功能配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 AWUM 位的
值。自动唤醒功能被使能后,CAN 接收单元会在检测到总线活动后自动唤醒。CAN_AWUM 可
供配置的选项有 ENABLE 和 DISABLE。
⑨ CAN_NART
是否使能自动重传功能配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 NART 位的
值。自动重传功能被使能后,CAN 发送单元在报文发送失败后会自动重传,直至发送成功为止。
CAN_NART 可供配置的选项有 ENABLE 和 DISABLE。
⑩ CAN_RFLM
是否使能锁定接收 FIFO 功能配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 RFLM
位的值。锁定接收 FIFO 功能被使能后,CAN 接收单元在 FIFO 发生溢出时会丢弃新数据。反之,
FIFO 发生溢出时新数据将会覆盖旧数据。CAN_RFLM 可供配置的选项有 ENABLE 和 DISABLE。
CAN_TXFP
发送报文的优先级判定方法配置,它实际配置 CAN 主控制寄存器(CAN_MCR)的 TXFP
位的值。CAN_TXFP 可供配置的选项有 ENABLE 和 DISABLE。
当 CAN_TXFP 被配置为 ENABLE 时,CAN 总线根据报文存入发送邮箱的先后顺序依次发送
报文。当 CAN_TXFP 被配置为 DISABLE 时,CAN 总线以报文的标识符为仲裁依据决定报文的发
送顺序。
(2)CAN 发送与接收结构体
在 bxCAN 组成框图的介绍内容中指出:bxCAN 使用发送邮箱与接收邮箱进行收发数据的缓
存,通信帧的各段内容分别存放在收发邮箱相应的寄存器中。
STM32F4 标准外设库为 CAN 报文收发提供了 CAN 发送结构体与 CAN 接收结构体,结构
体成员变量的命名完全参照收发邮箱中相应的寄存器名,使用者可以见名知意。这两个结构体的
成员变量相似,它们的原型定义如下:
- /**
- * @brief CAN Tx message structure definition
- * CAN 发送结构体定义
- */
- typedef struct {
- uint32_t StdId; /* 存储报文的标准标识符 11 bit , 0~0x7FF*/
- uint32_t ExtId; /* 存储报文的扩展标识符 29 bit , 0~0x1FFFFFFF*/
- uint8_t IDE; /* 存储 IDE 扩展标志 */
- uint8_t RTR; /* 存储 RTR 远程帧标志 */
- uint8_t DLC; /* 存储报文数据段的长度, 0~8B*/
- uint8_t Data[8]; /* 存储报文数据段的内容 */
- } CanTxMsg;
- /**
- * @brief CAN Rx message structure definition
- * CAN 接收结构体定义
- */
- typedef struct {
- uint32_t StdId; /* 存储报文的标准标识符 11 bit , 0~0x7FF*/
- uint32_t ExtId; /* 存储报文的扩展标识符 29 bit , 0~0x1FFFFFFF*/
- uint8_t IDE; /* 存储 IDE 扩展标志 */
- uint8_t RTR; /* 存储 RTR 远程帧标志 */
- uint8_t DLC; /* 存储报文数据段的长度, 0~8B*/
- uint8_t Data[8]; /* 存储报文数据段的内容 */
- uint8_t FMI; /* 报文所经由的筛选器编号 */
- } CanTxMsg;
对各结构体成员变量的作用介绍如下。
① StdId
存储报文的 11bit 标准标识符,范围 0~0x7FF。
② ExtId
存储报文的 29bit 扩展标识符,范围 0~0x1FFFFFFF。
③ IDE
标识符扩展标志位配置,它用于标示邮箱中存放报文的标识符的类型,可供配置的选项
如下:
标准标识符(CAN_Id_Standard);
扩展标识符(CAN_Id_Extended)。
④ RTR
报文类型标志位配置,它用于标示本报文是数据帧还是遥控帧,可供配置的选项如下:
数据帧(CAN_RTR_Data);
遥控帧(CAN_RTR_Remote)。
⑤ DLC
数据帧中数据段长度值的配置,值范围是 0~8 字节。若报文为遥控帧,则 DLC 应配置
为 0。
⑥ Data[8]
存储数据帧中数据段的内容。
⑦ FMI
FMI 用于存储筛选器的编号,只在接收结构体中定义,它标示了报文是经由哪个筛选器的筛
选而存入接收 FIFO 中的。
(3)CAN 筛选器结构体
通过对筛选器相关内容的学习,我们知道了不同的筛选器模式和尺度可以组合成 4 种不同的
工作状态。STM32F4 标准外设库提供了筛选器结构体,开发者利用该结构体可方便地配置筛选
器的工作状态,该结构体的原型定义如下:
- typedef struct {
- uint16_t CAN_FilterIdHigh; /*CAN_FxR1 寄存器的高 16 位 */
- uint16_t CAN_FilterIdLow; /*CAN_FxR1 寄存器的低 16 位 */
- uint16_t CAN_FilterMaskIdHigh; /*CAN_FxR2 寄存器的高 16 位 */
- uint16_t CAN_FilterMaskIdLow; /*CAN_FxR2 寄存器的低 16 位 */
- uint16_t CAN_FilterFIFOAssignment; /* 配置经过筛选后数据存储到哪个接收 FIFO*/
- uint8_t CAN_FilterNumber; /* 筛选器编号,范围 0~27*/
- uint8_t CAN_FilterMode; /* 筛选器模式 */
- uint8_t CAN_FilterScale; /* 设置筛选器的尺度 */
- FunctionalState CAN_FilterActivation;/* 是否使能本筛选器 */
- } CAN_FilterInitTypeDef;
对各结构体成员变量的作用介绍如下。
① CAN_FilterIdHigh
CAN_FilterIdHigh 成员变量的长度为 16bit,用于存放待筛选的标识符,它存放的内容根据
筛选器尺度配置的不同而变化。如果筛选器尺度被配置为 32bit,则 CAN_FilterIdHigh 存放待筛
选标识符的高 16bit;如果筛选器尺度被配置为 16bit,则 CAN_FilterIdHigh 存放完整的 16bit 待
筛选标识符。
② CAN_FilterIdLow
CAN_FilterIdLow 成员变量的长度为 16bit,用于存放待筛选的标识符,它存放的内容根据
筛选器尺度配置的不同而变化。如果筛选器尺度被配置为 32bit,则 CAN_FilterIdLow 存放待
筛选标识符的低 16 位;如果筛选器尺度被配置为 16bit,则 CAN_FilterIdLow 存放完整的 16bit
待筛选标识符。
③ CAN_FilterMaskIdHigh
CAN_FilterMaskIdHigh 成员变量的长度为 16bit,它存放的内容根据筛选器模式配置的不同
而变化。如果筛选器模式被配置为“标识符列表”模式,则 CAN_FilterMaskIdHigh 的功能与
CAN_FilterIdHigh 相同;如果筛选器模式被配置为“标识符掩码”模式,则 CAN_FilterMaskIdHigh
用于存放 CAN_FilterIdHigh 对应的掩码。
④ CAN_FilterMaskIdLow
CAN_FilterMaskIdLow 成员变量的长度为 16bit,它存放的内容根据筛选器模式配置的不同
而变化。如果筛选器模式被配置为“标识符列表”模式,则 CAN_FilterMaskIdLow 的功能与
CAN_FilterIdLow 相同;如果筛选器模式被配置为“标识符掩码”模式,则 CAN_FilterMaskIdLow
用于存放 CAN_FilterIdLow 对应的掩码。
⑤ CAN_FilterFIFOAssignment
报文经由筛选器的筛选后存入的接收 FIFO 的编号配置,可供配置的选项如下:
筛选器被关联到 FIFO0(CAN_Filter_FIFO0);
筛选器被关联到 FIFO1(CAN_Filter_FIFO1)。
⑥ CAN_FilterNumber
筛选器的编号配置,可选范围为 0~27。
⑦ CAN_FilterMode
筛选器模式配置,可供配置的选项如下:
标识符列表模式(CAN_FilterMode_IdList);
标识符掩码模式(CAN_FilterMode_IdMask)。
⑧ CAN_FilterScale
筛选器尺度配置,可供配置的选项如下:
筛选器工作在 32bit 长度模式(CAN_FilterScale_32bit);
筛选器工作在 16bit 长度模式(CAN_FilterScale_16bit)。
⑨ CAN_FilterActivation
该成员用于配置激活(ENABLE)或禁用(DISABLE)筛选器。
(4)CAN 报文收发相关函数
表 5-2-7 列出了 STM32F4 标准外设库提供的与 CAN 报文收发相关的函数。
5.2.3 任务实施
1.硬件连接
将两个(或两个以上)STM32F4 系列微控制器通过 CAN 通信接口连接起来,接线图可参考
图 5-2-1。
由于试验环境通信距离较近,我们可采用高速 CAN 总线的闭环网络,在总线两端各接一个
120Ω的电阻,最后将各开发板的 CAN 接线端子 CANH 与 CANL 分别相连。
2.初始化 CAN 通信相关的 GPIO 引脚
复制一份任务 2.3 的工程,重命名为“task5.2_CAN”,在“HARDWARE”文件夹下新建名
为“CAN”的子文件夹,新建“bsp_can.c”和“bsp_can.h”两个文件,将它们加入工程中,
并配置头文件包含路径。在“bsp_can.c”文件中编写 bxCAN 相关 GPIO 引脚的初始化程序,
在“bsp_can.h”文件中完成 bxCAN 相关 GPIO 引脚的宏定义和函数声明。
首先在“bsp_can.h”文件中输入以下代码:
- #ifndef __BSP_CAN_H
- #define __BSP_CAN_H
- #include "sys.h"
- #define CANx CAN1
- #define CAN_CLK RCC_APB1Periph_CAN1
- #define CAN_RX_PIN GPIO_Pin_11
- #define CAN_TX_PIN GPIO_Pin_12
- #define CAN_TX_GPIO_PORT GPIOA
- #define CAN_RX_GPIO_PORT GPIOA
- #define CAN_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
- #define CAN_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
- #define CAN_AF_PORT GPIO_AF_CAN1
- #define CAN_RX_SOURCE GPIO_PinSource11
- #define CAN_TX_SOURCE GPIO_PinSource12
- void CAN_GPIO_Config(void);
- void CAN_NVIC_Config(void);
- void CAN_Mode_Config(uint8_t canMode);
- void CAN_Filter_Config_Scale32_IdList(uint32_t StdId, uint32_t ExtId);
- void CAN_Filter_Config_Scale32_IdMask(uint32_t *ExtIdArray, uint8_t extLen);
- void CAN_Filter_Config_Scale16_IdMask(uint32_t *StdIdArray, uint8_t stdLen);
- void CAN_SetMsg(CanTxMsg *TxMessage, uint32_t StdID, uint32_t ExtID, uint8_t IDE);
- void Init_RxMes(CanRxMsg *RxMessage);
- #endif
然后在“bsp_can.c”文件中输入以下代码:
- #include "bsp_can.h"
- /**
- * @brief CAN 通信相关 GPIO 引脚初始化
- * @param None
- * @retval None
- */
- void CAN_GPIO_Config(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- /* 使能 GPIO 时钟 */
- RCC_AHB1PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE);
- /* 将 CAN1 收发引脚映射至 PA11 和 PA12 */
- GPIO_PinAFConfig(CAN_TX_GPIO_PORT, CAN_RX_SOURCE, CAN_AF_PORT);
- GPIO_PinAFConfig(CAN_RX_GPIO_PORT, CAN_TX_SOURCE, CAN_AF_PORT);
- /* 配置 CAN 发送引脚 */
- GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_Init(CAN_TX_GPIO_PORT, &GPIO_InitStructure);
- /* 配置 CAN 接收引脚 */
- GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);
- }
3.配置 CAN 工作模式、位时序以及波特率
继续在“bsp_can.c”文件中输入以下代码:
- /**
- * @brief CAN 工作模式与波特率配置
- * @param canMode: bxCAN 工作模式 (CAN_Mode_LoopBack|CAN_Mode_Normal 等 )
- * @retval None
- */
- void CAN_Mode_Config(uint8_t canMode)
- {
- CAN_InitTypeDef CAN_InitStructure;
- /* 使能 CAN 时钟 */
- RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);
- /* CAN 寄存器初始化 */
- CAN_DeInit(CANx);
- CAN_StructInit(&CAN_InitStructure);
- /* CAN 工作模式配置 */
- CAN_InitStructure.CAN_TTCM = DISABLE; // 关闭时间触发通信模式使能
- CAN_InitStructure.CAN_ABOM = DISABLE; // 自动离线管理
- CAN_InitStructure.CAN_AWUM = DISABLE; // 不使用自动唤醒模式
- CAN_InitStructure.CAN_NART = ENABLE; // 报文自动重传
- CAN_InitStructure.CAN_RFLM = DISABLE; // 接收 FIFO 不锁定
- CAN_InitStructure.CAN_TXFP = DISABLE; // 发送优先级取决于报文标识符
- CAN_InitStructure.CAN_Prescaler = 6; //bxCAN 时钟分频系数为 6
- CAN_InitStructure.CAN_Mode = canMode; // 配置 bxCAN 工作模式
- CAN_InitStructure.CAN_SJW = CAN_SJW_2tq; //SJW 设为 2 个时间单元
- CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq; //BS1 时间长度 9Tq
- CAN_InitStructure.CAN_BS2 = CAN_BS2_4tq; //BS2 时间长度 4Tq
- CAN_Init(CANx, &CAN_InitStructure); // 配置生效
4.配置筛选器的工作方式
表 5-2-6 讨论了筛选器工作在“标识符列表”和“标识符掩码”模式的优缺点及其适用的
场景。在实际的应用开发中,用户应根据待筛选标识符的类型与数量选择合适的筛选器工作方式。
表 5-2-8 列举了几个常见的应用场景。
接下来以表 5-2-8 中前 3 个应用场景为例,分别讲解对应的筛选器工作方式的配置过程。
(1)筛选一个标准 ID 和一个扩展 ID
在这个应用场景里,待筛选的标识符少,同时包含扩展 ID,因此应将筛选器配置成“32bit
标识符列表”工作方式。在“bsp_can.c”文件中编写如下所示的配置函数
- /**
- * @brief CAN 筛选器配置 (32bit 标识符列表 )
- * @param StdId: 标准 ID , ExtId: 扩展 ID
- * @retval None
- */
- void CAN_Filter_Config_Scale32_IdList(uint32_t StdId, uint32_t ExtId)
- {
- CAN_FilterInitTypeDef CAN_FilterInitStructure;
- CAN_FilterInitStructure.CAN_FilterNumber = 0;// 配置筛选器组 0
- /* 筛选器工作模式 : 标识符列表 */
- CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;
- /* 筛选器尺度为 32bit */
- CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
- /* 待筛选的标准 ID 高位 */
- CAN_FilterInitStructure.CAN_FilterIdHigh = StdId << 5;
- /* 待筛选的标准 ID 低位 */
- CAN_FilterInitStructure.CAN_FilterIdLow = CAN_Id_Standard | CAN_RTR_Data;
- /* 筛选器组 0 被关联到 FIFO0 */
- CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
- CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;// 使能筛选器
- CAN_FilterInit(&CAN_FilterInitStructure);// 配置生效
- CAN_FilterInitStructure.CAN_FilterNumber = 1;// 配置筛选器组 1
- CAN_FilterInitStructure.CAN_FilterMaskIdHigh = ((ExtId << 3) >> 16) & \
- 0xffff;
- CAN_FilterInitStructure.CAN_FilterMaskIdLow = ((ExtId << 3) & 0xffff) \
- | CAN_Id_Extended | CAN_RTR_Data;
- CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO1;
- CAN_FilterInit(&CAN_FilterInitStructure);// 配置生效
在上述代码片段中,使用筛选器组 0 对标准 ID 进行筛选,并与 FIFO0 接收缓存进行关联;
使用筛选器组 1 对扩展 ID 进行筛选,并与 FIFO1 接收缓存进行关联。
(2)筛选一组扩展 ID
在这个应用场景里,待筛选的标识符的长度为 29bit,且数量较多,因此应将筛选器配置成
“32bit 标识符掩码”的工作方式。
掩码的计算是“标识符掩码”模式的筛选器配置过程中关键的一步。如果想让一组扩展 ID
都能通过筛选器,则应将这一组 ID 中数字相同的位设置为关注项(对应的掩码位为 1),其他 位
设置为非关注项(对应的掩码位为 0)。具体的实现过程如下。
将扩展 ID 组中的所有成员进行两两“同或”运算,得到的最终运算结果就是相应的掩码。
在“bsp_can.c”文件中编写如下所示的配置函数。
- /**
- * @brief CAN 筛选器配置 (32bit 标识符掩码 )
- * @param ExtIdArray: 扩展 ID 数组, extLen: 扩展 ID 数组长度
- * @retval None
- */
- void CAN_Filter_Config_Scale32_IdMask(uint32_t *ExtIdArray, uint8_t extLen)
- {
- CAN_FilterInitTypeDef CAN_FilterInitStructure;
- CAN_FilterInitStructure.CAN_FilterNumber = 2;// 配置筛选器组 2
- /* 筛选器工作模式 : 标识符掩码 */
- CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
- /* 筛选器尺度为 32bit */
- CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
- /* 计算扩展 ID 的掩码 */
- extMask = 0x1fffffff;
- for (i=0; i<extLen; i++)
- {
- tmp = ExtIdArray[i] ^ (~ExtIdArray[0]);
- extMask &= tmp;
- }
- CAN_FilterInitStructure.CAN_FilterIdHigh = ((ExtIdArray[0] << 3) >> 16) & \
- 0xffff;// 扩展 ID 高 16 位
- CAN_FilterInitStructure.CAN_FilterIdLow = ((ExtIdArray[0] << 3) & 0xffff) \
- | CAN_Id_Extended | CAN_RTR_Data;
- CAN_FilterInitStructure.CAN_FilterMaskIdHigh = ((extMask << 3) >> 16) & \
- 0xffff;// 掩码高 16 位
- CAN_FilterInitStructure.CAN_FilterMaskIdLow = ((extMask << 3) & 0xffff) \
- | 0x06;// 掩码低 16 位
- /* 筛选器组 2 被关联到 FIFO0 */
- CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
- CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;// 使能筛选器
- CAN_FilterInit(&CAN_FilterInitStructure);// 配置生效
- }
在上述代码片段中,使用筛选器组 2 对一组扩展 ID 进行筛选,并与 FIFO0 接收缓存进行
关联。
(3)筛选一组标准 ID
在这个应用场景里,待筛选的标识符的长度为 11bit,且数量较多,因此将筛选器配置成“16bit
标识符掩码”或“32bit 标识符掩码”工作方式,本任务选取筛选器的“16bit 标识符掩码”工作
方式。在“bsp_can.c”文件中编写如下所示的配置函数。
- /**
- * @brief CAN 筛选器配置 (16bit 标识符掩码 )
- * @param StdIdArray: 标准 ID 数组, stdLen: 标准 ID 数组长度
- * @retval None
- */
- void CAN_Filter_Config_Scale16_IdMask(uint32_t *StdIdArray, uint8_t stdLen)
- {
- CAN_FilterInitTypeDef CAN_FilterInitStructure;
- CAN_FilterInitStructure.CAN_FilterNumber = 3;// 配置筛选器组 3
- /* 筛选器工作模式 : 标识符掩码 */
- CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
- /* 筛选器尺度为 16bit */
- CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;
- /* 计算标准 ID 的掩码 */
- stdMask = 0x7ff;
- for (i=0; i<stdLen; i++)
- {
- tmp = StdIdArray[i] ^ (~StdIdArray[0]);
- stdMask &= tmp;
- }
- /* 配置标准 ID */
- CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
- CAN_FilterInitStructure.CAN_FilterIdLow = StdIdArray[0] << 5 | \
- ((CAN_Id_Standard | CAN_RTR_Data) << 3);
- /* 配置标准 ID 掩码 */
- CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xffff;
- CAN_FilterInitStructure.CAN_FilterMaskIdLow = (stdMask << 5) | (0x03 << 3);
- /* 将筛选器组 3 被关联到 FIFO0 */
- CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
- CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;// 使能筛选器
- CAN_FilterInit(&CAN_FilterInitStructure);// 配置生效
- }
在上述代码片段中,使用筛选器组 3 对 1 组标准 ID 进行筛选,并与 FIFO0 接收缓存进行
关联。
5.编写报文收发结构体的填充程序
STM32F4 标准外设库提供了 CAN 通信报文发送(CanTxMsg)结构体类型与 CAN 通信报
文接收(CanRxMsg)结构体类型。发送报文前,用户应先对 CanTxMsg 结构体的各成员变量
进行赋值,然后调用 CAN_Transmit()函数将报文发送出去。接收数据前,可先将 CanRxMsg 结
构体的各成员变量进行初始化,然后调用 CAN_Receive()函数将接收到的数据存入其中。在
“bsp_can.c”文件中输入以下代码
- /**
- * @brief 初始化 RxMessage 数据结构体
- * @param RxMessage: 指向要初始化的数据结构体
- * @retval None
- */
- void Init_RxMes(CanRxMsg *RxMessage)
- {
- uint8_t ubCounter = 0;
- /* 把接收结构体清零 */
- RxMessage->StdId = 0x00;
- RxMessage->ExtId = 0x00;
- RxMessage->IDE = CAN_ID_STD;
- RxMessage->DLC = 0;
- RxMessage->FMI = 0;
- for (ubCounter = 0; ubCounter < 8; ubCounter++)
- {
- RxMessage->Data[ubCounter] = 0x00;
- }
- }
- /**
- * @brief 初始化 TxMessage 数据结构体
- * @param TxMessage: 指向要发送的数据结构体
- * @param ID: ID( 标准或扩展 )
- * @retval None
- */
- void CAN_SetMsg(CanTxMsg *TxMessage, uint32_t StdID, uint32_t ExtID, \
- uint8_t IDE)
- {
- int8_t ubCounter = 0;
- TxMessage->StdId = StdID;
- TxMessage->ExtId = ExtID;
- TxMessage->IDE = IDE;
- TxMessage->RTR = CAN_RTR_Data; // 设置为数据帧
- TxMessage->DLC = 8; // 数据长度为 8B
- /* 标准帧的数据段内容为 01234567 */
- if(IDE == CAN_Id_Standard)
- {
- for (ubCounter = 0; ubCounter < 8; ubCounter++)
- {
- TxMessage->Data[ubCounter] = ubCounter + 0x30;
- }
- }
- /* 扩展帧的数据段内容为 76543210 */
- else if(IDE == CAN_Id_Extended)
- {
- for (ubCounter = 7; ubCounter >= 0; ubCounter--)
- {
- TxMessage->Data[7-ubCounter] = ubCounter + 0x30;
- }
- }
- }
在上述代码片段中,Init_RxMes()函数用于在接收报文前清空报文接收结构体。CAN_SetMsg()
函数用于配置报文发送结构体,同时为了突显程序的演示效果,将标准帧与扩展帧的数据载荷初
始化为不同内容。
6.使能 CAN 接收中断,编写中断服务程序
在步骤 4(配置筛选器的工作方式)中,我们将筛选器组 0、2、3 与 FIFO0 接收缓存相
关联,筛选器组 1 与 FIFO1 接收缓存相关联。在配置 CAN 中断时,我们应同时使能 FIFO0
和 FIFO1 的消息挂起中断,并编写相应的中断服务程序。在“bsp_can.c”文件中输入以下
代码:
- /**
- * @brief 使能 CAN 接收中断,中断优先级配置
- * @param None
- * @retval None
- */
- void CAN_NVIC_Config(void)
- {
- CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);// 使能 FIFO0 消息挂起中断
- CAN_ITConfig(CANx, CAN_IT_FMP1, ENABLE);// 使能 FIFO1 消息挂起中断
- NVIC_InitTypeDef NVIC_InitStructure;
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- /* CAN1_FIFO0 接收中断优先级配置 */
- NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
- /* CAN1_FIFO1 接收中断优先级配置 */
- NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
- NVIC_Init(&NVIC_InitStructure);
- }
- /**
- * @brief CAN1 FIFO0 接收中断服务程序
- * @param None
- * @retval None
- */
- void CAN1_RX0_IRQHandler(void)
- {
- /* FIFO0 接收邮箱有消息挂起 */
- if(CAN_GetITStatus(CANx,CAN_IT_FMP0) != RESET)
- {
- /* 接收数据结构体初始化 */
- Init_RxMes(&RxBuf);
- /* 从邮箱中读出报文 */
- CAN_Receive(CANx, CAN_FIFO0, &RxBuf);
- /* 判断方式 :ID 值与 ID 类型,判断是否收到了单个标准 ID 的数据 */
- if(RxBuf.StdId == StandardID && RxBuf.IDE == CAN_Id_Standard \
- && RxBuf.DLC == 8)
- {
- dataType_flag = 0;
- }
- /* 判断方式 : 筛选器邮箱编号, 本例中 FMI=2 对应一组扩展 ID */
- if(RxBuf.FMI == 2)
- {
- dataType_flag = 2;
- }
- /* 判断方式 : 筛选器邮箱编号, 本例中 FMI=3 对应一组扩展 ID */
- else if(RxBuf.FMI == 3)
- {
- dataType_flag = 3;
- }
- /* 释放 FIFO0 邮箱 */
- CAN_FIFORelease(CANx, CAN_FIFO0);
- }
- }
- /**
- * @brief CAN1 FIFO1 接收中断服务程序
- * @param None
- * @retval None
- */
- void CAN1_RX1_IRQHandler(void)
- {
- /* FIFO1 接收邮箱有消息挂起 */
- if(CAN_GetITStatus(CANx,CAN_IT_FMP1) != RESET)
- {
- /* 接收数据结构体初始化 */
- Init_RxMes(&RxBuf);
- /* 从邮箱中读出报文 */
- CAN_Receive(CANx, CAN_FIFO1, &RxBuf);
- /* 判断方式 :ID 值与 ID 类型,判断是否收到了单个扩展 ID 的数据 */
- if(RxBuf.ExtId == ExtendedID && RxBuf.IDE == CAN_Id_Extended \
- && RxBuf.DLC == 8)
- {
- dataType_flag = 1;
- }
- /* 释放 FIFO1 邮箱 */
- CAN_FIFORelease(CANx, CAN_FIFO1);
- }
- }
上述代码片段的第 8、9 行分别使能了 FIFO0 和 FIFO1 消息挂起中断,当 FIFO0 邮箱接收
到消息且未处理时,便会触发 CAN_IT_FMP0 中断;同样地,FIFO1 邮箱接收到消息且未处理时,
也会触发 CAN_IT_FMP0 中断。
在 STM32F4 标准外设库中,bxCAN 的多个中断对应同一个中断入口,因此进入中断后须
调用 CAN_GetITStatus()函数以判断发生了哪种中断。具体的程序编写方式见上述代码片段第 35
行和第 71 行。
调用 CAN_Receive()函数从 FIFO 邮箱中读出报文后,将其存入 CanRxMsg 结构体成员变
量中(上述代码片段第 40 行和第 76 行)。
CanRxMsg 结构体包含多个成员变量,因此用户有多种方法可以判断收到的数据类型。上
述代码片段演示了两种接收数据类型的判别方法。
第一种方法的判别依据是 ID 值与 ID 类型(见上述代码片段第 43、79 行)。
第二种方法的判别依据是筛选器邮箱编号(FMI 值)(见上述代码片段第 49、54 行)。
7.编写测试程序,收发报文并校验
在“main.c”文件中输入以下代码:
- #include "sys.h"
- #include "delay.h"
- #include "usart.h"
- #include "led.h"
- #include "key.h"
- #include "exti.h"
- #include "bsp_can.h"
- uint8_t keyValue = 0;
- CanTxMsg TxBuf; //CAN 发送缓冲区
- CanRxMsg RxBuf; //CAN 接收缓冲区
- __IO int8_t dataType_flag = -1; // 指明接收数据类型
- uint32_t StandardID = 0x702, ExtendedID = 0x1801f003;
- uint32_t uwCounter = 0;
- uint8_t TransmitMailbox = 0;
- /* 定义一组标准 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};
- /* 函数声明 */
- void CAN_Debug_Info(CanRxMsg *RxMessage);
- int main(void)
- {
- uint8_t ExtIdArray_length = sizeof(ExtIdArray) / sizeof(ExtIdArray[0]);
- uint8_t StdIdArray_length = sizeof(StdIdArray) / sizeof(StdIdArray[0]);
- delay_init(168); // 延时函数初始化
- LED_Init(); //LED 端口初始化
- Key_Init(); // 按键端口初始化
- EXTIx_Init();
- USART1_Init(115200); // 串口 1 初始化
- CAN_GPIO_Config();
- CAN_Mode_Config(CAN_Mode_Normal); // 配置 bxCAN 工作模式
- /* 筛选一个标准 ID 和 一个扩展 ID */
- CAN_Filter_Config_Scale32_IdList(StandardID, ExtendedID);
- /* 筛选一组扩展 ID */
- CAN_Filter_Config_Scale32_IdMask(ExtIdArray, ExtIdArray_length);
- /* 筛选一组标准 ID */
- CAN_Filter_Config_Scale16_IdMask(StdIdArray, StdIdArray_length);
- CAN_NVIC_Config();
- while (1)
- {
- if (keyValue == KEY_D_PRESS) // “下键”按下
- {
- keyValue = 0;
- /* 配置数据发送结构体 */
- CAN_SetMsg(&TxBuf, StandardID, 0x00, CAN_Id_Standard);
- /* 数据发送 */
- TransmitMailbox = CAN_Transmit(CANx, &TxBuf);
- uwCounter = 0;
- /* 等待传输完成 */
- while ((CAN_TransmitStatus(CANx, TransmitMailbox) != CANTXOK) \
- && (uwCounter != 0xFFFF)) {
- uwCounter++;
- }
- }
- else if (keyValue == KEY_U_PRESS) // “上键”按下
- {
- keyValue = 0;
- /* 配置数据发送结构体 */
- CAN_SetMsg(&TxBuf, 0x00, ExtendedID, CAN_Id_Extended);
- /* 数据发送 */
- TransmitMailbox = CAN_Transmit(CANx, &TxBuf);
- uwCounter = 0;
- /* 等待传输完成 */
- while ((CAN_TransmitStatus(CANx, TransmitMailbox) != CANTXOK) \
- && (uwCounter != 0xFFFF)) {
- uwCounter++;
- }
- }
- else if (keyValue == KEY_L_PRESS) // “左键”按下
- {
- keyValue = 0;
- static int n = 0;
- /* 配置数据发送结构体 */
- CAN_SetMsg(&TxBuf, 0x00, ExtIdArray[n++], CAN_Id_Extended);
- if (n == 10)
- n = 0;
- /* 数据发送 */
- TransmitMailbox = CAN_Transmit(CANx, &TxBuf);
- uwCounter = 0;
- /* 等待传输完成 */
- while ((CAN_TransmitStatus(CANx, TransmitMailbox) != CANTXOK) \
- && (uwCounter != 0xFFFF)) {
- uwCounter++;
- }
- }
- else if (keyValue == KEY_R_PRESS) // “右键”按下
- {
- keyValue = 0;
- static int m = 0;
- /* 配置数据发送结构体 */
- CAN_SetMsg(&TxBuf, StdIdArray[m++], 0x00, CAN_Id_Standard);
- if (m == 10)
- m = 0;
- /* 数据发送 */
- TransmitMailbox = CAN_Transmit(CANx, &TxBuf);
- uwCounter = 0;
- /* 等待传输完成 */
- while ((CAN_TransmitStatus(CANx, TransmitMailbox) != CANTXOK) \
- && (uwCounter != 0xFFFF)) {
- uwCounter++;
- }
- }
- /* 判断接收到了什么类型的数据 */
- switch (dataType_flag)
- {
- case 0: // 单个标准 ID
- printf("Single StdId.\r\n");
- CAN_Debug_Info(&RxBuf);
- dataType_flag = -1;
- break;
- case 1: // 单个扩展 ID
- printf("Single ExtId.\r\n");
- CAN_Debug_Info(&RxBuf);
- dataType_flag = -1;
- break;
- case 2: // 一组扩展 ID
- printf("Array ExtId.\r\n");
- CAN_Debug_Info(&RxBuf);
- dataType_flag = -1;
- break;
- case 3: // 一组标准 ID
- printf("Array StdId.\r\n");
- CAN_Debug_Info(&RxBuf);
- dataType_flag = -1;
- break;
- default:
- break;
- }
- }
- }
- /**
- * @brief CAN 调试信息输出
- * @param RxMessage: 指向存放接收数据的结构体
- * @retval None
- */
- void CAN_Debug_Info(CanRxMsg *RxMessage)
- {
- printf("StdId: 0x%x\r\n", RxMessage->StdId);
- printf("ExtId: 0x%x\r\n", RxMessage->ExtId);
- printf("Filter Mailbox Index: %d\r\n", RxMessage->FMI);
- printf(" 收到的数据 : ");
- for (int i = 0; i < 8; i++)
- {
- printf("%c", RxMessage->Data[i]);
- }
- printf("\r\n************************************\r\n");
- }
8.观察试验现象
将接收单元与上位机通过串行通信口相连,然后打开上位机的串口调试助手,设置好波特率
等参数。按下发送单元电路板上的不同按键,可在串口调试助手中查看接收到的 CAN 报文帧信
息,具体如图 5-2-23 所示。
从图 5-2-23 中可以看到,不同 ID 的报文会经由不同的筛选器(邮箱编号 FMI 不同)筛选
后存入接收 FIFO,如单个标准 ID(0x702)对应的邮箱编号 FMI 为 0,一组扩展 ID(0x1900fab1)
对应的邮箱编号 FMI 为 2。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。