CAN总线学习笔记一:CAN自收发程序解读
花了一整个下午的时间,彻头彻尾的把 PIAE小组提供的CAN自收发源程序解读了一遍。解读别人的程序是一件挺费时费力的一件事,但是在对某项技术或者说某个芯片的入门阶段参考别人的程序又是一项必不可少的任务。
对于这个程序,头一个任务当然是把头文件先浏览一遍,能弄明白的还是先弄明白,对后面程序的解读有好处。C文件里给出了三个头文件:
#include#include#include
第一个reg52.h我就不废话了,下一个intrins.h我在上一篇日志里也详细的作了说明,这里也不提了。can_selfdef.h是程序员自己定义的一个头文件,在这个头文件里除了一些宏定义和管脚的一些说明外,最重要的就是要弄明白“CAN总线SJA1000寄存器地址定义”。这个我开始也没弄明白,后来反复琢磨,才发现作者在这个程序里吧SJA1000的寄存器作为单片机的外部扩展RAM寻址了,从而省去了编写一些底层的驱动程序,这就让大家连SJA1000的datasheet的时序图都不用看了(不过下一步我想用驱动程序来控制SJA1000)。
看完头文件,可不能从第一个程序依次往下看。应该直接找到主程序main()解读:
void main(void)
{
//MCU初始化(主要是各中断寄存器的初始化)
SJA_RST = 1; //CAN总线复位管脚复位无效
SJA_CS = 0; //CAN总线片选有效
EX1 = 1; //开MCU外部中断INT1
IT1 = 0; //MCU外部中断INT1为电平触发,也是CAN总线接收中断口
IT0 = 1; //MCU外部中断INT0为下降沿触发
EX0 = 1; //开MCU外部中断INT0
EA = 1; //开MCU总中断
SJA_CS = 1; //CAN总线片选无效,使得对数据总线的操作不会影响SJA1000。
//SJA1000初始化
CAN_init(); //对SJA1000寄存器的读写是采用外部寄存器寻址方式,
//所以不需要程序单独控制片选有效无效
_nop_();
_nop_();
//主循环
while(1)
{
_nop_();
_nop_();
Rxd_deal(); //接收处理程序
Txd_deal(); //发送处理程序
led_seg7(0,Txd_data); //数码管1-2显示发送数据子程序
led_seg7(1,Rxd_data+3); //数码管3-4显示接收数据子程序
}
}
上面的注释是本人详细做了加工的,先是单片机中断寄存器的初始化,打开了单片机的INT0和INT1两个外部中断。
INT0是外接按键的,所以是下降沿触发。这个按键每按下一次,待发送的数据的第一个字节就会加一,这个数据同时会显示在数码管上。
而INT1是外界着SJA1000的发送数据中断端口的,采用电平触发,也就是说当SJA1000发送数据时,就会触发中端口INT1,从而让单片机进行数据的接收工作。
接下来是SJA1000的各个寄存器的初始化,主要是在CAN_init();这个程序里完成的,主要是设置一些寄存器使得满足本次试验的要求。
最后就进入了主循环中。不断的进行接收数据处理、发送数据处理以及将数据的头一个字节的数值显示在数码管上。每当按键被按下后就会置位发送数据状态标志位,这时就进入发送状态,无非就是把ID码和数据等13个字节送入SJA1000的相应寄存器里,其它的事情就交给SJA1000来完成就可以了。由于是进行自传送,所以SJA1000在接收到数据后会给单片机一个中断,此时也就进入了接收数据状态下,同样只要把SJA1000里相应的寄存器读出来就可以了。为了观察发送和接收的数据是否一致,这个程序里就把发送的数据的头一个字节通过数码管显示出来。发送数据正常显示,而接收数据为了便于观察,我把它+3,就是说显示的接收数据会比发送数据大3。最后很好的得到验证了。
CAN总线学习笔记二:验收滤波
了解CAN总线的人都知道,CAN总线在的帧数据在总线上传送时,其它的CAN控制器是通过验收滤波来决定总线上的数据帧的ID是否和本节点相吻合,如果与本节点吻合,那么总线上的数据就被存入总线控制器的相应寄存器里,否则就抛弃该数据,从而也能够减轻总线控制器的工作量。换句话说,总线上数据帧的ID通过待接收节点的验收滤波后是吻合的,是可以被接收的。
那么,总线控制器是如何进行验收滤波的呢?验收滤波分单滤波和双滤波。标准帧和扩展帧由于ID长度不同,它们的两种滤波也有所区别。这里我只重点举一个例子,因为只要理解了一种滤波方式,其它的滤波方式都是类似的,也很容易就理解了。
这里就说扩展帧的双滤波方式。所谓双滤波,就是有两次的滤波,但并非两次滤波都需要通过才双通过,两次滤波只要有一次滤波成功那么就默认滤波通过,可以接收数据了。
ACR0 |
ACR1 |
ACR2 |
ACR3 |
AMR0 |
AMR1 |
AMR2 |
AMR3 |
ID.28-ID.21 |
ID.20-ID.13 |
ID.28-ID.21 |
ID.20-ID.13 |
如上表所示,ACR寄存器是接收代码寄存器,AMR是接收屏蔽寄存器。ACR一般是需要与对应的ID相吻合的,但是如果AMR的相应位上设置为1的时候,ID的那一位数据可以不和AMR的相应位一样,也就是起到屏蔽的作用。
举个例子。如果ACR0=11101111,AMR0=00000000,那么要想通过验收滤波,必须ID.28-ID.21=ACR0=11101111。如果AMR0=00010000,那么ID.28-ID.21=11111111时,也可以通过验收滤波,因为此时AMR0的第五位为1,也就是屏蔽了ACR0的第五位。所以ID的相应位可以不合ACR0一致。
在扩展帧的双滤波方式下,ACR0\ACR1分别对应ID.28-ID.13 ,ACR2\ACR3分别也对应ID.28-ID.13,这就达到了两次滤波的效果。
另外要说明的一点是:通过验收滤波后符合节点要求的数据就存储到节点的相应寄存器里,其它的帧信息并不做存储。
CAN总线学习笔记三:CAN总线通信
在理解了CAN总线的自通信程序后,再来探讨CAN总线间的相互通信变得容易了许多。对于是自通信还是相互通信,这个肯定是需要对寄存器进行必要的设置的,分析PIAE的两个程序后不难发现,自通信和互通信需要设置的知识模式寄存器,这个是在SJA1000的初始化时进行设定的。
SJA1000的初始化程序我根据习惯,直接把它独立成一个子程序了。如下,是工作于自通信时的初始化程序。
///////////////////////////////////////////////
//函数:init_sja1000
//说明:独立CAN控制器SJA1000的初始化
//入口:无
//返回:无
///////////////////////////////////////////////
void init_sja1000(void)
{
uchar state;
uchar ACRR[4];
uchar AMRR[4];
// 接收代码寄存器
ACRR[0] = 0xff;
ACRR[1] = 0x22;
ACRR[2] = 0x33;
ACRR[3] = 0x44;
// 接收屏蔽寄存器,只接收主机发送的信息
AMRR[0] = 0xff;
AMRR[1] = 0Xff;
AMRR[2] = 0xff;
AMRR[3] = 0xff;
// 使用do--while语句确保进入复位模式
do
{ // 设置MOD.0=1--进入复位模式,以便设置相应的寄存器
MODR = 0x09;
state = MODR;
} while( !(state & 0x01) );
// 对SJA1000部分寄存器进行初始化设置
CDR = 0x88; // CDR为时钟分频器,CDR.3=1--时钟关闭, //CDR.7=0---basic CAN, CDR.7=1---Peli CAN
BTR0 = 0x31; // 总线定时寄存器0 ;总线波特率设定
BTR1 = 0x1c; // 总线定时寄存器1 ;总线波特率设定
IER = 0x01; // IER.0=1--接收中断使能; IER.1=0--关闭发送中断使能
OCR = 0xaa; // 配置输出控制寄存器
CMR = 0x04; // 释放接收缓冲器
// 初始化接收代码寄存器
ACR0 = ACRR[0];
ACR1 = ACRR[1];
ACR2 = ACRR[2];
ACR3 = ACRR[3];
// 初始化接收屏蔽寄存器
AMR0 = AMRR[0];
AMR1 = AMRR[1];
AMR2 = AMRR[2];
AMR3 = AMRR[3];
// 使用do--while语句确保进入自接收模式
do
{ //MOD.2=1--进入自接收模式,MOD.3=0--双滤波器模式
MODR = 0x04;
state = MODR;
} while( !(state & 0x04) );
}
两个数组ACRR[4]和AMRR[4]分别存储着需要设置的接收代码寄存器和接收屏蔽寄存器的数值。这两个数组的设置设计者可以根据需要自己设定(在互通信时就需要在这里做文章了)。AMRR的四个值都设定为OXFF说明无论总线上传输的ID值是什么,也不管ACRR的数值设定是什么,这个SJA1000都照单全收(至于为什么,上节关于验收滤波的日志做了详细讨论了)。
然后进入设定模式寄存器进入复位模式。在复位模式下,可以对SJA1000部分寄存器进行初始化设置,并且把刚才两个数组的数据存入接收代码寄存器和接收屏蔽寄存器里,自此初始化算是完成了。但是因为CAN总线控制器要进行自通信,所以必须对模式寄存器设定使得SJA1000进入复位模式,这就是最后的do--while语句的作用。
接下来,我们看CAN总线互相通信的初始化设置。
///////////////////////////////////////////////
//函数:init_sja1000
//说明:独立CAN控制器SJA1000的初始化
//入口:无
//返回:无
///////////////////////////////////////////////
void init_sja1000(void)
{
uchar state;
uchar ACRR[4];
uchar AMRR[4];
// 接收代码寄存器
ACRR[0] = 0x11;
ACRR[1] = 0x22;
ACRR[2] = 0x33;
ACRR[3] = 0x44;
// 接收屏蔽寄存器
AMRR[0] = 0x00;
AMRR[1] = 0Xff;
AMRR[2] = 0x00;
AMRR[3] = 0xff;
// 使用do--while语句确保进入复位模式
do
{ // 设置MOD.0=1--进入复位模式,以便设置相应的寄存器
MODR = 0x09;
state = MODR;
} while( !(state & 0x01) );
// 对SJA1000部分寄存器进行初始化设置
CDR = 0x88; // CDR为时钟分频器,CDR.3=1--时钟关闭, //CDR.7=0---basic CAN, CDR.7=1---Peli CAN
BTR0 = 0x31; // 总线定时寄存器0 ;总线波特率设定
BTR1 = 0x1c; // 总线定时寄存器1 ;总线波特率设定
IER = 0x01; // IER.0=1--接收中断使能; IER.1=0--关闭发送中断使能
OCR = 0xaa; // 配置输出控制寄存器
CMR = 0x04; // 释放接收缓冲器
// 初始化接收代码寄存器
ACR0 = ACRR[0];
ACR1 = ACRR[1];
ACR2 = ACRR[2];
ACR3 = ACRR[3];
// 初始化接收屏蔽寄存器
AMR0 = AMRR[0];
AMR1 = AMRR[1];
AMR2 = AMRR[2];
AMR3 = AMRR[3];
// 使用do--while语句确保退出复位模式
do
{
MODR = 0x08; //MOD.3=0--双滤波器模式
state = MODR;
} while( state & 0x01 );
}
很容易可以发现,CAN互通信和自通信的初始化设置,只有最后的设置是不一样的,自通信时把模式寄存器的自通信寄存器位置位,而互通信时只要退出复位模式(同时把自通信寄存器位清零)即可。其它的设置根据需要设定。
上面讨论了自通信和互通信两种工作方式下的寄存器设置。那么,我就在想,如果用互通信的模式下,如果发送帧的ID设定和自身接收的验收滤波吻合,是不是也能进行自接收呢?答案是否定的,根据以上的设想做的实验表明,在互通信模式下,CAN总线上若只有一个节点,那么CAN总线是不会达到你预想的变化的,按下键后红灯会一直亮着,说明CAN总线陷入了无法接收或者正在接收的死循环跳不出来了。
另外,我还做了一个实验。就是当CAN节点1发送的数据帧ID不仅和节点2的吻合,也和自身的验收滤波吻合,那么当节点1的数据帧发送后是不是节点1和节点2都能接收到数据呢?结果证明这个设想是成立的。这也就说明了挂靠在CAN总线上的每一个节点,只要CAN总线上的数据帧ID和某节点验收滤波通过,该节点就可以接收数据。
在初始化设置了模式寄存器后,下一步就是设定不同的发送帧的ID和本节点的接收代码寄存器值和屏蔽寄存器值。我这里只有两个节点通信,一般如果要使CAN总线上的两个节点相互接收到对方的数据。那么根据上一节的原理设定接收代码寄存器值和屏蔽寄存器值以及发送帧的ID值就可以了。
在看懂并且理解了PIAE工作组的CAN自通信和互通信后,我想对于CAN总线协议的工作方式算是已经入门了,剩下的就是继续从官方或者第三方提供的datasheet里补知识,根据不同的场合和不同的应用把CAN总线控制器的寄存器设置用活来
本文来源:http://bbs.ednchina.com/BLOG_ARTICLE_125404.HTM