重要的内容写在前面:

  1. 该系列是以up主江协科技的STM32视频教程为基础写下去的,大部分内容都参考了老师的课件,对于一些个人认为比较重要但是老师仅口述的部分,笔者都有用文字的方式记录并标出了重点。
  2. 文中的图片基本都来源于老师的课件以及开发板和芯片的手册,粘贴过来是为了方便阅读。
  3. 如果有条件的可以先学习一些相关课程再去看STM32的教程,学起来会更加轻松(不太建议零基础开始直接STM32,听起来可能会有点困难,可以先学51单片机),相关课程有数字电路(强烈推荐先学数电,不然可能会有很多地方理解起来很困难)、模拟电路、计算机组成原理(像寄存器、存储器、中断等在这门课里有很详细的介绍)、计算机网络等。
  4. 如有错漏欢迎指出。

视频链接:[1-1] CAN简介&硬件电路_哔哩哔哩_bilibili

一、CAN协议

1、概述

(1)CAN总线(Controller Area Network Bus)的全称是控制器局域网总线,它构建的是一种局域网网络,每一个挂载在CAN总线上的设备都可以利用这个局域网发送自己的消息,也可以接收局域网的各种消息

(2)CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线,广泛应用于汽车、嵌入式、工业控制等领域。在2012年,Bosch发布了新的CAN FD标准(CAN with Flexible Data Rate),CAN FD继承了CAN的绝大多数特性,弥补了CAN在总线带宽和数据长度方面的不足。

(3)CAN总线的特征:

只需要两根通信线(CAN_H、CAN_L),线路少,无需共地

②使用差分信号通信(利用两个信号的电压差值传递信息),抗干扰能力强。

异步通信,无需时钟线,通信速率由设备各自约定。

半双工通信,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序。

⑤CAN网络中,一个数据帧可配置1~8字节的有效载荷;CAN FD网络中,一个数据帧可配置1~64字节的有效载荷。

⑥可实现广播式和请求式两种传输方式。

⑦拥有应答、CRC校验、位填充、位同步、错误处理等特性。

(4)CAN总线的两种标准:

①高速CAN(ISO11898):125k~1Mbps, <40m。

②低速CAN(ISO11519):10k~125kbps, <1km。

(5)CAN报文ID:

①CAN网络中通过CAN报文传递信息,每个报文有11位/29位报文ID,不同报文ID对应的CAN报文数据将有不同的定义,具体由工程师自行定义。

②CAN报文ID也称为“仲裁段”,它决定消息发送的优先级,ID号小的报文发送优先级更高。

2、CAN协议硬件电路

(1)CAN协议硬件电路有闭环CAN总线和开环CAN总线两种接法,每个设备通过CAN收发器挂载在CAN总线网络上,CAN控制器引出的TX和RX与CAN收发器相连,CAN收发器引出的CAN_H和CAN_L分别与总线的CAN_H和CAN_L相连

①高速CAN使用闭环网络,CAN_H和CAN_L两端添加120Ω的终端电阻,终端电阻可以防止回波反射,并在没有设备操作总线的情况下将两根通信线的电压维持在同一水平。(高速CAN在实际中更常用,如无特殊说明,下面都以高速CAN为基础进行介绍)

②低速CAN使用开环网络,CAN_H和CAN_L其中一端添加2.2kΩ的终端电阻,它们同样有防止回波反射的作用。

(2)CAN电平标准:CAN总线采用差分信号,即两线电压差V_{CAN_H}-V_{CAN_L}传输数据位

①高速CAN规定:电压差为0V时表示逻辑1(隐性电平),电压差为2V时表示逻辑0(显性电平)。

②低速CAN规定:电压差为-1.5V时表示逻辑1(隐性电平),电压差为3V时表示逻辑0(显性电平)。

(3)CAN总线中逻辑1与逻辑0的控制方式:

①设备需要发送显性逻辑0时,CAN收发器内部驱动管导通,主动拉高CAN_H、拉低CAN_L,使两根信号线产生约2V的差分电压,总线表现为显性0。

②设备需要发送隐性逻辑1时,CAN收发器的发送驱动管完全关断,仅保留接收监测功能,总线依靠各节点内部静态偏置电路,将CAN_H、CAN_L维持在2.5V等电位,差分电压接近0V,总线表现为隐性1。

③CAN总线具备线与特性,总线上任意一台设备输出显性0,会强制覆盖所有节点的隐性1请求,整条总线电平固定为显性0,该机制用于总线仲裁与错误帧主动报错。

(4)CAN收发器的引脚定义:

①VCC需要连接5V的电源正极,GND需要接地。

②TXD和RXD与设备的CAN控制器相连;CANH和CANL与CAN总线相连。

③Vref是参考电压输出,可以不使用。

④S用于选择高速模式/静默模式,可以不选择(默认输入低电平)。

(5)CAN收发器的内部框图:

①输入部分:

[1]CANH和CANL与接收器(RECEIVER)相连,接收器可以时刻检测总线电压差,如果总线有一定阈值的电压差就输出逻辑电平1,如果没有就输出逻辑电平0。

[2]接收器的输出通过输出驱动器(由两个场效应管组成)输出到RXD引脚,当接收器输出逻辑电平1时,上接VCC的场效应管断开,下接GND的场效应管导通,RXD输出逻辑电平0;当接收器输出逻辑电平0时,上接VCC的场效应管导通,下接GND的场效应管断开,RXD输出逻辑电平1。(两个场效应管整体上可视作一个反相器)

②输出部分:

[1]当TXD输入逻辑电平1时,驱动器(DRIVER)会让其连接的两个场效应管全部断开,CANH和CANL之间都各通过一个25kΩ的电阻连接到0.5VCC(2.5V),这样,CANH和CANL的默认对地电压都是2.5V左右,CANH与CANL之间的电压差接近0V。

[2]当TXD输入逻辑电平0时,驱动器(DRIVER)会让其连接的两个场效应管全部导通,CANH通过导通的上管经限流电阻接到VCC,电压被抬升至约3.5V,CANL通过导通的下管经限流电阻接到GND,电压被拉低至约1.5V,CANH与CANL之间形成约2V差分电压。

[3]当TXD悬空时,其输入端连接的VCC会让TXD保持默认输入高电平1的状态(电流源的作用相当于一个上拉电阻)。

[4]如果TXD出现异常,始终输入逻辑电平0,那么CAN总线将始终呈现显性电平0的状态,整条总线将会瘫痪,为了解决这个问题,TXD的输入侧设有TXD显性超时计时器(TXD DOMINANT TIME-OUT TIMER),当TXD一直不释放CAN总线时,超过一定时间后计时器将会强制将总线释放。

[5]当温度出现异常时,温度保护(TEMPERATURE PROTECTION)会控制驱动器切断输出,以免干扰CAN总线。

(6)CAN物理层的特性:

3、CAN协议的帧格式

(1)CAN协议规定了以下5种类型的帧:

帧类型

用途

数据帧

发送设备主动发送数据(广播式)

遥控帧

接收设备主动请求数据(请求式)

错误帧

某个设备检测出错误时向其他设备通知错误

过载帧

接收设备通知其尚未做好接收准备

帧间隔

用于将数据帧及遥控帧与前面的帧分离开

(2)数据帧的构成:

①CAN数据帧分为标准格式和扩展格式两种,其中扩展格式增加了18位ID位数,能承载更多种类的ID。

②图例说明:灰色方块D表示显性电平0,白色方块R表示隐性电平1,紫色方块D/R表示0/1;灰白双色块是应答位特有,在这一位中发送方必须发送隐性电平1,接收方必须发送显性电平0,表示接收方对发送方的应答;格式图中方块内的数字表示方块中有多少位数据。

③标准格式数据帧(从左往右):

[1]在发送数据帧前,总线必须处于空闲状态,空闲状态时总线处于隐性电平1。

[2]数据帧第一位SOF代表帧起始,此位必须是显性电平0。

[3]11位报文ID,可以表示后面数据段Data的功能,同时可用于区分优先级,当多个设备同时发送报文时,根据仲裁规则,ID小的报文优先发送,ID大的报文等待下一次总线空闲再重试发送。

[4]RTR位是远程请求标志位,用于区分数据帧和遥控帧,数据帧中该位必须为显性电平0;该位连同报文ID被称为仲裁段,当报文ID相同时,数据帧的优先级高于遥控帧。

[5]IDE位用于区分标准格式和扩展格式,标准格式中该位必须为显性电平0;r0位为保留位,为显性电平0。

[6]CAN总线的一帧数据可以有0~8个字节有效载荷,而DLC正是用于表示数据场Data的长度,0b0001表示数据场包含1字节数据,0b1000表示数据场包含8字节数据,以此类推(数据场的长度实际上可以是0字节,但必须是1字节的自然数倍且最大为8字节,不允许出现类似1.25字节这种长度)。

[7]CRC段的内容根据前面所有的数据位得出,接收方根据CRC段校验前面的数据是否有误,接着发送CRC界定符,必须为隐性电平1。

[8]ACK段包含ACK槽和ACK界定符,它们的长度都是1位,在ACK槽这一位中发送方必须发送隐性电平1,接收方必须发送显性电平0,表示接收方对发送方的应答,值得注意的是,这位可以允许多个接收方同时发送显性电平0,因为一个报文消息可以被多个设备同时接收。在ACK槽之前,发送方必须释放总线,这一步在发送方发送CRC界定符时已经完成了;在ACK槽之后,接收方必须释放总线,这一步将在发送方发送ACK界定符的时候完成。

