如何一步一步建立CAN通讯
第一步:硬件环境的建立。
这里采用的是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<canbe_c) 关闭次数小于设定值 <br=""> {
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);
}
如何一步一步建立CAN通讯的更多相关文章
- 一步一步建立 webpack 4 项目
一步一步建立 webpack 4 项目 cnpm init -y cnpm i webpack --save-dev cnpm i webpack-cli --save-dev 修改 package. ...
- xmppmini 项目详解:一步一步从原理跟我学实用 xmpp 技术开发 2.登录的实现
第二章登录的实现 金庸<倚天屠龙记> 张三丰缓缓摇头,说道:“少林派累积千年,方得达成这等绝技,决非一蹴而至,就算是绝顶聪明之人,也无法自创.”他顿了一顿,又道:“我当年在少林寺中住过,只 ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...
- 如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发
阅读目录 前言 单元测试 纠正错误,重新出发 结语 一.前言 实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发 ...
- 一步一步使用ABP框架搭建正式项目系列教程之本地化详解
返回总目录<一步一步使用ABP框架搭建正式项目系列教程> 本篇目录 扯扯本地化 ABP中的本地化 小结 扯扯本地化 本节来说说本地化,也有叫国际化.全球化的,不管怎么个叫法,反正道理都是一 ...
- 一步一步安装UEFI分区方式的windows 10 企业版
发现很多坛友不会安装UEFI分区的windows 10 从启动设置,到分区,到最后的引导与激活都是很大的问题. 在我看来这是最不容易出错的安装方式适合于刚刚上手的菜鸟,自己按照图片一步一步的就可以安装 ...
- 一步一步跟我学DeviceOne开发 - 仿微信应用(一,二,三)
这是一个系列的文档,长期目标是利用DeviceOne开发一些目前使用广泛的优质手机应用,我们会最大化的实现这些应用的每一个功能和细节,不只停留在简单的UI模仿和Demo阶段,而是一个基本可以使用的实际 ...
随机推荐
- 微信公众号本地断点调试(frp反向代理或Remote Debugger)
问题描述: 需要开发微信授权和订阅推送,但是感觉调试不方便,就试着几种方式.因为是用的C#开发,Visual Studio工具自带配套的远程工具 (Remote Debugger).但是感觉不稳定,容 ...
- [SDOI2010]猪国杀
题面 (这个做题面的大佬太赞啦) 无聊啊~~~然后就写大模拟,然后就从早上写到下午,生活得到了极大的充实 注意事项: 牌库为空之后再抽牌,会重复抽最后一张被抽走牌 无论在任何过程中,游戏结束(主公死或 ...
- js-jQuery性能优化(二)
5.数组方式使用jQuery对象 使用jQuery选择器获取结果是一个jQuery对象.然而,jQuery类库会让你感觉正在使用一个定义了索引和长度的数组.在性能方面,建议使用简单的for或者whil ...
- node 搭建静态服务
对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解. 基本功能 不急着写下第一行代码,而是先梳理一下就基本功能而言有哪 ...
- ActiveReports 报表应用教程 (16)---报表导出
葡萄城ActiveReports报表支持多种格式的报表导出,包括PDF.Excel.Word.RTF.HTML.Text.TIFF以及其它图片格式,用户可以将它们应用到Windows Forms.We ...
- Expo大作战(十八)--expo如何发布成独立应用程序,打包成apk或者ipa,发布到对应应用商店
简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...
- Python Django框架笔记(一):安装及创建项目
#推荐一本书<Python核心编程>(适合有一定基础的),美国人Wesley Chun编写的,京东.淘宝应该都有.我是觉得写的很好,详细.简洁.满满的干货,不像有的书整本看完也没什么用. ...
- 机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记
机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记 关键字:k-均值.kMeans.聚类.非监督学习作者:米仓山下时间: ...
- ChatOps如何变革企业业务
[编者按]本文作者为日志分析软件公司 Logz.io 的联合创始人 Tomer Levy,主要介绍 ChatOps 的特点与发展历程,以及将来可能带来的业务变革.文章系国内 ITOM 管理平台 One ...
- Sqlserver的Transaction做Rollback的时候要小心(转载)
仔细研究了下,发现sql server里面的explicit transaction(显示事务)还是有点复杂的.以下是有些总结: Commit transaction 会提交所有嵌套的transact ...