×
接口总线驱动 > 总线 > 详情

如何一步一步建立CAN通讯

发布时间:2020-05-21 发布时间:
|
CAN通讯的优点在此就不多说了,10公里,5Kb/s的速度是能保证的。 
第一步:硬件环境的建立。 
这里采用的是SJA1000作为总线控制器,CTM8251模块作为总线驱动器。MCU采用的是MEGA16:利用I/O口模拟数据总线,当然也可以使用有总线的MCU:MCS-51,MEGA8515等。 
原理图如下: 

第二步:SJA1000的控制 
首先阅读下SJA1000的手册,基本了解下SJA1000的结构,主要是寄存器方面的。还要了解下CAN总线方面的东西:BasicCAN,Peli CAN,远程帧,数据帧等等…… 
SJA1000工作之前需要配置一下,才能正常工作,没有经过配置的SJA1000回拉坏总线的:组成网络的时候,如果其中有的SJA1000没有正确配置,这个设备会干扰总线,使其它设备的数据发送不出去。 
怎么才能控制SJA1000呢,请看下面的SJA1000读写的时序图: 

写的时序 

根据时序要求,可以利用I/O口模拟总线了: 
//**************************读SJA1000*************************// 
uint Read_SJA1000(uint address) 

uchar data; 
asm("nop"); 
ALE_off; 
WR_on; 
RD_on; 
CAN_cs_on; 
DDRA=0xff; //数据口为输出 
PORTA=address; //输出数据的地址 
asm("nop");//delay5us(1); 
ALE_on; 
asm("nop");//delay5us(1); 
//DDRA=0xff; //数据口为输出 
PORTA=address; //输出数据的地址 //再次输出地址,确保一致。 
asm("nop");//delay5us(1); 
ALE_off; 
//delay5us(1); 
CAN_cs_off; 
RD_off; 
asm("nop");//delay5us(2); 
asm("nop"); 
DDRA=0x00; //数据口为输入 
PORTA=0xff; //上拉 
asm("nop"); 
data=PINA; //获得数据 
asm("nop");//delay5us(1); 
RD_on; 
CAN_cs_on; 
asm("nop");//delay5us(2); 
//dog(); 
return data; 