[9]EOF是7个隐性电平1,代表数据帧结束。

④扩展格式数据帧与标准格式数据帧的差别:

[1]SRR位替代了标准格式中的RTR位,它与仲裁流程相关,原本的RTR位则是挪到了新增的18位ID之后。

[2]IDE位用于区分标准格式和扩展格式,扩展格式中该位必须为隐性电平1,IDE位之前扩展格式数据帧和标准格式数据帧没有差异。

[3]在IDE之后是新增的18位ID(加上标准数据帧原有的11位ID,一共29位),报文ID发送完毕后发送RTR位,之后的帧格式和标准格式基本一样,由于IDE位在先前已经出现,所以标准格式中的IDE位在这里可以闲置,成为保留位r1。

(3)CAN FD数据帧的构成(本章中的后续内容如无特别说明,“数据帧”均为CAN数据帧):

①CAN FD数据帧也分为标准格式和扩展格式两种,其中扩展格式增加了18位ID位数,能承载更多种类的ID。

②CAN FD数据帧格式是基于CAN数据帧升级而来的,能处理CAN FD报文的节点也能处理CAN报文(反之则不一定),以下仅介绍CAN FD数据帧格式与CAN数据帧格式有区别的点。

③CAN FD数据帧与CAN数据帧的区别:

[1]原CAN数据帧中的保留位r0,在CAN FD数据帧中替换为FDF位,为隐性电平1时表示该数据帧报文是CAN FD报文,为显性电平0时表示该数据帧报文是CAN报文。

[2]CAN FD数据帧新增BRS位,表示位速率转换,当BRS位为显性电平0时,数据段的位速率与仲裁段的位速率保持一致,当BRS位为隐性电平1时,数据段的位速率可变(这也是CAN FD的传输速率比CAN更快的核心原因,数据段可以以更快的速率传输)。

[3]CAN FD数据帧新增ESI位,用于发送节点发出错误状态指示,发送节点处于主动错误态(通信正常)时发送显性电平0,发送节点处于被动错误态(总线存在大量报错)时发送隐性电平1。

[4]CAN FD总线的一帧数据可以有0~64个字节有效载荷,而DLC正是用于表示数据场Data的长度,不过CAN FD数据帧的DLC长度仍然为4位,DLC十进制值与对应数据场长度(单位为字节)的映射关系如下:

DLC十进制值

数据场长度(单位为字节)

DLC十进制值

数据场长度(单位为字节)

0

0

8

8

1

1

9

12

2

2

10

16

3

3

11

20

4

4

12

24

5

5

13

32

6

6

14

48

7

7

15

64

(4)数据帧的发展历史:

①CAN 1.2时期,仅存在标准格式,IDE位当时仍为保留位r1。

②CAN 2.0时期,ID不够用,出现了扩展格式,增加了ID的位数,为了区分标准格式与扩展格式,协议将标准格式中的r1赋予了新功能—IDE。

③在2012年,Bosch发布了新的CAN FD标准(CAN with Flexible Data Rate),CAN FD继承了CAN的绝大多数特性,弥补了CAN在总线带宽和数据长度方面的不足。

(4)其它4种类型的帧:

①遥控帧:遥控帧无数据场,RTR为隐性电平1,其它部分与数据帧相同。(一个设备发送遥控帧,如果收到遥控帧的某个设备有遥控帧ID号对应的数据,该设备会通过数据帧将该数据广播)

②错误帧:总线上所有设备都会监督总线的数据,一旦发现“位错误”或“填充错误”或“CRC错误”或“格式错误”或“应答错误”,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备。

③过载帧:当接收方收到大量数据而无法处理时,可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失。

④帧间隔:将数据帧和遥控帧与前面的帧分隔开。(下一图是设备处于主动错误状态时的帧间隔,下二图是设备处于被动错误状态时的帧间隔)

4、位填充

(1)位填充规则:针对数据帧和遥控帧,发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位时,会自动移除填充位,恢复原始数据

(2)位填充的作用:

①增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机。

②将正常数据流与“错误帧”和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性。

③保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲。

(3)举例:

(4)位填充由CAN收发器的硬件完成,软件无需关注位填充的操作细节。

5、位时序与位同步

(1)接收方数据采样:

①CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长发送方以约定的位时长每隔固定时间输出一个数据位接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位

②理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近。

(2)接收方数据采样可能会遇到的问题:

①接收方以约定的位时长进行采样,但是采样点没有对齐数据位中心附近。

②接收方刚开始采样正确,但是时钟有误差,随着误差积累,采样点逐渐偏离。

(3)为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分(即位时序),分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成,其中PBS1和PBS2的交界处即为采样点

(4)硬同步:

①每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文,其它所有设备(接收方)收到SOF的下降沿时,接收方会将自己的位时序计时周期拨到SS段的位置,与发送方的位时序计时周期保持同步

硬同步只在帧的第一个下降沿(SOF下降沿)有效

③经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然都会对齐数据位中心附近。

(5)再同步:

①若发送方或接收方的时钟有误差,随着误差积累,数据位边沿逐渐偏离SS段,此时分为两种情况:

[1]隐性电平到显性电平的边沿出现在PTS和PBS1之间,接收方根据再同步补偿宽度值(SJW)加长该位的PBS1段,这样,下一个数据位的边沿就能对齐SS段,以调整同步,如果不能对齐,继续分情况进行调整。

[2]隐性电平到显性电平的边沿出现在PBS2中,接收方根据再同步补偿宽度值(SJW)缩短该位的PBS2段,这样,下一个数据位的边沿就能对齐SS段,以调整同步,如果不能对齐,继续分情况进行调整。

再同步可以发生在第一个下降沿之后的每个数据位跳变边沿

③SJW的取值有一定讲究,过大或者过小都可能会造成反复补偿,总之,SJW的设定需要通过实验来测试决定。

(6)波特率计算:波特率 = 1/一个数据位的时长。

6、总线的资源分配规则(仲裁)

(1)CAN总线只有一对差分信号线,同一时间只能有一个设备操作总线发送数据,若多个设备同时有发送需求,这时需要制定资源分配规则,依次满足多个设备的发送需求,确保同一时间只有一个设备操作总线。

(2)资源分配规则——先占先得:

①若当前已经有设备正在操作总线发送数据帧/遥控帧,则其它任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据)。

任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧。一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平,其它设备自然也不会破坏当前发送。

③若总线活跃状态下其它设备有发送需求,则需要等待总线变为空闲,才能执行发送需求。

(3)资源分配规则——非破坏性仲裁:

①若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(或者说仲裁段)进行非破坏性仲裁,ID号小的(优先级高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试发送

②实现非破坏性仲裁的两个要求:

[1]线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态

[2]回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实的发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1。

③非破坏性仲裁的过程:

[1]数据位从前到后依次比较,出现差异且数据位为1的设备仲裁失利。(位填充不会影响仲裁过程,不需要考虑位填充的问题)

[2]ID号相同的情况下,数据帧的优先级高于遥控帧。(ID号相同的两个数据帧或两个遥控帧是不允许同时出现的)

[3]标准格式11位ID号和扩展格式29位ID号的高11位一样时,标准格式的优先级高于扩展格式(扩展格式的SRR位必须始终为1,以保证此要求)。如果是标准遥控帧和扩展数据帧竞争总线使用权,标准遥控帧的RTR位为1,扩展数据帧的SRR位也为1,二者同位,这种情况下标准遥控帧会顺利度过仲裁段,不过标准遥控帧和扩展数据帧的下一个位都是IDE位,标准遥控帧的IDE位为0,扩展数据帧的IDE位为1,这时扩展数据帧将会被淘汰。

7、错误处理

(1)当某个设备检测到总线的帧传输出错时,它可以主动发出错误帧以通知其它设备该帧有误,其它设备将放弃该帧。CAN总线规定了5种可能出现的错误类型,如下所示。

(2)如果总线上有一个设备自身出现问题,它将接收到的正常帧认为是传输有误的帧,那么它将会不断破坏总线上的帧,这样总线将会面临瘫痪,为了避免这个问题,还需要设置一个“错误状态”,每个设备内部管理一个TEC(发送错误计数器,设备在发送时,每发现一次发送错误计数增加,进行一次正常的发送计数减小)和REC(接收错误计数器,设备在接收时,每发现一次接收错误计数增加,进行一次正常的接收计数减小),根据TEC和REC的值确定自己的状态

当TEC和REC的计数值较小时,设备检出错的频率较低,进入主动错误状态主动错误状态的设备正常参与通信,并在检测到错误时发出主动错误标志

当TEC或REC的计数值较大时,设备检出错的频率稍高,进入被动错误状态被动错误状态的设备正常参与通信,但检测到错误时只能发出被动错误标志

在被动错误状态下,当TEC的计数值较大时,意味着设备检测出最近自己发送的内容出错概率很大,为了防止继续发送错误的内容影响其它设备,它需要进入总线关闭状态总线关闭状态的设备不能参与通信,不过考虑到这个设备可能非常重要,当它在总线上检测到128次连续的11个位的隐性位时,将转回主动错误状态,TEC和REC的计数清零,如果该设备的发送功能确实存在问题,那么它不久后还是会回到总线关闭态。

(3)错误计数值的变动规则:

二、STM32中的CAN外设

1、CAN外设简介

(1)STM32内置bxCAN外设(CAN控制器),支持CAN2.0A和2.0B,可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节。bxCAN的一些主要参数如下:

①波特率最高可达1兆位/秒。

②具有3个可配置优先级的发送邮箱。

③具有2个3级深度的接收FIFO。

④具有14个过滤器组(互联型28个)。

(2)STM32F103C8T6的硬件CAN资源:CAN1。

2、CAN网拓扑结构和收发器电路

(1)STM32引出的CAN控制器引脚,CAN_RX引脚复用在PA11引脚上(CAN_RX可以重映射到PB8引脚),CAN_TX引脚复用在PA12引脚上(CAN_TX可以重映射到PB9引脚)。需要注意的是,STM32F103C8T6的USB资源的两个引脚也复用在PA11和PA12这两个引脚上,所以CAN资源和USB资源无法同时使用。

(2)下图所示的是CAN通信的网络拓扑结构,如果STM32作为其中一个CAN通信节点,那么STM32的“CAN资源”则是对应下图中的CAN控制器。

(3)下图中的TJA1050是CAN收发器,其CANH和CANL引脚连接在CAN总线上,GND和S引脚接地,VCC引脚连接5V电压源,TXD和RXD引脚与CAN控制器(可以是STM32的CAN资源)相连。

3、CAN框图

(1)CAN2辅助CAN1工作,它们共同管理一个CAN总线,但只有互联型设备才有CAN2,对于STM32F103C8T6,它只有CAN1。

(2)可以通过读写CAN控制器的寄存器对CAN外设进行配置,或者获取CAN外设的状态位。

(3)发送邮箱有3个,每个邮箱可以存入一个CAN报文,如果想发出一个报文,可以把这个报文写入到其中一个空置邮箱,然后设置相应的寄存器请求发送即可,后续步骤均由硬件电路自动执行。

(4)程序员可以设置过滤规则,也就是作为接收方需要接收ID号为多少的报文,不符合ID号的报文不予以处理,符合ID号的报文则存入接收FIFO(先进先出寄存器,也可认为是队列寄存器,每个队列可存放3个报文,这是为了防止CPU处理报文的速度慢于CAN控制器接收报文的速度而造成报文丢失)。当CAN总线上出现一个数据帧或者遥控帧时,CAN硬件电路会把这个报文缓存,过滤器根据报文ID判断是否将该报文存入FIFO。需要说明的是,过滤器中有若干滤波器,当报文ID符合要求时,总有一个滤波器会允许报文通过,否则任何一个滤波器都不允许报文通过。

4、CAN外设基本工作流程

(1)对于STM32F103C8T6,CAN_TX对应PA12,需要将其配置为复用推挽输出模式,CAN_RX对应PA11,需要将其配置为上拉输入模式

(2)当CPU想发送报文时,需要将报文信息写进空置的发送邮箱(若邮箱中有未发送报文,则不是空置邮箱),发送和接收控制器会在CAN总线空闲时将报文发送到总线上。当不止有一个发送邮箱中有待发送报文时,可以配置发送策略,以决定哪个报文优先发送。

(3)当CAN总线上有报文信息时,发送和接收控制器会将报文接收,ID符合过滤规则的报文将存入FIFO中,等待CPU读取(按照先进先出原则)。每个过滤器都可进行单独配置,配置参数主要有该过滤器对应的报文ID(即过滤规则),还有过滤后的报文进入FIFO 0或是FIFO1,比如通过过滤器0的报文进入FIFO 0,当FIFO 0满以后,通过过滤器0的下一个报文是否丢弃取决于其配置的是否是FIFO锁定,如果是则丢弃,如果不是则顶替FIFO 0队列的队尾报文(即邮箱2中的报文)。

(4)工作流程中寄存器状态的变化:

①发送报文的基本流程:选择一个空置邮箱→写入报文→请求发送。(下图所示的是其中任意一个发送邮箱的状态图,X表示状态寄存器标志位可为1或0)

②接收报文的基本流程:接收到一个报文→匹配过滤器后进入FIFO 0或FIFO 1→CPU读取。(下图所示的是其中任意一个接收FIFO的状态图,其中FMP真正使用的应该只有两个位,因为一个FIFO只有3个邮箱,仅用两位二进制数就能完成当前非空置邮箱数的表示)

③几个发送和接收配置位:

[1]NART:置1,关闭自动重传,CAN报文只被发送1次,不管发送的结果如何(成功、出错或仲裁丢失);置0,启用自动重传,CAN硬件在发送报文失败时会一直自动重传,直到发送成功。

[2]TXFP:置1,优先级由发送请求的顺序来决定,先请求的先发送;置0,优先级由报文标识符来决定,标识符值小的先发送(标识符值相等时,邮箱号小的报文先发送)。

[3]RFLM:置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖。

(5)标识符过滤器的设置:

①每一个过滤器由两个32位寄存器组成——R1[31:0]和R2[31:0],ID号等关键过滤信息都会存储在这两个关键的32位寄存器中。

②每个过滤器还有如下一些配置位来配置功能细节:(x=0...13)

[1]FSCx:用于配置过滤器的位宽,置0即配置为16位位宽,置1即配置为32位位宽。

[2]FBMx:用于配置过滤器的模式,置0即配置为屏蔽模式,置1即配置为列表模式。

[3]FFAx:过滤器关联设置,置0即配置通过此过滤器的报文存入FIFO 0,置1即配置通过此过滤器的报文存入FIFO 1。

[4]FACTx:过滤器激活设置,置0即禁用该过滤器,置1即启用该过滤器。

③STM32F103C8T6的CAN资源有14个过滤器,后面都以其中一个过滤器为例进行介绍。

④假设过滤器工作在2个32位过滤器的列表模式下,两个寄存器可各存储一个报文ID,如接收到有符合任意一个ID的报文,则允许其通过过滤器。

[1]寄存器的高11位需要存入标准格式的ID号(STID),后面的18位需要存入扩展格式的ID号(EXID),扩展ID号共29位,故需额外占用寄存器的高11位。

[2]IDE是扩展标志位,置1表示前面写入的是扩展ID号,置0表示前面写入的是标准ID号。

[3]RTR是遥控帧标志位,置1表示允许遥控帧通过,置0表示允许数据帧通过。

⑤假设过滤器工作在4个16位过滤器的列表模式下,两个寄存器可各存储两个标准格式的报文ID,如接收到有符合任意一个ID的报文,则允许其通过过滤器。

[1]RTR是遥控帧标志位,置1表示允许遥控帧通过,置0表示允许数据帧通过。

[2]IDE是扩展标志位,一般固定写0。

⑥假设过滤器工作在1个32位过滤器的屏蔽模式下,其中一个寄存器可存储一个报文ID,另一个寄存器设置屏蔽位(置0表示该位不参与报文ID对比,相当于位掩码),在对比报文ID时可忽略置0的屏蔽位,在此基础上,如接收到有符合任意一个ID的报文,则允许其通过过滤器。

[1]寄存器的高11位需要存入标准格式的ID号(STID),后面的18位需要存入扩展格式的ID号(EXID),扩展ID号共29位,故需额外占用寄存器的高11位。

[2]IDE是扩展标志位,置1表示前面写入的是扩展ID号,置0表示前面写入的是标准ID号。IDE的屏蔽位理论上需要置1,否则可能会无法区分标准格式和扩展模式的报文。

[3]RTR是遥控帧标志位,RTR的屏蔽位置1,RTR置1表示允许遥控帧通过,RTR置0表示允许数据帧通过;RTR的屏蔽位置0,表示允许遥控帧和数据帧通过。

⑦假设过滤器工作在2个16位过滤器的屏蔽模式下,其中两个寄存器可各存储一个标准格式的报文ID,另外两个寄存器设置屏蔽位(置0表示该位不参与报文ID对比,相当于位掩码),在对比报文ID时可忽略置0的屏蔽位,在此基础上,如接收到有符合任意一个ID的报文,则允许其通过过滤器。

[1]RTR是遥控帧标志位,RTR的屏蔽位置1,RTR置1表示允许遥控帧通过,RTR置0表示允许数据帧通过;RTR的屏蔽位置0,表示允许遥控帧和数据帧通过。

[2]IDE是扩展标志位,一般固定写0,其屏蔽位置1。

5、测试模式和工作模式

(1)测试模式:

①静默模式:发送端与接收端相接,CAN_TX引脚置隐性电平(即不对外发送任何内容),可进行自发自收测试,也可用于分析CAN总线的活动,不会对总线造成影响。

②环回模式:CAN_RX引脚断开(即不接收任何外部内容),同时发送的报文可以在CAN_TX引脚上检测到,可用于自测试。

③环回静默模式:发送端与接收端相接,与外部完全隔绝,可用于热自测试,自测的同时不会影响CAN总线。

(2)工作模式:

①三种工作模式:

[1]初始化模式:用于配置CAN外设,禁止报文的接收和发送。

[2]正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文。

[3]睡眠模式:CAN外设处于低功耗状态,时钟停止,可使用软件唤醒或者硬件自动唤醒。

②AWUM标志位:置1,配置为自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP位,唤醒CAN外设;置0,配置为手动唤醒,需要软件清零SLEEP位,唤醒CAN外设。

③模式切换状态机:

[1]复位后,CAN外设默认处于睡眠模式,SLAK=1表示硬件已经确认进入睡眠模式,INAK=0表示硬件目前没有确认进入初始化模式;初始化模式下,SLAK=0,INAK=1,表示硬件目前确认进入初始化模式;正常模式下,SLAK=0,INAK=0,表示硬件目前没有确认进入睡眠模式和初始化模式。

[2]SLEEP位置1表示请求进入睡眠模式;SLEEP位置0表示请求退出睡眠模式;INRQ位置1表示请求进入初始化模式,INRQ位置0表示请求退出初始化模式。

[3]其它模式进入正常模式时,需要等待总线空闲,即SYNC=1;除了进入正常模式以外,需要等应答信号ACK=1后,CAN外设才能进行模式切换。

6、位时间特性(位时序)

        STM32中把CAN协议规定的PTS段和PBS1段合二为一,其它与CAN标准基本一致

7、CAN外设的中断类型

(1)当CAN外设内部发生一些重要的事件时,可以申请中断,CPU可以自动跳转到相应的中断函数执行。

(2)CAN外设占用4个专用的中断向量:

①发送中断:发送邮箱空时产生。

②FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生。

③FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生。

④状态改变错误中断:出错/唤醒/进入睡眠时产生。

8、时间触发通信

(1)该功能是ISO11898-4协议里规定的更高级的功能,它可以对所有节点进行同步调度,也就是每个节点只在一个固定的时间段内发送报文,这样可以避免优先级仲裁。

(2)硬件配置:

①TTCM位:置1,开启时间触发通信功能;置0,关闭时间触发通信功能。

②CAN外设内置一个16位的计数器,用于记录时间戳,TTCM位置1后,该计数器在每个CAN位的时间自增一次,溢出后归零。

③每个发送邮箱和接收FIFO都有一个TIME[15:0]寄存器,发送帧SOF时,捕获计数器值存到发送邮箱的TIME寄存器,接收帧SOF时,捕获计数器值存到接收FIFO的TIME寄存器。

④发送邮箱可配置TGT位,捕获计数器值的同时,也把此值写入到数据帧数据段的最后两个字节,不过为了使用此功能,DLC必须设置为8。

9、错误处理

        下图所示的是CAN外设错误处理的状态机,和CAN协议规定的基本一样,不过STM32中有一些个性化设计:

        ①TEC和REC根据错误的情况增加或减少

        ②ABOM位:配置为1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;配置为0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启

三、代码编写与示例程序

1、stm32提供的库函数

        在stm32f10x_can.h文件中有CAN相关的函数,这些库函数已经封装好了,程序员可以忽视底层硬件,直接调用库函数即可实现CAN通信

        CAN_DeInit函数:将指定的CAN外设的所有寄存器恢复为默认值(即硬件复位后的状态)

函数原型:void CAN_DeInit(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要进行复位的CAN外设

返回值:无返回值

        CAN_Init函数:根据CAN_InitTypeDef结构体中指定的参数(工作模式、波特率、位时序以及各种通信控制选项等),初始化指定的CAN外设

函数原型:uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);

参数解释:

        CAN_TypeDef* CANx:指定要初始化的CAN外设

        CAN_InitTypeDef* CAN_InitStruct:指向CAN初始化结构体的指针

返回值:0表示初始化成功,1表示初始化失败

        CAN_FilterInit函数:根据CAN_FilterInitTypeDef结构体中指定的参数(过滤器组编号、过滤器模式、过滤器位宽、标识符寄存器、屏蔽寄存器、关联接收FIFO等),初始化CAN外设的接收过滤器

函数原型:void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);

