STM32—CAN总线

一、CAN 总线简介

CAN 总线(Controller Area Network,控制器局域网)是一种广泛应用于汽车、工业控制等领域的串行通信协议,由德国博世(Bosch)公司在 1980 年代开发,主要用于设备间的实时数据交换,当一个设备发送信息给 CAN 总线上,当这个信息通过了一些接收设备的过滤器,则接收设备就会收到发送设备的信息,如果多个接收设备都符合接收的条件,则有多个接收设备;下面是 CAN 总线的特征:

  • 两根通信线(CAN_H、CAN_L),线路少
  • 差分信号通信,抗干扰能力强
  • 高速 CAN(ISO11898):125k ~ 1Mbps, < 40m;低速 CAN(ISO11519):10k ~ 125kbps, < 1km
  • 异步通信,无需时钟线,通信速率由设备各自约定
  • 半双工,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序
  • 11位/29位报文ID,用于区分消息功能,同时决定优先级
  • 可配置1~8字节的有效载荷
  • 可实现广播式和请求式两种传输方式
  • 应答、CRC校验、位填充、位同步、错误处理等特性

下面表格是主流通信协议的对比:

名称引脚双工时钟电平设备应用场景
UARTTX、RX全双工异步单端点对点两个设备互相通信
I2CSCL、SDA半双工同步单端多设备一个主控外挂多个模块
SPISCK、MOSI、MISO、SS全双工同步单端多设备一个主控外挂多个模块(费导线,但传输速度快)
CANCAN_H、CAN_L半双工异步差分多设备多个主控互相通信

1.1.差分信号

差分信号是一种用两根信号线传输一个数据的技术。这两根线(如 CAN_H 和 CAN_L)始终传输大小相等、极性相反的电压信号,接收端通过计算两者的差值来判断逻辑状态。关键特点:

  • 抗干扰能力强
  • 抑制共模噪声
  • 更低的电压摆幅,更高的速度

高速 CAN:
逻辑1(隐性):CAN_H = 2.5V,CAN_L = 2.5V → 差值 = 0V
逻辑0(显性):CAN_H = 3.5V,CAN_L = 1.5V → 差值 = +2V

在这里插入图片描述

低速 CAN:
逻辑1(隐性):CAN_H = 3.25V,CAN_L = 1.75V → 差值 = -1.5V
逻辑0(显性):CAN_H = 4V, CAN_L = 1V → 差值 = 3V

在这里插入图片描述
为什么逻辑 0 是显性电平,逻辑 1 是显性电平?因为该协议具有“线与”的特性,0 和 0 相与为 0;0 和 1 相与也为 0。这样的好处也可以区分出优先级,当多个设备同时发送消息,这些消息就会在仲裁段数据里开始比对,谁的数据不一致先出现了 1,那输掉仲裁,优先处理胜出那一个。

1.2.CAN 硬件电路

每个设备通过 CAN 收发器挂载在 CAN 总线网络上,CAN 控制器引出的 TX 和 RX 与 CAN 收发器相连,CAN 收发器引出的 CAN_H 和 CAN_L 分别与总线的 CAN_H 和 CAN_L 相连,CAN 硬件电路的连接方式不同,添加的电阻阻值不同,也分为两种类型:

  • 高速 CAN:CAN_H 和 CAN_L 两端添加 120Ω 的终端电阻
  • 低俗 CAN:CAN_H 和 CAN_L 其中一端添加 2.2kΩ 的终端电阻

如下图所示:

在这里插入图片描述

CAN 总线上的两个电阻类似 I2C 上的上拉电阻,如果 CAN 总线上没有设备发送显性电平“拉开”总线,那就保持默认的隐性电平。

下图是 CAN 总线物理层的特性:

在这里插入图片描述

二、CAN 总线帧格式

下面内容均以高速 CAN 为例子。

帧类型用途
数据帧发送设备主动发送数据(广播式)
遥控帧接收设备主动请求发送数据(请求式)
错误帧某个设备检测出错误时向其他设备通知错误
过载帧接收设备通知发送设备,接收设备还没做好接收准备
帧间隔用于将数据帧及遥控帧与前面的帧分开

2.1.数据帧