//**************************写SJA10000*************************// 
void Write_SJA1000(uint address,uint data) 
{ asm("nop"); 
//uint temp1,temp2; 
DDRA=0xff; //数据口为输出 
PORTA=address; //输出数据的地址 
CAN_cs_on; 
ALE_off; 
WR_on; 
RD_on; 
asm("nop");//delay5us(1); 
ALE_on; 
asm("nop");//delay5us(1); 
//DDRA=0xff; //数据口为输出 
PORTA=address; //输出数据的地址 再次输出地址,确保数据准确 
asm("nop");//delay5us(1); 
ALE_off; 
//delay5us(1); 
CAN_cs_off; 
WR_off; 
asm("nop");//delay5us(1); 
asm("nop"); 
//DDRA=0xff; 
PORTA=data; //输出数据 
asm("nop");//delay5us(2); 
WR_on; 
PORTA=data; //再次输出数据,取保一致 
CAN_cs_on; 
asm("nop");//delay5us(2); 
asm("nop"); 
//dog(); 

现在可以读写SJA1000了。 
配置SJA1000需要使SJA1000进入复位模式,然后对一些寄存器写入数据。在这里,CAN使用Pelican模式,速率为5K,双滤波工作, 
//*************************CAN复位初始化********************// 
void CAN_Init(void) 
{ uchar i_temp=0,j_temp=0; 

CLI(); 
//Read_SJA1000(CAN_IR); //读中断寄存器,清除中断位 
Write_SJA1000(CAN_MOD,0x01); 
while(!(Read_SJA1000(CAN_MOD)&0x01))//保证进入复位模式,bit0.0不为1,再写CAN_MOD 

Write_SJA1000(CAN_MOD,0x01); 
dog(); 

Write_SJA1000(CAN_CDR,0xc8); //配置时钟分频寄存器-Pelican,CBP=1, 
//关闭TX1中断与时钟输出 
Write_SJA1000(CAN_AMR0,0xff); //配置验收屏蔽AMR0=0FFH 
Write_SJA1000(CAN_AMR1,0x00); //配置验收屏蔽AMR1=000H 
Write_SJA1000(CAN_AMR2,0xff); //配置验收屏蔽AMR2=0FFH 
Write_SJA1000(CAN_AMR3,0x00); //配置验收屏蔽AMR3=000H 
Write_SJA1000(CAN_ACR1,0x00); //配置验收代码ACR1=0:广播 
Write_SJA1000(CAN_ACR3,addr); //配置验收代码ACR3=地址 
Write_SJA1000(CAN_BTR0,0x7f); //配置总线定时--5kbps 
Write_SJA1000(CAN_BTR1,0xff); 
Write_SJA1000(CAN_OCR,0x1a); //配置输出控制 
Write_SJA1000(CAN_EWLR,0xff); //配置错误报警限制为255 
do 

Write_SJA1000(CAN_MOD,0x00); //进入工作模式双滤波 
dog(); 

while((Read_SJA1000(CAN_MOD))&0x01); // 确认复位标志是否被删除 
Write_SJA1000(CAN_TXB+4,ID3); //配置发送缓冲区的ID3- 
Write_SJA1000(CAN_IER,0x07); //配置SJA10000中断-错误报警/发送/接收中断 
SEI(); 

在这之前,需要获取设备的地址,就是读取拨码开关各个脚的电平。需要注意的是,SJA1000使用的是双滤波模式,响应地址有:广播的:0x00,还有自己的地址:0x**。为什么要这么做呢,一个系统中,主机的地址一般是0X00,从机地址从0X01开始,这里面如果有两个从机的地址一样,就很可能产生一些混乱。从机一旦多了起来,查找地址相同的设备就有些麻烦了。 
在程序的初始化的时候,进行SJA1000的配置。 
第三部:工作程序 
接下来,做的工作就是CAN试发送,别小看这个试发送,这可是解决地址重复的问题的哦,还能检测CAN网络是否正常。 
//****************CAN第一次发送 通讯地址测试2e*****************// 
void CAN_first_send(void) 
{ //uchar add_temp=0; 
uchar a_temp=0; 
uchar SR_temp; 
asm("nop"); //延时 
NET_LED_on; //打开网络灯 
do 

a_temp=Read_SJA1000(CAN_SR);//读CAN_SR,直到SR.2=1:CPU可以发送数据 
dog(); 

while(!(a_temp&0x04)) 
CLI(); //关CAN中断,即总中断 

Write_SJA1000(CAN_TXB+0,0xc0); //发送远程帧0xc0 
Write_SJA1000(CAN_TXB+1,0x00); //发送转接器地址 
Write_SJA1000(CAN_TXB+2,addr); //发送传感器地址 
Write_SJA1000(CAN_TXB+3,0x2e); //发送命令码0x2e 
Write_SJA1000(CAN_TXB+4,ID3); //发送ID3 
Write_SJA1000(CAN_CMR,0x01); //启动发送, 
//网络故障错误在中断中处理,短接H、L,按复位,先亮绿灯,后黄灯亮 
asm("nop"); 
//SEI(); 

SJA1000的中断引脚接到MEGA16的INT1上,需要在程序初始化的时候,配置一些INT1,使MCU能响应SJA1000的中断。 
数据发送前,点亮网络指示灯,什么时候熄灭它呢,在发送中断中熄灭它。 
下面看看MCU对SJA1000中断的一些处理:在这里只处理:接收中断、发送中断、总线关闭中断。 
#pragma interrupt_handler can_int:3 
void can_int(void) 

asm("nop"); 
CAN_IR_temp=Read_SJA1000(CAN_IR); //读取中断寄存器 
if(CAN_IR_temp&0x01) //接收中断 

Get_RXB_temp(); 

if(RxBuffer[0]==0x80) //地址测试数据帧 

reload(); //数据帧中有和自己相同的地址 

if(RxBuffer[0]==0xc0) // 远程帧则释放接收缓冲区 

type=RxBuffer[3]; //读命令码 

//处理命令码 

if(type==0x30) 
{ if(type==0x34) 
{CAN_now_value_send();type=0;} //传瞬时值数据 
if (type==0x27) 
{reload(); type=0;}//装置复位 
if(type==0x2e) 
{active();type=0;} //通讯地址测试 


Write_SJA1000(CAN_CMR,0x04); //释放接收缓冲区 



if(CAN_IR_temp&0x02) //发送中断 

NET_LED_off; //关闭网络灯 
ERR_LED_off; //关闭故障灯 
CANBE_JSQ=0; //复位总线关闭计数器 
asm("nop"); 

if(CAN_IR_temp&0x04) //错误报警中断(仅有总线关闭处理) 
{ //读状态寄存器,SR.7总线关闭:CAN控制器不参与总线活动 
CAN_SR_temp=Read_SJA1000(CAN_SR); 

if(CAN_SR_temp&0x80) 

CANBE_JSQ=CANBE_JSQ+1; //关闭次数加1 
if(CANBE_JSQ
do 

Write_SJA1000(CAN_MOD,0x00); //重新进入工作模式 


while((Read_SJA1000(CAN_MOD))&0x01);//等待进入工作模式 
Write_SJA1000(CAN_CMR,0x01); //启动CAN重新发送 

if(CANBE_JSQ>=CANBE_C) //总线关闭次数到达设定次数 

NET_LED_off; //关闭网络灯 
ERR_LED_on; //打开故障灯 
CANBE_JSQ=0; //复位总线关闭计数器 
do 

Write_SJA1000(CAN_MOD,0x00); //重新进入工作模式 


while((Read_SJA1000(CAN_MOD))&0x01);//等待进入工作模式 
Write_SJA1000(CAN_CMR,0x01); //启动CAN重新发送 
CANBE_JSQ=CANBE_C; //防止CANBE_JSQ溢出 



asm("nop"); 



中断程序中,对命令码等于0x2e的处理程序是:active(); 
active()程序如下: 
//************************通讯地址测试2EH***********************// 
void active(void) 

uchar temp1,temp2; 
asm("nop"); //延时 
NET_LED_on; //打开网络灯 
CLI(); //关CAN中断,即总中断 
do 

temp1=Read_SJA1000(CAN_SR);//读CAN_SR,直到SR.2=1:CPU可以发送数据 
dog(); 

while(!(temp1&0x04)); 

Write_SJA1000(CAN_TXB+0,0x80); //发送数据帧0x80 
temp2=Read_SJA1000(CAN_RXB+1); 
Write_SJA1000(CAN_TXB+1,temp2); //发送转接器地址 
Write_SJA1000(CAN_TXB+2,addr); //发送传感器地址 
Write_SJA1000(CAN_TXB+3,0x2e); //发送命令码0x2e 
Write_SJA1000(CAN_TXB+4,ID3); //发送ID3 
Write_SJA1000(CAN_CMR,0x01); //启动发送 
SEI(); //开中断 
asm("nop"); 


大家仔细看看 active()程序的内容,发送了一个没有数据的数据帧:0X80,再回过头看看中断处理函数,里面有这段程序, if(RxBuffer[0]==0x80) //地址测试数据帧 

reload(); //数据帧中有和自己相同的地址 

reload(); 程序很简单,就是停止喂狗,等待复位。复位之后呢,它会进行试发送,哈哈,接下来的两个地址相同的设备就“打架”起来了,现象就是一个设备不断复位,一个设备通讯灯不断闪烁。怎么样,很容易就判断出哪两个地址重复了。 
命令码等于0x27时,设备复位,一般是主机发送这个远程帧。 
0x34时,发送数据: 
//************************瞬时值发送 34H*********************// 
void CAN_now_value_send(void) 

//uchar a_temp=0; 
uchar c_temp=0; 
js_now_send_value(); //计算需要发送的瞬间数值 
asm("nop"); //延时 
NET_LED_on; //打开网络灯 
do 

b_temp=Read_SJA1000(CAN_SR); //读CAN_SR,直到SR.2=1:CPU可以发送数据 
dog(); 

while(!(b_temp&0x04)) 
CLI(); //关CAN中断,即总中断 

Write_SJA1000(CAN_TXB+0,0x84); //发送数据帧0x84 
Write_SJA1000(CAN_TXB+1,RxBuffer[1]); //发送转接器地址 
Write_SJA1000(CAN_TXB+2,addr); //发送传感器地址 
Write_SJA1000(CAN_TXB+3,0x34); //发送命令码0x34 
Write_SJA1000(CAN_TXB+4,ID3); //发送ID3 
Write_SJA1000(CAN_TXB+5,CBDJ_Send_L); // 
Write_SJA1000(CAN_TXB+6,CBDJ_Send_H); // 
Write_SJA1000(CAN_TXB+7,GD_Send_L); // 
Write_SJA1000(CAN_TXB+8,GD_Send_H); // 
Write_SJA1000(CAN_CMR,0x01); //启动发送 
SEI(); //开中断 
asm("nop"); 


发送了一个数据帧,这个数据帧有四字节的数据。 
CAN的数据帧最多支持有8个字节的数据帧,如果数据较多,可以分为多个数据帧,在命令码里面区分这些数据帧。 

第四步:建立自己的CAN通讯网络。 
主机可以是一台有CAN接口的计算机,一般在计算机上装一个CAN接口卡,有ISA接口的,比如PCL-841;PCI接口的。CAN卡的销售商都会提供驱动,依靠驱动里面的函数,来控制CAN卡,此项不是专长,不好多说,反正就是这个思路。 

好了,昨天从南京回来的路上,就考虑发个CAN的东西。咱们这个论坛,目前还没有多少关于CAN的帖子,意在抛砖引玉…………本坛高手很多,尤其是有很多潜水的高高手~~~~ 
-------------------- 
程序中的一些DEFINE 
//******************引脚信号定义***************************// 
#define CS_1 (PORTB|= (1<<4 )) //AD7705片选 
#define CS_0 (PORTB&= ~(1<<4 )) 
#define DRDY (PINB&0x08) //AD转换DRDY信号输入 
#define NET_LED_off (PORTB|= (1<<0 )) //网络故障灯高电平,熄灭 
#define NET_LED_on (PORTB&= ~(1<<0 )) //网络故障灯低电平,点亮 
#define ERR_LED_off (PORTB|= (1<<1 )) //装置故障灯高电平,熄灭 
#define ERR_LED_on (PORTB&= ~(1<<1 )) //装置故障灯低电平,点亮 
#define DOG_on (PORTB|= (1<<2 )) //看门狗高 
#define DOG_off (PORTB&= ~(1<<2 )) //看门狗低 
#define WR_on (PORTD|= (1<<0 )) //WR高 
#define WR_off (PORTD&= ~(1<<0)) //WR低 
#define RD_on (PORTD|= (1<<1 )) //RD高 
#define RD_off (PORTD&= ~(1<<1)) //RD低 
#define CAN_cs_on (PORTD|= (1<<4 )) //CAN高 
#define CAN_cs_off (PORTD&= ~(1<<4)) //CAN低 
#define ALE_on (PORTD|= (1<<2 )) //ALE高 
#define ALE_off (PORTD&= ~(1<<2)) //ALE低 
#define FALSE 0 
#define TRUE 1 
#define CANBE_C 6 //总线关闭次数设定值 
//*******************CAN寄存器地址**************************// 
#define CAN_MOD 0 //模式寄存器 
#define CAN_CMR 1 //命令寄存器 只写 
#define CAN_SR 2 //状态寄存器 只读 
#define CAN_IR 3 //中断寄存器 只读 
#define CAN_IER 4 //中断使能寄存器 
#define CAN_BTR0 6 //总线定时寄存器0 
#define CAN_BTR1 7 //总线定时寄存器1 
#define CAN_OCR 8 //输出控制寄存器 
#define CAN_TEST 9 //测试寄存器 
#define CAN_ALC 11 //仲裁丢失寄存器 
#define CAN_ECC 12 //错误代码捕捉寄存器 
#define CAN_EWLR 13 //错误报警限制寄存器 
#define CAN_EXERR 14 //RX错误计数寄存器 
#define CAN_TXERR 15 //TX错误计数寄存器 
#define CAN_ACR0 16 //验收码寄存器0 
#define CAN_ACR1 17 //验收码寄存器1 
#define CAN_ACR2 18 //验收码寄存器2 
#define CAN_ACR3 19 //验收码寄存器3 
#define CAN_AMR0 20 //验收屏蔽寄存器0 
#define CAN_AMR1 21 //验收屏蔽寄存器1 
#define CAN_AMR2 22 //验收屏蔽寄存器2 
#define CAN_AMR3 23 //验收屏蔽寄存器3 
#define CAN_TXB 16 //发送缓冲区首地址(工作模式) 
#define CAN_RXB 16 //接收缓冲区首地址(工作模式) 
#define CAN_RMC 29 //RX信息计数器 
#define CAN_RBSA 30 //RX缓冲区起始地址寄存器 
#define CAN_CDR 31 //时钟分频器 
#define ID3 00 //ID3 
----------------------------- 
初始化程序 
uchar main_ch=0; 
IO_Init(); //I/O口初始化 
INT1_Init(); 
GET_add(); //获取地址,地址为0,反复获取地址,直到不为0。 
NET_LED_on; 
ERR_LED_on; //初始化中,点亮故障灯和通讯灯, 
delay50ms(2); 
dog(); 
delay50ms(2); 
dog(); 
delay50ms(2); 
dog(); 
CAN_Init(); //CAN初始化 
NET_LED_off; 
ERR_LED_off; 
SEI(); 
CAN_first_send(); //CAN试发送 
delay50ms(1); 
dog(); 
void GET_add(void) //地址获取程序 

uchar add_temp=0,add_temp1=0,add_temp2=0,add_temp3=0,addr_temp=0; 
do 

dog(); 
NET_LED_on; 
ERR_LED_on; 
add_temp1=PINC&0xc3; 
add_temp2=add_temp1>>4; 
add_temp1=add_temp1&0x03; 
add_temp3=(PIND&0xe0)>>1; 
add_temp=add_temp1+add_temp2+add_temp3; 
add_temp=(~add_temp)&0x7f; 
addr=add_temp; 
delay50ms(2); 

while(addr==0); 

}
 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
汽车电子系统的LED大灯驱动设计