参数解释:

        CAN_FilterInitTypeDef* CAN_FilterInitStruct:指向CAN过滤器初始化结构体的指针

返回值:无返回值

        CAN_StructInit函数:将CAN_InitTypeDef结构体中的所有成员填充为安全、确定的默认值

函数原型:void CAN_StructInit(CAN_InitTypeDef* CAN_InitStruct);

参数解释:

        CAN_InitTypeDef* CAN_InitStruct:指向CAN初始化结构体的指针

返回值:无返回值

        CAN_SlaveStartBank函数:选择从CAN(Slave CAN,即CAN2)的过滤器起始组编号(Start Bank),决定CAN1和CAN2之间如何分配共享的28个过滤器组

函数原型:void CAN_SlaveStartBank(uint8_t CAN_BankNumber);

参数解释:

        uint8_t CAN_BankNumber:指定从CAN(CAN2)的过滤器起始组编号

返回值:无返回值

        CAN_DBGFreeze函数:在调试模式下使能或禁用CAN外设的“调试冻结”功能

函数原型:void CAN_DBGFreeze(CAN_TypeDef* CANx, FunctionalState NewState);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        FunctionalState NewState:指定调试冻结功能的使能状态,ENABLE表示使能调试冻结,DISABLE表示禁用调试冻结

返回值:无返回值

        CAN_TTComModeCmd函数:使能或禁用指定CAN外设的时间触发通信模式

函数原型:void CAN_TTComModeCmd(CAN_TypeDef* CANx, FunctionalState NewState);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        FunctionalState NewState:指定时间触发通信模式的使能状态,ENABLE表示使能时间触发通信模式,DISABLE表示禁用时间触发通信模式

返回值:无返回值

        CAN_Transmit函数:将用户填充好的报文数据(标识符、帧类型、数据长度、数据内容等)写入指定CAN外设的空闲发送邮箱,并触发硬件开始发送

函数原型:uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);

参数解释:

        CAN_TypeDef* CANx:指定要使用的CAN外设

        CanTxMsg* TxMessage:指向CAN发送消息结构体的指针,包含了待发送报文的所有信息(标识符、帧类型、数据长度、数据内容等)

返回值:用于发送该报文的邮箱编号,或表示没有空闲邮箱的错误码

        CAN_TransmitStatus函数:查询指定CAN发送邮箱中报文的发送状态,判断报文是否已成功发送到CAN总线上

函数原型:uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);

参数解释:

        CAN_TypeDef* CANx:指定要查询的CAN外设

        uint8_t TransmitMailbox:指定要查询的发送邮箱编号

返回值:指定邮箱中报文的当前发送状态

        CAN_CancelTransmit函数:取消一个正在等待发送的CAN报文请求

函数原型:void CAN_CancelTransmit(CAN_TypeDef* CANx, uint8_t Mailbox);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        uint8_t Mailbox:指定要取消发送请求的邮箱编号

返回值:无返回值

        CAN_Receive函数:从指定的CAN接收FIFO中读取一个已接收到的CAN帧,通过CAN报文结构体指针形参RxMessage将报文内容返回,同时释放FIFO,使FIFO的读指针向前移动一个位置

函数原型:void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        uint8_t FIFONumber:指定要从哪个接收FIFO中读取报文

        CanRxMsg* RxMessage:指向用户定义的接收消息结构体,函数会将收到的报文数据填充到此结构体中

返回值:无返回值

        CAN_FIFORelease函数:释放指定的CAN接收FIFO,使FIFO的读指针向前移动一个位置

函数原型:void CAN_FIFORelease(CAN_TypeDef* CANx, uint8_t FIFONumber);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        uint8_t FIFONumber:指定要释放的接收FIFO

返回值:无返回值

        CAN_MessagePending函数:查询指定CAN接收FIFO中待处理报文的数量

函数原型:uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber);

参数解释:

        CAN_TypeDef* CANx:指定要查询的CAN外设

        uint8_t FIFONumber:指定要查询的接收FIFO

返回值:指定FIFO中当前待处理的报文数量

        CAN_OperatingModeRequest函数:请求将指定的CAN外设切换到一种新的工作模式

函数原型:uint8_t CAN_OperatingModeRequest(CAN_TypeDef* CANx, uint8_t CAN_OperatingMode);

参数解释:

        CAN_TypeDef* CANx:指定要操作的CAN外设

        uint8_t CAN_OperatingMode:指定要切换到的CAN工作模式

返回值:模式切换的请求结果,CAN_ModeStatus_Success表示切换成功,CAN_ModeStatus_Failed表示切换失败

        CAN_Sleep函数:请求CAN外设进入低功耗睡眠模式

