CAN是控制器局域网络(Controller Area Network, CAN)的简称,是由研发和生产汽车电子产品著称的德国BOSCH公司开发了的,并最终成为国际标准(ISO 11898)。SJA1000是飞利浦公司一款并行接口的CAN协议控制器,为了减少IO口资源占用,Microchip推出SPI接口CAN协议控制器,型号:MCP2515。这两款芯片都支持CAN V2.0B 技术规范,能发送和接收标准和扩展数据帧以及远程帧。
CAN电平:
CAN数据格式:
标准帧
扩展帧
远程帧(省略)
2.1、MCP2515的原理图
2.2、SJA1000的原理图
第一步:配置CLKOUT输出(CANCTRL寄存器配置CLKEN和分频系数),通过示波器观察CLKOUT引脚输出。确认MCP2515受控。
第二步:环路测试(自发自收)
首先MCP2515配置成环路模式,下面是验证环路测试
1、配置RXB0禁止滤波屏蔽标识符功能
1、收发功能是否正常。验证:RXB0接收数据(可以读取接收缓冲区获取)和发送数据 对比,是否一致。
2、开启CANINTE.TX0IE、CANINTE.RX0IE中断,监测/INT和 /RX0B 引脚电平变化。
理论上:
1)成功发送数据,TXB0CTRL.TXREQ由1变成0和 /INT引脚产生中断,且CANINTF.TX0IF发送中断标志位置1。
2)成功接收数据,/INT引脚产生中断,且CANINTF.RX0IF发送中断标志位置1。同时RX0BF引脚出现低电平(前提使能RX0BF 接收缓冲区满中断引脚,BFPCTRL.BxBFE和BFPCTRL.BxBFM 置1)
2、配置RXB0使能滤波屏蔽标识符功能(11位标准标识符)
1、发送两组数据帧: 先发送不符合滤波码的数据帧,然后发送符合滤波码的数据帧。
验证方法: 1、读取RX接收缓冲区,打印出来
2、通过/INT 和 RX0BF引脚 时序判断。理论:当成功发送第一帧数据是,中断引脚/INT置低,但数据的标识符不符合滤波码;发送第二帧数据符合滤波码,所以RX0BF置低。
注:/INT引脚为什么一直置低?因为响应中断后,未清除中断标志位。当清除中断标志位后,/INT恢复高电平。
第三步:MCP2515同SJA1000通讯 (MCP2515发送)
初步调试现象:SJA1000未将接收数据 发送。个人分析可能原因:
1、硬件连接:通过示波器监测 TJA1040T的发送端TXD 和 PCA82C250的接收端RXD波形一致。
2、波特率:通过一对SJA1000模块收发,测试CAN总线时序,可知波特率100K。而MCP2515 发送数据时,测试的波特率也是100K。
3、电平兼容性:可以排除。
4、有无验证滤波码: 查看外SJA1000程序,该程序中未设置屏蔽码(即接收标准标识符的所有数据),可以排除。
5、CAN终端匹配电阻:TJA1040T的匹配电阻参考datasheet设计,而SJA1000未接匹配电阻,后来焊接60欧,可以排除。
6、通过CAN错误标志寄存器EFLG状态判定问题
TXB0CTRL [1B] ----- 报文发送期间发送总线错误 和报文发送请求位
CANINTF [A0] ----- 报文错误中断标志位和 错误中断标志位置1
EFLG [20] ----- 总线关闭错误标志位置1 (即发送错误计数器TEC > 255)
TEC [00]--- ---总线发送错误计数器
REC [5E]----- 总线接收错误计数器
综上所述:CAN总线异常。
7、查看CAN电平转换芯片TJ1040引脚信号,发现该芯片由3.3V,而datasheet表示需要5V。(该步骤应该放在第一步,自己过失一)。通过焊下芯片割断VCC与3.3V连接,并同5V连接。重新测试后,发现CAN总线上无数据,而TJ1040的TXD有数据。why??
查看TJ1040的PIN8的引脚一直为高电平,由datasheet可知:芯片处于待机模式,无法实现CAN电平转换。通过确认:TJ1040的PIN8的引脚虚焊,导致的。
排除上述问题后,在CAN总线上有数据通讯。已知MCP2515发送数据格式:
11位标识符ID=0x110、数据长度DLC=8、data0-7=0x00 -0x07。通过示波器解码CAN_H的数据得出下图,对比后发现一致,且SJA1000接收到数据也一致。
第三步:MCP2515同SJA1000通讯 (MCP2515接收)
由第二步成功发送,表明CAN总线可以正常通讯。所以接收程序可以参考环路模式中接收程序。
通过上面调试感想:此次调试主要问题 硬件上大意:
1.、未发现TJ1040T芯片电源不合适(3.3V)
2、焊接TJ1040T时,虚焊。这两点都是低级错误。以后必须杜绝。
以后调试硬件,首先将该芯片相关信号(VCC、GND、RET、CS等)全部测试一下确认无误,在进行软件调试。
void MCP2515_init(void)
{
MCP2515_Reset();
/* NO1 check if MODE_CONFIG */
while( (MCP2515_Read(MCP2515REG_CANSTAT) & MODE_CONFIG) != MODE_CONFIG )
{
MCP2515_Write(MCP2515REG_CANCTRL , 0x86);//如果未进入MODE_CONFIG,重新配置
}
/*NO2 CNF1-3 ,Baud=100K对应20个*TQ*/
MCP2515_Write(CNF1, SJW1|0x4); //Synchronization Jump Width Length =1*TQ
MCP2515_Write(CNF2, BTLMODE_CNF3|(SEG8<<3)|SEG3); // Phase Seg 1 = 8*TQ, Prop Seg = 3*TQ
MCP2515_Write(CNF3, SEG8); // Phase Seg 2 = 8*TQ
/*NO3 设置MCP2515中断使能寄存器,使能接收缓冲器中断*/
MCP2515_Write(CANINTE, 0xff);
MCP2515_Write(CANINTF, 0x00);
/*NO4 设置数据接收相关寄存器 */
MCP2515_Write( RXB0CTRL, RXB_RXM0 ); // 设置RXM[1:0]=01,接收缓冲器0接收 标准标识符报文;禁止滚存功能
MCP2515_Write( RXB1CTRL, RXB_RXM0|RXB_RXF2); // 设置RXM[1:0]=10,,接收缓冲器1 接收标准标识符报文;禁止滚存功能
/*NO5 配置引脚 设置接收相关引脚控制寄存器,配置它们禁用第二功能 */
MCP2515_Write( BFPCTRL, 0x05 ); //当由接收报文时,引脚/RX0BF 产生中断信号
MCP2515_Write( RXB0DLC, 0x08); // 设置RX size
MCP2515_Write( RXB1DLC, 0x08); // 设置RX size
/*NO6 设置2个验收滤波寄存器 */
MCP2515_Write(RXF0SIDH,0x22);//RXF0=0x22,即验收滤波器ID[10:3]=0x22
MCP2515_Write(RXF0SIDL,0x00);//验收滤波器ID[2:0]=0x00
MCP2515_Write(RXF2SIDH,0x22);//RXF0=0x000
MCP2515_Write(RXF2SIDL,0x00);
/*NO7 设置屏蔽滤波寄存器为 */
MCP2515_Write(RXM0SIDH,0xFF);//RXM0=0x3FF,验收屏蔽使能[10:3]=0xFF
MCP2515_Write(RXM0SIDL,0xE0);//验收屏蔽使能[2:0]=0x7 即是需要验证所有标准帧的11位ID。
MCP2515_Write(RXM1SIDH,0xFF);//RXM0=0x3FF,验收屏蔽使能[10:3]=0xFF
MCP2515_Write(RXM1SIDL,0xE0);//验收屏蔽使能[2:0]=0x7 即是需要验证所有标准帧的11位ID。
// MCP2515_Write(RXM0SIDH,0x00);//RXM0=0x00,验收屏蔽使能[10:3]=0x00
// MCP2515_Write(RXM0SIDL,0x00);//验收屏蔽使能[2:0]=0x0 即是不验证标准帧的11位ID,接收任何标准帧数据。
/*N07 设置发送相关引脚控制寄存器,配置它们禁用第二功能 */
MCP2515_Write(TXRTSCTRL, 0x00);
/*NO8 set MODE_LOOPBACK MCP2515进入环回模式,进行功能测试*/
#if DEBUG_loopback
MCP2515_Write(MCP2515REG_CANCTRL ,(MODE_LOOPBACK | CLKEN |CLK2));
#else
MCP2515_Write(MCP2515REG_CANCTRL ,(MODE_NORMAL | CLKEN |CLK2));
#endif
}
void MCP2515_loopback (void)
{
Uint8 data[8]={1,2,3,4,5,6,7,8},temp;
printf("\n This MCP2515_loopback test\n");
MCP2515_Write(TXB0CTRL,((MCP2515_Read(TXB0CTRL)) & (~TXB_TXREQ_M) ));//清除请求发送位TXB_TXREQ_M
/*发送第一帧不可识别标识符数据帧*/
MCP2515_Write(TXB0CTRL,0x03);//设置为发送最高优先级
MCP2515_Write(TXB0SIDH,0x88);// SID10--SID3
MCP2515_Write(TXB0SIDL,0x00);//SID2--SID0
MCP2515_Write(TXB0DLC,0x08);// 发送数据长度为8 字节
MCP2515_Write(TXB0D0,0x11);// 发送数据长度为8 字节
MCP2515_Write(TXB0D1,0x22);// 发送数据长度为8 字节
MCP2515_Write(TXB0D2,0x33);// 发送数据长度为8 字节
MCP2515_Write(TXB0D3,0x44);// 发送数据长度为8 字节
MCP2515_Write(TXB0D4,0x55);// 发送数据长度为8 字节
MCP2515_Write(TXB0D5,0x66);// 发送数据长度为8 字节
MCP2515_Write(TXB0D6,0x77);// 发送数据长度为8 字节
MCP2515_Write(TXB0D7,0x88);// 发送数据长度为8 字节
temp = MCP2515_Read(TXB0CTRL);
MCP2515_Write(TXB0CTRL,(temp | TXB_TXREQ_M ) );//请求发送
while((MCP2515_Read(TXB0CTRL) & TXB_TXREQ_M) == TXB_TXREQ_M )//等待发送完毕
{
// printf("TXB0CTRL [%02X]\n", MCP2515_Read(TXB0CTRL));
// printf("CANINTF [%02X]\n", MCP2515_Read(CANINTF));
}
printf("CANINTF [%02X]\n", MCP2515_Read(CANINTF));
if ( MCP2515_Read(CANINTF) & 0x01 == 01)
{
MCP2515_Write(CANINTF,0);//清除中断标志位
printf("RXB0SIDH [%02X]\n", MCP2515_Read(RXB0SIDH ));
printf("RXB0SIDL [%02X]\n", MCP2515_Read(RXB0SIDL ));
// printf("TXB0CTRL [%02X]\n", MCP2515_Read(TXB0CTRL));
// printf("CANINTF [%02X]\n", MCP2515_Read(CANINTF));
printf("RXB0D0 [%02X]\n", MCP2515_Read(RXB0D0));
printf("RXB0D1 [%02X]\n", MCP2515_Read(RXB0D1));
printf("RXB0D2 [%02X]\n", MCP2515_Read(RXB0D2));
printf("RXB0D3 [%02X]\n", MCP2515_Read(RXB0D3));
printf("RXB0D4 [%02X]\n", MCP2515_Read(RXB0D4));
printf("RXB0D5 [%02X]\n", MCP2515_Read(RXB0D5));
printf("RXB0D6 [%02X]\n", MCP2515_Read(RXB0D6));
printf("RXB0D7 [%02X]\n", MCP2515_Read(RXB0D7));
}
/*发送第二帧可识别标识符数据帧*/
MCP2515_Write(CANINTF,0);//清除中断标志位
MCP2515_Write(TXB0CTRL,0x03);//设置为发送最高优先级
MCP2515_Write(TXB0SIDH,0x22);// SID[10:3]=0x22
MCP2515_Write(TXB0SIDL,0x00);//SID2--SID0
MCP2515_Write(TXB0DLC,0x08);// 发送数据长度为8 字节
MCP2515_Write(TXB0D0,data[0]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D1,data[1]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D2,data[2]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D3,data[3]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D4,data[4]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D5,data[5]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D6,data[6]);// 发送数据长度为8 字节
MCP2515_Write(TXB0D7,data[7]);// 发送数据长度为8 字节
temp = MCP2515_Read(TXB0CTRL);
MCP2515_Write(TXB0CTRL,(temp | TXB_TXREQ_M ) );//请求发送
while((MCP2515_Read(TXB0CTRL) & TXB_TXREQ_M) == TXB_TXREQ_M )//等待发送完毕
{
printf("TXB0CTRL [%02X]\n", MCP2515_Read(TXB0CTRL));
printf("CANINTF [%02X]\n", MCP2515_Read(CANINTF));
}
if ( MCP2515_Read(CANINTF) & 0x01 == 01) //判断是否接收到符合规则数据帧
{
MCP2515_Write(CANINTF,0);//清除中断标志位
printf("RXB0D0 [%02X]\n", MCP2515_Read(RXB0D0));
printf("RXB0D1 [%02X]\n", MCP2515_Read(RXB0D1));
printf("RXB0D2 [%02X]\n", MCP2515_Read(RXB0D2));
printf("RXB0D3 [%02X]\n", MCP2515_Read(RXB0D3));
printf("RXB0D4 [%02X]\n", MCP2515_Read(RXB0D4));
printf("RXB0D5 [%02X]\n", MCP2515_Read(RXB0D5));
printf("RXB0D6 [%02X]\n", MCP2515_Read(RXB0D6));
printf("RXB0D7 [%02X]\n", MCP2515_Read(RXB0D7));
}
}
/**********************************************
工程:芯片SJA1000从CAN总线接收任何标识符的数据
功能:从CAN总线接收一帧数据,并通过RS232发送出去。
数据格式= 标识符(任意)+长度(8)+数据
参数:CAN总线波特率100K,RS232通讯波特率57600
时间:2011年4月6日
************************************************/
// 根据时序要求,可以利用I/O口模拟总线了:
//**************************读SJA1000*************************//
uint Read_SJA1000(uint address)
{
uchar data;
asm("nop");
ALE_0;
WR_1;
RD_1;
//DDRB=0xff; //数据口为输出
//PORTB=address; //输出数据的地址
DATA_D1;//数据口为输出
DATA=address;//输出数据的地址
asm("nop");//delay5us(1);
ALE_1;
asm("nop");//delay5us(1);
asm("nop");//delay5us(1);
ALE_0;
RD_0;
asm("nop");//delay5us(2);
asm("nop");
//DDRB=0x00; //数据口为输入
//PORTB=0xff; //上拉
DATA_D0;//数据口为输入
DATA=0xff;//上拉
asm("nop");
//data=PINB; //获得数据
data=DATA_IN;//获得数据
asm("nop");//delay5us(1);
RD_1;
asm("nop");//delay5us(2);
return data;
}
//**************************写SJA10000*************************//
void Write_SJA1000(uint address,uint data)
{ asm("nop");
//DDRB=0xff; //数据口为输出
//PORTB=address; //输出数据的地址
DATA_D1; //数据口为输出
DATA=address; //输出数据的地址
ALE_0;
WR_1;
RD_1;
asm("nop");//delay5us(1);
ALE_1;
asm("nop");//delay5us(1);
asm("nop");//delay5us(1);
ALE_0;
WR_0;
asm("nop");//delay5us(1);
asm("nop");
//PORTB=data; //输出数据
DATA=data; //输出数据
asm("nop");//delay5us(2);
WR_1;
asm("nop");//delay5us(2);
asm("nop");
//dog();
}
void CAN_init(void)//CAN初始化
{
uchar a;
do
{ Write_SJA1000(CAN_MOD,0x01);//设定复位模式的请求位
a=Read_SJA1000(CAN_MOD);} //读取模式寄存器中状态
while( (a&0x01)==0x00); //保证进入复位模式,bit0.0不为1,再写CAN_MOD
Write_SJA1000(CAN_CDR,0x40);//写时钟分频寄存器,Basic模式
Write_SJA1000(CAN_BTR0,0x43);//写总线定时器0寄存器 晶振 16M,通讯波特率100K
Write_SJA1000(CAN_BTR1,0x2f);//写总线定时器1寄存器
Write_SJA1000(CAN_OCR,0x1a);//写输出控制寄存器
Write_SJA1000(CAN_ACR,0x01);//验收码寄存器,表示需要验证的接收标识符
Write_SJA1000(CAN_AMR,0xff);//写验收屏蔽寄存器,
//当某一位为1时,表示对应验收码寄存器的位无屏蔽作用
//报文标识符高8位(ID.10-ID03)对应着验收代码AC.7-AC.0
// 而AM.7-AM.0对应AC.7-AC.0
do
{ Write_SJA1000(CAN_MOD,0x00);//退出复位模式
a=Read_SJA1000(CAN_MOD);}//读取模式寄存器中状态
while( (a&0x01)==0x01);//保证进入复位模式,bit0.0不为0,再写CAN_MO
}
/*-----------------------------------------------------------------
函数名称: void Usart_Receive(void)
函数功能: 查询方式,接收数据
参 数:
返 回 值: 无
-----------------------------------------------------------------*/
unsigned char Usart_Receive(void) //定义返回值类型,否则出错
{
while(!(UCSRA&(1<
1、MCP2515和SJA1000波特率配置如何配置?配置成功,如何验证?
波特率概念:串行通讯中,每秒内传送bit数。例如波特率9600,即每秒传送9600bit。
MCP2515波特率配置100K
同波特率配置相关寄存器CNF1-3,波特率NBR = fbit =1/Tbit,而
Tbit =同步段 + 传播段 +相位缓冲段PS1 +相位缓冲段PS2,其中这些段都是时间份额TQ为单位。TQ=2*BRP*Tosc
已知:Fosc=20 MHz,Tosc=50ns, TQ==2*BRP*Tosc=( CFG1[5:0] = 4)=2*5*50=500ns 则波特率=100K占用TQ数计算如下:
波特率baud = 1/Tbit = 1/N*TQ (此处用N表示同步段、传播段、相位缓冲段PS1、相位缓冲段PS2总共对应TQ数),则
N= 1/baud*TQ=1/100 000*500us =20 个TQ
同步段: 1个TQ(固定值)
传播段: 3个TQ(配置CNF2[2:0] = 0x02)
相位缓冲段PS1: 8个TQ(配置CNF2[5:3] = 0x07)
相位缓冲段PS2: 8个TQ(配置CNF3[2:0] = 0x07)
同步跳转宽度位SJW: 1个TQ( 该位用于为补偿总线上各节点振荡器频率之间的相移,详细参见MCP2515手册)
SJA1000波特率配置
验证波特率方法:
通过示波器监测CAN总线或转换芯片的RXD、TXD端,一位数据占用时间长度。例如波特率100K,每位占用的时间是 1/100K =10us
具体详见下图:该数据位寻找原则:最小时间段的电平。