基于CAN总线的数据传输与分析讨论
首先我们来讨论一下为什么要使用CAN通信?以及使用CAN通信能给我们带来什么样的方便?
首先不得不说的是CAN通信现在被应用于很多的领域,比如说汽车And so on........CAN通信的好处很简单,它可以以最少的CPU消耗来处理一大批的文件,当然这里所说的文件是指传输的数据之类的,为什么要传输数据呢?因为有些数据你想管理,哈哈哈!
为什么说CAN通信可以减少CPU的消耗呢?因为很简单,CAN把你要处理的特殊数据帮你给处理好了,就好像别人帮你做好了一样,你只要告诉CAN你想要哪些数据就好了,它就会帮你把你想要的数据给你挑选出来,来供你使用。
而且最重要的一点就是CAN的数据传输只是需要两根线就OK了,哈哈哈 ,你很难想像两根线来完成那么多数据的处理吧。通过这两根线,就可以轻松搭建一个最小局域网,简单来说就是让这些控制器通过CAN总线相互打交道,让它们自己嗨起来!
![大笑](http://static-blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
当下我们来分析一下STM32中的CAN通信原理,我所使用的是一款32的单片机(ZET6),不过都一样了,不管使用什么样的控制器,原理都是一样了。
![大笑](http://static-blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
首先来让我们看看CAN的工作框图
哈哈 是不是觉得还是比较简单的,首先允许我说一下那两个电阻是什么东东,这两个电阻如果你学习过射频的话,估计都不用我多说啥,这里照顾一下那些没有接触过射频的童鞋了,这两个电阻其实它有一个好听的名字是叫做终端电阻,用途是用来做阻抗匹配的,减少回波反射的。
(不懂的话可以自己查查)
![得意](http://static-blog.csdn.net/xheditor/xheditor_emot/default/proud.gif)
CAN总线上有两个电平,一个显性,一个是隐性电平。也就是CAN_High,CAN_Low,这两个电平在工作的时候电压差是2.5V。这里不难理解了。如果对这个电平不太了解的你可以看下TGA1050这款芯片,是CAN的一个收发芯片。(你值得了解)
好了我们接下来开始进入重点学习了
![可怜](http://static-blog.csdn.net/xheditor/xheditor_emot/default/cute.gif)
CAN呢!它的协议主要是通过5种类型饿帧构成的。
帧类型
帧用途
数据帧
用于发送单元向接收单元传送数据的帧
遥控帧
用于接收单元向具有相同ID的发送单元请求数据的帧
错误帧
用于当检查出错时向其他单元通知错误的帧
过载帧
用于接收单元通知其尚未做好接收准备的帧
间隔帧
用于将数据帧及遥控帧与前面的帧分离开来的帧
由于本人比较懒啦,所以以最重要的一个数据帧来讲解一下
数据帧有7个段构成,即
1、帧起始。表示数据帧开始的段。
2、仲裁段。表示该帧优先级的段。
3、控制段。表示数据的字节数及保留位的段。
4、数据段。数据的内容,一帧可以发送0~8个字节的数据。
5、CRC段。检查帧的传输错误的段。
6、ACK段。表示确认正常接收的段。
7、帧结束。表示数据帧结束的段。
一下是数据帧的两种形式,一种是标准帧,一种是扩展帧。
这里提一下,不要将ID视为高7位全部设置为隐性位。RTR表示是否为远程帧,(0:数据帧。1:远程帧)IDE位表示表示符选择位(0:使用标准标识符。1:表示使用扩展标识符)r0和r1为保留位,必须全部以显性电平发出。SRR位为代替远程请求位,为隐性位,它代替了标准帧中RTR,DLC是数据长度表示段,DLC段的有效值为0~8。数据段最多可以有8个字节,从最高位MSB位开始送出。CRC段,该段用于检查帧传输错误,由15个位的CRC顺序和1个位的CRC界定符(用于分隔的位)组成。CRC的计算范围包括帧起始,仲裁段,控制段和数据段。接收方以同样的方式进行计算,两者不一致时会通报错误。ACK段有ACK曹和ACK界定符两个位组成,发送单元的ACK,发送两个隐性位,人而接收单元在ACK槽f发送显性位,EOF是帧结束,由7个隐性位组成。
至此数据帧的7个段就介绍完成了。
好了下面我们再来介绍一下什么叫做位速率?
![睡觉](http://static-blog.csdn.net/xheditor/xheditor_emot/default/sleep.gif)
由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可以分为4段。
1、同步段(ss)
2、传播时间段(PTS)
3、相位缓冲段1(PBS1)
4、相位缓冲段2(PBS2)
这些段又可称为Time Quantum(简称Tq)的最小时间单位构成。
1位分为4个段,每个段又由若干个Tq构成,这称为位时序。
一个位又由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可以任意设定采样点。各段的作用和Tq可如下表示
这些位的设置可以用来设置CAN的波特率,具体计算如下:
OK到此就把CAN通信的波特率以及工作模式和帧类型介绍的差不多了。
![奋斗](http://static-blog.csdn.net/xheditor/xheditor_emot/default/struggle.gif)
接下来开始真正的进入CAN最为关键而且最为有意思的CAN的过滤器组。
首先来介绍一下CAN的一些资源,CAN支持时间触发通信,具有3个发送邮箱,具有3级深度的2个接收FIFO和可变的过滤器组(最多有28个)。
让我们来看一下CAN过滤器的工作情况
这里我用的芯片是STM32ZET6的一款,只有一个CAN控制器,
这个就是CAN的有趣之处了,就是说它可以占用CPU资源然后自己去筛选合适的来进行过滤。哈哈哈
![骂人](http://static-blog.csdn.net/xheditor/xheditor_emot/default/curse.gif)
每个过滤器组都有2个32位的寄存器,CAN_FxR1和CAN_FxR2组成。
每个过滤器组的位宽都可以独立配置,以满足不同应用程序的不同需求。
1个32位的过滤器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位
2个16位的过滤器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位
过滤器可以配置为屏蔽位模式和表示符列表模式。
屏蔽位模式就是说可以对标识符的一些位进行屏蔽,可以让过滤器组不去匹配这些位,然后匹配通过后才让数据进来。
标识符列表模式,收到报文的标识符的每一位都必须和过滤器标识符相同,才能让你进来,否则不让通过。
可以通过相关寄存器配置它的模式和位宽。如下
好了所有的条件都已经准备好了,接下来我们可以进入CAN的接收和发送流程了
CAN的发送流程为:总共有三个邮箱,程序选择1个空的邮箱(TME-1)->设置标识符的ID,数据长度和发送的数据->设置CAN_TIxR的TXRQ位为1,请求发送->邮箱挂号(等待成为最高的优先级)->预定发送(等待总线空闲)->发送->邮箱空。流程如下图:
CAN接收到的有效报文,被存放在3级邮箱深度的FIFO中。FIFO完全由硬件管理,从而节省了CPU的处理负荷。
CAN的接收流程为:FIFO空->收到有效报文->挂号_1(存入FIFO的一个邮箱,这个是由硬件控制,我们不需要理会)->收到有效的报文->挂号_2->收到有效报文->收到有效报文->挂号_3->收到有效报文->溢出。每次读取一个报文时挂号就减1,直到FIFO空,CAN的接收流程图如下:
好了至此我们已经将CAN的重要关卡都打通了,
![大笑](http://static-blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
![大笑](http://static-blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
![大笑](http://static-blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
下面来看一下软件的配置
#include "CAN.H"
/*
CAN 初始化
Fclk初始化时钟设置为36Mhz
tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
tbs2:时间段 2 的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;
tbs1:时间段 1 的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq
brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
Fpclk1 的时钟在初始化的时候设置为 36M,如果设置
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
则波特率为:36M/((8+9+1)*4)=500Kbps
返回值:0,初始化 OK;
其他,初始化失败;
*/
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//使能CAN时钟
//判断是否使能CAN的接收中断?
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//设置为复用推挽输出,用来输出CAN的数据
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_Init(GPIOA,&GPIO_InitStructure);
//CAN的单元设计
CAN_InitStructure.CAN_TTCM=DISABLE;//非时间触发通信
CAN_InitStructure.CAN_ABOM=DISABLE;//软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒
CAN_InitStructure.CAN_NART=ENABLE;//禁止报文自动传输
CAN_InitStructure.CAN_RFLM=DISABLE;//报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE;//优先级由报文标识符决定
CAN_InitStructure.CAN_Mode=mode;//模式设置 mode:0,普通模式 1,回环模式
//设置波特率
CAN_InitStructure.CAN_SJW=tsjw;//设置从新跳跃宽度
CAN_InitStructure.CAN_BS1=tbs1;//时间段1设置
CAN_InitStructure.CAN_BS2=tbs2;//时间段2设置
CAN_InitStructure.CAN_Prescaler=brp;//分频系数
CAN_Init(CAN1,&CAN_InitStructure);
//设置滤波器的相关系数
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;//过滤器0
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//关联到FIFO0这个接收邮箱上
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterNumber=0;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;//32位宽的寄存器
CAN_FilterInit(&CAN_FilterInitStructure);
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE); //FIFO0消息挂号中断允许
NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//次优先级为0
NVIC_Init(&NVIC_InitStructure);
#endif
return 0;
}
/*
接收中断函数管理 目前没有做其它事情
*/
#if CAN_RX0_INT_ENABLE
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
CAN_Receive(CAN1,0,&RxMessage);
}
#endif
/*
CAN 初始化
Fclk初始化时钟设置为36Mhz
tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
tbs2:时间段 2 的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;
tbs1:时间段 1 的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq
brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
Fpclk1 的时钟在初始化的时候设置为 36M,如果设置
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
则波特率为:36M/((8+9+1)*4)=500Kbps
返回值:0,初始化 OK;
其他,初始化失败;
*/
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//使能CAN时钟
//判断是否使能CAN的接收中断?
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//设置为复用推挽输出,用来输出CAN的数据
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_Init(GPIOA,&GPIO_InitStructure);
//CAN的单元设计
CAN_InitStructure.CAN_TTCM=DISABLE;//非时间触发通信
CAN_InitStructure.CAN_ABOM=DISABLE;//软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒
CAN_InitStructure.CAN_NART=ENABLE;//禁止报文自动传输
CAN_InitStructure.CAN_RFLM=DISABLE;//报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE;//优先级由报文标识符决定
CAN_InitStructure.CAN_Mode=mode;//模式设置 mode:0,普通模式 1,回环模式
//设置波特率
CAN_InitStructure.CAN_SJW=tsjw;//设置从新跳跃宽度
CAN_InitStructure.CAN_BS1=tbs1;//时间段1设置
CAN_InitStructure.CAN_BS2=tbs2;//时间段2设置
CAN_InitStructure.CAN_Prescaler=brp;//分频系数
CAN_Init(CAN1,&CAN_InitStructure);
//设置滤波器的相关系数
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;//过滤器0
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//关联到FIFO0这个接收邮箱上
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterNumber=0;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;//32位宽的寄存器
CAN_FilterInit(&CAN_FilterInitStructure);
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE); //FIFO0消息挂号中断允许
NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//次优先级为0
NVIC_Init(&NVIC_InitStructure);
#endif
return 0;
}
/*
接收中断函数管理 目前没有做其它事情
*/
#if CAN_RX0_INT_ENABLE
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
CAN_Receive(CAN1,0,&RxMessage);
}
#endif
上面的一段程序是CAN的一个初始化,可以看到我把过滤器设置为屏蔽位模式,而且全部都是不去关心标识符,也就是说所有的报文都可以通过滤波器组0进来了。
下面配置的是接收和发送函数,不过这里用的是32的库做开发了,有兴趣的也可以用寄存器开发了
![羡慕](http://static-blog.csdn.net/xheditor/xheditor_emot/default/envy.gif)
#include "CAN.H"
/*
can 发送一组数据(固定格式为:ID 0X12,标准帧,数据帧)
len 数据长度
msg 数据指针 最多为八个字节
返回值 0 表示成功
其它 表示失败
*/
u8 Can_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; //标准标识符为0
TxMessage.ExtId=0x12; //设置扩展标识符(29位)
TxMessage.IDE=CAN_Id_Standard; //标准帧
TxMessage.RTR=CAN_RTR_Data; //数据帧
TxMessage.DLC=len; //要发送的数据长度
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
mbox=CAN_Transmit(CAN1,&TxMessage);
i=0;
while((CAN_TransmitStatus(CAN1,mbox)!=CAN_TxStatus_Ok)&&(i<0xfff)) i++;//等待结束
if(i>=0xfff)
return 1;
return 0;
}
/*
can 口接收数据查询
buf 数据缓存区
返回值 0 表示无数据接收
其它 数据接收的数据长度
*/
u8 Can_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if(CAN_MessagePending(CAN1,CAN_FIFO0)==0) return 0;//没有接收到数据直接退出
CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);//读取数据
for(i=0;i<8;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;//返回接收到的数据长度
}
/*
can 发送一组数据(固定格式为:ID 0X12,标准帧,数据帧)
len 数据长度
msg 数据指针 最多为八个字节
返回值 0 表示成功
其它 表示失败
*/
u8 Can_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; //标准标识符为0
TxMessage.ExtId=0x12; //设置扩展标识符(29位)
TxMessage.IDE=CAN_Id_Standard; //标准帧
TxMessage.RTR=CAN_RTR_Data; //数据帧
TxMessage.DLC=len; //要发送的数据长度
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
mbox=CAN_Transmit(CAN1,&TxMessage);
i=0;
while((CAN_TransmitStatus(CAN1,mbox)!=CAN_TxStatus_Ok)&&(i<0xfff)) i++;//等待结束
if(i>=0xfff)
return 1;
return 0;
}
/*
can 口接收数据查询
buf 数据缓存区
返回值 0 表示无数据接收
其它 数据接收的数据长度
*/
u8 Can_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if(CAN_MessagePending(CAN1,CAN_FIFO0)==0) return 0;//没有接收到数据直接退出
CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);//读取数据
for(i=0;i<8;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;//返回接收到的数据长度
}
好了在此就讲解完了CAN的通信设计了。这里我强调一点 CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);//读取数据,这个函数在进行收据接收的时候,同时也对FIFO中的邮箱进行释放。好了 。想继续了解的同学们可以具体看看CAN的相关协议,或者读一读32的相关章节,还是很有帮助的。
![再见](http://static-blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)
![再见](http://static-blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)
![再见](http://static-blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)
![再见](http://static-blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)
![再见](http://static-blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)