函数原型:uint8_t CAN_Sleep(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要进入睡眠模式的CAN外设

返回值:睡眠模式请求的结果,CAN_Sleep_Ok表示睡眠成功,CAN_Sleep_Failed表示睡眠失败

        CAN_WakeUp函数:将指定的CAN外设从低功耗睡眠模式唤醒,使其恢复到正常模式

函数原型:uint8_t CAN_WakeUp(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要唤醒的CAN外设

返回值:唤醒操作的结果,CAN_WakeUp_Ok表示唤醒成功,CAN_WakeUp_Failed表示唤醒失败

        CAN_GetLastErrorCode函数:获得指定CAN外设最近一次的错误码

函数原型:uint8_t CAN_GetLastErrorCode(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要读取错误代码的CAN外设

返回值:最近一次发生的错误代码

        CAN_GetReceiveErrorCounter函数:获取指定CAN外设REC计数器的值

函数原型:uint8_t CAN_GetReceiveErrorCounter(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要读取接收错误计数器的CAN外设

返回值:当前CAN接收错误计数器的值

        CAN_GetLSBTransmitErrorCounter函数:获取指定CAN外设TEC计数器的值

函数原型:uint8_t CAN_GetLSBTransmitErrorCounter(CAN_TypeDef* CANx);

参数解释:

        CAN_TypeDef* CANx:指定要读取发送错误计数器的CAN外设

返回值:当前CAN发送错误计数器的值

        CAN_ITConfig函数:使能或禁用指定的CAN外设中断源,决定当某种事件发生时,CAN外设是否向NVIC发送中断请求

函数原型:void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);

参数解释:

        CAN_TypeDef* CANx:指定要配置的CAN外设

        uint32_t CAN_IT:指定要使能或禁用的中断源类型;该参数可以使用按位或“|”组合多个中断源,一次性使能多个中断

        FunctionalState NewState:指定中断源的新状态,ENABLE表示使能指定的中断源,DISABLE表示禁用指定的中断源

返回值:无返回值

        CAN_GetFlagStatus函数:检查指定CAN外设的某个状态标志位是否被置位,以判断对应的硬件事件是否已经发生(如发送完成、接收挂起、错误警告、总线关闭等)

函数原型:FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG);

参数解释:

        CAN_TypeDef* CANx:指定要检查标志位的CAN外设

        uint32_t CAN_FLAG:指定要检查的状态标志位

返回值:SET表示指定的标志位被置位,RESET表示指定的标志位未被置位

        CAN_ClearFlag函数:手动清除指定CAN外设的一个或多个待处理状态标志位

函数原型:void CAN_ClearFlag(CAN_TypeDef* CANx, uint32_t CAN_FLAG);

参数解释:

        CAN_TypeDef* CANx:指定要清除标志位的CAN外设

        uint32_t CAN_FLAG:指定要清除的状态标志位

返回值:无返回值

        CAN_GetITStatus函数:检查指定的CAN中断源是否已经发生且被使能,即判断是否有有效的中断请求正在等待处理

函数原型:ITStatus CAN_GetITStatus(CAN_TypeDef* CANx, uint32_t CAN_IT);

参数解释:

        CAN_TypeDef* CANx:指定要检查中断状态的CAN外设

        uint32_t CAN_IT:指定要检查的中断源类型

返回值:SET表示指定的中断源已发生且已被使能,RESET表示指定的中断源未发生,或虽已发生但中断未被使能

        CAN_ClearITPendingBit函数:清除指定CAN外设的一个或多个待处理中断挂起位

函数原型:void CAN_ClearITPendingBit(CAN_TypeDef* CANx, uint32_t CAN_IT);

参数解释:

        CAN_TypeDef* CANx:指定要清除中断挂起位的CAN外设

        uint32_t CAN_IT:指定要清除的中断挂起位类型

返回值:无返回值

2、单个设备环回测试

(1)CAN总线上的设备首先进行环回测试,也就是自己发送数据帧,然后接收之,如果能够成功接收,说明测试通过,该设备再允许自己和其它设备互相通信。

(2)按照下图所示将电路连接好,将OLED显示屏的项目文件夹复制一份作为模板使用,并在项目的Hardware组中添加按键模块的代码,即Key.h文件和Key.c文件。(进行环回测试时可不接CAN收发器,也不需要接与CAN通信相关的其它设备)

(3)在项目的Hardware组中添加MyCAN.h文件和MyCAN.c文件,用于封装CAN模块的代码。

①MyCAN.h文件:

#ifndef __MYCAN_H
#define __MYCAN_H

void MyCAN_Init(void);
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data);
uint8_t MyCAN_ReceiveFlag(void);
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data);

#endif

②MyCAN.c文件:

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启CAN1引脚的GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //开启CAN1外设的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //Tx引脚配置为复用推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;         //Tx引脚复用在PA12
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;      //Rx引脚配置为上拉输入模式(显性电平为高电平)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;         //Rx引脚复用在PA11
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;               //用于初始化CAN外设的结构体
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;  //配置为环回模式
	CAN_InitStructure.CAN_Prescaler = 48;		//Prescaler——分频系数(BRP[9:0]+1)
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;    //BS1段有2个tq
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;    //BS2段有3个tq
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;    //SJW段有2个tq
	CAN_InitStructure.CAN_NART = DISABLE;       //不开启不自动重传功能
	CAN_InitStructure.CAN_TXFP = DISABLE;       //ID较小的报文先发送
	CAN_InitStructure.CAN_RFLM = DISABLE;       //FIFO没有空间时,新收到的报文覆盖最近一次收到的报文
	CAN_InitStructure.CAN_AWUM = DISABLE;       //手动唤醒模式(软件唤醒)
	CAN_InitStructure.CAN_TTCM = DISABLE;       //关闭时间触发通信模型
	CAN_InitStructure.CAN_ABOM = DISABLE;       //离线自动恢复为手动
	CAN_Init(CAN1, &CAN_InitStructure);         //初始化完成后,CAN外设进入正常模式
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;       //用于初始化过滤器的结构体
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                         //过滤器编号(可选值0~13)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;   //这里把过滤器配置成任何报文都能通过
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; 
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;     //过滤器位宽(32位)
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;      //过滤器模式(屏蔽模式)
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //关联的FIFO(FIFO0)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               //使能过滤器
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)   //单个数据帧报文发送
{
	CanTxMsg TxMessage;                     //CAN报文结构体
	TxMessage.StdId = ID;                   //标准格式的报文ID
	TxMessage.ExtId = ID;                   //扩展格式的报文ID
	TxMessage.IDE = CAN_Id_Standard;		//扩展标志位(选择标准格式)
	TxMessage.RTR = CAN_RTR_Data;           //遥控标志位(选择数据帧)
	TxMessage.DLC = Length;                 //数据段长度
	for (uint8_t i = 0; i < Length; i ++)   //根据数据段长度将形参中的数据数组Data复制到CAN报文结构体成员Data数组中
	{
		TxMessage.Data[i] = Data[i];
	}
	
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);  //将CAN报文结构体中的报文写入发送邮箱,返回发送邮箱编号
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)  //等待邮箱将报文发送
	{
		Timeout ++;
		if (Timeout > 100000)    //等待超时,需要强行退出等待循环
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)    //判断FIFO0中是否有报文接收
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)   //判断FIFO0中挂起报文的数目是否大于0
	{
		return 1;   //有报文则返回1
	}
	return 0;       //无报文则返回0
}

void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)   //单个数据帧报文读取
{
	CanRxMsg RxMessage;                        //CAN报文结构体
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);  //接收报文,报文信息存于CAN报文结构体中
	
	if (RxMessage.IDE == CAN_Id_Standard)      //标准格式和扩展格式的ID分情况进行处理
	{
		*ID = RxMessage.StdId;                 //ID指针指向的内存存储标准格式报文ID
	}
	else
	{
		*ID = RxMessage.ExtId;                 //ID指针指向的内存存储扩展格式报文ID
	}
	
	if (RxMessage.RTR == CAN_RTR_Data)         //处理数据帧报文
	{
		*Length = RxMessage.DLC;              
		for (uint8_t i = 0; i < *Length; i ++) //根据报文数据段长度将报文中的数据拷贝到Data指针指向的内存中
		{
			Data[i] = RxMessage.Data[i];
		}
	}
	else
	{
		//遥控帧的处理,这里没有实现
	}
}

(4)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按下按键,观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;
uint32_t TxID = 0x555;                          //发送报文ID
uint8_t TxLength = 4;                           //发送报文数据段长度DLC(最大为8)
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44};   //发送报文数据段的内容

uint32_t RxID;                                  //接收报文ID
uint8_t RxLength;                               //接收报文数据段长度DLC
uint8_t RxData[8];                              //接收报文数据段的内容

int main(void)
{
	OLED_Init();    //OLED屏初始化
	Key_Init();     //按键初始化
	MyCAN_Init();   //CAN外设初始化
	
	OLED_ShowString(1, 1, "TxID:");
	OLED_ShowHexNum(1, 6, TxID, 3);
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();    //读取按键码
		
		if (KeyNum == 1)          //按下按键1
		{
			TxData[0] ++;         //数据段第1个字节自增
			TxData[1] ++;         //数据段第2个字节自增
			TxData[2] ++;         //数据段第3个字节自增
			TxData[3] ++;         //数据段第4个字节自增
			
			MyCAN_Transmit(TxID, TxLength, TxData);    //发送一个数据帧
		}
		
		if (MyCAN_ReceiveFlag())    //如果收到报文,则将其接收并将数据段显示在OLED屏上
		{
			MyCAN_Receive(&RxID, &RxLength, RxData);    //接收一个数据帧(本例中的数据帧是自发自收的)
			
			OLED_ShowHexNum(2, 6, RxID, 3);          //接收报文的ID
			OLED_ShowHexNum(3, 6, RxLength, 1);      //接收报文的数据段长度
			OLED_ShowHexNum(4, 6, RxData[0], 2);     //数据段第1个字节
			OLED_ShowHexNum(4, 9, RxData[1], 2);     //数据段第2个字节
			OLED_ShowHexNum(4, 12, RxData[2], 2);    //数据段第3个字节
			OLED_ShowHexNum(4, 15, RxData[3], 2);    //数据段第4个字节
		}
	}
}

3、三个设备互相通信

(1)实现该功能的代码和单个设备环回测试的代码差不多,仅需修改一个参数,即将环回模式切换回正常模式。