数据帧由 7 个不同的位场组成:

  • 帧起始:当 CAN 总线空闲时,也就是处于隐性电平时,发送设备首先“拉开”电平,和各个设备说“我要发数据了”,起到通知作用,这个段只有 1 个位数据,也就是逻辑 0
  • 仲裁段:用于解决多个节点同时发送数据时的冲突问题,确保优先级高的报文优先传输,而无需中央控制器协调。其核心作用是实现非破坏性逐位仲裁;该段分为标准格式和扩展格式,在 IDE 位上写入逻辑 1,即为扩展格式,之后的 r1 就是原来的 IDE
  • 控制段:控制段控制前面的仲裁段是否为扩展格式,也控制后面数据段的长度
  • 数据段:填写数据,数据段的长度可以为 0
  • CRC 段:循环冗余校验,校验数据是否正确
  • 应答段(ACK):发送设备将信息发送之后将总线控制权交给接收设备,需要接收设备回应逻辑 0
  • 帧结束:以 7 个逻辑 1 为结束帧

在这里插入图片描述

  • SOF(Start of Frame):帧起始,表示后面一段波形为传输的数据位
  • ID(Identify):标识符,区分功能,同时决定优先级
  • RTR(Remote Transmission Request ):远程请求位,区分数据帧和遥控帧
  • IDE(Identifier Extension):扩展标志位,区分标准格式和扩展格式
  • SRR(Substitute Remote Request):替代RTR,协议升级时留下的无意义位
  • r0/r1(Reserve):保留位,为后续协议升级留下空间
  • DLC(Data Length Code):数据长度,指示数据段有几个字节
  • Data:数据段的1~8个字节有效数据
  • CRC(Cyclic Redundancy Check):循环冗余校验,校验数据是否正确
  • ACK(Acknowledgement):应答位,判断数据有没有被接收方接收
  • CRC/ACK界定符:为应答位前后发送方和接收方释放总线留下时间
  • EOF(End of Frame ):帧结束,表示数据位已经传输完毕

2.2.遥控帧

遥控帧与数据帧相比,只是将仲裁段的 RTR 位写入逻辑 1,没有了数据段,遥控帧用于请求其他设备发送消息,如下图所示:

在这里插入图片描述

2.3.错误帧

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

在这里插入图片描述

错误帧的错误种类共有图下几种:

在这里插入图片描述

错误状态有三种:

  • 主动错误状态:(初始状态)主动错误状态的设备正常参与通信并在检测到错误时发出主动错误帧,6 个或以上的逻辑 0,处于主动错误状态的设备在 CAN 总线上具有强大的破坏数据的能力,可以破坏别的设备数据(根据“线与”特性),如果某个设备接收或发送错误的计数值超过 127,那就将该设备转变为被动错误状态
  • 被动错误:被动错误状态的设备正常参与通信但检测到错误时只能发出被动错误帧,6 个或以上的逻辑 1,处于被动错误状态中的设备只能发出逻辑 1 来破坏自己的数据,不能影响其他设备所发出的信息,如果处于被动错误状态中的设备,减少了发送或接收错误的计数值并 <= 127,该设备就有能够回到主动错误状态;如果继续错误,发送错误的计数值 > 255,就将该设备转变为关闭总线状态
  • 关闭总线状态:总线关闭状态的设备不能参与通信,该设备只能“闭嘴”,等待在总线上检测到 128 次连续个位的逻辑 1 才能转变为主动错误状态(初始状态),也就是总线处于空闲

下图是错误状态:
在这里插入图片描述

下面表格是错误计数器的计数规则,由下表可知,容错率是比较低的:

在这里插入图片描述

2.4.过载帧

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

在这里插入图片描述

2.5.间隔帧

将数据帧和遥控帧与前面的帧分离开,如下图所示:

在这里插入图片描述

三、位填充和位同步

3.1.位填充

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

在这里插入图片描述

位填充作用:

  • 增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机
  • 将正常数据流与“错误帧”和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性
  • 保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲

3.2.位同步

3.2.1.接收方采样的理想状态和实际可能遇到的状态

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

在这里插入图片描述
上图是理想的采样,如果没有约定某个波特率,可能会出现以下情况:

  • 接收方以约定的位时长进行采样,但是采样点没有对齐数据位中心附近
    在这里插入图片描述
  • 接收方刚开始采样正确,但是时钟有误差,随着误差积累,采样点逐渐偏离
    在这里插入图片描述

