CAN总线通信协议

目录

一、CAN总线的介绍

二、主流通信协议对比

1.CAN硬件电路

 三、CAN电平标准

四、CAN总线帧格式

1.CAN总线的5种类型帧

2.CAN总线的帧介绍

1.CAN总线的两种数据格式(标准格式与扩展格式)

2.遥控帧

3.错误帧

4.过载帧

5.帧间隔

3.位填充

4.波形示例

五、接收方数据采集

1.接收方数据采样遇到的问题

2.位时序

3.硬同步

4.再同步

5.波特率计算

六、多设备同时发送遇到的问题

1.资源分配规则1-先占先得

2.资源分配规则2-非破坏性仲裁

1.数据帧和遥控帧的优先级

2.标准格式和扩展格式的优先级

3.为什么叫非破坏性仲裁呢?

七、CAN总线错误介绍

1.错误类型

2.错误状态

3.错误状态

4.波形示例

八、STM32 CAN外设简介(CAN控制器)

1.外设介绍

2.CAN网络拓扑结构

3.CAN框图

4.CAN基本结构

5.发送过程

6.接收过程

7.发送和接收配置位

8.标识符过滤器

过滤器配置示例

九、测试模式

十、工作模式

十一、位时间特性

十二、中断

十三、时间触发通信

十四、错误处理和离线恢复

十五、CAN总线的数据传输策略

1.定时传输,适合频繁使用的数据

2.触发传输,适合一些报警或者通知信息

3.请求传输,适合接收方偶尔有请求才需要的数据

十六、代码示例(使用的是标准库)

1.CAN总线单个设备环回测试

2.标准格式-扩展格式-数据帧-遥控帧

3.中断式接收

4.数据传输策略


一、CAN总线的介绍

  1. CAN总线--控制器局域网总线
  2. CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、性能高的串行通信总线,广泛用于汽车、嵌入式、工业控制等领域
  3. CAN总线特征:
  • 两根通信线(CAN_H、CAN_L),线路少
  • 差分通信,抗干扰能力强
  • 高速CAN(ISO11898):125k~1Mbps,<40m
  • 低速CAN(ISO11519):10k~125kbps,<1km
  • 异步,无需时钟线,通信速率由设备各自约定
  • 半双工,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序
  • 11位/29位报文ID,用于区分消息功能,同时决定优先级
  • 可配置1~8字节的有效载荷
  • 可实现广播式和请求式两种传输方式
  • 应答、CRC校验、位填充、位同步、错误处理等特性

二、主流通信协议对比

1.CAN硬件电路

  • 每个设备通过CAN收发器挂载在CAN总线网络上
  • CAN控制器引出的TX和RX与CAN收发器相连,CAN收发器引出的CAN_H和CAN_L分别与总线的CAN_H和CAN_L相连
  • 高速CAN使用闭环网络,CAN_H和CAN_L两端添加120Ω的终端电阻
  • 低速CAN使用开环网络,CAN_H和CAN_L其中一端添加2.2kΩ的终端电阻

 三、CAN电平标准

这里我总结的就是:

  • 隐性->收缩->1 
  • 显性->拉伸->0

这个后面分析数据帧的时候会用到

四、CAN总线帧格式

1.CAN总线的5种类型帧

  1. 数据帧:发送设备主动发送数据(广播式)
  2. 遥控帧:接收设备主动请求数据(请求式)
  3. 错误帧:某个设备检测出错误时向其他设备通知错误
  4. 过载帧:接收设备通知其尚未做好接收设备
  5. 帧间隔:用于将数据帧及遥控帧与前面的帧分离开

注意:数据帧必须以显性0开头,作用是打破总线空闲,开始一帧数据,同时也告诉接收方,接下来的一段时间里,我要是再释放总线(总线处于隐性1),那就不是空闲了,而是我想要发送的就是1。

2.CAN总线的帧介绍

1.CAN总线的两种数据格式(标准格式与扩展格式)

 数据帧各部分用途简介

  1. SOF:帧起始,表示后面一段波形为传输的数据位
  2. ID:标识符,区分功能,同时决定优先级(ID越小优先级越大)
  3. RTR:占据1位,远程请求位,区分数据帧和遥控帧,数据帧为显性0,遥控帧隐性1
  4. IDE:ID扩展标志位,用于区分标准格式还是拓展格式。标准格式显性0,拓展格式隐性1
  5. SRR:代替RTR,协议升级时留下的无意义位
  6. r0/r1:保留位,为后续协议升级留下空间,且必须为显性0
  7. DLC:数据长度,指定数据段有几个字节
  8. Data:数据段的1~8个字节有效数据
  9. CRC:循环冗余校验,校验数据是否正确
  10. ACK:应答位,判断数据有没有被接收方接收。这里发送方会释放总线,如果接收方发出显性电平0的话就表示有设备接收,反之则没有
  11. CRC/ACK界定符:为应答位前后发送方和接收方释放总线留下时间
  12. EOF:帧结束,表示数据位已经传输完毕,7个隐性1作为EOF

CAN总线的设计,取消了从机地址的概念,它不对从机地址进行分配,而是对报文信息ID进行分配,这样做的好处是同一条报文,可以被多个设备同时接收。

2.遥控帧

遥控帧无数据段,RTR为隐性电平1,其他部分与数据帧相同

3.错误帧

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

4.过载帧

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

5.帧间隔

将数据帧和遥控帧与前面的帧分离开

3.位填充

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

例如:

          即将发送:    100000110      10000011110      0111111111110

          实际发送:    1000001110     1000001111100  011111011111010

          实际接收:    1000001110     1000001111100  011111011111010

          移除填充后: 100000110      10000011110      0111111111110

位填充的作用:

  • 增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精准掌握数据采样时机。(长时间不变的波形容易导致接收出错)(一直连续相同的电平,会使位与位之间没有分界点,接收方没法利用数据的跳变沿,判定数据的采样点是不是在一位的中间,也无法得知采样点的位置是否正确)
  • 将正常数据与“错误帧”(包含连续6个相同的电平)和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性
  • 保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲(CAN总线规定,当总线出现连续11个隐性后,即认为空闲)

4.波形示例

五、接收方数据采集

  • CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长
  • 发送方以约定的位时长每个固定时间输出一个数据位
  • 接收方以约定的位时长每个固定的时间采样总线的电平,输入一个数据位
  • 理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近

1.接收方数据采样遇到的问题

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

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

2.位时序

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

PTS传播时间段用于吸收网络上的物理延迟。

3.硬同步

解决的问题是:是接收方第一个采样点与波形的第一位对齐,防止出现第一次采样太靠近边沿的问题

  • 每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文其他所有设备(接收方)收到SOF的下降沿时,接收方会将自己的位时序计时周期拨到SS段的位置,与发送方的位时序计时周期保持同步。
  • 硬同步只在帧的第一个下降沿(SOF下降沿)有效
  • 经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然会对齐数据中心附近