(2)按照下图所示将电路连接好,将OLED显示屏的项目文件夹复制一份作为模板使用,并在项目的Hardware组中添加按键模块的代码,即Key.h文件和Key.c文件,也可直接复制上例的项目文件夹。

(3)在项目的Hardware组中添加MyCAN.h文件和MyCAN.c文件用于封装CAN模块的代码。

①MyCAN.h文件:

#ifndef __MYCAN_H
#define __MYCAN_H

void MyCAN_Init(void);
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data);
uint8_t MyCAN_ReceiveFlag(void);
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data);

#endif

②MyCAN.c文件:

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启CAN1引脚的GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //开启CAN1外设的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //Tx引脚配置为复用推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;         //Tx引脚复用在PA12
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;      //Rx引脚配置为上拉输入模式(显性电平为高电平)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;         //Rx引脚复用在PA11
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;               //用于初始化CAN外设的结构体
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;  //配置为正常模式
	CAN_InitStructure.CAN_Prescaler = 48;		//Prescaler——分频系数(BRP[9:0]+1)
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;    //BS1段有2个tq
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;    //BS2段有3个tq
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;    //SJW段有2个tq
	CAN_InitStructure.CAN_NART = DISABLE;       //不开启不自动重传功能
	CAN_InitStructure.CAN_TXFP = DISABLE;       //ID较小的报文先发送
	CAN_InitStructure.CAN_RFLM = DISABLE;       //FIFO没有空间时,新收到的报文覆盖最近一次收到的报文
	CAN_InitStructure.CAN_AWUM = DISABLE;       //手动唤醒模式(软件唤醒)
	CAN_InitStructure.CAN_TTCM = DISABLE;       //关闭时间触发通信模型
	CAN_InitStructure.CAN_ABOM = DISABLE;       //离线自动恢复为手动
	CAN_Init(CAN1, &CAN_InitStructure);         //初始化完成后,CAN外设进入正常模式
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;       //用于初始化过滤器的结构体
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                         //过滤器编号(可选值0~13)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;   //这里把过滤器配置成任何报文都能通过
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; 
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;     //过滤器位宽(32位)
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;      //过滤器模式(屏蔽模式)
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //关联的FIFO(FIFO0)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               //使能过滤器
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)   //单个数据帧报文发送
{
	CanTxMsg TxMessage;                     //CAN报文结构体
	TxMessage.StdId = ID;                   //标准格式的报文ID
	TxMessage.ExtId = ID;                   //扩展格式的报文ID
	TxMessage.IDE = CAN_Id_Standard;		//扩展标志位(选择标准格式)
	TxMessage.RTR = CAN_RTR_Data;           //遥控标志位(选择数据帧)
	TxMessage.DLC = Length;                 //数据段长度
	for (uint8_t i = 0; i < Length; i ++)   //根据数据段长度将形参中的数据数组Data复制到CAN报文结构体成员Data数组中
	{
		TxMessage.Data[i] = Data[i];
	}
	
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);  //将CAN报文结构体中的报文写入发送邮箱,返回发送邮箱编号
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)  //等待邮箱将报文发送
	{
		Timeout ++;
		if (Timeout > 100000)    //等待超时,需要强行退出等待循环
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)    //判断FIFO0中是否有报文接收
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)   //判断FIFO0中挂起报文的数目是否大于0
	{
		return 1;   //有报文则返回1
	}
	return 0;       //无报文则返回0
}

void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)   //单个数据帧报文读取
{
	CanRxMsg RxMessage;                        //CAN报文结构体
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);  //接收报文,报文信息存于CAN报文结构体中
	
	if (RxMessage.IDE == CAN_Id_Standard)      //标准格式和扩展格式的ID分情况进行处理
	{
		*ID = RxMessage.StdId;                 //ID指针指向的内存存储标准格式报文ID
	}
	else
	{
		*ID = RxMessage.ExtId;                 //ID指针指向的内存存储扩展格式报文ID
	}
	
	if (RxMessage.RTR == CAN_RTR_Data)         //处理数据帧报文
	{
		*Length = RxMessage.DLC;              
		for (uint8_t i = 0; i < *Length; i ++) //根据报文数据段长度将报文中的数据拷贝到Data指针指向的内存中
		{
			Data[i] = RxMessage.Data[i];
		}
	}
	else
	{
		//遥控帧的处理,这里没有实现
	}
}

(4)在main.c文件中粘贴以下代码,对每个开发板,其发送的报文ID(全局变量TxID)应该不同,ID自拟,然后编译,将程序分别下载到各个开发板中,依次按下各个开发板对应的按键,观察它们OLED屏上显示的报文内容是否正确,主要观察报文ID(理论上按下按键后,开发板会广播自己的报文,总线上的所以开发板都可以接收之)。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;
uint32_t TxID = 0x555;                          //发送报文ID
uint8_t TxLength = 4;                           //发送报文数据段长度DLC(最大为8)
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44};   //发送报文数据段的内容

uint32_t RxID;                                  //接收报文ID
uint8_t RxLength;                               //接收报文数据段长度DLC
uint8_t RxData[8];                              //接收报文数据段的内容

int main(void)
{
	OLED_Init();    //OLED屏初始化
	Key_Init();     //按键初始化
	MyCAN_Init();   //CAN外设初始化
	
	OLED_ShowString(1, 1, "TxID:");
	OLED_ShowHexNum(1, 6, TxID, 3);
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();    //读取按键码
		
		if (KeyNum == 1)          //按下按键1
		{
			TxData[0] ++;         //数据段第1个字节自增
			TxData[1] ++;         //数据段第2个字节自增
			TxData[2] ++;         //数据段第3个字节自增
			TxData[3] ++;         //数据段第4个字节自增
			
			MyCAN_Transmit(TxID, TxLength, TxData);    //发送一个数据帧
		}
		
		if (MyCAN_ReceiveFlag())    //如果收到报文,则将其接收并将数据段显示在OLED屏上
		{
			MyCAN_Receive(&RxID, &RxLength, RxData);    //接收一个数据帧(本例中的数据帧是自发自收的)
			
			OLED_ShowHexNum(2, 6, RxID, 3);          //接收报文的ID
			OLED_ShowHexNum(3, 6, RxLength, 1);      //接收报文的数据段长度
			OLED_ShowHexNum(4, 6, RxData[0], 2);     //数据段第1个字节
			OLED_ShowHexNum(4, 9, RxData[1], 2);     //数据段第2个字节
			OLED_ShowHexNum(4, 12, RxData[2], 2);    //数据段第3个字节
			OLED_ShowHexNum(4, 15, RxData[3], 2);    //数据段第4个字节
		}
	}
}

4、标准格式/扩展格式—数据帧/遥控帧完整程序

(1)按照下图所示将电路连接好,直接复制上例的项目文件夹。

(2)将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;       //过滤器允许任何报文通过
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)  //发送TxMessage(结构体指针形参)指向的报文
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)   //读取FIFO0中的报文,借用结构体指针RxMessage将其返回
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

(3)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按四次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x555, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x12345678, CAN_Id_Extended, CAN_RTR_Data,   4, {0xAA, 0xBB, 0xCC, 0xDD}},
	{0x666, 0x00000000, CAN_Id_Standard, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	{0x000, 0x0789ABCD, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
};  //该数组中的待发送报文依次为标准格式数据帧、扩展格式数据帧、标准格式遥控帧、扩展格式遥控帧

uint8_t pTxMsgArray = 0;    //待发送报文数组下标索引

CanRxMsg RxMsg;        //存储接收报文结构体的全局变量

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)   //按下按键,发送一帧报文
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);   //四个不同的报文轮流发送
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);               //接收一帧报文
			
			if (RxMsg.IDE == CAN_Id_Standard)      //如果是标准格式的报文
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);   //显示标准格式ID
			}
			else if (RxMsg.IDE == CAN_Id_Extended) //如果是扩展格式的报文
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);   //显示扩展格式ID
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)         //如果是数据帧
			{
				OLED_ShowString(1, 10, "Data  ");        //显示报文类型为数据帧
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);     //显示数据段长度
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);   //显示数据段内容
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)  //如果是遥控帧
			{
				OLED_ShowString(1, 10, "Remote");        //显示报文类型为遥控帧
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);     //显示数据段长度
				
				OLED_ShowHexNum(4, 6, 0x00, 2);   //遥控帧没有数据段
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

5、标识符过滤器的配置

(1)按照下图所示将电路连接好,直接复制上例的项目文件夹。

(2)编写代码实现下图的5种情况,本例的开发板自发自收报文,但是有些ID的报文或者某种类型的报文无法通过过滤器,也即无法接收(下面的示例代码都只使用了1个过滤器,实际运用中可以配置若干个过滤器,全部过滤器允许通过报文的并集就是开发板可接收的报文)。

①例1——16位列表模式(允许某些ID的报文通过):

[1]将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x234 << 5;      //允许通过的报文配置1
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x345 << 5;       //允许通过的报文配置2
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x567 << 5;  //允许通过的报文配置3
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x000 << 5;   //允许通过的报文配置4
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;  //16位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;   //列表模式
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

[2]在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按六次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x123, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x234, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x345, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x456, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x567, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x678, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
};  //6个ID不同的标准格式数据帧

