CAN总线通信协议(基础)
1.CAN简介
CAN总线(Controller Area Network Bus)控制器局域网总线 CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线,广泛应用于汽车、嵌入式、工业控制等领域 CAN总线特征: 两根通信线(CAN_H、CAN_L),线路少,无需共地 差分信号通信,抗干扰能力强,用电压差值传递数据 高速CAN(ISO11898):125k~1Mbps, <40m,用处多,着重学 低速CAN(ISO11519):10k~125kbps, <1km 异步,无需时钟线,通信速率由设备各自约定 半双工,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序 11位(标准格式)/29位(扩展格式)报文ID,用于区分消息功能,同时决定优先级 可配置1~8字节的有效载荷
广播式(发送者向接收者写入数据,这个lan网络下的其他设备被动接收,可根据ID确定要不要接收)和
请求式(发一个数据就需要先请求再接收)两种传输方式 应答、CRC校验、位填充、位同步、错误处理等特性主流通信协议对比
对比其他串行通信协议:
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 | 应用场景 |
UART | TX、RX | 全双工 | 异步 | 单端 | 点对点 | 两个设备互相通信 |
I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 | 一个主控外挂多个模块 |
SPI | SCK、MOSI、MISO、 SS | 全双工 | 同步 | 单端 | 多设备 | 一个主控外挂多个模块(高速) |
CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 | 多个主控互相通信 |
UART
IIC
SPI
2.物理层
CAN 通讯并不是以时钟信号来进行同步的,它是一种半双工异步通讯,只具有CAN_High 和CAN_Low 两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。
发送数据时:控制器把要发送的二进制编码通过CAN_Tx 线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CAN_High 和CAN_Low 线输出到CAN 总线网络。
接收数据时:收发器把总线上收到的CAN_High 及CAN_Low 信号转化成普通的逻辑电平信号,通过CAN_Rx 输出到控制器中
怎么确定和哪个从机通信,读写关系确定?
can类似于广播,发送者向信道写入数据,这个lan网络下的其他设备被动接收(可以根据ID确定要不要接收)
两种CAN的连接结构
1.开环总线网络:低速、远距离“开环网络”,它的最大传输距离为1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2 千欧”的电阻。
低速CAN使用开环总线网络
2.闭环总线网络:高速、短距离“闭环网络”,它的总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个“120 欧”的电阻。
电阻的作用
1.防止回波反射 尤其是 高频信号、远距离传输的场景 如果不加终端电阻 信号波形会在线路终端反射,进而干扰原始信号 。加了终端电阻 且 阻抗匹配 则电平跳变会比较平稳 2.在没有设备操作时 将两根差分线的电压 “收紧” (收紧为 电平1 呈隐性) 使其电压一致。 收紧:在没有设备操作总线的情况下 电阻就和弹簧一样 将两根线(CAN_H CAN_L) 的电压拉到同一个电平 和I2C的上拉电阻很像。
差分协议
表示逻辑1 时(隐性电平),CAN_High 和CAN_Low 线上的电压均为2.5v,即它们的电压差VH-VL=0V;
表示逻辑0 时(显性电平),CAN_High 的电平为3.5V,CAN_Low 线的电平为1.5V,即它们的电压差为VH-VL=2V。
例如,当CAN 收发器从CAN_Tx 线接收到来自CAN 控制器的低电平信号时(逻辑0),它会使CAN_High 输出3.5V,同时CAN_Low 输出1.5V,从而输出显性电平表示逻辑0。
3.协议层
在原始数据段的前面加上传输起始标签、片选标签和控制标签,在数据的尾段加上CRC校验标签、应答标签和传输结束标签,把这些内容按特定的格式打包好,就可以用一个通道表达各种信号了,当整个数据包被传输到其它设备时,只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为CAN 的“数据帧”。
为了更有效地控制通讯,CAN 一共规定了5 种类型的帧:
数据帧的结构
数据帧是在CAN 通讯中最主要、最复杂的报文。
帧起始
SOF 段(Start Of Frame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平(0),它用于通知各个节点将有数据传输。
仲裁段(ID)
当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。
仲裁段的内容主要为本数据帧的ID 信息(标识符),数据帧具有标准格式和扩展格式两种,区别就在于ID 信息的长度,标准格式的ID 为11 位,扩展格式的ID 为29 位,它在标准ID 的基础上多出18 位。
在CAN 协议中,ID 起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。CAN 协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,我们会给它打包上一个优先级高的ID,使它能够及时地发送出去。也正因为它这样的优先级分配原则,使得CAN 的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。
如果总线上同时出现显性电平(0)和隐性电平(1),总线的状态会被置为显性电平,CAN 正是利用这个特性进行仲裁。若两个节点同时竞争CAN 总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权。
具体方式是当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性 电平最多的单元可继续发送。
仲裁段ID 的优先级也影响着接收设备对报文的反应。因为在CAN 总线上数据是以广播的形式发送的,所有连接在CAN 总线的节点都会收到所有其它节点发出的有效数据,因而我们的CAN控制器大多具有根据ID 过滤报文的功能,它可以控制自己只接收某些ID 的报文。
回看上图数据帧的结构中的数据帧格式,可看到仲裁段除了报文ID 外,还有RTR、IDE 和SRR 位。
(1) RTR 位(Remote Transmission Request Bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平(0)时表示数据帧,隐性电平(1)时表示遥控帧。
(2) IDE 位(Identifier Extension Bit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。
(3) SRR 位(Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的RTR 位。由于扩展帧中的SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个ID 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。
控制段
在控制段中的r1 和r0 为保留位,默认设置为显性位。它最主要的是DLC段(Data Length Code),译为数据长度码,它由4 个数据位组成,用于表示本报文中的数据段含有多少个字节,DLC 段表示的数字为0~8。
数据段
数据段为数据帧的核心内容,它是节点要发送的原始信息,由0~8 个字节组成,MSB 先行。
CRC段(校验段)
为了保证报文的正确传输,CAN 的报文包含了一段15 位的CRC 校验码,一旦接收节点算出的CRC 码跟接收到的CRC 码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC 部分的计算一般由CAN 控制器硬件完成,出错时的处理则由软件控制最大重发数。
(错误帧的用途:某个设备检测出错误时向其他设备通知错误。当某个设备发现总线上的波形不对,产生错误了,他就会发出错误帧,错误帧可以叠加在数据帧上,并且可以破坏数据帧的数据,意思就是我发现这一帧数据有错,现在我破坏了这个数据,大家都不要用了,这是一种错误处理机制。)
在CRC 校验码之后,有一个CRC 界定符,它为隐性位(1),主要作用是把CRC 校验码与后面的ACK段间隔起来。
ACK段(确认信号)
ACK 段包括一个ACK 槽位和一个ACK 界定符位。类似I2C 总线,在ACK 槽位中,发送节点发送的是隐性位(1),而接收节点则在这一位中发送显性位(0)以示应答。在ACK 槽和帧结束之间由ACK 界定符间隔开。
帧结束
EOF 段(End Of Frame),译为帧结束,帧结束段由发送节点发送的7 个隐性位表示结束
4.CAN的波特率及位序
由于CAN 属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯,特别地,CAN 还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。
位时序
为了实现位同步,CAN 协议把每一个数据位的时序分解成 位时序分解图所示的SS 段(同步段)、PTS 段(传播时间段)、PBS1 段(相位缓冲段1)、PBS2 段(相位缓冲段2),这四段的长度加起来即为一个CAN 数据位的长度。分解后最小的时间单位是Tq。
1 位分为4 个段,每个段又由若干个Tq 构成,这称为位时序。
1 位由多少个Tq 构成、每个段又由多少个Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。
上图第一张图设定CAN 通讯信号每一个数据位的长度为10Tq,其中SS 段占1Tq,PTS 段占3Tq,PBS1段占3Tq,PBS2 段占3Tq。
信号的采样点位于PBS1 段与PBS2 段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样,例如上图第二第三张图。
CAN通信频率:CAN总线时钟分频 = 42MHz /PSC(不需要+1) = 42 / 6 =7MHz
CAN通信单位时间Tq: 1/ 频率 = 1/ 7MHz = 1/7us
can把每个电平划分为了四个段:SS(起始段,固定一个Tq)、PTS(传播时间段)、PBS1、PBS2(至少两个Tq)
采样在PBS1和PBS2之间完成,PBS1可以延长、PBS2可以缩短(延长缩短的极限是SJW,TIPS:以上所有的值都是多少个单位时间Tq)。利用划分的四段,保证电平变换在SS段完成,采样点在PBS1和PBS2之间。
各段的作用如介绍下:
• SS 段
SS 译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在SS 段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS 段的大小固定为1Tq。
• PTS 段
PTS 译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上延时总和的两倍。PTS 段的大小可以为1~8Tq。
• PBS1 段
PBS1 译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1 段的初始大小可以为1~8Tq。
• PBS2 段
PBS2 这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2 段的初始大小可以为2~8Tq。
通讯的波特率
总线上的各个通讯节点只要约定好1 个Tq 的时间长度以及每一个数据位占据多少个Tq,就可以确定CAN 通讯的波特率。
例如,假设上图中的1Tq=1us,而每个数据位由10 个Tq 组成,则传输一位数据需要时间T=10us,从而每秒可以传输的数据位个数为:
1x10^6/10 = 100000 (bps)
这个每秒可传输的数据位的个数即为通讯中的波特率。
取得同步的方法
1.硬同步
节点将自己的位时序种的SS段平移至总线出现下降沿的部分,获得同步
硬同步只是当存在“帧起始信号”时起作用,无法确保后续一连串的位时序都是同步的,而重新同步方式可解决该问题。
2.再同步
前面的硬同步只是当存在帧起始信号时才起作用,如果在一帧很长的数据内,节点信号与总线信号相位有偏移时,这种同步方式就无能为力了。因而需要引入再同步方式,它利用普通数据位的隐性至显性电平的跳变沿来同步。重新同步与硬同步方式相似的地方是它们都使用SS 段来进行检测,同步的目的都是使节点内的SS 段把跳变沿包含起来。
同步的方式分为超前和滞后两种情况,以总线跳变沿与SS 段的相对位置进行区分。
-
相位超前:
假设节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前2Tq,这时控制器在下一个位时序中的PBS1 段增加2Tq 的时间长度,使得节点与总线时序重新同步。
-
相位滞后:
假设节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后2Tq,这时控制器在前一个位时序中的PBS2 段减少2Tq 的时间长度,获得同步。
在重新同步的时候,PBS1 和PBS2 中增加或减少的这段时间长度被定义为“重新同步补偿宽度SJW (reSynchronization Jump Width)”。一般来说CAN 控制器会限定SJW 的最大值,如限定了最大SJW=3Tq 时,单次同步调整的时候不能增加或减少超过3Tq 的时间长度,若有需要,控制器会通过多次小幅度调整来实现同步。当控制器设置的SJW 极限值较大时,可以吸收的误差加大,但通讯的速度会下降。
5.CAN 初始化结构体
/**
* CAN filter init structure definition
* CAN 筛选器结构体
*/
typedef struct {
//配置两个16位ID和两个16位掩码 - 32位是由两个16位拼接来的,在标志列表模式下也可以把掩码当成ID
uint16_t CAN_FilterIdHigh; /*CAN_FxR1 寄存器的高16 位*/
uint16_t CAN_FilterIdLow; /*CAN_FxR1 寄存器的低16 位*/
uint16_t CAN_FilterMaskIdHigh; /*CAN_FxR2 寄存器的高16 位*/
uint16_t CAN_FilterMaskIdLow; /*CAN_FxR2 寄存器的低16 位*/
uint16_t CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; /* 设置经过筛选后数据存储到哪个接收FIFO
uint8_t CAN_FilterNumber; /* 筛选器编号,范围0-27*/
uint8_t CAN_FilterMode; /* 筛选器模式*/
uint8_t CAN_FilterScale; /* 设置筛选器的尺度 - 16位ID或32位ID*/
FunctionalState CAN_FilterActivation; /* 是否使能本筛选器*/
} CAN_FilterInitTypeDef;
(1) CAN_FilterIdHigh
用于存储要筛选的ID,若筛选器工作在32 位模式,它存储的是所筛选ID 的高16 位;若筛选器工作在16 位模式,它存储的就是一个完整的要筛选的ID。
(2) CAN_FilterIdLow
也是用于存储要筛选的ID,若筛选器工作在32 位模式,它存储的是所筛选ID 的低16 位;若筛选器工作在16 位模式,它存储的就是一个完整的要筛选的ID。
(3) CAN_FilterMaskIdHigh
CAN_FilterMaskIdHigh 分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterIdHigh 相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterIdHigh 成员对应的掩码,与CAN_FilterMaskIdLow 组成一组筛选器。
4) CAN_FilterMaskIdLow
CAN_FilterMaskIdLow 存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterIdLow 相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterIdLow 成员对应的掩码,与CAN_FilterMaskIdHigh组成一组筛选器。
不同模式下各结构体成员的内容:
用库函数CAN_FilterInit 即可把这些参数写入到筛选控制寄存器中。
数据收发流程
封装或者解析相应的数据报,收(判断FIFO缓冲区有没有数据,没数据就收不了),发(返回发送所用的邮箱号,读取和存储报文数据段的内容)
can.c
#include "public.h"
//canrx PA11 cantx PA12
static void CAN_GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//CAN TX/RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//引脚复用
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);
}
static void CAN_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn; //CAN RX0中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void CAN_Mode_Config(void)
{
CAN_InitTypeDef CAN_InitStructure;
/************************CAN通信参数设置**********************************/
/* Enable CAN clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
/* CAN Baudrate = 1 MBps (1MBps已为stm32的CAN最高速率) (CAN 时钟频率为 APB 1 = 42 MHz) */
CAN_InitStructure.CAN_Prescaler=6;//时钟频率=42MHz/6=7MHz 周期 = 1/7
CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;//回环模式
CAN_InitStructure.CAN_SJW=CAN_SJW_2tq;//重新同步跳跃宽度 2个时间单元
/* ss=1 bs1=4 bs2=2 位时间宽度为(1+4+2) 波特率即为时钟周期tq*(1+4+2) *///7MHz / ss+bs1+bs2 = 1MHz
CAN_InitStructure.CAN_BS1=CAN_BS1_4tq;//时间段1 占用了4个时间单元
CAN_InitStructure.CAN_BS2=CAN_BS2_2tq;//时间段2 占用了2个时间单元
CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时钟港出发
CAN_InitStructure.CAN_ABOM = ENABLE;//离线自动管理
CAN_InitStructure.CAN_AWUM = ENABLE;//自动唤醒功能
CAN_InitStructure.CAN_NART = ENABLE;//自动重传功能 - 直到成功
CAN_InitStructure.CAN_RFLM = ENABLE;//锁定FIFO,数据缓冲区满了新数据自动丢弃
CAN_InitStructure.CAN_TXFP = DISABLE;//是否使能优先级判定,不使能则按照报文ID的优先级
CAN_Init(CAN1, &CAN_InitStructure);
}
/*CAN的过滤器 配置
把筛选器第0 组配置成了32 位的掩码模式,并且把它的输出连接到接收FIFO0,若通
过了筛选器的匹配,报文会被存储到接收FIFO0
*/
static void CAN_Filter_init(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
/*CAN筛选器初始化*/
CAN_FilterInitStructure.CAN_FilterNumber = 1; /* 筛选器编号,范围0-27*/
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask ; /* 筛选器模式 - 掩码模式*/
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; /* 设置筛选器的尺度 - 位宽为单个32位*/
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; /*要筛选的寄存器的高16 位 - CAN_FxR1 */
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; /*要筛选的寄存器的低16 位 - CAN_FxR1 */
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; /*筛选器的高16 位每位必须匹配 - CAN_FxR2 */
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; /*筛选器的低16 位每位必须匹配 - CAN_FxR2 */
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 设置经过筛选后数据存储到哪个接收FIFO
FunctionalState CAN_FilterActivation = ENABLE; /* 是否使能本筛选器*/
//CAN_FilterInit(&CAN_FilterInitStructure);
/*CAN通信中断使能*/
//CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}
//完整配置CAN的功能
void CAN_Config(void)
{
CAN_GPIO_init();
//CAN_NVIC_Config();
CAN_Mode_Config();
CAN_Filter_init();
}
u8 CAN_Send_data(u8 *data)
{
CanTxMsg datatosend;
int i= 0;
uint8_t Mailbox = 0;
datatosend.StdId = 0x01; /* 存储报文的标准标识符11 位,0-0x7FF. */
datatosend.ExtId = 0x01; /* 存储报文的扩展标识符29 位,0-0x1FFFFFFF. */
datatosend.IDE = CAN_Id_Extended; /* 存储IDE 扩展标志*/
datatosend.RTR = CAN_RTR_Data; /* 存储RTR 远程帧标志*/
datatosend.DLC = 8; /* 存储报文数据段的长度,0-8 */
for(i=0;i<8;i++)
{
datatosend.Data[i] = data[i]; /* 存储报文数据段的内容*/
}
Mailbox = CAN_Transmit(CAN1,&datatosend);
while((CAN_TransmitStatus(CAN1,Mailbox)==CAN_TxStatus_Failed) && (i<0xFFFF))
i++;//等待发送结束
if(i>=0xFFFF)
return 0;//超时跳出
return 1;
}
u8 CAN_Receive_data(u8 *data)
{
CanRxMsg datatorev;
int i = 0;
if(CAN_MessagePending(CAN1,CAN_FIFO0) == 0)
{
return 0;//没有接收到数据,直接退出
}
CAN_Receive(CAN1,CAN_FIFO0,&datatorev);//读取数据
for(i=0;i<datatorev.DLC;i++)
{
data[i] = datatorev.Data[i]; /* 存储报文数据段的内容*/
}
return datatorev.DLC;
}
main.c
#include "public.h"
int main(void)
{
u8 msg[8] = {45,46,47,48,49,50};
u8 canbuf[8];
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SysTick_Init();
USART_Init_Config();
KEY_Init();
LED_Init();
CAN_Config();
u8 k =0;
u8 i =0;
while(1)
{
if(GetStaKey() == true)
{
CAN_Send_data(msg);
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
Delay_ms(1);
k = CAN_Receive_data(canbuf);
if(k)
{
for(i=0;i<k;i++)
{
printf("%d ",canbuf[i]);
}
}
}
}