4.再同步

  • 若发送方或接收方的时钟有误差,随着误差积累,数据位边沿逐渐偏离SS段,则此时接收方根据再同步补偿宽度值(SJW最大补偿值)通过加长PBS1段,或缩短PBS2段,以调整同步
  • 再同步可以发生在第一个下降沿之后的每个数据位跳变边沿

5.波特率计算

  • 波特率 = 1 / 一个数据位的时长 = 1 / (TSS + TPTS + TPBS1 + TPBS2)
  • 例如:
    •  SS = 1TqPTS = 3TqPBS1 = 3TqPBS2 = 3Tq
    • Tq = 0.5us
    • 波特率 = 1 / (0.5us + 1.5us + 1.5us + 1.5us) = 200kbps

六、多设备同时发送遇到的问题

  • CAN总线只有一对差分信号线,同一时间只能有一个设备操作总线发送数据,若多个设备同时有发送需求,该如何分配总线资源?
  • 解决问题的思路:制定资源分配规则,依次满足多个设备的发送需求。确保同一时间只有一个设备操作总线。

1.资源分配规则1-先占先得

是解决发送需求再时间片有重叠,类似于stm32优先级只有响应式没有抢占式

  • 若当前已经有设备正在操作总线发送数据帧/遥控帧,则其他任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据)
  • 任何设备检测到连续11个隐性电平(判断当前是否有设备发送数据的方法),即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧
  • 一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平,其他设备自然也不会破坏当前发送
  • 若总线活跃状态其他设备有发送需求,则需要等待总线变为空闲,才能执行发送需求

2.资源分配规则2-非破坏性仲裁

  • 若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(仲裁段)进行非破坏性仲裁,ID号小的(优先高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试发送。
  • 实现非破坏性仲裁需要两个要求:
    • 线与特性:总线上任何一个设备发送显性0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0&X&X=0,1&1&1=1
    • 回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发送0读回必然是0,发出1读回不一定是1

非破坏仲裁过程:数据位从前到后依次比较,出现差异且数据位为1的设备仲裁失利

在仲裁场里,会执行位填充,位填充插入的位,也会参与仲裁,但是插入的填充位并不会影响仲裁,也不会改变原有的ID号的优先级

1.数据帧和遥控帧的优先级

数据帧和遥控帧ID号一样时,数据帧的优先级高于遥控帧(数据帧在RTR位为显性0,遥控帧在RTR位为隐性1)

2.标准格式和扩展格式的优先级

标准格式11位ID号和扩展格式29位ID号的高11位一样时,标准格式的优先级高于扩展格式(SRR必须始终为1,以保证此要求)

3.为什么叫非破坏性仲裁呢?

从仲裁过程中我们可以看出,仲裁胜利者,它仲裁段所有位发出和读回的都一样,从胜利者的视角来看,它根本就没有感受到任何的竞争者,他的发送过程也没有因为仲裁而受到任何影响,所以整个过程是非破坏性的。

七、CAN总线错误介绍

1.错误类型

错误类型共有5种:位错误填充错误、CRC错误、格式错误、应答错误        

位错误:利用回读机制,每个设备在发送一个位后,自己都会再从总线上采样,把这一位读回来,然后对比,发出的位和总线现实读的位,两者是否一样。

2.错误状态

  • 主动错误状态的设备正常参与通信并在检测到错误时发出主动错误帧
  • 被动错误状态的设备正常参与通信单检测到错误时只能发出被动错误帧
  • 总线关闭状态的设备不能参与通信
  • 每个设备内部管理一个TEC和REC,根据TEC和REC的值确定自己的状态

作为发送器发送错误标志时,无论“主动错误”还是“被动错误”都必然包括6个连续同极性的位,使其他节点也识别到总线错误,进而使所有节点都能丢弃当前出错的帧。

作为接收器发送错误标志时,“主动错误”标志使其他节点也识别到总线错误从而使所有节点(包括作为发送器的节点)都能丢弃当前出错的帧;“被动错误”标志不影响总线通信从而使其他节点都能成功接收当前帧,处于“被动错误”状态的节点属于“不可信”状态,其检测到错误仅是自己丢弃当前帧,这也是错误管理的灵活性所在,即错误响应并不是严格在任何情况下都使所有节点丢弃同一帧报文。

综上所述,错误管理机制可以使所有的节点同时接收或丢弃总线的同一帧报文,又可以使作为接收器的被动错误状态的节点仅自己丢弃当前报文而不影响其他节点。因此,错误管理是实现CAN通信数据一致性的机制之一。

3.错误状态

4.波形示例

设备处于主动错误状态,发送标准数据帧,正常传输

设备处于主动错误状态,发送标准错误帧,检测到ACK错误

设备处于被动错误状态,发送标准数据帧,检测到ACK错误

提问:为什么正常传输的EOF要给7个隐性1,不是一个就可以判断数据结束嘛

从ACK错误的现象大概推测,可能这7位EOF是为错误标志位的叠加留下空间,要不然你的EOF太短了,错误标志还没检测到,数据帧就直接结束了,那接收方就不好对对应的错误进行处理了

八、STM32 CAN外设简介(CAN控制器)

1.外设介绍

  • STM32内置bxCAN外设(CAN控制器),支持CAN2.0A和2.0B,可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节
  • 波特率最高可达1兆/秒
  • 3个可配置优先级的发送邮箱
  • 2个3级深度的接收FIFO
  • 14个过滤器组(互联型28个),过滤器组的作用是过滤报文直接收想要的报文
  • 时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式

2.CAN网络拓扑结构

这里注意VCC供电必须接5V不能接3.3V,因为协议规定,CANH引脚,在显性电平时,电压为3.5V,如果供电只有3.3V的话,那CAN总线电压,就没法符合规范了

CAN收发器与单片机的CAN控制器的接线不需要交叉连接

3.CAN框图

4.CAN基本结构

5.发送过程

基本流程:选择一个空闲邮箱-->写入报文-->请求发送

RQCP:邮箱发送请求完成标志位,TXOK:邮箱发送成功标志位,TME:发送邮箱空标志位

6.接收过程

基本流程:接收到一个报文-->匹配过滤器后进入FIFO 0或FIFO 1-->CPU读取

FMP:报文数量、 FOVR:FIFO溢出标志位

7.发送和接收配置位

  • NART:置1,关闭自动重传,CAN报文只被发送1次,不管发送的结果如何(成功、出错或仲裁丢失);置0,自动重传,CAN硬件在发送报文失败时会一直自动重传直到发送成功
  • TXFP:置1,优先级由发送请求的顺序来决定,先请求先发送;置0,优先级由报文标识符ID来决定,标识符ID置越小的先发送(标识符相等时,邮箱号小的报文先发送)
  • RFLM:置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖

8.标识符过滤器

  • 每个过滤器的核心由两个32位寄存器组成:R1[31:0]和R2[31:0]
  • FSCx:位宽设置,置0,16位;置1,32位
  • FBMx:模式设置,置0,屏蔽模式;置1,列表模式
  • FFAx:关联设置,置0,FIFO 0;置1,FIFO 1
  • FACTx:激活设置,置0,禁用;置1,启用

列表模式就是我们把一个个目标ID,直接写道R1、R2寄存器中,但在实际情况中,某一类的数据可能会非常多,ID多到根本写不下。作为设计者,我们通常会把他们的ID高位设计成一样的,而ID低位是可变的。作为子类的区分这样的设计我们就可以使用屏蔽模式。

在屏蔽模式下,可以在R2屏蔽映像里表明在过滤时,那些位是必须匹配的,而那些位是无关的,在R2对应的数据位中写1,表示R1中ID号所对应的位必须匹配一致,写0表示R1里的ID对应位1和0均可。

过滤器配置示例

九、测试模式

  • 静默模式:用于分析CAN总线的活动,不会对总线造成影响-显性位(确认位、 错误帧)不会真正发送到总线上。在静默模式下,bxCAN可以正常地接收数据帧和远程帧,但只能发出隐性位,而不能真正发送 报文。如果bxCAN需要发出显性位(确认位、过载标志、主动错误标志),那么这样的显性位在内 部被接回来从而可以被CAN内核检测到,同时CAN总线不会受到影响而仍然维持在隐性位状态。
  • 环回模式:用于自测试,同时发送的报文可以在CAN_TX引脚上检测到,bxCAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里。
  • 环回静默模式:用于热自测试,自测试的同时不会影响CAN总线

三种测试模式可以来回的切换,这个切换只需要修改代码,而不需要实际的去接线

十、工作模式

  • 初始化模式:用于配置CAN外设,禁止报文的接收和发送
  • 正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文
  • 睡眠模式:低功耗,CAN外设时钟停止,可使用软件唤醒或硬件自动唤醒
  • AWUM:置1,自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP,唤醒CAN外设;置0,手动唤醒,软件清零SLEEP,唤醒CAN外设

十一、位时间特性

波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量

           = 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))