uint8_t pTxMsgArray = 0;

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
			}
			else if (RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

②例2——16位屏蔽模式(允许某些ID的报文通过):

[1]将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x200 << 5;   //允许通过的报文配置1
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (0x700 << 5) | 0x10 | 0x8;  //屏蔽位
	
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x320 << 5;    //允许通过的报文配置2
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = (0x7F0 << 5) | 0x10 | 0x8;   //屏蔽位
	
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;  //16位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;   //屏蔽模式
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

[2]在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按十六次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE           RTR        DLC         Data[8] */
	{0x100, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x101, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x1FE, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x1FF, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第一组报文无法通过过滤器
	{0x200, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x201, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x2FE, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x2FF, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第二组报文可以通过过滤器
	{0x310, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x311, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x31E, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x31F, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第三组报文无法通过过滤器
	{0x320, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x321, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x32E, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x32F, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第四组报文可以通过过滤器
};  //16个ID不同的标准格式数据帧

uint8_t pTxMsgArray = 0;

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
			}
			else if (RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

③例3——32位列表模式(允许某些ID的报文通过):

[1]将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	
	uint32_t ID1 = 0x123 << 21;                              //允许通过的报文配置1
	CAN_FilterInitStructure.CAN_FilterIdHigh = ID1 >> 16;    //允许通过的报文配置1(高位)
	CAN_FilterInitStructure.CAN_FilterIdLow = ID1;           //允许通过的报文配置1(低位)
	
	uint32_t ID2 = (0x12345678u << 3) | 0x4;                 //允许通过的报文配置2
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = ID2 >> 16;//允许通过的报文配置2(高位)
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = ID2;       //允许通过的报文配置2(低位)
	
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;  //32位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;   //列表模式
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

[2]在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按六次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x123, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x234, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x345, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x456, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x12345678, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789ABCD, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
};  //6个ID不同的数据帧

uint8_t pTxMsgArray = 0;

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
			}
			else if (RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

④例4——32位屏蔽模式(允许某些ID的报文通过):

[1]将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	
	uint32_t ID = (0x12345600u << 3) | 0x4;                     //允许通过的报文配置
	CAN_FilterInitStructure.CAN_FilterIdHigh = ID >> 16;        //允许通过的报文配置(高位)
	CAN_FilterInitStructure.CAN_FilterIdLow = ID;               //允许通过的报文配置(低位)
	
	uint32_t Mask = (0x1FFFFF00u << 3) | 0x4 | 0x2;             //屏蔽位
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = Mask >> 16;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = Mask;
	
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;   //32位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;    //屏蔽模式
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

[2]在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按八次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x000, 0x12345600, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x12345601, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x123456FE, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x123456FF, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第一组报文可以通过过滤器
	{0x000, 0x0789AB00, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789AB01, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789ABFE, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789ABFF, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	//第二组报文无法通过过滤器
};  //8个ID不同的扩展格式数据帧

uint8_t pTxMsgArray = 0;

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
			}
			else if (RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

⑤例5——32位屏蔽模式(允许遥控帧通过):

[1]将上例中的MyCAN.c文件修改成如下代码。

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	
	uint32_t ID = 0x2;
	CAN_FilterInitStructure.CAN_FilterIdHigh = ID >> 16;
	CAN_FilterInitStructure.CAN_FilterIdLow = ID;
	
	uint32_t Mask = 0x2;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = Mask >> 16;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = Mask;
	
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

[2]在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按十二次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x123, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x234, 0x00000000, CAN_Id_Standard, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	{0x345, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x456, 0x00000000, CAN_Id_Standard, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	
	{0x000, 0x12345600, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x12345601, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	{0x000, 0x123456FE, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x123456FF, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	
	{0x000, 0x0789AB00, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789AB01, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	{0x000, 0x0789ABFE, CAN_Id_Extended, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x0789ABFF, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
};  //待发送报文中有遥控帧和数据帧

uint8_t pTxMsgArray = 0;

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
			}
			else if (RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
			}
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
			}
			else if (RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

6、中断式接收

(1)按照下图所示将电路连接好,将OLED显示屏的项目文件夹复制一份作为模板使用,并在项目的Hardware组中添加按键模块的代码,即Key.h文件和Key.c文件。

(2)在项目的Hardware组中添加MyCAN.h文件和MyCAN.c文件,用于封装CAN模块的代码。(中断触发的事件有几种,本例仅演示一种,即接收报文触发中断)

①MyCAN.h文件:

#ifndef __MYCAN_H
#define __MYCAN_H

extern CanRxMsg MyCAN_RxMsg;
extern uint8_t MyCAN_RxFlag;

void MyCAN_Init(void);
void MyCAN_Transmit(CanTxMsg *TxMessage);

#endif

②MyCAN.c文件:

#include "stm32f10x.h"                  // Device header

CanRxMsg MyCAN_RxMsg;      //存储接收报文
uint8_t MyCAN_RxFlag;      //标志是否有新接收报文未处理

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);    //中断源选择FMP0,它与FIFO有关
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;   //FIFO0中有接收到报文时会触发中断
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

void USB_LP_CAN1_RX0_IRQHandler(void)    //FIFO0中有接收到报文时触发中断,进入该中断函数
{
	if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) == SET)
	{
		CAN_Receive(CAN1, CAN_FIFO0, &MyCAN_RxMsg);   //接收新报文,存在结构体变量MyCAN_RxMsg中
		MyCAN_RxFlag = 1;                       //置标志位为1,表示有接收到新报文
	}
}

(3)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中,按四次按键,每次操作都观察OLED屏上显示的报文内容是否正确。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsgArray[] = {
/*   StdId     ExtId         IDE             RTR        DLC         Data[8]          */
	{0x555, 0x00000000, CAN_Id_Standard, CAN_RTR_Data,   4, {0x11, 0x22, 0x33, 0x44}},
	{0x000, 0x12345678, CAN_Id_Extended, CAN_RTR_Data,   4, {0xAA, 0xBB, 0xCC, 0xDD}},
	{0x666, 0x00000000, CAN_Id_Standard, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
	{0x000, 0x0789ABCD, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
};

uint8_t pTxMsgArray = 0;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, " Rx :");
	OLED_ShowString(2, 1, "RxID:");
	OLED_ShowString(3, 1, "Leng:");
	OLED_ShowString(4, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);    //发送一个报文
			
			pTxMsgArray ++;
			if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
			{
				pTxMsgArray = 0;
			}
		}
		
		if (MyCAN_RxFlag == 1)     //如果有新报文接收,处理新报文
		{
			MyCAN_RxFlag = 0;      //标志位置0,为下一个新报文的接收做准备
			//报文信息处理……
			if (MyCAN_RxMsg.IDE == CAN_Id_Standard)
			{
				OLED_ShowString(1, 6, "Std");
				
				OLED_ShowHexNum(2, 6, MyCAN_RxMsg.StdId, 8);
			}
			else if (MyCAN_RxMsg.IDE == CAN_Id_Extended)
			{
				OLED_ShowString(1, 6, "Ext");
				
				OLED_ShowHexNum(2, 6, MyCAN_RxMsg.ExtId, 8);
			}
			
			if (MyCAN_RxMsg.RTR == CAN_RTR_Data)
			{
				OLED_ShowString(1, 10, "Data  ");
				
				OLED_ShowHexNum(3, 6, MyCAN_RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, MyCAN_RxMsg.Data[0], 2);
				OLED_ShowHexNum(4, 9, MyCAN_RxMsg.Data[1], 2);
				OLED_ShowHexNum(4, 12, MyCAN_RxMsg.Data[2], 2);
				OLED_ShowHexNum(4, 15, MyCAN_RxMsg.Data[3], 2);
			}
			else if (MyCAN_RxMsg.RTR == CAN_RTR_Remote)
			{
				OLED_ShowString(1, 10, "Remote");
				
				OLED_ShowHexNum(3, 6, MyCAN_RxMsg.DLC, 1);
				
				OLED_ShowHexNum(4, 6, 0x00, 2);
				OLED_ShowHexNum(4, 9, 0x00, 2);
				OLED_ShowHexNum(4, 12, 0x00, 2);
				OLED_ShowHexNum(4, 15, 0x00, 2);
			}
		}
	}
}

7、数据传输策略

(1)按照下图所示将电路连接好,将OLED显示屏的项目文件夹复制一份作为模板使用,并在项目的Hardware组中添加按键模块的代码,即Key.h文件和Key.c文件。

(2)本例实现了三种数据传输策略,分别为:

①定时发送:数据发送方每隔一段时间就广播一个数据帧(本例的定时时间为100ms),实际开发中也叫“周期报文”。

②触发发送:本例将触发事件定为按键被按下,每发生一次触发事件,数据发送方广播一个数据帧,实际开发中也叫“事件报文”。

③请求发送:数据发送方接收到(数据接收方广播的)特定报文时,广播一个数据帧,实际开发中也叫“事件报文”。

(3)复制项目文件夹,作为发送方的代码工程,在项目的Hardware组中添加MyCAN.h文件和MyCAN.c文件,用于封装CAN模块的代码,同时更新main.c文件。(本工程使用了TIM2的定时中断,所以还需要从定时器定时中断的项目文件夹中将Timer.c文件和Timer.h文件拷贝至本项目的Hardware组中,但需要注释Timer.c中已实现的中断函数TIM2_IRQHandler由于几乎无变动,下面不进行Timer模块的代码文件细节展示

①MyCAN.h文件:

#ifndef __MYCAN_H
#define __MYCAN_H

void MyCAN_Init(void);
void MyCAN_Transmit(CanTxMsg *TxMessage);
uint8_t MyCAN_ReceiveFlag(void);
void MyCAN_Receive(CanRxMsg *RxMessage);

#endif

②MyCAN.c文件:

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

③main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"
#include "Timer.h"

uint8_t KeyNum;

uint8_t TimingFlag;     //定时标志
uint8_t TriggerFlag;     //触发标志
uint8_t RequestFlag;    //请求标志

CanTxMsg TxMsg_Timing = {
	.StdId = 0x100,
	.ExtId = 0x00000000,
	.IDE = CAN_Id_Standard,
	.RTR = CAN_RTR_Data,
	.DLC = 4,
	.Data = {0x11, 0x22, 0x33, 0x44}
};

CanTxMsg TxMsg_Trigger = {
	.StdId = 0x200,
	.ExtId = 0x00000000,
	.IDE = CAN_Id_Standard,
	.RTR = CAN_RTR_Data,
	.DLC = 4,
	.Data = {0x11, 0x22, 0x33, 0x44}
};

CanTxMsg TxMsg_Request = {
	.StdId = 0x300,
	.ExtId = 0x00000000,
	.IDE = CAN_Id_Standard,
	.RTR = CAN_RTR_Data,
	.DLC = 4,
	.Data = {0x11, 0x22, 0x33, 0x44}
};

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	Timer_Init();
	
	OLED_ShowString(1, 1, "Tx");
	OLED_ShowString(2, 1, "Tim:");
	OLED_ShowString(3, 1, "Tri:");
	OLED_ShowString(4, 1, "Req:");
	
	while (1)
	{
		/*定时发送*/
		if (TimingFlag == 1)     //定时时间到,将进入if语句程序块
		{
			TimingFlag = 0;      //定时标志清零,为下一次定时做准备
			
			TxMsg_Timing.Data[0] ++;          //数据段内容修改
			TxMsg_Timing.Data[1] ++;          //数据段内容修改
			TxMsg_Timing.Data[2] ++;          //数据段内容修改
			TxMsg_Timing.Data[3] ++;          //数据段内容修改
			
			MyCAN_Transmit(&TxMsg_Timing);    //发送定时数据帧
			
			OLED_ShowHexNum(2, 5, TxMsg_Timing.Data[0], 2);
			OLED_ShowHexNum(2, 8, TxMsg_Timing.Data[1], 2);
			OLED_ShowHexNum(2, 11, TxMsg_Timing.Data[2], 2);
			OLED_ShowHexNum(2, 14, TxMsg_Timing.Data[3], 2);
		}
		
		/*触发发送*/
		KeyNum = Key_GetNum();
		if (KeyNum == 1)          //按下按键1
		{
			TriggerFlag = 1;      //触发标志置1
		}
		
		if (TriggerFlag == 1)     //触发标志为1,将进入if语句程序块
		{
			TriggerFlag = 0;      //触发标志清零,为下一次触发做准备
			
			TxMsg_Trigger.Data[0] ++;          //数据段内容修改
			TxMsg_Trigger.Data[1] ++;          //数据段内容修改
			TxMsg_Trigger.Data[2] ++;          //数据段内容修改
			TxMsg_Trigger.Data[3] ++;          //数据段内容修改
			
			MyCAN_Transmit(&TxMsg_Trigger);    //发送触发数据帧
			
			OLED_ShowHexNum(3, 5, TxMsg_Trigger.Data[0], 2);
			OLED_ShowHexNum(3, 8, TxMsg_Trigger.Data[1], 2);
			OLED_ShowHexNum(3, 11, TxMsg_Trigger.Data[2], 2);
			OLED_ShowHexNum(3, 14, TxMsg_Trigger.Data[3], 2);
		}
		
		/*请求发送*/
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);     //接收一帧报文
			
			if (RxMsg.IDE == CAN_Id_Standard &&
				RxMsg.RTR == CAN_RTR_Remote &&
				RxMsg.StdId == 0x300)  //如果报文是ID为0x300的标准格式遥控帧
			{
				RequestFlag = 1;       //请求标志置1
			}
			
			if (RxMsg.IDE == CAN_Id_Standard &&
				RxMsg.RTR == CAN_RTR_Data &&
				RxMsg.StdId == 0x3FF)  //如果报文是ID为0x3FF的标准格式数据帧
			{
				RequestFlag = 1;       //请求标志置1
			}
		}
		
		if (RequestFlag == 1)     //请求标志为1,将进入if语句程序块
		{
			RequestFlag = 0;      //请求标志清零,为下一次请求做准备
			
			TxMsg_Request.Data[0] ++;          //数据段内容修改
			TxMsg_Request.Data[1] ++;          //数据段内容修改
			TxMsg_Request.Data[2] ++;          //数据段内容修改
			TxMsg_Request.Data[3] ++;          //数据段内容修改
			
			MyCAN_Transmit(&TxMsg_Request);    //发送请求数据帧
			
			OLED_ShowHexNum(4, 5, TxMsg_Request.Data[0], 2);
			OLED_ShowHexNum(4, 8, TxMsg_Request.Data[1], 2);
			OLED_ShowHexNum(4, 11, TxMsg_Request.Data[2], 2);
			OLED_ShowHexNum(4, 14, TxMsg_Request.Data[3], 2);
		}
	}
}