3.2.2.位时序

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

在这里插入图片描述

  • SS = 1Tq
  • PTS = 1~8Tq
  • PBS1 = 1~8Tq
  • PBS2 = 2~8Tq

将 SS 移到边沿变化的位置,即可采样,采样点通常位于 PBS1 和 PBS2 的交界处,如果采样点比较靠前,可以通过增加 PBS1 的 Tp ,使得采样点靠后走;如果采样点比较靠后,可以通过增加 PBS2 的 Tp ,使得采样点靠前走。

3.2.3.硬同步

发生在打破宁静的帧起始(SOF)的下降沿,所有节点强制调整自己的位时序,使 SS 段对齐起始帧的下降沿。如下图所示:

在这里插入图片描述

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

3.2.3.再同步

上面小点说到了:经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然都会对齐数据位中心附近。如果有误差,随着误差积累,数据位边沿逐渐偏离 SS 段,则此时接收方根据再同步补偿宽度值(SJW=1~4Tq)通过加长 PBS1 段,或缩短 PBS2 段,以调整同步;再同步可以发生在第一个下降沿之后的每个数据位跳变边沿

在这里插入图片描述

3.3.波特率

波特率 = 1 / 一个数据位的时长 = 1 / (TSS + TPTS + TPBS1 + TPBS2)
例如:

  • SS = 1Tq,PTS = 3Tq,PBS1 = 3Tq,PBS2 = 3Tq
  • Tq = 0.5us
  • 波特率 = 1 / (0.5us + 1.5us + 1.5us + 1.5us) = 200kbps

在这里插入图片描述

四、资源分配规则

CAN 总线支持多信息(报文)交替传输,但同一时间只能有一个节点发送数据,CAN总线像一条单车道高速公路,所有车辆(报文)按交通规则(仲裁)有序通过。如果有一个设备正在发送数据,中间不允许插入另一个设备发送数据打断该数据,必须等该数据发送完毕,下一个设备才能发送信息;又如果有多个设备想同时发送信息,那就要比对仲裁段里的 ID 了,一开始的数据都一样,谁先出现隐性电平,谁就仲裁失利,等待下一次发送。

4.1.先占先得

先占先得的规矩如下:

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

4.2.非破坏性仲裁

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

  • 线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态;只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0 & X & X = 0,1 & 1 & 1 = 1
  • 回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1

在这里插入图片描述

当数据帧和遥控帧的 ID 一样时,数据帧的优先级高于遥控帧,因为 RTR 位需要逻辑 1 才能表示遥控帧:

在这里插入图片描述

标准格式 11 位 ID 号和扩展格式 29 位 ID 号的高 11 位一样时,标准格式的优先级高于扩展格式(SRR 必须始终为 1,以保证此要求),原因和上面同理。
在这里插入图片描述

五、STM32F103C8T6 上的 CAN 外设

5.1.简介

STM32 内置 bxCAN 外设(CAN控制器),支持 CAN2.0A 和 2.0B,可以自动发送 CAN 报文和按照过滤器自动接收指定 CAN 报文,程序只需处理报文数据而无需关注总线的电平细节,该芯片上的 CAN 外设具有下面特点:

  • 波特率最高可达1兆位/秒
  • 3个可配置优先级的发送邮箱
  • 2个3级深度的接收FIFO
  • 14个过滤器组(互联型28个)
  • 时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式

5.2.CAN 网拓扑结构

下图是各个结点与 CAN 总线相连:
在这里插入图片描述

下面 PA11 和 PA12 是默认复用的:

在这里插入图片描述

下面 PB8 和 PB9 是重定义:

在这里插入图片描述

5.3.CAN收发器电路

在这里插入图片描述
在这里插入图片描述

5.4.CAN 的基本结构

在这里插入图片描述
如上图所示,CPU 写入数据是由发送邮箱装载的,这三个发送邮箱是由用户来决定谁先被发送,可以是先进先出,或是根据优先级来排队;CPU 读取数据:引脚读取数据,并由接受控制器传入到接收过滤器,经过过滤器,确定是设备想要的信息,就把该数据存放在接收 FIFO0 或 FIFO1 里面,如果短时间读取到很多数据,两个接收 FIFO 就起到分流的作用了,也能够起到保存重要信息的作用,重要的信息放在 FIFO0 或 FIFO1,不重要的就放在另一个队伍里,如果实在不够放,用户可以开启 FIFO 的锁定状态,就会使新的数据直接丢弃,如果没开启,则新的数据占据邮箱 2 的位置,成为新的数据。