十二、中断

  • CAN外设占用4个专用的中断向量
  • 发送中断:发送邮箱空时产生
  • FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生
  • FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生
  • 状态改变错误中断:出错/唤醒/进入睡眠时产生

十三、时间触发通信

  • TTCM:置1,开启时间触发通信功能;置0,关闭时间触发通信功能
  • CAN外设内置一个16位的计数器,用于记录时间戳
  • TTCM置1后,该计数器在每个CAN位的时间自增一次,溢出后归零
  • 每个发送邮箱和接收FIFO都有一个TIME[15:0]寄存器,发送帧SOF时,硬件捕获计数器值到发送邮箱的TIME寄存器,接收帧SOF时,硬件捕获计数器值到接收FIFO的TIME寄存器
  • 发送邮箱可配置TGT位,捕获计数器值的同时,也把此值写入到数据帧数据段的最后两个字节,为了使用此功能,DLC必须设置为8

十四、错误处理和离线恢复

  • TEC和REC根据错误的情况增加或减少
  • ABOM:置1,开启离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启

十五、CAN总线的数据传输策略

1.定时传输,适合频繁使用的数据

数据的提供方,测得一批数据,供其他节点使用,这是这个传感器数据就可以放在数据帧内然后定时广播出去,定时器发送的时间,取决于传感器数刷新的频率,和其他节点使用这个数据的频率

2.触发传输,适合一些报警或者通知信息

触发传输指的是发送方平时不会主动广播这个数据,只有发送方内部达到了某种条件,才会主动发送这个数据,也就是这个数据的发送频率是不定的

3.请求传输,适合接收方偶尔有请求才需要的数据

请求传输整个过程由一去一回两个阶段组成,接收方想要这个数据时,就先广播一个遥控帧,进行请求,当数据帧提供方收到遥控帧后,如果他有请求对应的数据,他就会在接收到遥控帧之后主动发送对应的数据帧

定时传输和触发传输都是由发送方主导的,而请求传输是由接收方主导

十六、代码示例(使用的是标准库)

1.CAN总线单个设备环回测试

1.MYCAN.c文件

#include "MYCAN.H"void MYCAN_init_func(void){/* 开启RCC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 初始化GPIO */GPIO_InitTypeDef MyCAN_gpio_init;MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;/* PA12输出引脚设置位复用推挽输出 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_12;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_IPU;/* PA11输出引脚设置为上拉输入模式 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_11;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);/* 初始化CAN总线配置 */CAN_InitTypeDef mycan_structure;mycan_structure.CAN_Mode = CAN_Mode_LoopBack;/* can的工作模式,这里设置为环回模式 */mycan_structure.CAN_Prescaler = 36;/* 配置can的分频系数 波特率=36M/36/(1+2+3) 这里不需要减一因为在init函数里面帮我们减1了*/mycan_structure.CAN_BS1 = CAN_BS1_2tq;/* 时间段1 */mycan_structure.CAN_BS2 = CAN_BS2_3tq;/* 时间段2 */mycan_structure.CAN_SJW = CAN_SJW_2tq;/* 再同步补偿宽度 */mycan_structure.CAN_ABOM = DISABLE;/* 关闭离线自动恢复 */mycan_structure.CAN_AWUM = DISABLE;/* 关闭自动唤醒模式 手动唤醒*/mycan_structure.CAN_NART = DISABLE;/* 启用自动重传 CAN硬件在发送报文失败时会一直自动重传直到发送成功 */mycan_structure.CAN_RFLM = DISABLE;/* 禁用接收FIFO锁定模式,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖 */mycan_structure.CAN_TTCM = DISABLE;/* 关闭时间触发通信功能 */mycan_structure.CAN_TXFP = DISABLE;/* 根据id号来判断优先级 */CAN_Init(CAN1,&mycan_structure);/* 过滤器初始化 */CAN_FilterInitTypeDef CAN_Filter_init;CAN_Filter_init.CAN_FilterActivation = ENABLE;/* 启用过滤器 */CAN_Filter_init.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;/* 关联设置,将此过滤器关联致FIFO0,就是过滤出来的信息存储进FIFO0 */CAN_Filter_init.CAN_FilterMode = CAN_FilterMode_IdMask;/* 指定过滤器模式为屏蔽模式 */CAN_Filter_init.CAN_FilterNumber = 0;/* 指定要初始化的过滤器 */CAN_Filter_init.CAN_FilterScale = CAN_FilterScale_32bit;/* 指定过滤器宽度为32bit */CAN_Filter_init.CAN_FilterIdHigh = 0x0000;/* 指定id为0x00000000 */CAN_Filter_init.CAN_FilterIdLow = 0x0000;CAN_Filter_init.CAN_FilterMaskIdHigh = 0x0000;/* 指定为接收所有标准格式的数据帧 */CAN_Filter_init.CAN_FilterMaskIdLow = 0x0006;CAN_FilterInit(&CAN_Filter_init);
}/* 发送数据帧 */
int MyCAN_Transmit(uint8_t *Data , uint8_t Leng ,uint32_t StdId){CanTxMsg T1Message;uint8_t i = 0;T1Message.IDE = CAN_Id_Standard;/* id格式为标准格式 */T1Message.RTR = CAN_RTR_Data;/* 数据帧 */T1Message.ExtId = StdId;/* 扩展id */T1Message.StdId = StdId;/* 标准id */T1Message.DLC = Leng;/* 表示数据段长度 */for(i = 0;i<Leng ;i++){T1Message.Data[i] = Data[i];}uint8_t transmit_mailbox = CAN_Transmit(CAN1,&T1Message);/* 返回数据写入的邮箱 *//* 等待数据发送完成 */uint32_t Timeout = 0;while(CAN_TransmitStatus(CAN1,transmit_mailbox) != CAN_TxStatus_Ok){Timeout++;if(Timeout > 100000){/* 设置超时等待 */return -1;/* 发送可能失败了 */}}return 1;
}
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void){if(CAN_MessagePending(CAN1,CAN_FIFO0) > 0){/* 返回挂起消息的数目 */return 1;}return 0;
}
/* 读取数据帧 */
void MyCAN_Receive(uint32_t* ID,uint8_t* Length,uint8_t* Data){CanRxMsg RxMessage;/* 保存接收消息的结构体 */CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);if(RxMessage.IDE == CAN_Id_Standard){/* 判断ID消息的类型 *//* 获取标准格式的ID */*ID = RxMessage.StdId;}else{/* 获取扩展格式的ID */*ID = RxMessage.ExtId;}/* 获取数据长度 */*Length = RxMessage.DLC;/* 获取数据 */uint8_t i = 0;for(i = 0;i < RxMessage.DLC;i++){Data[i] = RxMessage.Data[i];}
}