void TIM2_IRQHandler(void)     //配置为每隔100ms进入一次TIM2的中断函数
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		TimingFlag = 1;        //定时标志置1,表示定时时间到
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

(4)复制项目文件夹,作为接收方的代码工程,在项目的Hardware组中添加MyCAN.h文件和MyCAN.c文件用于封装CAN模块的代码,同时更新main.c文件。

①MyCAN.h文件:

#ifndef __MYCAN_H
#define __MYCAN_H

void MyCAN_Init(void);
void MyCAN_Transmit(CanTxMsg *TxMessage);
uint8_t MyCAN_ReceiveFlag(void);
void MyCAN_Receive(CanRxMsg *RxMessage);

#endif

②MyCAN.c文件:

#include "stm32f10x.h"                  // Device header

void MyCAN_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
	CAN_InitStructure.CAN_Prescaler = 48;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
	CAN_InitStructure.CAN_NART = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MyCAN_Transmit(CanTxMsg *TxMessage)
{
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
	
	uint32_t Timeout = 0;
	while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
	{
		Timeout ++;
		if (Timeout > 100000)
		{
			break;
		}
	}
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}

③main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;

CanTxMsg TxMsg_Request_Remote = {
	.StdId = 0x300,
	.ExtId = 0x00000000,
	.IDE = CAN_Id_Standard,
	.RTR = CAN_RTR_Remote,
	.DLC = 0,
	.Data = {0x00}
};

CanTxMsg TxMsg_Request_Data = {
	.StdId = 0x3FF,
	.ExtId = 0x00000000,
	.IDE = CAN_Id_Standard,
	.RTR = CAN_RTR_Data,
	.DLC = 0,
	.Data = {0x00}
};

CanRxMsg RxMsg;

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	OLED_ShowString(1, 1, "Rx");
	OLED_ShowString(2, 1, "Tim:");
	OLED_ShowString(3, 1, "Tri:");
	OLED_ShowString(4, 1, "Req:");
	
	while (1)
	{
		/*请求部分*/
		KeyNum = Key_GetNum();
		if (KeyNum == 1)
		{
			MyCAN_Transmit(&TxMsg_Request_Remote);  //发送特定遥控帧进行请求
		}
		if (KeyNum == 2)
		{
			MyCAN_Transmit(&TxMsg_Request_Data);    //发送特定数据帧进行请求
		}
		
		/*接收部分*/
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			
			if (RxMsg.RTR == CAN_RTR_Data)
			{
				/*收到定时数据帧*/
				if (RxMsg.StdId == 0x100 && RxMsg.IDE == CAN_Id_Standard)
				{
					OLED_ShowHexNum(2, 5, RxMsg.Data[0], 2);
					OLED_ShowHexNum(2, 8, RxMsg.Data[1], 2);
					OLED_ShowHexNum(2, 11, RxMsg.Data[2], 2);
					OLED_ShowHexNum(2, 14, RxMsg.Data[3], 2);
				}
				
				/*收到触发数据帧*/
				if (RxMsg.StdId == 0x200 && RxMsg.IDE == CAN_Id_Standard)
				{
					OLED_ShowHexNum(3, 5, RxMsg.Data[0], 2);
					OLED_ShowHexNum(3, 8, RxMsg.Data[1], 2);
					OLED_ShowHexNum(3, 11, RxMsg.Data[2], 2);
					OLED_ShowHexNum(3, 14, RxMsg.Data[3], 2);
				}
				
				/*收到请求数据帧*/
				if (RxMsg.StdId == 0x300 && RxMsg.IDE == CAN_Id_Standard)
				{
					OLED_ShowHexNum(4, 5, RxMsg.Data[0], 2);
					OLED_ShowHexNum(4, 8, RxMsg.Data[1], 2);
					OLED_ShowHexNum(4, 11, RxMsg.Data[2], 2);
					OLED_ShowHexNum(4, 14, RxMsg.Data[3], 2);
				}
			}
		}
	}
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