1. CAN BUS 是什么
由德国电器商 BOSCH(博世) 设计,其为世界第一汽车技术供应商
CAN BUS:Controller Area Network Bus 控制器局域网总线,也是像 UART / IIC / SPI 等协议一样用于通讯,当时主要用于汽车电子 ===> 半双工,异步,总线
为什么要设计 CAN 总线呢?因为汽车中有大量的电子器件(比如传感器等),有大量的数据需要与汽车的主控进行通信,而 UART / IIC / SPI 总线并不适合汽车电子:
1. UART 是一对一通信,所以一个电子器件就需要一个串口,设备开销大
2. IIC 虽然可以一对多,但是 IIC 速率较低(100K bps—400K bps,400K bps相当于 48.8KB/s),而且 IIC 另外的一个缺点是,当主控与总线上的一个设备 A 进行通信时,假设有另外一个设备 B 需要报告一个非常紧急的事件给主控,因 A 的原因,B 会导致紧急事件反应不及时
3. SPI 虽然速率较 IIC 快(可以达到几M甚至几十M),但是多台设备就需要多个片选
引脚(CS),同样设备开销较大
4. 以上协议都有同样的问题就是信号传输距离较短(几米甚至几厘米)
相对而言采用 SPI 更加合理,但 SPI 总线线束数量太多,所以为了解决汽车中大量线束的问题,设计出了 CAN 总线。可以通过多个 LAN(局域网),进行大量数据的高速传输
CAN 怎么实现的?CAN 总线只有两根差分信号的数据线,没有其他的线束了
差分信号: 单端信号用一根线传递信号,差分信号是指两根线(双绞线)上传输同样的信号,振幅相同,相位相同,极性相反,抗干扰能力强,传输距离远
因为只有数据线,没有时钟线,所以是异步的
没有时钟线进行同步,所以只能像 UART 一样收发双方约定一个既定的速率(波特率)去收发
处于同一个 LAN 下设备按照相同的速率进行收发,如果有多个 LAN,LAN 与 LAN 之间速率可能不一样,但同样可以通信,利用 网关 / 转发器 即可
其中 CAN 总线的两根数据线分别为 CAN_H / CAN_LCAN_H ---> 高电位驱动(正)
CAN_L ---> 低电位驱动(负)
在不同的 LAN 中 CAN_H 有不同的颜色
LAN COLOR
CAN_L ALL 棕色
CAN_H 驱动系统 黑色
舒适系统 绿色
信息系统 紫色
......
那么这两根数据线是怎么传输数据的呢?
CAN_H ≈ CAN_L 对应于 Logic 1 隐性:Recessive (相差不明显)
CAN_H - CAN_L ≈ 2.0V 对应于 Logic 0 显性:Dominant (相差很明显)
2. CAN BUS 协议
CAN总线上的数据传输是以 Frame(帧) 为单位进行传输
帧(或者报文)分为四种:
- 数据帧:最常见的报文类型,用来发送数据用的,传输某某数据
- 远程帧(遥控帧):用于接收单元向具体的发送单元请求数据
- 错误帧
- 过载帧:overload,你们发的太频繁了,我实在是忙不过来接收不了啦
- 间隔帧:用于将数据帧及远程帧与前面的帧分离开来
帧格式:详情见<STM32F4xx中文参考手册.pdf>623页(图236.CAN帧)
Field Name Length(Bits) Purpose(用途) 字段名 ------------------------------------------------------------------------------- SOF 1bit 标志一个消息帧的开始 Start Of Frame (Dominant,0) ------------------------------------------------------------------------------- 11bits CAN 总线是广播类型的总线,位于 标准 ID 总线上的所有设备都可以收到总线上 传输的报文,并且 CAN 总线上的设备 Identifier 29bits 没有地址的概念,所以无法将报文单独 (for the data) 扩展 ID 发给指定节点,所有节点都将始终捕获 所有报文。那怎么实现数据的收发? 我们在CAN总线上用不同的 Identifier 标识 A unique identifier CAN BUS 没有“地址的概念” 也没有某某设备的概念,它只有某某数据内容 CAN BUS 只标识某某数据 id = 1 表示xx数据 id = 2 表示xx数据 id = 3 表示xx数据 ... -------------------------------------------------------------------------------- RTR (Romote 0 数据帧 ---> 主动发数据 Transmission 1bit 1 远程帧(请求帧) ---> 请求别人发数据 Request) -------------------------------------------------------------------------------- IDE 0 表示本帧使用的是标准ID 11bits (Identifier 1bit 1 表示本帧使用的是扩展ID 29bits Extension) ------------------------------------------------------------------------------- R0 (Reserved Bit 0) 1bit 保留位,无意义,但必须为0 ------------------------------------------------------------------------------- DLC 用来确定该消息中,正文数据内容的长度 (Data Length Code) 4bits 可取值:0 ~ 8bytes ------------------------------------------------------------------------------- Data Field 0~8bytes 消息正文,也就是你要发送的数据 ------------------------------------------------------------------------------- CRC 15bits 循环冗余校验多项式(大于0,并且收发方一 致就可以了) ------------------------------------------------------------------------------- CRC Delimter 1bit CRC分隔符,用来将CRC和后面的内容分隔开 ------------------------------------------------------------------------------- ACK Slot 1bit 应答位:发送时为 Recessive ==> 1 接收时为 Dominant ==> 0 ------------------------------------------------------------------------------- ACK Delimter 1bit ACK 分隔符,必须为1(Recessive) ------------------------------------------------------------------------------- EDF (End Of Frame) 7bits Must Be Recessive(1) -------------------------------------------------------------------------------
思考:CAN总线是以广播的形式进行通信,大家都可以往总线上发送数据和接收数据,如果两个或者两个以上的节点同时往CAN总线上发送数据呢?
此时我们就需要进行总线仲裁:
总线仲裁是依据于仲裁字段进行的,根据<图236.CAN帧>可知,标准ID数据帧中仲裁字段一共12bit(中文参考手册标的是32bit),其中:
RTR 占最后1bit
ID 11bit 标准ID
29bit 扩展ID
怎么依据仲裁字段进行总线仲裁呢?
我们约定,CAN总线上每一个节点,都需要去总线上采样(包括发送者,这种行为称之为回读:每个节点往总线上发送1bit 数据的同时会读取1bit总线上的数据,并与自己发送的数据作对比)。采样后就可以进行仲裁啦
例子:
R:Recessive 隐性 D:Dominant 显性
发送者1
1(R)
0(D)
0(D)
0(D)
...
EOF
发送者2
1(R)
0(D)
1(R)^2 Loser(仲裁失败) --> 转变为接收者
发送者3
1(R)
0(D)
0(D)
1(R) 仲裁失败
总线值
1(R)
0(D)
0(D)^1
0(D)
...
EOF
注: 1. 只有总线上所有节点发出的数据都为1时,总线状态才为1, 只要有一个节点发送0,则总线上的状态为0,称之为线与
2. 当节点回读的时候发现总线上的电平与自己发送的不一致的时候,则节点会采取相应的措施,比如退出仲裁或者报错
总结:在总线空闲时,最先开始发送消息的节点获得发送权。多个节点同时开始发送的时候,各发送节点从仲裁字段的第一位开始进行仲裁,连续输出显性电平(0)最多的节点可继续发送。说白了,仲裁其实就是通过比较数据标识符(Identifier)的大小来进行的,标识符越小优先级越高,也就会获得仲裁,标识符越大则退出仲裁
CAN总线的特点:
- 串行通信,半双工,但是有两根数据线(构成差分信号)
- 多主机的协议
- 异步的,没有时钟线
- 以消息内容中的 ID 进行仲裁,每个发送节点,也必须去采集总线上的数据,如果发现采集到的数据与自己发送的不同,你就是Loser,你就变为接收者
- 传输速率高达1M bps,同一个LAN,必须按照相同的速率传输,不同的LAN,也可以通过网关连接
由于每一个发送者发送一个 bit 又会从总线上采集一个 bit,为了确保数据的发送和采集没有误差,每个 bit 位的传输CAN总线都做了规定:
每个 bit 位的传输,都需要经历如下4个阶段的时间:
SYNC_SEG PROP_SEG PHASE_SEG1 PHASE_SEG2
同步段 传播段 相位缓冲段1 相位缓冲段2
把这四个阶段合称为NBT:Norminal Bit Time 一个 bit 位的时序 ---> 位时序
位时序决定CAN总线的波特率的大小
为什么会有四个阶段呢?
分阶段的原因有多个:
(1) 网络(LAN)上有延时
(2) 发送者发送需要时间,自己本身采样也需要时间
(3) 同一个LAN上的所有节点都要求按照相同的速率进行收发
比如:总线上的设备都按500K Hz进行通信
那么T = (1/500K) s
CAN总线上的时钟周期为(1/500K)s,记为 tq
(4) 因为每一个节点以各自独立的时钟工作,为了补偿节点间的晶振误差,所以会有 PHASE_SEG 相位缓冲段,又分为相位缓冲段1(PS1)和相位缓冲段2(PS2)
PHASE_SEG1 用来对每个节点进行同步,此段花费的时间为1~8tq,记为同步时间长度SJW
PHASE_SEG2用来对信号进行处理,此段花费的时间为1~8tq,记为信号处理时间 IPT (Information Proccessing Time)
四个阶段各自的作用为:(1) YNC_SEG同步段,占1个CAN总线时钟周期 tq
主要用于电平从隐性--->显性或从显性--->隐性的变换所需要的时间
(2) PROP_SEG传播段(网络延时段),占1~8个tq
tPROP_SEG = 2(tTX + tBUS + tRX)
tTX:发送延时,从发送方发出指令,到总线上电平变化所需要的时间
取总线上发送数据最慢的那个节点所需要的 tTX
tBUS:总线传输延时
取总线上数据传输到最远节点所需要的时间
tRX:接收延时,从总线上电平的变化到接收者保存数据所需要的时间
取总线上接收数据最慢的那个节点所需要的 tRX
(3) PHASE_SEG(PHASE_SEG1 / PHASE_SEG2)相位缓冲段
为了消除不同设备节点之间的晶振差异,以及同步时间和信息处理时间
PHASE_SEG1:1~8 tq 主要负责同步时间长度(SJW)
PHASE_SEG2:1~8 tq 主要负责信息处理时间(IPT)
PHASE_SEG2 = MAX(PHASE_SEG1, tIPT)
采样是发生在PHASE_SEG1的末尾,一般来说,采样点要处于整个NBT的75%-80%处
同时 tNBT = tSYNC_SEG + tPROP_SEG + tPHASE_SEG1 + tPHASE_SEG2
3. STM32F4xx的CAN总线控制器
STM32自带了基本扩展的CAN外设,又称为bxCAN,其传输频率最高可达1M bps
CAN控制器的特性:1. 筛选器
用来筛选特定的消息(比如:按消息的ID来筛选),其它的消息会被丢弃掉。共有28个筛选器(0,1,2,..,27),每个筛选器都有两个寄存器,用来配置筛选内容(比如:配置要筛选的ID)
2. 三个发送邮箱(邮箱相当于缓冲区)
可以同时缓存3个CAN消息等待发送
3. 两个接收FIFO
每个FIFO有3个接收邮箱,一共可以临时存储6个CAN消息
STM32F4xxCAN控制器模式:1)工作模式
a. 初始化模式:主要初始化 CAN 寄存器 / 筛选器等等
b. 正常模式:可以收发 CAN 总线消息
c. 睡眠模式(低功耗模式):可以收 CAN 总线消息
2)测试模式
a. 静默模式 SILM
b. 回环模式 LBKM:LoopBack Mode
自己的 Tx 线接自己的 Rx ----> 自己发的数据自己接收
回环模式主要是测试软件及上层业务逻辑是否正确
c. 环回静默模式
3)调试模式
bxCAN 功能说明:1)发送处理
发送的数据必须要按帧格式处理
2)接收处理
3)时间触发通信模式
用来为发送和接收消息生成时间戳(可以实现定时发送等功能)
4)标识筛选器
4. STM32F4xx 固件库函数
(1) 配置CAN对应的GPIO(CANH和CANL是由GPIO复用而来)
RCC_AHB1PeriphClockCmd
GPIO_Init
GPIO_PinAFConfig
(2) 初始化CAN控制器a. 使能时钟 RCC_APB1PeriphClockCmd // CAN1/CAN2都在APB1上
b. CAN_Init
uint8_t CAN_Init(CAN_TypeDef *CANx, CAN_InitTypeDef *CAN_InitStruct); @CANx:指定CAN BUS编号 @CAN_InitStruct:指向初始化信息结构体 typedef struct { uint16_t CAN_Prescaler; 指定CAN总线时钟频率的预分频值,范围为1~1024 Fcan = FAPB1 / CAN_Prescaler(FAPB1 = 42M Hz) 只需注意CAN总线波特率最高1Mbps即可 uint8_t CAN_Mode; 指定CAN总线模式 CAN_Mode_Normal 正常模式 CAN_Mode_LoopBack 回环模式 CAN_Mode_Silent 静默模式 CAN_Mode_Silent_LoopBack 静默和回环模式 uint8_t CAN_SJW; 指定SJW(同步时间长度)的值,1~4 tq CAN_SJW_1tq CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq uint8_t CAN_BS1; 指定收到同步信号到采样点之间经历的时钟周期数 BS1 = tPROP_SEG + tPHASE_SEG1 CAN_BS1_1tq CAN_BS1_2tq ... CAN_BS1_16tq uint8_t CAN_BS2; BS2 = tPHASE_SEG2 指定采样点到这个bit处理结束经历的时钟周期数 tPHASE_SEG2 = MAX(tPHASE_SEG1, tIPT) FunctionalState CAN_TTCM; 使能或禁止收发消息的时间戳 ENABLE DIASBLE FunctionalState CAN_ABOM; ABOM:Automatic Bus-Off Manangement 自动离线管理 FunctionalState CAN_AWUM; AWUM:Automatic Wake-Up Manangement 自动唤醒管理 FunctionalState CAN_NART; Non-Automatic ReTransmittion 不自动重发,默认是重发 FunctionalState CAN_RFLM; RFLM:Receive FIFO Lock Manangement 接收缓冲区锁 FunctionalState CAN_TXFP; Tx FIFO Prioty 发送缓存区优先级 } CAN_InitTypeDef;
(3) 筛选器配置(可以配置也可以不配置,需要筛选则配置不需要则不配置)
可以选择性接收CAN消息
两个CAN控制器(CAN1, CAN2)共有28个筛选器
CAN1 0 ~13
CAN2 14~27
每个筛选器都有两个32bits的寄存器(Fx_R1, Fx_R2 x=0,,1,2...27),主要功能是用来筛选收到的消息的ID,RTR...
筛选器可以配置为32bits筛选,也可以配置为 16bits 筛选
为STDID[10:0]、EXTID[17:0]、IDE和RTR位提供一个 32 位筛选器(扩展ID时,32位筛选)
为STDID[10:0]、RTR、IDE 和 EXTID[17:15] 位提供两个 16 位筛选器。
筛选功能:
a. 掩码模式
它只检查ID中MASK为1的那个bit位的值是否匹配
Fx_R1 ---> ID
Fx_R2 ---> MASK掩码
举例子:
b10
b9
b8
b7
b6
b5
b4
b3
b2
b1
b0
ID
0
1
0
1
x
x
x
x
x
x
x
MASK
1
1
1
1
0
0
0
0
0
0
0
如按上述设置筛选器的话,只要ID是以0101开头的消息都会接收,不以0101开头的则直接丢弃
所以(R_ID & MASK) == (ID & MASK)//相等就接收
b. ID列表模式
Fx_R1与Fx_R2都作为一个ID
只有Fx_R1、Fx_R2指定的段(ID,RTR,IDE)要完全与收到的消息的ID匹配,才会被接收
可以指定2个32bits的ID或4个16bits的ID
void CAN_FilterInit(CAN_TypeDef *CANx, CAN_FilterInitTypeDef *CAN_FilterInitStruct); typedef struct { uint16_t CAN_FilterIdHigh; Fx_R1 高16位 uint16_t CAN_FilterIdLow; Fx_R1 低16位 uint16_t CAN_FilterMaskIdHigh; Fx_R2 高16位 uint16_t CAN_FilterMaskIdLow; Fx_R2 低16位 uint16_t CAN_FilterFIFOAssignment; 指定接收的消息分配到哪个FIFO CAN_Filter_FIFO0 接收消息存入FIFO0 CAN_Filter_FIFO1 接收消息存入FIFO1 uint8_t CAN_FilterNumber; 筛选器编号0~13 uint8_t CAN_FilterMode; 筛选模式 CAN_FilterMode_IdMask 掩码模式 CAN_FilterMode_IdList ID列表模式 uint8_t CAN_FilterScale; 指定筛选器宽度 CAN_FilterScale_16bit 16bits CAN_FilterScale_32bit 32bits FunctionalState CAN_FilterActivation; 使能或禁止筛选 } CAN_FilterInitTypeDef;
(4) 收发消息
a. 发送消息
STM32F4xx有3个发送邮箱(至多同时发送3个消息) uint8_t CAN_Transmit(CAN_TypeDef *CANx, CanTxMsg *TxMessage); @CANx:指定CAN编号 @TxMessage:指向发送消息结构体,CAN中的数据是以消息帧进行发送 typedef struct { uint32_t StdId; 指定消息标准ID,自己赋值就可以了 11bits uint32_t ExtId; 扩展ID,29bits uint8_t IDE; 指定ID类型 CAN_Id_Standard 使用StdId CAN_Id_Extended 使用ExtId uint8_t RTR; 指定是数据帧还是远程帧 CAN_RTR_Data 数据帧,表示发送数据出去 CAN_RTR_Remote远程帧,表示请求对方发数据给"我" uint8_t DLC; 指定发送数据正文长度 0~8 uint8_t Data[8]; 消息正文,最多8个字节,具体长度与DLC要相同 } CanTxMsg; @返回值: 0 把消息发送到了 0号邮箱 1 把消息发送到了 1号邮箱 2 把消息发送到了 2号邮箱 CAN_TxStatus_NoMailBox 表示所有邮箱都满了,不能发送,请稍后再尝试发送 CAN_Transmit这个只是把Message发送到控制器的发件箱,然后在控制器的作用下 会自动将消息发送出去,我们可以通过另一个函数可以得知有没有发送成功或者失败: uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox); @CANx:指定CAN编号 @TransmitMailbox:指定要检测的邮箱号0,1,2 @返回值: CAN_TxStatus_Ok 发送完成 CAN_TxStatus_Failed 发送失败
b. 接收消息(轮询方式/中断发送)
轮询的方式: CAN_MessagePending用来检测指定接收FIFO中的邮箱状态 uint8_t CAN_MessagePending(CAN_TypeDef *CANx, uint8_t FIFONumber); @FIFONumber:用来指定要检测的是哪个FIFO @返回值:返回指定FIFO中有多少个message 0 该FIFO没有消息 1 该FIFO有1个消息 2 该FIFO有2个消息 3 该FIFO有3个消息 ------------------------------------------------------------------------------- CAN_Receive用来从指定缓存区FIFO接收消息 void CAN_Receive(CAN_TypeDef *CANx, uint8_t FIFONumber, CanRxMsg *RxMessage); @FIFONumber:指定FIFO @RxMessage:指向一个接收消息结构体,用来存储接收到的消息 ------------------------------------------------------------------------------- 中断的方式: 先在配置CAN的时候使能中断: CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // 当FIFO0有消息时,触发中断 CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE); // 当FIFO1有消息时,触发中断 NVIC_Init(); 编写中断处理函数: void CAN1_RX0_IRQHandler(void) { CanRxMsg msg; // 保存接收到的消息 CAN_Receive(CAN1, 0, &msg); } void CAN1_RX1_IRQHandler(void) { CanRxMsg msg; // 保存接收到的消息 CAN_Receive(CAN1, 1, &msg); }
5. 练习
1. 在回环模式下,测试能否收到自己发送的 CAN 消息
2. 两两对接,测试对方能否收到自己发送的 CAN 消息(TJA105CAN 收发器)