2.MYCAN.h文件

#ifndef __MYCAN_H__
#define __MYCAN_H__
#include "stm32f10x.h"                  // Device header
#include "MYCAN.H"
void MYCAN_init_func(void);
/* 发送数据帧 */
int MyCAN_Transmit(uint8_t *Data , uint8_t Leng ,uint32_t StdId);
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void);
/* 读取数据帧 */
void MyCAN_Receive(uint32_t* ID,uint8_t* Length,uint8_t* Data);
#endif

3.main.c文件

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "key.h"
#include "MYCAN.h"
int main(){/* 发送的数据 */uint8_t Tran_buff[4] = {0x66,0x77,0x88,0x99};/* 报文ID */uint32_t ID;/* 数据长度 */uint8_t Length;/* 接收数据 */uint8_t Data[8];/* OLED初始化,这里我使用了0.96的oled显示屏 */OLED_Init();/* 按键初始化 */KEY_init();/* CAN初始化 */MYCAN_init_func();OLED_ShowString(1,1,"RxID:");OLED_ShowString(2,1,"Leng:");OLED_ShowString(3,1,"Data:");while(1){/* 发送报文数据 */if(Read_Key1() == 1){MyCAN_Transmit(Tran_buff , 4 ,0x666);Tran_buff[0]++;Tran_buff[1]++;Tran_buff[2]++;Tran_buff[3]++;}/* 接收报文数据 */if(MyCAN_Receive_FIFO_value()>0){MyCAN_Receive(&ID,&Length,Data);OLED_ShowHexNum(1,6,ID,3);OLED_ShowHexNum(2,6,Length,3);OLED_ShowHexNum(3,6,Data[0],2);OLED_ShowHexNum(3,10,Data[1],2);OLED_ShowHexNum(4,6,Data[2],2);OLED_ShowHexNum(4,10,Data[3],2);}}	
}

2.标准格式-扩展格式-数据帧-遥控帧

1.MYCAN.c文件

#include "MYCAN.H"void MYCAN_init_func(void){/* 开启RCC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 初始化GPIO */GPIO_InitTypeDef MyCAN_gpio_init;MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;/* PA12输出引脚设置位复用推挽输出 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_12;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_IPU;/* PA11输出引脚设置为上拉输入模式 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_11;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);/* 初始化CAN总线配置 */CAN_InitTypeDef mycan_structure;mycan_structure.CAN_Mode = CAN_Mode_LoopBack;/* can的工作模式,这里设置为环回模式 */mycan_structure.CAN_Prescaler = 36;/* 配置can的分频系数 波特率=36M/36/(1+2+3) 这里不需要减一因为在init函数里面帮我们减1了*/mycan_structure.CAN_BS1 = CAN_BS1_2tq;/* 时间段1 */mycan_structure.CAN_BS2 = CAN_BS2_3tq;/* 时间段2 */mycan_structure.CAN_SJW = CAN_SJW_2tq;/* 再同步补偿宽度 */mycan_structure.CAN_ABOM = DISABLE;/* 关闭离线自动恢复 */mycan_structure.CAN_AWUM = DISABLE;/* 关闭自动唤醒模式 手动唤醒*/mycan_structure.CAN_NART = DISABLE;/* 启用自动重传 CAN硬件在发送报文失败时会一直自动重传直到发送成功 */mycan_structure.CAN_RFLM = DISABLE;/* 禁用接收FIFO锁定模式,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖 */mycan_structure.CAN_TTCM = DISABLE;/* 关闭时间触发通信功能 */mycan_structure.CAN_TXFP = DISABLE;/* 根据ID号来判断优先级 */CAN_Init(CAN1,&mycan_structure);/* 过滤器初始化 */CAN_FilterInitTypeDef CAN_Filter_init;CAN_Filter_init.CAN_FilterActivation = ENABLE;/* 启用过滤器 */CAN_Filter_init.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;/* 关联设置,将此过滤器关联致FIFO0,就是过滤出来的信息存储进FIFO0 */CAN_Filter_init.CAN_FilterMode = CAN_FilterMode_IdMask;/* 指定过滤器模式为屏蔽模式 */CAN_Filter_init.CAN_FilterNumber = 0;/* 指定要初始化的过滤器 */CAN_Filter_init.CAN_FilterScale = CAN_FilterScale_32bit;/* 指定过滤器宽度为32bit */CAN_Filter_init.CAN_FilterIdHigh = 0x0000;/* 指定id为0x00000000 */CAN_Filter_init.CAN_FilterIdLow = 0x0000;CAN_Filter_init.CAN_FilterMaskIdHigh = 0x0000;/* 指定为接收所有标准格式的数据帧 */CAN_Filter_init.CAN_FilterMaskIdLow = 0x0000;//0x0006;改为不屏蔽接收全部数据CAN_FilterInit(&CAN_Filter_init);
}/* 发送数据帧 */
int MyCAN_Transmit(CanTxMsg* T1Message){uint8_t transmit_mailbox = CAN_Transmit(CAN1,T1Message);/* 返回数据写入的邮箱 *//* 等待数据发送完成 */uint32_t Timeout = 0;while(CAN_TransmitStatus(CAN1,transmit_mailbox) != CAN_TxStatus_Ok){Timeout++;if(Timeout > 100000){/* 设置超时等待 */return -1;/* 发送可能失败了 */}}return 1;
}
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void){if(CAN_MessagePending(CAN1,CAN_FIFO0) > 0){/* 返回挂起消息的数目 */return 1;}return 0;
}
/* 读取数据帧 */
void MyCAN_Receive(CanRxMsg *RxMessage){CAN_Receive(CAN1,CAN_FIFO0,RxMessage);/* 读取数据帧 */
}

