STM32入门教程(CAN通信篇)
重要的内容写在前面:
- 该系列是以up主江协科技的STM32视频教程为基础写下去的,大部分内容都参考了老师的课件,对于一些个人认为比较重要但是老师仅口述的部分,笔者都有用文字的方式记录并标出了重点。
- 文中的图片基本都来源于老师的课件以及开发板和芯片的手册,粘贴过来是为了方便阅读。
- 如果有条件的可以先学习一些相关课程再去看STM32的教程,学起来会更加轻松(不太建议零基础开始直接STM32,听起来可能会有点困难,可以先学51单片机),相关课程有数字电路(强烈推荐先学数电,不然可能会有很多地方理解起来很困难)、模拟电路、计算机组成原理(像寄存器、存储器、中断等在这门课里有很详细的介绍)、计算机网络等。
- 如有错漏欢迎指出。
视频链接:[1-1] CAN简介&硬件电路_哔哩哔哩_bilibili
一、CAN协议
1、概述
(1)CAN总线(Controller Area Network Bus)的全称是控制器局域网总线,它构建的是一种局域网网络,每一个挂载在CAN总线上的设备都可以利用这个局域网发送自己的消息,也可以接收局域网的各种消息。
(2)CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线,广泛应用于汽车、嵌入式、工业控制等领域。
(3)CAN总线的特征:
①只需要两根通信线(CAN_H、CAN_L),线路少,无需共地。
②差分信号通信(利用两个信号的电压差值传递信息),抗干扰能力强。
③CAN总线的两种标准:
[1]高速CAN(ISO11898):125k~1Mbps, <40m。
[2]低速CAN(ISO11519):10k~125kbps, <1km。
④异步通信,无需时钟线,通信速率由设备各自约定。
⑤半双工通信,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序。
⑥11位/29位报文ID,用于区分消息的功能,同时决定消息发送的优先级(ID号小的优先发送)。
⑦一个数据帧可配置1~8字节的有效载荷。
⑧可实现广播式和请求式两种传输方式。
⑨拥有应答、CRC校验、位填充、位同步、错误处理等特性。
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_H和CAN_L其中一端添加2.2kΩ的终端电阻,它们同样有防止回波反射的作用。
(2)CAN电平标准:CAN总线采用差分信号,即两线电压差(VCAN_H-VCAN_L)传输数据位。
①高速CAN规定:电压差为0V时表示逻辑1(隐性电平),电压差为2V时表示逻辑0(显性电平)。
②低速CAN规定:电压差为-1.5V时表示逻辑1(隐性电平),电压差为3V时表示逻辑0(显性电平)。
(3)闭环CAN总线中,当设备想发送数据0时,CAN收发器会将两根通信线的电压差“拉大”,此时通信线上的数据为0;当设备想发送数据1时,CAN收发器不做任何操作,终端电阻会将将两根通信线的电压维持在同一水平,此时通信线上的数据为1。
(4)CAN收发器的引脚分布:
①GND和VCC需要连接5V的电源。
②TXD和RXD与设备的CAN控制器相连。
③CANH和CANL与CAN总线相连。
④Vref是参考电压输出,可以不使用。
⑤S用于选择高速模式/静默模式,可以不选择(默认输入低电平)。
(5)CAN收发器的内部框图:
①输入部分:
[1]CANH和CANL与接收器(RECEIVER)相连,接收器可以时刻检测总线电压差,如果总线有电压差就输出1,如果没有就输出0。
[2]接收器输出通过两个场效应管的输出驱动器输出到RXD引脚,其中当接收器输出1时,上面的场效应管断开,下面的场效应管导通,RXD输出0,当接收器输出0时,上面的场效应管导通,下面的场效应管断开,RXD输出1。(简单说,两个场效应管整体上可视作一个反相器)
②输出部分:
[1]当TXD输入1时,驱动器(DRIVER)会让其连接的两个场效应管全部断开,相当于不对总线进行任何操作,总线在终端电阻的作用下电压差为0(简称“收紧”),另外CANH和CANL之间都通过一个“中拉电阻”连接到0.5V的电源,这样会使CANH和CANL的默认对地电压都是2.5V左右,而且这两个电阻也有一定的“收紧”作用。
[2]当TXD输入0时,驱动器(DRIVER)会让其连接的两个场效应管全部导通,CANH受VCC影响,电压水平升高,CANL受GND影响,电压水平降低,由此形成电压差。
[3]当TXD悬空时,其输入端连接的VCC会让TXD保持默认输入高电平1的状态(电流源的作用相当于一个上拉电阻)。
[4]如果TXD出现异常,始终输入0,那么CAN总线将始终呈现显性电平0的状态,整条总线将会瘫痪,为了解决这个问题,TXD的输入侧设有TXD显性超时计时器,当TXD一直不释放CAN总线时,超过一定时间后计时器将会强制将总线释放。
[5]当温度出现异常时,温度保护(TEMPERATURE PROTECTION)会控制驱动器切断输出,以免干扰CAN总线。
(6)CAN物理层的特性:
3、CAN协议帧格式
(1)CAN协议规定了以下5种类型的帧:(下面主要介绍数据帧,除非有特殊说明)
帧类型 | 用途 |
数据帧 | 发送设备主动发送数据(广播式) |
遥控帧 | 接收设备主动请求数据(请求式) |
错误帧 | 某个设备检测出错误时向其他设备通知错误 |
过载帧 | 接收设备通知其尚未做好接收准备 |
帧间隔 | 用于将数据帧及遥控帧与前面的帧分离开 |
(2)数据帧的构成:
①数据帧分为标准格式和扩展格式两种,其中扩展格式增加了18位ID位数,能承载更多种类的ID。
②图例说明:灰色方块D表示显性电平0,白色方块R表示隐性电平1,紫色方块D/R表示0/1;灰白双色块是应答位特有,在这一位中发送方必须发送隐性电平1,接收方必须发送显性电平0,表示接收方对发送方的应答;格式图中方块内的数字表示方块中有多少位数据。
③标准格式数据帧发送过程:
[1]在发送数据帧前,总线必须处于空闲状态,空闲状态时总线处于隐性电平1。
[2]数据帧第一位代表帧起始,此位必须是显性电平0。
[3]起始位发送完毕后,发送11位报文ID,它可以表示后面数据段Data的功能,同时可用于区分优先级,当多个设备同时发送报文时,根据仲裁规则,ID小的报文优先发送,ID大的报文等待下一次总线空闲再重试发送。
[4]报文ID发送完毕后发送RTR位,它是远程请求标志位,用于区分数据帧和遥控帧,数据帧中该位必须为显性电平0,该位连同报文ID被称为仲裁段,当报文ID相同时,数据帧的优先级高于遥控帧。
[5]接着发送IDE位和r0位,IDE用于区分标准格式和扩展格式,标准格式中该位必须为显性电平0,r0位现在改为FDF位,用于区分CAN与CANFD格式,CAN格式报文中该位必须也为显性电平0。
[6]CAN总线的一帧数据可以有1~8个字节有效载荷,而DLC正是用于表示数据段的长度,0001表示数据段包含1字节数据,1000表示数据段包含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]最后发送方发送7个1作为EOF,即帧结束。
④扩展格式数据帧与标准格式数据帧的差别:
[1]SRR位替代了标准格式中的RTR位,它与仲裁流程相关,原本的RTR位则是挪到了新增的18位ID之后。
[2]IDE位用于区分标准格式和扩展格式,扩展格式中该位必须为显性电平1,IDE位之前扩展格式数据帧和标准格式数据帧没有什么差异。
[3]在IDE之后是新增的18位ID,报文ID发送完毕后发送RTR位,之后的帧格式和标准格式基本一样(由于IDE位在先前已经出现,所以标准格式中的IDE位在这里可以闲置,成为保留位r1,当然,现在r1位已经被FDF位占用,用于判断报文格式是CAN还是CANFD)。
(3)数据帧的发展历史:
①CAN 1.2时期,仅存在标准格式,IDE位当时仍为保留位r1。
②CAN 2.0时期,ID不够用,出现了扩展格式,增加了ID的位数,为了区分标准格式与扩展格式,协议将标准格式中的r1赋予了新功能—IDE。
(4)其它4种类型的帧:
①遥控帧:遥控帧无数据段,RTR为隐性电平1,其它部分与数据帧相同。(一个设备发送遥控帧,如果收到遥控帧的某个设备有遥控帧ID号对应的数据,该设备会通过数据帧将该数据广播)
②错误帧:总线上所有设备都会监督总线的数据,一旦发现“位错误”或“填充错误”或“CRC错误”或“格式错误”或“应答错误”,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备。
③过载帧:当接收方收到大量数据而无法处理时,其可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失。
④帧间隔:将数据帧和遥控帧与前面的帧分离开。(下一图是设备处于主动错误状态时的帧间隔,下二图是设备处于被动错误状态时的帧间隔)
(5)位填充:
①位填充规则:针对数据帧和遥控帧,发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位时,会自动移除填充位,恢复原始数据。
②位填充的作用:
[1]增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机。
[2]将正常数据流与“错误帧”和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性。
[3]保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲。
③举例:
4、位时序与位同步
(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/一个数据位的时长。
5、总线的资源分配规则(仲裁)
(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,这时扩展数据帧将会被淘汰。
6、错误处理
(1)当某个设备检测到总线的帧传输出错时,它可以主动发出错误帧以通知其它设备该帧有误,其它设备将放弃该帧。CAN总线规定了5种可能出现的错误类型,如下所示。
(2)如果总线上有一个设备自身出现问题,它将接收到的正常帧认为是传输有误的帧,那么它将会不断破坏总线上的帧,这样总线将会面临瘫痪,为了避免这个问题,还需要设置一个“错误状态”,每个设备内部管理一个TEC(发送错误计数器,设备在发送时,每发现一次发送错误计数增加,进行一次正常的发送计数减小)和REC(接收错误计数器,设备在接收时,每发现一次接收错误计数增加,进行一次正常的接收计数减小),根据TEC和REC的值确定自己的状态。
①当TEC和REC的计数值较小时,设备检出错的频率较低,进入主动错误状态,主动错误状态的设备正常参与通信,并在检测到错误时发出主动错误标志。
②当TEC或REC的计数值较大时,设备检出错的频率稍高,进入被动错误状态,被动错误状态的设备正常参与通信,但检测到错误时只能发出被动错误标志。
③在被动错误状态下,当TEC的计数值较大时,意味着设备检测出最近自己发送的内容出错概率很大,为了防止继续发送错误的内容影响其它设备,它需要进入总线关闭状态,总线关闭状态的设备不能参与通信,不过考虑到这个设备可能非常重要,当它在总线上检测到128次连续的11个位的隐性位时,将转回主动错误状态,TEC和REC的计数清零,如果该设备的发送功能确实存在问题,那么它不久后还是会回到总线关闭态。
(3)错误计数值的变动规则:
二、STM32中的CAN外设
1、简介
(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_TX引脚复用在PA12引脚上(两个引脚默认给CAN外设使用)。要想使用STM32F103C8T6的CAN资源,可以通过PA11和PA12这两个引脚(CAN_RX可以重映射到PB8引脚,CAN_TX可以重映射到PB9引脚),另外USB资源的两个引脚也复用在了PA11和PA12这两个引脚上,所以CAN资源和USB资源无法同时使用。
(2)前面一直提到的STM32中的CAN资源,就是下图中的CAN控制器。
(3)下图中的TJA1050是CAN收发器,其CANH和CANL引脚连接在CAN总线上,GND和S引脚接地,VCC引脚连接5V电压源,TXD和RXD引脚与CAN控制器(可以是STM32的CAN资源)相连。
3、CAN框图
(1)STM32中的CAN外设框图如下图所示,CAN2辅助CAN1工作,它们共同管理一个CAN总线,只有互联型设备才有CAN2,对于STM32F103C8T6,它只有CAN1。
(2)可以通过读写下图左侧中的寄存器对CAN电路进行配置,或者获取电路的各种状态。
(3)发送邮箱有3个,每个邮箱可以存入一个CAN报文,如果想发出一个报文,可以把这个报文写入到其中一个空置邮箱,然后设置寄存器请求发送即可,后续步骤均由硬件电路自动执行。
(4)当CAN总线上出现一个数据帧或者遥控帧时,CAN硬件电路都会把这个报文缓存,首先报文进入过滤器,程序员可以设置过滤规则,也就是作为接收方需要接收ID号为多少的报文,不符合规则的报文不予以处理,符合规则的报文则存入接收FIFO(先进先出寄存器,也可认为是队列寄存器,每个队列可存放3个报文,这是为了防止CPU处理报文的速度慢于CAN控制器接收报文的速度而造成报文丢失)。需要说明的是,过滤器中有若干滤波器,当报文ID符合要求时,总有一个滤波器会允许报文通过,否则任何一个滤波器都不允许报文通过。
4、CAN基本工作流程
(1)对于STM32F103C8T6,CAN_TX对应PA12,需要将其配置为复用推挽输出模式,CAN_RX对应PA11,需要将其配置为上拉输入模式。
(2)当CPU想发送报文时,需要将报文信息写进空置的发送邮箱(若邮箱中有未发送报文,则不是空置邮箱),发送和接收控制器会将报文按CAN协议在CAN总线空闲时发送到总线上。当不止有一个发送邮箱中有待发送报文时,可以配置发送策略,以决定哪个报文优先发送。
(3)当CAN总线上有报文信息时,发送和接收控制器会将报文按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应该只代表两个位,因为报文在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、中断
(1)当CAN外设内部发生一些重要的事件时,程序可以自动跳转到相应的中断函数执行。
(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协议规定的基本一样,不过STM32中有一些个性化设计。
TEC和REC根据错误的情况增加或减少。
ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启。
三、代码编写
1、stm32提供的库函数
在stm32f10x_can.h文件中有CAN相关的函数,这些库函数已经封装好了,程序员可以忽视底层硬件,直接调用库函数即可实现CAN通信。几个库函数的作用如下:
CAN_DeInit函数:恢复CAN缺省配置。
CAN_Init函数:初始化CAN外设。
CAN_FilterInit函数:初始化过滤器。
CAN_StructInit函数:初始化用来初始化CAN外设的结构体,给它赋一个默认值。
CAN_SlaveStartBank函数:用于配置CAN2的起始滤波器信号(互联型设备需要配置)。
CAN_DBGFreeze函数:配置用于调试的冻结模式。
CAN_TTComModeCmd函数:使能TTCM模式中的TGT位。
CAN_Transmit函数:用于发送CAN报文结构体指针形参TxMessage指向的报文,结构体成员存储报文各段的内容(比如数据段、报文ID),函数返回报文存入的发送邮箱编号。
CAN_TransmitStatus函数:获取指定发送邮箱的状态。
CAN_CancelTransmit函数:取消指定邮箱中待发送报文的发送。
CAN_Receive函数:接收指定FIFO中的报文,通过CAN报文结构体指针形参RxMessage将报文内容返回。
CAN_FIFORelease函数:释放指定邮箱(该步骤其实已经集成在CAN_Receive函数中)。
CAN_MessagePending函数:获取指定FIFO中的报文数目。
CAN_OperatingModeRequest函数:配置指定CAN外设的工作模式。
CAN_Sleep函数:使指定CAN外设进入睡眠模式。
CAN_WakeUp函数:唤醒指定CAN外设。
CAN_GetLastErrorCode函数:获得指定CAN外设最近一次的错误码。
CAN_GetReceiveErrorCounter函数:获取指定CAN外设REC计数器的值。
CAN_GetLSBTransmitErrorCounter函数:获取指定CAN外设TEC计数器的值。
CAN_ITConfig函数:用于启用中断功能,需要选择中断源及指定CAN外设。
CAN_GetFlagStatus函数:获取状态寄存器标志位。
CAN_ClearFlag函数:清除状态寄存器标志位。
CAN_GetITStatus函数:获取中断标志位。
CAN_ClearITPendingBit函数:清除中断标志位。
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)发送方部分代码(定时器2的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"
#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)接收方部分代码:
①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);
}
}
}
}
}