5.4.1.发送过程

选择一个空置邮箱→写入报文 →请求发送:

在这里插入图片描述
看图和了解每个缩写的意思即可:

  • RQCP(Request completed):请求完成
  • TXOK(Transmission OK):发送成功
  • TME(Transmit mailbox empty):发送邮箱空
  • TXRQ(Transmit mailbox request):发送请求控制位
  • NART(No automatic retransmission):禁止自动重传
  • ABRQ(Abort request):中止发送

5.4.2.接收过程

接收到一个报文→匹配过滤器后进入 FIFO0 或 FIFO1 → CPU 读取:(下面图纠正,表示 2 应该使用二进制,表示 3 也一样)

在这里插入图片描述

  • FMP(FIFO message pending):报文数目
  • FOVR(FIFO overrun):FIFO 溢出
  • RFOM(Release FIFO output mailbox):释放邮箱

注意,当邮箱处于溢出状态时,释放邮箱之后会变成挂号 2 状态,因为溢出状态与挂号 3 状态是一样的。

5.5.过滤器

CAN 总线过滤器(Filter)是 CAN 控制器中的一个硬件功能,用于筛选接收到的报文,只让符合特定条件的报文进入接收缓冲区,从而减少 CPU 处理无关数据的负担。这些过滤器分为两大类:

  • 标识符屏蔽
  • 列表模式

下面是一个过滤器的寄存器介绍,本次实验的开发板共有 14 个过滤器,也就是有 14 个下面的寄存器,每个过滤器的核心由两个 32 位寄存器组成:R1[31:0] 和 R2[31:0]:

  • FSC(Filter scale configuration):位宽设置,置 0:16 位;置 1:32 位
  • FBM(Filter mode):模式设置:置 0:屏蔽模式;置 1:列表模式
  • FFA(Filter FIFO assignment):关联设置:置 0:关联到 FIFO0;置 1:关联到 FIFO1
  • FACT(Filter active) :激活设置:置 0:禁用;置 1:启用

5.5.1.标识符屏蔽模式

标识符屏蔽模式将 FBM 寄存器置 0,该模式常用于接收报文多于过滤器的数量,通过锁定特定位,来区分能否通过该过滤器,可以部分匹配 ID 的机制。通过配置 FSC 寄存器又可以分为 32 位和 16 位的过滤器

  • 32 位的过滤器:通常用于过滤扩展格式的 ID,R1 用来填写目标 ID,需要过滤的位一般置 0;R2 用来匹配用户需要的位,写入的码叫掩码,置 1,报文匹配 R1 寄存器的位才能通过,置 0,则不匹配的不能通过

在这里插入图片描述

  • 16 位的过滤器:通常用于过滤标准格式的 ID,将 R1 和 R2 分为两个过滤器;R1 的低 16 位用于填写目标 ID,需要过滤的位一般置 0,高 16 位用于匹配需要的位,写入的码叫掩码,置 1,报文匹配 R1 寄存器的位才能通过,置 0,则不匹配的不能通过,16 位的过滤器这样设计,提高了资源利用率,减少了资源浪费

在这里插入图片描述

例子:下图是扩展格式的 ID,需要的 ID 是 0x12345600 - 0x123456FF,这些 ID 有规律,保留前面 0x123456xx,这种情况使用 32 位的屏蔽模式最合适,R1 寄存器写入目标 ID:0x12345600,后面两个 0 是随便填写的,左移 3 位跨过 IDE 位、RTR 位和最后的置 0 位,或上 0x4,是说明 IDE 置 1,报文是扩展格式;R2 寄存器写入掩码,前面 29 位都置 1,说明有匹配前面要求才能通过,左移 3 位与上面说明一致,或上 0x4,是说明只有扩展格式才能通过过滤器,再或上 0x2 是用于区分数据帧还是遥控帧,这里是锁定了只有数据帧才能通过过滤器。

在这里插入图片描述

例子:下图是标准格式的 ID,需要的 ID 是 0x200 - 0x2FF 和 0x320 - 0x32F,符合 0x2 开头的和 0x3 开头的 ID 才能通过该过滤器,过程和 32 位的一致,只是一个寄存器的低 16 位用于填写目标 ID,高 16 位用于填写掩码:

在这里插入图片描述

5.5.1.列表模式

列表模式用于精确匹配特定 ID 的场景,R1 和 R2 都可以填写用户想要的目标 ID,其中 16 位的过滤器可以填写 4 个标志格式的目标 ID。

在这里插入图片描述

在这里插入图片描述

例子:下面 ID 中,只想要 0x123 和 0x12345678,就需要用到 32 位过滤器的 R1 和 R2,用 32 位寄存器写入标准格式的 ID,只需要写完前 11 位的 ID 之后,后面补 0 即可。
在这里插入图片描述

5.6.测试模式

当一个设备想要测试自己发送或接收 CAN 总线上的数据是否正常,又不影响别的设备在 CAN 总线上的运行,这里有三个模式用来测试:

  • 静默模式:用于分析 CAN 总线的活动,不会对总线造成影响

在这里插入图片描述

  • 环回模式:用于自测试,同时发送的报文可以在 CAN_TX 引脚上检测到

在这里插入图片描述

  • 环回静默模式:用于热自测试,自测的同时不会影响 CAN 总线

在这里插入图片描述

5.7.工作模式

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

一个 CAN 外设一开始就要进入初始化模式进行配置,配置结束之后才能进入正常模式,如果在正常模式初始化,还没配置完成就在错误的参数下运行。
在这里插入图片描述

  • AWUM(Automatic wakeup mode):自动唤醒模式
  • SLAK(Sleep ack):睡眠应答
  • INAK(Inti ack):初始化应答
  • INRQ(Init request):请求初始化

六、实验

6.1.单个设备环回测试

本实验目的是测试该设备在 CAN 总线的发送和接收是否正常,按下按键显示本设备的 ID 信息,再按下按键数据就自增,配置 CAN 的时候,将模式配置成CAN_Mode_LoopBack;,下面是 MyCAN.c 代码:

#include "stm32f10x.h"                  // Device header
//配置GPIO口的PA11和PA12;配置CAN等
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;		//波特率 = 36M / 48 / (1 + 2 + 3) = 125K
	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(uint32_t ID, uint8_t Length, uint8_t *Data)
{
	CanTxMsg TxMessage;
	TxMessage.StdId = ID;
	TxMessage.ExtId = ID;
	TxMessage.IDE = CAN_Id_Standard;		//CAN_ID_STD
	TxMessage.RTR = CAN_RTR_Data;
	TxMessage.DLC = Length;
	for (uint8_t i = 0; i < Length; i ++)
	{
		TxMessage.Data[i] = Data[i];
	}
	
	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(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{
	CanRxMsg RxMessage;
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
	
	if (RxMessage.IDE == CAN_Id_Standard)
	{
		*ID = RxMessage.StdId;
	}
	else
	{
		*ID = RxMessage.ExtId;
	}
	
	if (RxMessage.RTR == CAN_RTR_Data)
	{
		*Length = RxMessage.DLC;
		for (uint8_t i = 0; i < *Length; i ++)
		{
			Data[i] = RxMessage.Data[i];
		}
	}
	else
	{
		//...
	}
}

下面是头文件:

#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

下面是 main.c 代码:

#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"

uint8_t KeyNum;
uint32_t TxID = 0x555;
uint8_t TxLength = 4;
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44};

uint32_t RxID;
uint8_t RxLength;
uint8_t RxData[8];

int main(void)
{
	OLED_Init();
	Key_Init();
	MyCAN_Init();
	
	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)
		{
			TxData[0] ++;
			TxData[1] ++;
			TxData[2] ++;
			TxData[3] ++;
			
			MyCAN_Transmit(TxID, TxLength, TxData);
		}
		
		if (MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxID, &RxLength, RxData);
			
			OLED_ShowHexNum(2, 6, RxID, 3);
			OLED_ShowHexNum(3, 6, RxLength, 1);
			OLED_ShowHexNum(4, 6, RxData[0], 2);
			OLED_ShowHexNum(4, 9, RxData[1], 2);
			OLED_ShowHexNum(4, 12, RxData[2], 2);
			OLED_ShowHexNum(4, 15, RxData[3], 2);
		}
	}
}

6.2.三个设备互相通信