2.MYCAN.h文件

#ifndef __MYCAN_H__
#define __MYCAN_H__
#include "stm32f10x.h"                  // Device header
#include "MYCAN.H"
void MYCAN_init_func(void);
/* 发送数据帧 */
int MyCAN_Transmit(CanTxMsg* T1Message);
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void);
/* 读取数据帧 */
void MyCAN_Receive(CanRxMsg *RxMessage);
#endif

3.main.c文件

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "key.h"
#include "MYCAN.h"int main(){/* 发送数据 */CanTxMsg T1Message[] = {/* StdId标准id		ExtId扩展id					IDE							RTR		 				DLC					Data			*/{0x222,					0x00000000,			CAN_Id_Standard,CAN_RTR_Data,			4	,	{0x11,0x22,0x33,0x44}},//标准id的数据帧{0x333,					0x00000000,			CAN_Id_Standard,CAN_RTR_Remote,		4	,	{0x11,0x22,0x33,0x44}},//标准id的控制帧{0x000,					0x00234567,			CAN_Id_Extended,CAN_RTR_Data,			4	,	{0x11,0x22,0x33,0x44}},//扩展id的数据帧{0x000,					0x00034567,			CAN_Id_Extended,CAN_Id_Extended,	4	,	{0x11,0x22,0x33,0x44}},//标准id的控制帧};/* 接收数据 */CanRxMsg RxMessage;/* OLED初始化 */OLED_Init();/* 按键初始化 */KEY_init();/* CAN总线初始化 */MYCAN_init_func();OLED_ShowString(1,1,"TxID:");OLED_ShowString(2,1,"RxID:");OLED_ShowString(3,1,"Leng:");OLED_ShowString(4,1,"Data:");while(1){/* 发送报文数据 */if(Read_Key1() == 1){static uint16_t value = 0;MyCAN_Transmit(&T1Message[value]);if(T1Message[value].IDE == CAN_Id_Standard){/* 判断是标准帧时 */OLED_ShowHexNum(1,6,T1Message[value].StdId,8);}else if(T1Message[value].IDE == CAN_Id_Extended){/* 判断是扩展帧时 */OLED_ShowHexNum(1,6,T1Message[value].ExtId,8);}value++;if(value >= sizeof(T1Message)/sizeof(CanTxMsg)){value = 0;}}/* 接收报文数据 */if(MyCAN_Receive_FIFO_value()>0){MyCAN_Receive(&RxMessage);if(RxMessage.IDE == CAN_Id_Standard){/* 标准格式 */OLED_ShowHexNum(2,6,RxMessage.StdId,8);}else if(RxMessage.IDE == CAN_Id_Extended){/* 扩展格式 */OLED_ShowHexNum(2,6,RxMessage.ExtId,8);}OLED_ShowHexNum(3,6,RxMessage.DLC,3);if(RxMessage.RTR == CAN_RTR_Data){/* 数据帧 */OLED_ShowHexNum(4,6,RxMessage.Data[0],2);OLED_ShowHexNum(4,9,RxMessage.Data[1],2);OLED_ShowHexNum(4,12,RxMessage.Data[2],2);OLED_ShowHexNum(4,15,RxMessage.Data[3],2);}else if(RxMessage.RTR == CAN_RTR_REMOTE){/* 遥控帧 */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.中断式接收

使用中断的三个步骤

  1. 用ITConfig打开外设的中断信号输出
  2. 配置NVIC,接收外设的中断信号,进入NVIC
  3. 写中断函数

1.MYCAN.c文件

#include "MYCAN.H"void MYCAN_init_func(void){/* 开启RCC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 初始化GPIO */GPIO_InitTypeDef MyCAN_gpio_init;MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;/* PA12输出引脚设置位复用推挽输出 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_12;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_IPU;/* PA11输出引脚设置为上拉输入模式 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_11;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);/* 配置中断 */CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);/* 启用CAN1总线的中断 FMP0不为0时产生中断 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/* 进行中断分组 */NVIC_InitTypeDef CAN_nvic;CAN_nvic.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;CAN_nvic.NVIC_IRQChannelCmd = ENABLE;CAN_nvic.NVIC_IRQChannelPreemptionPriority = 2;CAN_nvic.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&CAN_nvic);/* 初始化CAN总线配置 */CAN_InitTypeDef mycan_structure;mycan_structure.CAN_Mode = CAN_Mode_LoopBack;/* can的工作模式,这里设置为环回模式 */mycan_structure.CAN_Prescaler = 36;/* 配置can的分频系数 波特率=36M/36/(1+2+3) 这里不需要减一因为在init函数里面帮我们减1了*/mycan_structure.CAN_BS1 = CAN_BS1_2tq;/* 时间段1 */mycan_structure.CAN_BS2 = CAN_BS2_3tq;/* 时间段2 */mycan_structure.CAN_SJW = CAN_SJW_2tq;/* 再同步补偿宽度 */mycan_structure.CAN_ABOM = DISABLE;/* 离线自动恢复功能是否开启 */mycan_structure.CAN_AWUM = DISABLE;/* 关闭自动唤醒模式 */mycan_structure.CAN_NART = DISABLE;/* 启用自动重传 CAN硬件在发送报文失败时会一直自动重传直到发送成功 */mycan_structure.CAN_RFLM = DISABLE;/* 禁用接收FIFO锁定模式,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖 */mycan_structure.CAN_TTCM = DISABLE;/* 关闭时间触发通信功能 */mycan_structure.CAN_TXFP = DISABLE;/* 根据id号来判断优先级 */CAN_Init(CAN1,&mycan_structure);/* 过滤器初始化 */CAN_FilterInitTypeDef CAN_Filter_init;CAN_Filter_init.CAN_FilterActivation = ENABLE;/* 启用过滤器 */CAN_Filter_init.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;/* 关联设置,将此过滤器关联致FIFO0,就是过滤出来的信息存储进FIFO0 */CAN_Filter_init.CAN_FilterMode = CAN_FilterMode_IdMask;/* 指定过滤器模式为屏蔽模式 */CAN_Filter_init.CAN_FilterNumber = 0;/* 指定要初始化的过滤器 */CAN_Filter_init.CAN_FilterScale = CAN_FilterScale_32bit;/* 指定过滤器宽度为32bit */CAN_Filter_init.CAN_FilterIdHigh = 0x0000;/* 指定id为0x00000000 */CAN_Filter_init.CAN_FilterIdLow = 0x0000;CAN_Filter_init.CAN_FilterMaskIdHigh = 0x0000;/* 指定为接收所有标准格式的数据帧 */CAN_Filter_init.CAN_FilterMaskIdLow = 0x0006;CAN_FilterInit(&CAN_Filter_init);
}/* 发送数据帧 */
int MyCAN_Transmit(uint8_t *Data , uint8_t Leng ,uint32_t StdId){CanTxMsg T1Message;uint8_t i = 0;T1Message.IDE = CAN_Id_Standard;/* id格式为标准格式 */T1Message.RTR = CAN_RTR_Data;/* 数据帧 */T1Message.ExtId = StdId;/* 扩展id */T1Message.StdId = StdId;/* 标准id */T1Message.DLC = Leng;/* 表示数据段长度 */for(i = 0;i<Leng ;i++){T1Message.Data[i] = Data[i];}uint8_t transmit_mailbox = CAN_Transmit(CAN1,&T1Message);/* 返回数据写入的邮箱 *//* 等待数据发送完成 */uint32_t Timeout = 0;while(CAN_TransmitStatus(CAN1,transmit_mailbox) != CAN_TxStatus_Ok){Timeout++;if(Timeout > 100000){/* 设置超时等待 */return -1;/* 发送可能失败了 */}}return 1;
}
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void){if(CAN_MessagePending(CAN1,CAN_FIFO0) > 0){/* 返回挂起消息的数目 */return 1;}return 0;
}
/* 读取数据帧 */
void MyCAN_Receive(uint32_t* ID,uint8_t* Length,uint8_t* Data){CanRxMsg RxMessage;/* 保存接收消息的结构体 */CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);if(RxMessage.IDE == CAN_Id_Standard){/* 判断ID消息的类型 *//* 获取标准格式的ID */*ID = RxMessage.StdId;}else{/* 获取扩展格式的ID */*ID = RxMessage.ExtId;}/* 获取数据长度 */*Length = RxMessage.DLC;/* 获取数据 */uint8_t i = 0;for(i = 0;i < RxMessage.DLC;i++){Data[i] = RxMessage.Data[i];}
}

