本文档意在记录笔者开发CAN时的心得与体会,纯属为了加深自己印象
CAN的基本知识:
① CAN的概念
CAN是一种多主方式的串行通讯总线,1993年,CAN 已形成国际标准 ISO11898(高速应用)和 ISO11519-2(低速应用)
② CAN的总线结构:
1. ISO 11898
ISO 11898标准的CAN总线网络,是闭环结构。总线两端各连接一个120欧的电阻,两根信号线形成回路。是高速、短距离的CAN网络,通信速率为125kbit/s~1Mkbit/s。在1Mbit/s通讯速率时,总线长度最长可达到40m。
2. ISO 11519-2
ISO 11519-2标准的CAN总线网络,是开环结构,两根信号线独立,各自串联一个2.2k欧的电阻。是低速、远距离的CAN网络,通信速率最高可达125kbit/s。在40kbit/s速率时,总线长度可达到1000m。
3. ISO11898和 ISO11519-2对比图
4. CAN总线是异步通信
CAN总线由两根信号线组成,CAN_H与CAN_L,CAN没有时钟信号,没有时间同步,是一种异步通信方式,注意的是,SPI、I2C是同步通信方式,他们有时钟线。
5. CAN总线的差分信号
CAN总线的两根信号线通常采用双绞线,传输的是差分信号,通过CAN_H与CAN_L的电压差来表示总线电平。
差分信号的优点:抗干扰能力强,能有效抑制外部电磁干扰。
同样使用差分信号来表示总线电平的还有RS485
6. CAN总线的显性与隐性
CAN总线逻辑1为隐性,总线逻辑0为显性(反着来,我是这样记的)
CAN的总线逻辑1与逻辑0,对于开环结构和闭环结构是不一样的,因为开环结构与闭环结构形成的CAN网络中的CAN_H、CAN_L电压值不同,隐性电平和显性电平的电压值不同。
大体判断:
CAN_H与CAN_L的电压差大(开口)---->显性---->逻辑0
CAN_H与CAN_L的电压差小(闭合)---->隐性---->逻辑1
6.1 ISO 11898的显性与隐性(高速,闭环,短)
显性---->总线逻辑0---->CAN_H与CAN_L开口---->电压差为2V左右
隐性---->总线逻辑1---->CAN_H与CAN_L闭口---->电压差为0V左右
6.2 ISO 11519-2的显性与隐性(低速,开环,长)
显性---->总线逻辑0---->CAN_H与CAN_L开口---->电压差大于2V
隐性---->总线逻辑1---->CAN_H与CAN_L闭口---->电压差小于0V
7. CAN节点
CAN网络中,没有主机、从机的概念,各个终端设备都称为节点,一个CAN节点的硬件部分,一般由CAN控制器+CAN收发器组成。
CAN控制器:负责CAN总线的逻辑控制,实现CAN传输协议
CAN收发器:负责MCU逻辑电平与CAN总线电平之间的转换
CAN收发器:TXD与RXD是由MCU中接出来的引脚,比如MCU要发送一个逻辑1,只需要传给TXD一个逻辑1,经过CAN收发器,会将逻辑1转为差分信号,例如ISO 11898中,会将CAN_H与CAN_L线上的电压设置为2.5V,传到总线上为CAN_H - CAN_L ≈ 0V 隐性电平,逻辑1。
同理,当CAN_H与CAN_L读到CAN总线电平分别为3.5V和1.5V,经过收发器转换,MCU可从RXD上读到逻辑0(CAN_H - CAN_L ≈ 2V 显性电平 逻辑0)
注意:
CAN控制器一般是MCU的片上外设,CAN控制器一般是外接的单独的芯片
在总线上显性电平具有优先权
③ CAN的波特率和位时序
CAN总线协议的每一帧,可以看作一串连续的电平信号,
波特率:一个CAN网络,需要规定一个统一的通信波特率,各节点都以相同的波特率进行数据通信。
位时序:指一个节点,采集CAN总线上的一个位数据的时序。
一位又分为4段:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)、相位缓冲段2(PBS2)
一位可以当做一条水平的时间轴,不同的段在整个时间轴的占比不一样,每一段由若干个最小时间单元Time Quantum(Tq)组成。
位由多少个Tq构成、位里的每个段由多少个Tq构成等,都可以任意设定,这些组成了位时序。
通过设置位时序,多个单元可同时采样,也可以任意设定采样点。
通过位时序的控制,CAN总线可以进行位同步,以消除各个节点时钟的差异所产生的波特率误差问题,确保接收数据的准确性。
有些地方也会将这样分
在s32ds中的设置为
在stm32中的配置为
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1=CAN_BS1_9tq;
CAN_InitStructure.CAN_BS2=CAN_BS2_6tq;
CAN_InitStructure.CAN_Prescaler=5;
SS 段(SYNC SEG):同步段
在这个段内,总线上应该发送一次位信号的跳变。如果节点在同步段检测到总线上的一个跳变沿,就表示节点与总线是同步的。同步段的长度固定为1个Tq
PTS 段(PROP SEG):传播时间段
这个时间段是用于补偿网络的物理延时时间,包括:发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟,一般大小为1~8Tq
PBS1 段(PHASE SEG1):相位缓冲段
主要是用来补偿边沿阶段的误差,它的时间长度在重新同步时可以加长。PBS1段的初始大小可以为1~8Tq
PBS2 段(PHASE SEG2):另一个相位缓冲段
也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2段的初始大小可以为2~8Tq
对于PBS段而言,当信号边沿不能被包含于SS段中时,可以在此段进行补偿,吸收时钟误差
SJW (reSynchronization Jump Width):重新同步补偿宽度
在重新同步时,PBS1和PBS2段的允许加长或缩短的时间长度,SJW加大后允许误差加大,但通信速度下降。SJW为补偿此误差的最大值,每一次误差补偿都不能超过这个值,1~4Tq
CAN的同步
1. 硬同步:在帧起始信号时,同步总线上所有器件的位时序,无法确保后续一连串的位时序都是同步的。
2. 重新同步:在检测到总线上的时序与节点使用的时序有相位差时(即总线上的跳变沿不在节点时序的SS段范围),通过延长PBS1段或PBS2段来获得同步。
同步过程:
硬同步:在硬同步阶段,当节点检测到本身SS段并不在总线电平下降沿跳变处,节点则会把自己的位时序中的SS段平移至总线出现下降沿的部分,后面部分的段,也会跟着后移,依次获得同步。即:节点在检测到帧起始信号(SOF)时,才开始"设置段"
重新同步:在重新同步阶段,利用普通数据位的高至低电平的跳变沿来同步(帧起始信号是特殊的跳变沿)。重新同步与硬同步相似的地方:它们都使用SS段来进行检测,同步的目的都是使节点内的SS段把跳变沿包含起来。
第一种情况:相位超前,PBS段太短,PBS2后的SS段在跳变沿前,此时要延长PBS1
第二种情况:相位滞后,PBS段太长,PBS2后的SS段在跳变沿后,此时要缩短PBS2
采样点
读取总线电平的时刻,并将读到的电平作为位值的点。一般在PBS1结束处。在使用CAN工具的时候一般都需要设置采样点。
延长/缩短PBS段来达到同步:
PTS+PBS1小而PBS2加大,此时采样点前移
PTS+PBS1大而PBS2减小,此时采样点后移
波特率的计算
位时间长度NBT(一位的时间) = (SS段大小 + PTS段大小 + PBS1段大小 + PBS2段大小) x Tq
注意:别忘记了,SS+PTS+PBS1+PBS2 是等于1个位,Tq的时间单位为s
波特率 = 1/位时间长度NBT (一秒能有多少位)
④ CAN的帧类型
跳出位时序的框框,我们从大一点的角度看CAN信号
CAN网络通信是通过5种类型的帧进行的:数据帧、遥控帧、错误帧、过载帧、帧间空间
数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11个位的标识符(11个位的ID),扩展格式有29个位的标识符(29个位的ID)
帧类型 | 帧用途 |
数据帧 | 节点发送的包含ID和数据的帧,用于发送单元向接收单元传送数据的帧 |
遥控帧 | 节点向网络上的其他节点发出的某个ID的数据请求,发送节点收到遥控帧后就可以发送相应ID的数据帧 |
错误帧 | 节点检测出错误时,向其他节点发送的通知错误的帧 |
过载帧 | 接收单元未做好接收数据的准备时发送的帧,发送节点收到过载帧后可以暂缓发送数据帧 |
帧间空间 | 用于将数据帧、遥控帧与前后的帧分隔开的帧 |
数据帧:
发送节点向接收节点发送数据的帧
数据帧由7个段组成:
1. 帧起始:表示数据帧开始的段。
2. 仲裁段:表示该帧优先级的段。
3. 控制段:表示数据的字节数及保留位的段。
4. 数据段:数据的内容,可发送0~8个字节的数据。
5. CRC段:检查帧的传输错误的段。
6. ACK段:表示确认正确接收的段。
7. 帧结束:表示数据帧结束的段。
帧起始:
1个位的显性电平,表示帧开始
仲裁段:
表示数据优先级的段,标准格式和扩展格式在此的结构有所不同,通讯网络矩阵中的帧ID,也就是这里的数据。
控制段:
控制段由6个位构成,表示数据段的字节数。标准格式和扩展格式有所不同。
数据段:
0~8个字节,CANFD就是0~64字节,从MSB开始输出
CRC段:
检查帧传输错误的帧。
ACK段:
用来确认是否正常接收。
帧结束:
表示该帧的结束。由7个隐性电平位构成。
遥控帧(远程帧):
用于接收节点向发送节点请求发送数据所用的帧。
错误帧:
当检测出错误时向其它单元通知错误的帧
过载帧:
接收节点通知其未做好接收准备的帧
帧间空间:
用于将数据帧及遥控帧与前面的帧分离的帧
CAN报文详解(S32K1系列)
CAN报文的组成:帧ID + 帧数据
帧ID分为:标准帧(11位ID)和扩展帧(29位ID)
在NXP S32K144的CAN模块(FlexCAN)中,CAN_Receive()函数的主要作用是从接收FIFO或邮箱中获取CAN帧数据。CAN_Receive函数会取出这些东西:
CAN_Receive()流程:
1. 检查当前是否可以接收数据
2. 读取ID段,判断该报文是否匹配所设置的接收过滤规则
3. 读取DLC,确定数据长度
4. 读取数据段,存储到用户提供的缓冲区中
5. 清除中断标志,准备下一次接收
ID(仲裁段)
对于标准帧(11位ID)
对于扩展帧(29位ID)
DLC(数据长度)
数据段的长度(CAN 0~8字节,CAN FD 0~64字节)
CRC(循环冗余校验)
硬件自动处理,不会暴露给用户代码
ACK、EOF(结束帧)、帧间隔等
这些字段在CAN硬件通信过程中自动处理,CAN_Receive()不会直接涉及这些字段
当有CAN报文来时,会进入注册的CAN中断中,在CAN中断中,创建一个结构体can_message_t *msg = NULL; 之后使用CAN_Receive()接收数据,并映射到这个结构体上,然后通过结构体,去获取帧ID与数据,将帧ID与数据,保存至我们自己创建的缓冲区中,然后轮询缓冲区是否有东西,这样去处理CAN报文
//首先,CAN模块初始化时,就需要注册CAN中断
CAN_InstallEventCallback(&can_pal1_instance, AnyName_Callback, NULL);
//具体的中断函数
void AnyName_Callback(uint32_t instance, can_event_t eventType, uint32_t objIdx, void *driverState)
{
if(CAN_EVENT_TX_COMPLETE == eventType)
{
//判断中断事件,这里是发送完成中断
}
if(CAN_EVENT_RX_COMPLETE == eventType)
{
//判断中断事件,这里是接收完成中断
can_message_t *msg = NULL;//创建一个结构体,待会好取数据
if(objIdx == RX_MB0)//判断是哪一个邮箱的数据
{
CAN_Receive(&can_pal1_instance, RX_MB0, &CAN_RX0_Message_Buffer[0]);
//CAN_RX0_Message_Buffer[0]是自己创建的
msg = &CAN_RX0_Message_Buffer[0];//映射过来,待会好取东西
else if(objIdx == RX_MB1)
{
CAN_Receive(&can_pal1_instance, RX_MB1, &CAN_RX1_Message_Buffer[0]);
//CAN_RX0_Message_Buffer[0]是自己创建的
msg = &CAN_RX1_Message_Buffer[0];//映射过来,待会好取东西
}
else if(objIdx == RX_MB2)
{
CAN_Receive(&can_pal1_instance, RX_MB2, &CAN_RX2_Message_Buffer[0]);
//CAN_RX0_Message_Buffer[0]是自己创建的
msg = &CAN_RX2_Message_Buffer[0];//映射过来,待会好取东西
}
}
if(msg != NULL)
{
//获取CAN数据
msg -> id
msg -> data
msg -> length //这个是代表数据的长度
根据需要自己取
}
}
解析CAN中断函数
void xxxxx(uint32_t instance, can_event_t eventType, uint32_t objIdx, void *driverState)
instance:CAN实例编号,作用为识别哪个CAN模块触发了该回调函数。
eventType:事件类型,作用为此次回调函数对应的事件类型,包括:
1.CAN_TX_COMPLETE(发送完成): 表示一条消息已经从指定邮箱成功发送出去。
2.CAN_RX_COMPLETE(接收完成): 表示一条消息已经成功接收到,通常需要进一步读取数据。
3.CAN_ERROR(错误事件): 表示在传输或接收过程中检测到了错误,如仲裁错误、总线错误等。
4.其它具体事件类型,根据 SDK 定义可能还会有其他细分事件
objIdx:对象索引,作用为标识触发该事件的邮箱或FIFO索引。对于Rx事件,它代表了哪个接收缓冲区中有新数据到来了
driverState:驱动状态指针,作用为,用户可以访问或修改驱动的运行状态、统计信息等
status_t CAN_Receive(const can_instance_t * const instance,
uint32_t buffIdx,
can_message_t *message);
instance:
说明:指向can_instance_t结构体的指针,该结构体包含了CAN控制器的配置、寄存器地址及驱动状态信息。
作用:用于标识和访问当前使用的CAN模块。在多CAN实例的应用中,此参数用于区分不同的硬件实例。
buffIdx:
说明:一个无符号 32 位整数,表示接收缓冲区或邮箱的索引。
作用:用于指定从哪个接收邮箱或 FIFO 缓冲区中读取数据。在配置接收滤波器或者循环使用接收邮箱的场景中,buffIdx 帮助驱动准确定位到目标消息存储区域。
message:
说明:指向 can_message_t
结构体的指针,该结构体用于存储接收到的 CAN 消息。自己定义好。
作用:函数将解析出的 CAN 数据帧(包括 ID、数据长度、数据内容等)填入这个结构体,供上层应用进行后续处理。