基于上面的 MyCAN.c,在 CAN 配置里,CAN 工作模式换成CAN_Mode_Normal;,其他不需要更改,然后用到三个 STM32 开发板,插上一个更改 ID 再按下按键保存 ID,另外两个也这样操作,最后按下其中一个开发板的按键,另外两个就会收到它的数据。

<think> 首先,用户的问题是关于STM32CAN总线的使用、配置及开发。参考引用提供了两个来源:引用[1]是关于使用CAN总线进行通信的详细文章,包括基础知识、硬件配置、操作和应用实例。引用[2]是关于CAN总线开发的要点,特别是波特率的计算。 我的回答需要遵循系统级指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 回答结构清晰,逐步解决问题,保证真实可靠。 用户的问题焦点是STM32 CAN总线的使用、配置和开发。我需要基于提供的引用构建回答,因为引用是给定的资源。引用[1]和[^1]是相关的。 关键点: - 引用[1]:介绍了CAN协议基础知识、STM32硬件配置、基本操作和应用实例。 - 引用[2]:强调了波特率的重要性,并给出了计算公式:波特率 = (pclk1 / ((1+8+7)*9)) = 36Mhz/16/9 = 250Kbits。这涉及CAN的配置,特别是波特率设置。 在回答中,我需要自然地引用这些。例如,当谈到配置时,提到波特率计算并添加引用。 结构回答: - 先介绍CAN总线STM32中的重要性。 - 然后分步讨论使用、配置和开发: 1. 基础知识:简要概述CAN协议(基于引用[1])。 2. 硬件配置:如何设置STM32CAN硬件,包括引脚、时钟等。 3. 波特率配置:详细说明如何计算波特率(基于引用[2]),并用LaTeX公式表示。 4. 软件开发:使用STM32CubeIDE或HAL库进行编程的例子(基于引用)。 5. 应用实例:一些常见应用(基于引用[1])。 - 确保使用LaTeX:例如,波特率公式用$$...$$表示。 - 结束部分生成相关问题。 生成相关问题:基于回答内容,提出几个相关问题,如优化、调试等。 LaTeX使用: - 行内数学:例如,波特率计算中的数字,如$250Kbits$。 - 独立公式:波特率公式,如$$ \text{波特率} = \frac{\text{pclk1}}{(1 + \text{BS1} + \text{BS2}) \times \text{Prescaler}} $$ 参考引用[2],具体为$$ \text{波特率} = \frac{\text{pclk1}}{(1 + 8 + 7) \times 9} = \frac{36 \text{MHz}}{16 \times 9} = 250 \text{Kbits} $$ [^2]。 添加引用:在相关段落末尾添加[^1]或[^2]。 确保回答真实可靠:基于提供的引用,因为它们是给定的资源。 完整回答结构: 1. 引言:简述STM32 CAN总线的重要性。 2. 使用:如何开始使用CAN总线。 3. 配置:硬件和软件配置,包括波特率计算。 4. 开发:编程实现。 5. 结语。 6. 相关问题。 使用中文。 </think>### STM32 CAN总线的使用、配置及开发 STM32微控制器广泛支持CAN(Controller Area Network)总线协议,这是一种高效的串行通信协议,常用于汽车电子、工业控制等领域。STM32CAN模块通过硬件加速和丰富的软件库(如HAL库或LL库),简化了开发过程。下面我将基于提供的引用资源,逐步介绍CAN总线的使用、配置和开发方法。回答内容参考了可靠的技术文档[^1][^2],确保真实可靠。 #### 1. **CAN总线基础知识** CAN总线是一种多主控、高可靠性的通信协议,支持差分信号传输,适用于噪声环境。在STM32中,CAN协议基于ISO 11898标准,支持标准帧(11位ID)和扩展帧(29位ID)。通信过程包括帧发送、接收、错误检测和仲裁机制。STM32CAN控制器内置于芯片,开发者可通过配置寄存器或使用库函数实现通信[^1]。 - **关键特性**:支持波特率高达1 Mbps、自动重传、硬件过滤。 - **应用场景**:汽车ECU(电子控制单元)、工业传感器网络、机器人控制系统。 #### 2. **硬件配置** 在使用CAN总线前,需配置STM32的硬件资源。这包括: - **引脚设置**:CAN总线使用两个引脚:CAN_RX(接收)和CAN_TX(发送)。在STM32CubeIDE中,通过图形化工具选择引脚并启用CAN外设(例如,CAN1或CAN2)。 - **时钟源**:CAN模块的时钟通常来自APB1总线(pclk1)。时钟频率需与波特率匹配。例如,若pclk1为36 MHz,波特率设置为250 Kbits,则需计算分频参数。 - **终端电阻**:在总线上添加120Ω终端电阻,以确保信号完整性。这通常在硬件设计阶段完成。 硬件配置完成后,使用STM32CubeMX生成初始化代码,自动设置GPIO和时钟树[^1]。 #### 3. **波特率配置** 波特率是CAN通信的核心参数,所有设备必须使用相同的波特率才能通信。STM32通过分频器(Prescaler)和时间段(BS1、BS2)实现波特率调整。计算公式如下(独立公式,单独成段): $$ \text{波特率} = \frac{\text{pclk1}}{(1 + \text{BS1} + \text{BS2}) \times \text{Prescaler}} $$ 其中: - $\text{pclk1}$ 是APB1总线时钟频率(单位:Hz)。 - $\text{BS1}$ 和 $\text{BS2}$ 是时间段参数,决定位时间的划分。 - $\text{Prescaler}$ 是分频系数。 例如,在引用[2]中,给定pclk1为36 MHz,设置BS1=8、BS2=7、Prescaler=9,则波特率计算为: $$ \text{波特率} = \frac{36 \times 10^6}{(1 + 8 + 7) \times 9} = \frac{36 \times 10^6}{16 \times 9} = 250 \times 10^3 \, \text{bits/s} = 250 \, \text{Kbits} $$ 在STM32CubeIDE中,通过图形界面设置这些参数,波特率计算器会自动验证配置。注意,BS1和BS2的值影响采样点位置,建议参考STM32参考手册优化抗噪性能[^2]。 #### 4. **软件开发和基本操作** STM32提供HAL库简化CAN通信开发。以下是使用STM32CubeIDE和HAL库的典型步骤: - **初始化**:调用`HAL_CAN_Init()`函数初始化CAN外设,设置波特率、工作模式(正常模式或环回模式)。 - **过滤器配置**:使用`HAL_CAN_ConfigFilter()`设置硬件过滤器,过滤无关ID帧,减少CPU负载。 - **发送数据**:创建CAN帧结构体(包含ID、数据长度、数据字节),调用`HAL_CAN_AddTxMessage()`发送。示例代码: ```c CAN_TxHeaderTypeDef TxHeader; uint8_t TxData[8] = {0x01, 0x02, 0x03}; // 示例数据 uint32_t TxMailbox; TxHeader.StdId = 0x123; // 标准ID TxHeader.IDE = CAN_ID_STD; // 标准帧 TxHeader.RTR = CAN_RTR_DATA; // 数据帧 TxHeader.DLC = 3; // 数据长度 if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) == HAL_OK) { // 发送成功 } ``` - **接收数据**:启用接收中断,在回调函数中处理帧。示例: ```c void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef RxHeader; uint8_t RxData[8]; if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK) { // 处理接收数据 } } ``` - **错误处理**:检测总线错误或仲裁丢失,使用`HAL_CAN_GetError()`诊断问题。开发时建议启用调试模式,通过逻辑分析仪验证波形。 完整的应用实例包括多节点通信(如主控制器与传感器节点),在引用[1]中详细描述了如何实现实时数据传输和错误恢复[^1]。 #### 5. **开发工具和资源** - **工具**:使用STM32CubeIDE免费开发环境,它集成STM32CubeMX配置工具和代码生成器。 - **库支持**:HAL库提供高级API,或LL库用于低级优化。参考STM32CubeF4或CubeL4固件包中的CAN例程。 - **调试技巧**:结合CAN分析仪(如PCAN-USB)监控总线流量;使用STM32内置的错误计数器优化配置。 - **注意事项**:确保波特率一致、避免ID冲突、处理电磁干扰(EMI)。在开发板上测试后,再部署到目标系统[^1][^2]。 通过以上步骤,您可以快速上手STM32 CAN总线的开发。STM32的硬件支持和软件库显著降低了门槛,适合从初学者到专业工程师使用。如果您有特定开发板型号(如STM32F4系列),我可以提供更详细的代码示例。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值