2.MYCAN.h文件

#ifndef __MYCAN_H__
#define __MYCAN_H__
#include "stm32f10x.h"                  // Device header
#include "MYCAN.H"
void MYCAN_init_func(void);
/* 发送数据帧 */
int MyCAN_Transmit(uint8_t *Data , uint8_t Leng ,uint32_t StdId);
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void);
/* 读取数据帧 */
void MyCAN_Receive(uint32_t* ID,uint8_t* Length,uint8_t* Data);
#endif

3.main.c文件

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "key.h"
#include "MYCAN.h"/* 发送的数据 */uint8_t Tran_buff[4] = {0x66,0x77,0x88,0x99};/* 报文ID */uint32_t ID;/* 数据长度 */uint8_t Length;/* 接收数据 */uint8_t Data[8];int main(){/* oled初始化 */OLED_Init();/* 按键初始化 */KEY_init();/* CAN总线初始化 */MYCAN_init_func();OLED_ShowString(1,1,"RxID:");OLED_ShowString(2,1,"Leng:");OLED_ShowString(3,1,"Data:");while(1){/* 发送报文数据 */if(Read_Key1() == 1){MyCAN_Transmit(Tran_buff , 4 ,0x666);Tran_buff[0]++;Tran_buff[1]++;Tran_buff[2]++;Tran_buff[3]++;}}
}void USB_LP_CAN1_RX0_IRQHandler(void){MyCAN_Receive(&ID,&Length,Data);OLED_ShowHexNum(1,6,ID,3);OLED_ShowHexNum(2,6,Length,3);OLED_ShowHexNum(3,6,Data[0],2);OLED_ShowHexNum(3,10,Data[1],2);OLED_ShowHexNum(4,6,Data[2],2);OLED_ShowHexNum(4,10,Data[3],2);}

4.数据传输策略

1.MYCAN.c文件

#include "MYCAN.H"void MYCAN_init_func(void){/* 开启RCC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 初始化GPIO */GPIO_InitTypeDef MyCAN_gpio_init;MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;/* PA12输出引脚设置位复用推挽输出 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_12;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);MyCAN_gpio_init.GPIO_Mode = GPIO_Mode_IPU;/* PA11输出引脚设置为上拉输入模式 */MyCAN_gpio_init.GPIO_Pin = GPIO_Pin_11;MyCAN_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&MyCAN_gpio_init);/* 初始化CAN总线配置 */CAN_InitTypeDef mycan_structure;mycan_structure.CAN_Mode = CAN_Mode_LoopBack;/* can的工作模式,这里设置为环回模式 */mycan_structure.CAN_Prescaler = 36;/* 配置can的分频系数 波特率=36M/36/(1+2+3) 这里不需要减一因为在init函数里面帮我们减1了*/mycan_structure.CAN_BS1 = CAN_BS1_2tq;/* 时间段1 */mycan_structure.CAN_BS2 = CAN_BS2_3tq;/* 时间段2 */mycan_structure.CAN_SJW = CAN_SJW_2tq;/* 再同步补偿宽度 */mycan_structure.CAN_ABOM = DISABLE;/* 关闭离线自动恢复功能 */mycan_structure.CAN_AWUM = DISABLE;/* 关闭自动唤醒模式 */mycan_structure.CAN_NART = DISABLE;/* 启用自动重传 CAN硬件在发送报文失败时会一直自动重传直到发送成功 */mycan_structure.CAN_RFLM = DISABLE;/* 禁用接收FIFO锁定模式,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖 */mycan_structure.CAN_TTCM = DISABLE;/* 关闭时间触发通信功能 */mycan_structure.CAN_TXFP = DISABLE;/* 根据id号来判断优先级 */CAN_Init(CAN1,&mycan_structure);/* 过滤器初始化 */CAN_FilterInitTypeDef CAN_Filter_init;CAN_Filter_init.CAN_FilterActivation = ENABLE;/* 启用过滤器 */CAN_Filter_init.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;/* 关联设置,将此过滤器关联致FIFO0,就是过滤出来的信息存储进FIFO0 */CAN_Filter_init.CAN_FilterMode = CAN_FilterMode_IdMask;/* 指定过滤器模式为屏蔽模式 */CAN_Filter_init.CAN_FilterNumber = 0;/* 指定要初始化的过滤器 */CAN_Filter_init.CAN_FilterScale = CAN_FilterScale_32bit;/* 指定过滤器宽度为32bit */CAN_Filter_init.CAN_FilterIdHigh = 0x0000;/* 指定id为0x00000000 */CAN_Filter_init.CAN_FilterIdLow = 0x0000;CAN_Filter_init.CAN_FilterMaskIdHigh = 0x0000;/* 指定为接收所有标准格式的数据帧 */CAN_Filter_init.CAN_FilterMaskIdLow = 0x0006;CAN_FilterInit(&CAN_Filter_init);
}/* 发送数据帧 */
int MyCAN_Transmit(CanTxMsg *T1Message){uint8_t transmit_mailbox = CAN_Transmit(CAN1,T1Message);/* 返回数据写入的邮箱 *//* 等待数据发送完成 */uint32_t Timeout = 0;while(CAN_TransmitStatus(CAN1,transmit_mailbox) != CAN_TxStatus_Ok){Timeout++;if(Timeout > 100000){/* 设置超时等待 */return -1;/* 发送可能失败了 */}}return 1;
}
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void){if(CAN_MessagePending(CAN1,CAN_FIFO0) > 0){/* 返回挂起消息的数目 */return 1;}return 0;
}
/* 读取数据帧 */
void MyCAN_Receive(CanRxMsg *RxMessage){CAN_Receive(CAN1,CAN_FIFO0,RxMessage);
}

