一、用STM32F103做CAN的收发通信
CAN 是Controller Area Network 的缩写(以下称为CAN),该通信使用的是ISO11898标准,该标准的物理层特征如下图所示。
CAN协议是通过以下5种类型的帧进行的:
- 数据帧
- 摇控帧
- 错误帧
- 过载帧
- 帧间隔
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11 个位的标识符(ID),扩展格式有29 个位的ID。
大部分系统使用的都是数据帧 ,我这里使用的也是数据帧。
数据帧一般由7个段构成,即:
(1) 帧起始。表示数据帧开始的段。
(2) 仲裁段。表示该帧优先级的段。
(3) 控制段。表示数据的字节数及保留位的段。
(4) 数据段。数据的内容,一帧可发送0~8个字节的数据。
(5) CRC段。检查帧的传输错误的段。
(6) ACK段。表示确认正常接收的段。
(7) 帧结束。表示数据帧结束的段。
明确了数据帧概念,还需要理解一下过滤器的作用。
STM32的标识符屏蔽滤波目的是减少了CPU处理CAN通信的开销。STM32的过滤器组最多有28个(互联型),但是STM32F103ZET6只有14个(增强型),每个滤波器组x由2个32为寄存器,CAN_FxR1和CAN_FxR2组成。
STM32每个过滤器组的位宽都可以独立配置,以满足应用程序的不同需求。根据位宽的不同,每个过滤器组可提供:
- 1个32位过滤器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位
- 2个16位过滤器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位
此外过滤器可配置为,屏蔽位模式和标识符列表模式。
在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理。
而在标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟过滤器标识符相同。相关文章:CAN总线详解。
一般也都是使用标识符列表模式,这里使用的也是标识符列表模式。滤波过程举例如下:
在程序中就是:
//要过滤的ID高位
CAN_FilterInitStructure.CAN_FilterIdHigh=0X00;
//要过滤的ID低位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF;
//过滤器屏蔽标识符的高16位值
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0xFFFF;
//过滤器屏蔽标识符的低16位值
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0xFFFF;
这里的CAN_FilterId和CAN_FilterMaskId是配合使用的,意思是CAN_FilterId指出需要屏蔽ID的什么内容,什么格式;CAN_FilterMaskId是指CAN_FilterId的每一位是否需要过滤,若CAN_FilterMaskId在某位上是1的话,ID对应位上的数值就必须和CAN_FilterId该位上的一样,保持一致,反之则是“不关心”。
上述程序的设置的含义就是:只接收来自0x1314的数据,屏蔽其他ID的数据。
程序思路
这里准备做一个主机与从机的通信,主要用扩展标识符ExtId来区分,分配的标识符是:
主机:0x1314
从机:0x1311
主机负责接收所有从机的数据,不需要过滤,用扩展标识符ExtId来区分不同从机的数据;主机还可以向不同从机发送信息。而从机则只接收来自主机的数据,同样用扩展标识符ExtId来区分是否是发向自己的数据;同时,也能够向主机发送信息。
相关代码
代码也是非常简单的,这里贴出了主机和从机的can.c和can.h两个文件。
从机相关代码
can.c文件:
#include "can.h"
/* 在中断处理函数中返回 */
//__IO uint32_t ret = 0;
//接收数据缓冲器
u8 RxBuf[5];
u8 Rx_flag=0;
void CAN1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
/* 复用功能和GPIOB端口时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);
/* CAN1 模块时钟使能 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
/* Configure CAN pin: RX */ // PB8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure CAN pin: TX */ // PB9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
//#define GPIO_Remap_CAN GPIO_Remap1_CAN1 本实验没有用到重映射I/O
GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
//CAN_NVIC_Configuration(); //CAN中断初始化
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
#ifdef VECT_TAB_RAM
/* Set the Vector Table base location at 0x20000000 */
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else /* VECT_TAB_FLASH */
/* Set the Vector Table base location at 0x08000000 */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
/* enabling interrupt */
NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN1_RX0_IRQn;;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//CAN_INIT();//CA初始化N模块
/* CAN register init */
CAN_DeInit(CAN1); //将外设CAN的全部寄存器重设为缺省值
CAN_StructInit(&CAN_InitStructure); //把CAN_InitStruct中的每一个参数按缺省值填入
/* CAN cell init */
CAN_InitStructure.CAN_TTCM=DISABLE; //没有使能时间触发模式
CAN_InitStructure.CAN_ABOM=DISABLE; //没有使能自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE; //没有使能自动唤醒模式
CAN_InitStructure.CAN_NART=DISABLE; //没有使能非自动重传模式
CAN_InitStructure.CAN_RFLM=DISABLE; //没有使能接收FIFO锁定模式
CAN_InitStructure.CAN_TXFP=DISABLE; //没有使能发送FIFO优先级
CAN_InitStructure.CAN_Mode=CAN_Mode_Normal; //CAN设置为正常模式
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq; //重新同步跳跃宽度1个时间单位
CAN_InitStructure.CAN_BS1=CAN_BS1_3tq; //时间段1为3个时间单位
CAN_InitStructure.CAN_BS2=CAN_BS2_2tq; //时间段2为2个时间单位
CAN_InitStructure.CAN_Prescaler=60; //时间单位长度为60
CAN_Init(CAN1,&CAN_InitStructure); //波特率为:72M/2/60(1+3+2)=0.1 即波特率为100KBPs
// CAN filter init 过滤器,注意,只接收主机发过来的数据,屏蔽其他数据
CAN_FilterInitStructure.CAN_FilterNumber=1; //指定过滤器为1
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; //指定过滤器为标识符屏蔽位模式
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //过滤器位宽为32位
//CAN_FilterInitStructure.CAN_FilterIdHigh= (((u32)0x1314<<3)&0xFFFF0000)>>16;
CAN_FilterInitStructure.CAN_FilterIdHigh=0X00; //要过滤的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要过滤的ID低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0xFFFF; //过滤器屏蔽标识符的高16位值
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0xFFFF; //过滤器屏蔽标识符的低16位值
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0; //设定了指向过滤器的FIFO为0
CAN_FilterInitStructure.CAN_FilterActivatinotallow=ENABLE; //使能过滤器
CAN_FilterInit(&CAN_FilterInitStructure); //按上面的参数初始化过滤器
/* CAN FIFO0 message pending interrupt enable */
CAN_ITConfig(CAN1,CAN_IT_FMP0, ENABLE); //使能FIFO0消息挂号中断
}
/* 发送两个字节的数据*/
u8 CAN_SetMsg(u8 Data1,u8 Data2)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x0000; //标准标识符为0x00
TxMessage.ExtId=0x1311; //扩展标识符0x1311,可以更改该标识符以示区分不同从机
TxMessage.IDE=CAN_ID_EXT; //使用扩展标识符
TxMessage.RTR=CAN_RTR_DATA; //为数据帧
TxMessage.DLC=2; //消息的数据长度为2个字节
TxMessage.Data[0]=Data1; //第一个字节数据
TxMessage.Data[1]=Data2; //第二个字节数据
//发送数据
mbox= CAN_Transmit(CAN1, &TxMessage);
while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))
i++; //等待发送结束
if(i>=0XFFF)
return 0;
return 1;
}
u8 CAN_GetMsg(u8 *msg1,u8 *msg2)
{
if(Rx_flag == 1)//发现数据
{
*msg1=RxBuf[0];
*msg2=RxBuf[1];
Rx_flag=0;//数据已经取走,可以更新数据
return 1;
}else
return 0;
}
/* USB中断和CAN接收中断服务程序,USB跟CAN公用I/O,这里只用到CAN的中断。 */
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
RxMessage.StdId=0x00;
RxMessage.ExtId=0x00;
RxMessage.IDE=0;
RxMessage.DLC=0;
RxMessage.FMI=0;
RxMessage.Data[0]=0x00;
RxMessage.Data[1]=0x00;
CAN_Receive(CAN1,CAN_FIFO0, &RxMessage); //接收FIFO0中的数据
if(Rx_flag == 0)//数据已取走或者缓冲器为空
{
RxBuf[0]=RxMessage.Data[0];
RxBuf[1]=RxMessage.Data[1];
Rx_flag=1;//数据已经备好,等待取走
}
}
can.h文件
主机相关代码
这里主机代码大部分是和从机类似的,就只贴出不同的地方了。
can.c文件:
#include "can.h"
/* 在中断处理函数中返回 */
//__IO uint32_t ret = 0;
void CAN1_Init(void)
{
......//以上与从机部分相同
//CAN filter init 过滤器,已经设置为任意,可以通过ExtId标识符区分从机代号
CAN_FilterInitStructure.CAN_FilterNumber=1; //指定过滤器为1
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; //指定过滤器为标识符屏蔽位模式
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //过滤器位宽为32位
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000; //过滤器标识符的高16位值
CAN_FilterInitStructure.CAN_FilterIdLow=CAN_ID_EXT|CAN_RTR_DATA;//过滤器标识符的低16位值
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000; //过滤器屏蔽标识符的高16位值
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000; //过滤器屏蔽标识符的低16位值
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0; //设定了指向过滤器的FIFO为0
CAN_FilterInitStructure.CAN_FilterActivatinotallow=ENABLE; //使能过滤器
CAN_FilterInit(&CAN_FilterInitStructure); //按上面的参数初始化过滤器
/* CAN FIFO0 message pending interrupt enable */
CAN_ITConfig(CAN1,CAN_IT_FMP0, ENABLE); //使能FIFO0消息挂号中断
}
//接收数据缓冲器
u8 CAN_RX_BUF[CAN_RX_LEN]={0}; //接收缓冲,最大USART_REC_LEN个字节.
//接收标志位
u8 Rx_flag=0;
/* USB中断和CAN接收中断服务程序,USB跟CAN公用I/O,这里只用到CAN的中断。 */
void USB_LP_CAN1_RX0_IRQHandler(void)
{
u8 i=0;
CanRxMsg RxMessage;
RxMessage.StdId=0x00;
RxMessage.ExtId=0x00;
RxMessage.IDE=0;
RxMessage.DLC=0;
RxMessage.FMI=0;
CAN_Receive(CAN1,CAN_FIFO0, &RxMessage); //接收FIFO0中的数据
if(Rx_flag == 0)//数据已取走或者缓冲器为空
{
if((RxMessage.DLC) == 2)//是否收到2位字节数据
{
CAN_RX_BUF[0]=RxMessage.Data[0];
CAN_RX_BUF[1]=RxMessage.Data[1];
}
}
}
/* 发送两个字节的数据*/
u8 CAN_SendMsg(u8* data1, u8* data2)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x0000; //标准标识符为0x00
TxMessage.ExtId=0x1314; //扩展标识符0x0000
TxMessage.IDE=CAN_ID_EXT; //使用扩展标识符
TxMessage.RTR=CAN_RTR_DATA; //为数据帧
TxMessage.DLC=2; //消息的数据长度为2个字节
TxMessage.Data[0]=Data1; //第一个字节数据
TxMessage.Data[1]=Data2; //第二个字节数据
//发送数据
mbox= CAN_Transmit(CAN1, &TxMessage);
while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))
i++; //等待发送结束
if(i>=0XFFF)
return 0;//发送失败
return 1;//发送成功
}
u8 CAN_GetMsg(u8 *msg1,u8 *msg2)
{
if(Rx_flag == 1)//发现数据
{
*msg1=CAN_RX_BUF[0];
*msg2=CAN_RX_BUF[1];
Rx_flag=0;//数据已经取走,可以更新数据
return 1;
}else
return 0;
}
void Clear_canBuffer(void)
{
Rx_flag=0;//清楚接收标志位
memset(CAN_RX_BUF, 0, sizeof(u8)*CAN_RX_LEN);//清空缓冲区
}
u8 Check_canRX(void)
{
return (Rx_flag == 6);
}
can.h文件:
#ifndef __CAN_H
#define __CAN_H
#include "sys.h"
#include "string.h"
#define CAN_RX_LEN 30 //定义最大接收字节数
extern u8 CAN_RX_BUF[CAN_RX_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
void CAN1_Init(void);
u8 CAN_SendMsg(u8* data1, u8* data2);
u8 CAN_GetMsg(u8 *msg1,u8 *msg2);
#endif /* __CAN_H */
开发板商城 天皓智联 CAN总线设备~~
二、CAN总线终端电阻为什么是120Ω?为什么是0.25W?
但是作为学渣的我,知道这个是在各种标准以及各种数据手册和应用笔记里面常用的电阻值,但是这两个终端电阻的具体作用是什么呢?之前就知道阻抗匹配,但是究竟匹配的是什么呢?
然后我就上知乎遨游了一下,半抄半写的总结了下面的这些知识点。知道终端电阻的作用,对于日常工作中波形不稳定等问题,也能更快的找到问题的原因。
终端电阻的作用
CAN总线终端电阻的作用有3个:
1、提高抗干扰能力,让高频低能量的信号迅速走掉
2、确保总线快速进入隐性状态,让寄生电容的能量更快走掉;
3、提高信号质量,放置在总线的两端,让反射能量降低。
1、提高抗干扰能力
CAN总线有“显性”和“隐性”两种状态,“显性”代表“0”,“隐性”代表“1”,由CAN收发器决定。下图是一个CAN收发器的典型内部结构图,CANH、CANL连接总线。
总线显性时,收发器内部Q1、Q2导通,CANH、CANL之间产生压差;隐性时,Q1、Q2截止,CANH、CANL处于无源状态,压差为0。
总线若无负载,隐性时差分电阻阻值很大,内部的MOS管属于高阻态,外部的干扰只需要极小的能量即可令总线进入显性(一般的收发器显性门限最小电压仅500mV)。这个时候如果有差模干扰过来,总线上就会有明显的波动,而这些波动没有地方能够吸收掉他们,就会在总线上创造一个显性位出来。所以为提升总线隐性时的抗干扰能力,可以增加一个差分负载电阻,且阻值尽可能小,以杜绝大部分噪声能量的影响。然而,为了避免需要过大的电流总线才能进入显性,阻值也不能过小。
2、确保快速进入隐性状态
在显性状态期间,总线的寄生电容会被充电,而在恢复到隐性状态时,这些电容需要放电。如果CANH、CANL之间没有放置任何阻性负载,电容只能通过收发器内部的差分电阻放电,这个阻抗是比较大的,按照RC滤波电路的特性,放电时间就会明显比较长。我们在收发器的CANH、CANL之间加入一个220PF的电容进行模拟试验,位速率为500kbit/s,波形如图,这个波形的下降沿就是比较长的状态。
为了让总线寄生电容快速放电,确保总线快速进入隐性状态,需要在CANH、CANL之间放置一个负载电阻。增加一个60Ω的电阻后,波形如图,从图中看出,显性恢复到隐性的时间缩减到128nS,与显性建立时间相当。
3、提高信号质量
信号在较高的转换速率情况下,信号边沿能量遇到阻抗不匹配时,会产生信号反射;传输线缆横截面的几何结构发生变化,线缆的特征阻抗会随之变化,也会造成反射。
能量发生反射时,导致反射的波形与原来的波形进行叠加,就会产生振铃。
在总线线缆的末端,阻抗急剧变化导致信号边沿能量反射,总线信号上会产生振铃,若振铃幅度过大,就会影响通信质量。在线缆末端增加一个与线缆特征阻抗一致的终端电阻,可以将这部分能量吸收,避免振铃的产生。
别人进行了一个模拟试验(图片都是我抄过来的),位速率为1Mbit/s,收发器CANH、CANL接一根10m左右的双绞线,收发器端接120Ω电阻保证隐性转换时间,末端不加负载。末端信号波形如图所示,信号上升沿出现了振铃。
若双绞线末端增加一个120Ω的电阻,末端信号波形明显改善,振铃消失。
一般在直线型拓扑中,线缆两端即是发送端,也是接收端,故线缆两端需各加一个终端电阻。
而在实际应用过程中,CAN总线一般都不是完美的总线式的设计,很多时候是总线型和星型的混合结构,这个时候一般都将CAN终端电阻布置在线束最远的两端,来尽量的模拟CAN总线的标准结构。whaosoft开发板商城用can/canfd测试设备进行测试
为什么选120Ω?
什么是阻抗?在电学中,常把对电路中电流所起的阻碍作用叫做阻抗。阻抗单位为欧姆,常用Z表示,是一个复数Z= R+i( ωL–1/(ωC))。具体说来阻抗可分为两个部分,电阻(实部)和电抗(虚部)。其中电抗又包括容抗和感抗,由电容引起的电流阻碍称为容抗,由电感引起的电流阻碍称为感抗。这里的阻抗是指Z的模。
任何一根线缆的特征阻抗都可以通过实验的方式得出。线缆的一端接方波发生器,另一端接一个可调电阻,并通过示波器观察电阻上的波形。调整电阻阻值的大小,直到电阻上的信号是一个良好的无振铃的方波:阻抗匹配与信号完整性,此时的电阻值可以认为与线缆的特征阻抗一致。
采用两根汽车使用的典型线缆,将它们扭制成双绞线,就可根据上述方法得到特征阻抗大约为120Ω,这也是CAN标准推荐的终端电阻阻值,所以这个120Ω是测出来的,不是算出来的,都是根据实际的线束特性进行计算得到的。当然在ISO 11898-2这个标准里面也是有定义的。
为什么功率还要选0.25W?
这个就要结合一些故障状态也计算,汽车ECU的所有接口都需要考虑短路到电源和短路到地的情况,所以我们也需要考虑CAN总线的节点短路到电源的情况,根据标准需要考虑短路到18V的情况,假设CANH短路到18V,电流会通过终端电阻流到CANL上,而CANL内部由于限流的原因,最大注入电流为50mA(TJA1145的规格书上标注),这时候120Ω电阻的功率就是50mA*50mA*120Ω=0.3W。考虑到高温情况下的降额,终端电阻的功率就是0.5W。
三、详解CAN总线
CAN(Controller Area Network)即控制器局域网,是一种能够实现分布式实时控制的串行通信网络。
想到CAN就要想到德国的Bosch公司,因为CAN就是这个公司开发的(和Intel)CAN有很多优秀的特点,使得它能够被广泛的应用。比如:传输速度最高到1Mbps,通信距离最远到10km,无损位仲裁机制,多主结构。
近些年来,CAN控制器价格越来越低,很多MCU也集成了CAN控制器。现在每一辆汽车上都装有CAN总线。一个典型的CAN应用场景:
CAN总线标准只规定了物理层和数据链路层,需要用户自定义应用层。不同的CAN标准仅物理层不同。
CAN收发器负责逻辑电平和物理信号之间的转换。
将逻辑信号转换成物理信号(差分电平),或者将物理信号转换成逻辑电平。
CAN标准有两个,即IOS11898和IOS11519,两者差分电平特性不同。
高低电平幅度低,对应的传输速度快;
*双绞线共模消除干扰,是因为电平同时变化,电压差不变。
CAN有三种接口器件
多个节点连接,只要有一个为低电平,总线就为低电平,只有所有节点输出高电平时,才为高电平。所谓"线与"。
CAN总线有5个连续相同位后,就插入一个相反位,产生跳变沿,用于同步。从而消除累积误差。和485、232一样,CAN的传输速度与距离成反比。
CAN总线,终端电阻的接法:
为什么是120Ω,因为电缆的特性阻抗为120Ω,为了模拟无限远的传输线
CAN总线传输的是CAN帧,CAN的通信帧分成五种,分别为数据帧、远程帧、错误帧、过载帧和帧间隔。
数据帧用来节点之间收发数据,是使用最多的帧类型;远程帧用来接收节点向发送节点接收数据;错误帧是某节点发现帧错误时用来向其他节点通知的帧;过载帧是接收节点用来向发送节点告知自身接收能力的帧;用于将数据帧、远程帧与前面帧隔离的帧。
数据帧根据仲裁段长度不同分为标准帧(2.0A)和扩展帧(2.0B)
帧起始由一个显性位(低电平)组成,发送节点发送帧起始,其他节点同步于帧起始;帧结束由7个隐形位(高电平)组成。
CAN总线是如何解决多点竞争的问题?
由仲裁段给出答案。
CAN总线控制器在发送数据的同时监控总线电平,如果电平不同,则停止发送并做其他处理。如果该位位于仲裁段,则退出总线竞争;如果位于其他段,则产生错误事件。
帧ID越小,优先级越高。由于数据帧的RTR位为显性电平,远程帧为隐性电平,所以帧格式和帧ID相同的情况下,数据帧优先于远程帧;由于标准帧的IDE位为显性电平,扩展帧的IDE位为隐形电平,对于前11位ID相同的标准帧和扩展帧,标准帧优先级比扩展帧高。
控制段
共6位,标准帧的控制段由扩展帧标志位IDE、保留位r0和数据长度代码DLC组成;扩展帧控制段则由IDE、r1、r0和DLC组成。
数据段为0-8字节,短帧结构,实时性好,适合汽车和工控领域;
CRC段CRC校验段由15位CRC值和CRC界定符组成。
ACK段当接收节点接收到的帧起始到CRC段都没错误时,它将在ACK段发送一个显性电平,发送节点发送隐性电平,线与结果为显性电平。
远程帧
远程帧分为6个段,也分为标准帧和扩展帧,且RTR位为1(隐性电平)
CAN是可靠性很高的总线,但是它也有五种错误。
- CRC错误:发送与接收的CRC值不同发生该错误;
- 格式错误:帧格式不合法发生该错误;
- 应答错误:发送节点在ACK阶段没有收到应答信息发生该错误;
- 位发送错误:发送节点在发送信息时发现总线电平与发送电平不符发生该错误;
- 位填充错误:通信线缆上违反通信规则时发生该错误。
当发生这五种错误之一时,发送节点或接受节点将发送错误帧
为防止某些节点自身出错而一直发送错误帧,干扰其他节点通信,CAN协议规定了节点的3种状态及行为
过载帧当某节点没有做好接收的"准备"时,将发送过载帧,以通知发送节点。
帧间隔用来隔离数据帧、远程帧与他们前面的帧,错误帧和过载帧前面不加帧间隔。
构建CAN节点构建节点,实现相应控制,由底向上分为四个部分:CAN节点电路、CAN控制器驱动、CAN应用层协议、CAN节点应用程序。
虽然不同节点完成的功能不同,但是都有相同的硬件和软件结构。
CAN收发器和控制器分别对应CAN的物理层和数据链路层,完成CAN报文的收发;功能电路,完成特定的功能,如信号采集或控制外设等;主控制器与应用软件按照CAN报文格式解析报文,完成相应控制。
CAN硬件驱动是运行在主控制器(如P89V51)上的程序,它主要完成以下工作:基于寄存器的操作,初始化CAN控制器、发送CAN报文、接收CAN报文;
如果直接使用CAN硬件驱动,当更换控制器时,需要修改上层应用程序,移植性差。在应用层和硬件驱动层加入虚拟驱动层,能够屏蔽不同CAN控制器的差异。
一个CAN节点除了完成通信的功能,还包括一些特定的硬件功能电路,功能电路驱动向下直接控制功能电路,向上为应用层提供控制功能电路函数接口。特定功能包括信号采集、人机显示等。
CAN收发器是实现CAN控制器逻辑电平与CAN总线上差分电平的互换。实现CAN收发器的方案有两种,一是使用CAN收发IC(需要加电源隔离和电气隔离),另一种是使用CAN隔离收发模块。推荐使用第二种。
CAN控制器是CAN的核心元件,它实现了CAN协议中数据链路层的全部功能,能够自动完成CAN协议的解析。CAN控制器一般有两种,一种是控制器IC(SJA1000),另一种是集成CAN控制器的MCU(LPC11C00)。
MCU负责实现对功能电路和CAN控制器的控制:在节点启动时,初始化CAN控制器参数;通过CAN控制器读取和发送CAN帧;在CAN控制器发生中断时,处理CAN控制器的中断异常;根据接收到的数据输出控制信号;
接口管理逻辑:解释MCU指令,寻址CAN控制器中的各功能模块的寄存器单元,向主控制器提供中断信息和状态信息。
发送缓冲区和接收缓冲区能够存储CAN总线网络上的完整信息。
验收滤波是将存储的验证码与CAN报文识别码进行比较,跟验证码匹配的CAN帧才会存储到接收缓冲区。
CAN内核实现了数据链路的全部协议。
CAN协议应用层概述
CAN总线只提供可靠的传输服务,所以节点接收报文时,要通过应用层协议来判断是谁发来的数据、数据代表了什么含义。常见的CAN应用层协议有:CANOpen、DeviceNet、J1939、iCAN等。
CAN应用层协议驱动是运行在主控制器(如P89V51)上的程序,它按照应用层协议来对CAN报文进行定义、完成CAN报文的解析与拼装。例如,我们将帧ID用来表示节点地址,当接收到的帧ID与自身节点ID不通过时,就直接丢弃,否则交给上层处理;发送时,将帧ID设置为接收节点的地址。
CAN收发器
SJA1000的输出模式有很多,使用最多的是正常输出模式,输入模式通常不选择比较器模式,可以增大通信距离,并且减少休眠下的电流。
收发器按照通信速度分为高速CAN收发器和容错CAN收发器。同一网络中要使用相同的CAN收发器。CAN连接线上会有很多干扰信号,需要在硬件上添加滤波器和抗干扰电路。
也可以使用CAN隔离收发器(集成滤波器和抗干扰电路)。
CAN控制器与MCU的连接方式SJA1000可被视为外扩RAM,地址宽度8位,最多支持256个寄存器
#define REG_BASE_ADDR 0xA000 // 寄存器基址
unsigned char *SJA_CS_Point = (unsigned char *) REG_BASE_ADDR ;
// 写SJA1000寄存器
void WriteSJAReg(unsigned char RegAddr, unsigned char Value) {
*(SJA_CS_Point + RegAddr) = Value;
return;
}
// 读SJA1000寄存器
unsigned char ReadSJAReg(unsigned char RegAddr) {
return (*(SJA_CS_Point + RegAddr));
}
将缓存区的数据连续写入寄存器
……
for (i=0;i<len;i++)
{
WriteSJAReg(RegAdr+i,ValueBuf[i]);
}
……
将连续多个寄存器连续读入缓存区
……
for (i=0;i<len;i++) {
ReadSJAReg(RegAdr+i,ValueBuf[i]);
}
……
头文件包含方案:
- 每个程序包含用到的头文件
- 每个程序包含一个公用头文件,公用头文件包含所有其他头文件
#ifndef __CONFIG_H__ // 防止头文件被重复包含
#define __CONFIG_H__
#include <8051.h> // 包含80C51寄存器定义头文件
#include "SJA1000REG.h" // 包含SJA1000寄存器定义头文件
// 定义取字节运算
#define LOW_BYTE(x) (unsigned char)(x)
#define HIGH_BYTE(x) (unsigned char)((unsigned int)(x) >> 8)
// 定义振荡器时钟和处理器时钟频率(用户可以根据实际情况作出调整)
#define OSCCLK 11059200UL
// 宏定义MCU的时钟频率
#define CPUCLK (OSCCLK / 12)
#endif // __CONFIG_H__
SJA1000上电后处于复位状态,必须初始化后才能工作。
- 置位模式寄存器Bit0位进入复位模式;
- 设置时钟分频寄存器选择时钟频率、CAN模式;
- 设置验收滤波,设定验证码和屏蔽码;
- 设置总线定时器寄存器0、1设定CAN波特率;
- 设置输出模式;
- 清零模式寄存器Bit0位退出复位模式。
模式寄存器
只检测模式:SJA1000发送CAN帧时不检查应答位;
只听模式:此模式下SJA1000不会发送错误帧,用于自动检测波特率;SJA1000以不同的波特率接收CAN帧,当收到CAN帧时,表明当前波特率与总线波特率相同。
波特率设置
CAN总线无时钟,使用异步串行传输;波特率是1秒发送的数据位;
CAN帧发送:发送CAN帧的步骤:
- 检测状态寄存器,等待发送缓冲区可用;
- 填充报文到发送缓冲区;
- 启动发送。
SJA1000具有一个12字节的缓冲区,要发送的报文可以通过寄存器16-28写入,也可通过寄存器96-108写入或读出。
设置发送模式char SetSJASendCmd(unsigned char cmd) {
unsigned char ret;
switch (cmd) {
default:
case 0:
ret = SetBitMask(REG_CAN_CMR, TR_BIT); //正常发送
break;
case 1:
ret = SetBitMask(REG_CAN_CMR, TR_BIT|AT_BIT); //单次发送
break;
case 2:
ret = SetBitMask(REG_CAN_CMR, TR_BIT|SRR_BIT);//自收自发
break;
case 0xff:
ret = SetBitMask(REG_CAN_CMR, AT_BIT);//终止发送
break;
}
return ret;
}
发送函数:
unsigned char SJA_CAN_Filter[8] = { // 定义验收滤波器的参数,接收所有帧
0x00, 0x00, 0x00, 0x00,
// ACR0~ACR3
0xff, 0xff, 0xff, 0xff
// AMR0~AMR3
};
unsigned char STD_SEND_BUFFER[11] = { // CAN 发送报文缓冲区
0x08, // 帧信息,标准数据帧,数据长度 = 8
0xEA, 0x60, // 帧ID = 0x753
0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa // 帧数据
};
void main(void) // 主函数,程序入口
{
timerInit();// 初始化
D1 = 0;
SJA1000_RST = 1; // 硬件复位SJA1000
timerDelay(50); // 延时500ms
SJA1000_RST = 0;
SJA1000_Init(0x00, 0x14, SJA_CAN_Filter); // 初始化SJA1000,设置波特率为1Mbps
// 无限循环,main()函数不允许返回
for(;;) {
SJASendData(STD_SEND_BUFFER, 0x0);
timerDelay(100); // 延时1000ms
}
}
为什么帧ID是0x753,这与CAN帧在缓冲区的存储格式有关。
终端电阻非常重要,当波特率较高而且没加终端电阻时,信号过冲非常严重。以下是SJA1000接收报文的主要流程。
SJA1000有64个字节的接收缓冲区(FIFO),这可以降低对MCU的要求。MCU可以通过查询或中断的方式确定SJA1000接收到报文后读取报文。