2.MYCAN.h文件

#ifndef __MYCAN_H__
#define __MYCAN_H__
#include "stm32f10x.h"                  // Device header
#include "MYCAN.H"void MYCAN_init_func(void);/* 发送数据帧 */
int MyCAN_Transmit(CanTxMsg *T1Message);
/* 检查FIFO里面有没有数据 */
uint8_t MyCAN_Receive_FIFO_value(void);
/* 读取数据帧 */
void MyCAN_Receive(CanRxMsg *RxMessage);#endif

3.main.c文件

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "TIME.h"
#include "key.h"
#include "MYCAN.h"/* 定时发送的数据 */
CanTxMsg Time_T1Message = {.StdId = 0x222,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Data,.DLC = 4,.Data = {0x00,0x11,0x22,0x33}
};
/* 触发传输的数据 */	
CanTxMsg Trigger_T1Message = {.StdId = 0x333,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Data,.DLC = 4,.Data = {0x00,0x11,0x22,0x33}
};
/* 请求传输的数据 */
CanTxMsg Request_T1Message = {.StdId = 0x444,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Data,.DLC = 4,.Data = {0x00,0x11,0x22,0x33}
};
/* 请求信号 我这里直接使用的是数据帧没有用遥控帧 */
CanTxMsg Request_T1Message2 = {.StdId = 0x555,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Data,.DLC = 4,.Data = {0x00,0x11,0x22,0x33}
};
/* 接收数据 */
CanRxMsg R1Message;
/* 定时发送标志位 */
uint8_t Time_value;
/* 请求发送标志位 */
uint8_t Request_value;int main(){OLED_Init();KEY_init();Time2_init();LED_init();MYCAN_init_func();OLED_ShowString(1,1,"Ti:");OLED_ShowString(2,1,"Tr:");OLED_ShowString(3,1,"Re:");while(1){/* 1.定时发送数据 */if(Time_value == 1){Time_value = 0;MyCAN_Transmit(&Time_T1Message);Time_T1Message.Data[0]++;Time_T1Message.Data[1]++;Time_T1Message.Data[2]++;Time_T1Message.Data[3]++;}/* 2.触发传输 */			if(Read_Key1() == 1){MyCAN_Transmit(&Trigger_T1Message);Trigger_T1Message.Data[0]++;Trigger_T1Message.Data[1]++;Trigger_T1Message.Data[2]++;Trigger_T1Message.Data[3]++;}/* 3.请求传输 *//* 发送请求信号 */if(Read_Key2() == 1){MyCAN_Transmit(&Request_T1Message2);LED1_ON;}	/* 接收到了请求信号,发送数据 */if(Request_value == 1){MyCAN_Transmit(&Request_T1Message);Request_value = 0;Request_T1Message.Data[0]++;Request_T1Message.Data[1]++;Request_T1Message.Data[2]++;Request_T1Message.Data[3]++;}/* 接收报文数据 */if(MyCAN_Receive_FIFO_value()>0){MyCAN_Receive(&R1Message);/* 接收到了定时传输的数据 */if(R1Message.StdId == 0x222){OLED_ShowHexNum(1,4,R1Message.Data[0],2);OLED_ShowHexNum(1,7,R1Message.Data[1],2);OLED_ShowHexNum(1,10,R1Message.Data[2],2);OLED_ShowHexNum(1,13,R1Message.Data[3],2);}/* 接收到了触发传输的数据 */if(R1Message.StdId == 0x333){OLED_ShowHexNum(2,4,R1Message.Data[0],2);OLED_ShowHexNum(2,7,R1Message.Data[1],2);OLED_ShowHexNum(2,10,R1Message.Data[2],2);OLED_ShowHexNum(2,13,R1Message.Data[3],2);}/* 接收到了请求传输的数据 */if(R1Message.StdId == 0x444){OLED_ShowHexNum(3,4,R1Message.Data[0],2);OLED_ShowHexNum(3,7,R1Message.Data[1],2);OLED_ShowHexNum(3,10,R1Message.Data[2],2);OLED_ShowHexNum(3,13,R1Message.Data[3],2);}/* 判断是否接收到了信号 */if(R1Message.StdId == 0x555){Request_value = 1;LED1_OFF;}}}
}
/* 定时器更新处理函数 这里我的是1s定时*/
void TIM2_IRQHandler(void){/* 判断tim2的更新中断是否发生 */if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){Time_value = 1;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/56292.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golang必备项目管理机制:工作区

在 Go 1.18 及以后的版本中&#xff0c;工作区&#xff08;Workspace&#xff09;是一种新的项目管理方式&#xff0c;可以让多个项目共享同一个模块缓存和依赖。这意味着你不需要在每个项目中单独安装依赖&#xff0c;而是可以共享依赖&#xff0c;这样可以节省空间和时间。 假…

CUDA 运行时GPU信息查询

cuda 官网文档名&#xff1a;CUDA_Runtime_API 运行时API查询GPU信息 调用 cudaDeviceProp prop; cudaGetDeviceProperties(&prop, device_id) 定义 由此可见&#xff0c;只能在主机上调用。 #include <cuda_runtime.h>#include <iostream> #include <…

ConditionVideo: 无训练的条件引导视频生成 | AAAI 2024

作者&#xff1a;彭博&#xff0c;上海人工智能实验室与上海交大2023级联培博士。 最近的工作已经成功地将大规模文本到图像模型扩展到视频领域&#xff0c;产生了令人印象深刻的结果&#xff0c;但计算成本高&#xff0c;需要大量的视频数据。在这项工作中&#xff0c;我们介…

游戏如何应对薅羊毛问题

在大众眼里&#xff0c;“薅羊毛”是指在电商领域&#xff0c;“羊毛党”利用平台、商家的促销规则&#xff0c;低价获取商品和服务的行为。如前不久“小天鹅被一夜薅走7000万”的案例震惊全网。 然而实际上&#xff0c;“薅羊毛”现象不仅存在于电商场景&#xff0c;在游戏中…

设计模式之适配器模式(通俗易懂--代码辅助理解【Java版】)

文章目录 设计模式概述1、适配器模式2、适配器模式的使用场景3、优点4、缺点5、主要角色6、代码示例1&#xff09;UML图2&#xff09;源代码&#xff08;1&#xff09;定义一部手机&#xff0c;它有个typec口。&#xff08;2&#xff09;定义一个vga接口。&#xff08;3&#x…

docker入门-快速学会docker

死记硬背一张图 镜像类似于我们是使用的虚拟机&#xff0c;创建虚拟机前需要下载的系统镜像文件&#xff0c;比如iso文件&#xff0c;img文件等等这样一些镜像文件。 容器可以比作正在运行中的一个虚拟机。 tar文件&#xff0c;tar文件类似于vm使用时的vmdk文件。通过load指…

在 Docker容器中安装 ROS-Melodic 并使用 rviz 进行图形化显示

文章目录 写在前面1. 背景描述2. 安装步骤2.1 允许本地机器上的用户或进程连接到 X server2.2 拉取 docker 镜像2.3 使用镜像osrf/ros:melodic-desktop-full创建并运行容器2.4 运行 roscore2.5 运行 rviz 参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04&#xff0…

面网易后台开发居然遇到了一个困难难度算法题

在提供面经的同学中&#xff0c;竟然有同学在面试网易后台研发的时候遇到了一道困难难度的算法题。 一般来说&#xff0c;大多数面试的算法题都是以中等难度为主&#xff0c;遇到困难难度的算法题也许是公司现在不缺人、也许是在选拔人才、当然也很可能是面试官其实并不想要你…

在三维可视化项目中,B/S和C/S架构该如何选择?

一、什么是B/S和C/S 在3D数据可视化中&#xff0c;有两种常见的架构模式&#xff1a;BS&#xff08;Browser/Server&#xff09;和CS&#xff08;Client/Server&#xff09; B/S模式 B/S模式是指将3D数据可视化的逻辑和处理放在服务器端&#xff0c;而在客户端使用浏览器进行…

前端新机部署

编辑器&#xff1a;vscode 下载地址 vscode常用插件 显示代码修改历史、作者等信息 GitLens Nodejs版本 Node版本管理工具 Nvm下载地址 nvm常用命令&#xff1a; nvm ls // 查看安装的所有node.js的版本nvm list available //查看可以安装的所有node.js版本nvm install 版本…

Linux:Ubuntu系统开启SSH服务

在Ubuntu上开启SSH服务&#xff0c;可以按照以下步骤进行&#xff1a; 1.安装OpenSSH服务 如果你还没有安装OpenSSH服务&#xff0c;可以使用以下命令安装&#xff1a; sudo apt update sudo apt install openssh-server2. 启动SSH服务 安装完成后&#xff0c;启动SSH服务&a…

Docker在linux系统中的下载与使用

Docker在linux系统中的下载与使用 一、docker作用,下载及安装二、docker镜像三、创建容器四、容器数据卷 一、docker作用,下载及安装 ubuntu下载安装docker 命令&#xff1a;在ubuntu上面执行这个命令 Docker version:查看docker是否安装成功 配置阿里云镜像加速:进入阿里云网站…

【WEB应用安全测试指南–蓝队安全测试2】--超详细-可直接进行实战!!!亲测-可进行安全及渗透测试

安全基础理论入门知识参考上一篇《WEB应用安全测试指南蓝队安全测试1》 WEB应用安全测试指南2 一、文件 I/O 类1.1、任意文件上传1.2、任意文件下载1.3、文件包含 二、接口安全类2.1、短信炸弹2.2、邮件炸弹2.3、短信内容可控2.4、邮件内容可控 三、逻辑流程类3.1、越权3.2、未…

k8s部署jenkins集群,配置集群kubernetes plugin的pod模板

先安装jenkins插件&#xff0c;对应的源码地址是kubernetes-plugin&#xff0c;以供参考。 进入节点管理&#xff0c;开始配置。 点击 “ configure clouds” 一、配置集群 填写k8s地址&#xff1a;https://kubernetes.default.svc.cluster.local 命名空间&#xff1a;kuberne…

Redis高并发缓存设计问题与性能优化

1、缓存设计典型问题 1.1、缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c;缓存层和存储层都不会命中&#xff0c;通常出于容错的考虑&#xff0c;如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c;失…

【Python】从零到一,搭建高效Web服务器,轻松上手!Python开发者必备(文末附带源码分享)

CSDN Python源码分享&#xff1a;实现一个简单的Web服务器 在CSDN上&#xff0c;我们经常分享各种技术文章和源码&#xff0c;帮助开发者们不断提升自己的技能。今天&#xff0c;我将为大家分享一个使用Python实现的简单Web服务器源码。这个Web服务器能够处理基本的HTTP GET请…

打破常规,BD仓储物流的效能提升!

当前&#xff0c;随着国家战略的推进&#xff0c;JS与民用领域的融合不断加深&#xff0c;物流业也步入了军民融合的新时代。在智能仓储物流方面&#xff0c;JS物流的智能化进展受到了BD系统的高度关注和重视。 一、建设JS仓储物流RFID基础设施 JS物流领域引入RFID技术的基础工…

Jenkins 改成中文语言显示

Jenkins 改成中文语言显示 到系统管理 插件管理中下载如下插件接口 Localization: Chinese (Simplified) 搜索的时候用ctrlf 进行搜索&#xff0c;不要用Jenkins下面下的filter 只有&#xff0c;到系统管理Manage Jenkins下的Config System下&#xff0c;如下截图的位置去…

动态规划算法:14.简单多状态 dp 问题_粉刷房子_C++

题目链接&#xff1a;LCR 091. 粉刷房子 - 力扣&#xff08;LeetCode&#xff09; 一、题目解析 题目&#xff1a; 解析&#xff1a; 由题可知&#xff1a; 涂刷房子有三种颜色可以选&#xff0c;所给的二维数组中三列固定不变&#xff0c;分别是红、蓝、绿相邻两件房子不可…

C++基础面试题 | 什么是内存对齐?为什么需要内存对齐?

文章目录 回答重点扩展知识 回答重点 内存对齐是指计算机在访问内存时&#xff0c;会根据一定规则将数据存储在合适的起始地址上&#xff0c;通常是数据大小的整数倍。这样做可以提升CPU的访问效率&#xff0c;特别是在读取和写入数据时。 为什么要内存对齐&#xff1f;主要有…