CAN-bus现场总线基础教程【第3章】CAN控制器驱动-发送CAN帧(12)
第3章CAN控制器驱动
1.1 发送CAN帧
1.1.1 发送流程
SJA1000发送流程详见图3.1。在发送CAN报文之前,需要确认已经初始化SJA1000参数;通过读取状态寄存器SR,测试其位2和位3是否都为1;当这两位都为1时,填充报文到发送缓冲区,并设置发送模式,SJA1000将启动发送过程;当SR的第2位和第3位不同时为1时,表明上一次发送还未完成,需要等待发送完成,或终止发送。
图3.1 SJA1000发送流程示意图
1.发送缓冲区
SJA1000具有1个12字节的发送缓冲区,位于SJA1000内部RAM空间。要发送的帧报文可以通过寄存器16~28写入,也可以通过寄存器96~108写入或读出。
图3.2 SJA1000发送缓冲区结构
SJA1000的发送缓冲区可以存放1个完整的标准帧或扩展帧报文,对于不同格式的帧报文,在发送缓冲区的定义不同,如图3.2所示,在发送不同格式的帧报文时,需要按对应的格式填充发送缓冲区。
SJA1000发送缓冲区由帧信息、帧ID、帧数据三部分组成。
2.帧信息
FF位决定了帧格式。当该位为0时,表示发送缓冲区中存放的是标准帧,SJA1000按图3.2的(a)的格式解释发送缓冲区内容;当该位为1时,表示发送缓冲区中存放的是扩展帧,SJA1000按图3.2的(b)的格式解释发送缓冲区内容。
RTR位决定了是远程帧还是数据帧。当该位为0时,表示发送缓冲区中存放的是数据帧,帧数据的有效字节数由DLC决定;当该位为1时,表示发送缓冲区中存放的是远程帧,帧数据部分不会被发送。
DLC指示了帧数据的有效数据字节数。DLC作为CAN帧的一部分,其有效值0~15都可以被传输,当DLC为0~8时,指示了帧数据的有效字节数,大于8时,帧数据的有效字节数固定为8。
3.帧ID
根据帧信息中的FF位的不同,帧ID具有不同的长度。FF为0时,帧ID占用2字节;FF 为1时,帧ID占用4字节。
4.帧数据
帧数据部分最多可存放8字节数据,有效长度由帧信息中的RTR和DLC决定。
1.1.2 发送模式
SJA1000的发送模式通过CMR寄存器(寄存器地址1)设置,CMR寄存器定义详见表3.1。
表3.1 CMR寄存器定义
1.正常发送
正常发送时,在仲裁丢失或发送错误时,SJA1000会自动重发,直到发送成功或总线关闭。正常发送通过置位TR位完成。
2.单次发送
在一些应用中,自动重发没有意义,如传输语音信息,它允许部分数据丢失,但不能容忍传输延时。在这种应用中,一般会以固定的时间间隔发送数据,自动重发会导致后面的数据无法发送,出现传输延时。单次发送通过同时置位TR和AT完成,如果仲裁丢失或发生错误,SJA1000不会重发报文。
3.自发自收
自发自收产生一次带自接收特性的单次发送,在发送出错或仲裁丢失的情况下不会执行重复发送。在发送完成后,可以从接收缓冲区中读到已发送的报文。
4.终止发送
通过设置位AT可以终止正在发送的报文数据。
5.设置发送模式
在发送CAN报文的时候,主控制器首先需要把CAN报文写入到CAN控制器的报文发送缓冲区,写入这个缓冲区的报文并不会直接发送出去,而是需要一个触发信号去启动CAN控制器完成发送工作,这时就需要调用程序清单3.1中所示的SetSJASendCmd函数(设置需要的发送模式)来启动CAN控制器发送功能。
程序清单3.1 SJA1000设置发送模式
1 /******************************************************************************************
2 ** 函数名称: char SetSJASendCmd
3 ** 函数功能: 设置SJA1000发送类型,启动发送
4 ** 输入参数: cmd-> 发送命令,0-正常发送;1-单次发送;2-自发自收;0xff-终止发送
5 ** 输出参数: 无
6 ** 返回参数: 1 ->
发送成功;0
->
发送失败
7 ******************************************************************************************/ 8
char SetSJASendCmd(unsigned char cmd) 9 { 10 unsigned char ret; 11 switch(cmd) { 12 default: 13 case 0:
// 正常发送
14 ret = SetBitMask(REG_CAN_CMR,TR_BIT); 15 break;
16 case 1:
// 单次发送
17 ret = SetBitMask(REG_CAN_CMR,TR_BIT|AT_BIT); 18 break;
19 case 2:
// 自发自收
20 ret = SetBitMask(REG_CAN_CMR,TR_BIT|SRR_BIT); 21 break;
22 case 0xff:
// 终止发送
23 ret = SetBitMask(REG_CAN_CMR, A T_BIT); 24
break;
25 }
26 return ret; // 返回值
27
}
1.1.3 SJA1000发送函数
SJA1000的发送函数具体实现如程序清单3.2所示。
按照图3.3的流程,首先读取状态寄存器并判断发送缓冲区是否锁定(不可写)或正在发送(程序清单3.2第14行)。如果SJA1000处于空闲状态并且发送缓冲区可写,则根据发送数据的帧信息计算需要写到SJA1000发送缓冲区的字节数(程序清单3.2第17~38行),并写到发送缓冲区中,最后调用SetSJASendCmd 函数启动报文发送流程(程序清单3.2第42行)。
程序清单3.2 SJA1000发送函数
28 /****************************************************************************************** 29 ** 函数原型: char SJASendData(unsigned char *databuf, unsigned char cmd) 30
** 参数说明: databuf
->
发送CAN 帧缓冲区
图3.3 SJA1000发送流程
31** : cmd -> 发送命令,0-正常发送;1-单次发送;2-自发自收
32** 返回值 : 1 -> 发送成功;0 -> 发送失败
33** 说明: 将用户定义的数据写入SJA1000发送缓冲区,再将其发送到总线上。
34*****************************************************************************************/ 35char SJASendData(unsigned char *databuf, unsigned char cmd)
36{
37char status = 1;
38unsigned char len;
39unsigned char dlc;
40// 判断SJA1000发送缓冲区是否锁定,或正在发送
41if ((ReadSJAReg(REG_CAN_SR) & (TBS_BIT|TCS_BIT)) != (TBS_BIT|TCS_BIT)){
42status = 0; // 返回发送失败
43} else {
44dlc = (*databuf & 0x0f); // 从发送缓冲区的帧信息中取得CAN数据长度45if(dlc > 8) {
46dlc = 8;
47}
48switch(*databuf & 0xC0) { // 根据帧类型确定写到发送缓冲区中的数据长度49case 0x00: // 标准帧、数据帧
50len = STD_FRAMEID_LENTH + dlc + 1;
51break;
52case 0x40: // 标准帧、远程帧
53len=STD_FRAMEID_LENTH+1;
54break;
55case 0x80: // 扩展帧、数据帧
56len = EXT_FRAMEID_LENTH + dlc + 1;
57break;
58case 0xC0: // 扩展帧、远程帧
59len=EXT_FRAMEID_LENTH+1;
60break;
61default:
62len = 0;
63status = 0;
64break;
65}
66if (len > 0) {
67// 填充发送缓冲区
68WriteSJARegBlock(REG_CAN_TXFMINFO, databuf, len);
69SetSJASendCmd(cmd); // 设置发送命令
70status = 1;
71}
72}
73return(status);
74}
1.1.4 测试例程
SJA1000发送示例详见程序清单3.3。该例程设置SJA1000的CAN波特率为1Mbps,并以1秒的间隔发送CAN报文,CAN报文的格式(见程序清单3.3的第73~77行)为标准帧,数据长度为8字节,帧ID使用0x753,帧数据是0x55 0x55 0x55 0x55 0xaa 0xaa 0xaa 0xaa。
程序清单3.3 SJA100发送例程
75/****************************************************************************************** 76文件:main.c
77功能:设置SJA1000的波特率为1M,以1秒的间隔发送CAN帧
78******************************************************************************************/ 79#include "config.h" // 包含头文件
80// 定义帧ID长度
81#define STD_FRAMEID_LENTH 2 // 标准帧ID长度
82#define EXT_FRAMEID_LENTH 4 // 扩展帧ID长度
83#define D1 P1_0 // 定义LED
84#define SJA1000_RST P1_2 // 定义SJA1000复位引脚
85// 定义SJA1000访问基地址
86#define SJA_BASE_ADDR 0xA000
87xdata unsigned char *SJA_CS_Point = ( xdata unsigned char *) SJA_BASE_ADDR;
88
89void WriteSJAReg(unsigned char RegAdr, unsigned char Value) // 写SJA1000寄存器
90{
91...... // 程序代码请参考程序清单3.3
92}
93
94unsigned char ReadSJAReg(unsigned char RegAdr) // 读SJA1000寄存器
95{
96...... // 程序代码请参考程序清单3.3
97}
98
99char SetBitMask(unsigned char RegAdr, unsigned char BitValue) // 设置寄存器位
100{
101...... // 程序代码请参考程序清单3.4
102}
103char ClearBitMask(unsigned char RegAdr, unsigned char BitValue) // 清零寄存器位
104{
105...... // 程序代码请参考程序清单3.5
106}
107// 连续写多个寄存器
108char WriteSJARegBlock(unsigned char RegAddr,unsigned char *ValueBuf, unsigned char len)
109{
110...... // 程序代码请参考程序清单3.6
111}
112// 连续读多个寄存器
113char ReadSJARegBlock(unsigned char RegAddr, unsigned char *ValueBuf, unsigned char len)
114{
115...... // 程序代码请参考程序清单3.7
116}
117
118void SJA1000_Init(unsigned char btr0, unsigned char btr1, unsigned char *filter) // SJA1000初始化119{
120...... // 程序代码请参考程序清单3.13
121}
122
123char SetSJASendCmd(unsigned char cmd) // 设置发送命令
124{
125...... // 程序代码请参考程序清单3.14
126}
127
128char SJASendData(unsigned char *databuf, unsigned char cmd) // 发送CAN报文
129{
130...... // 程序代码请参考程序清单3.13
131}
132
133void timerInit(void) // Timer初始化
134{
135...... // 程序代码请参考程序清单3.9
136}
137void timerDelay(unsigned int n) // 延时(0.01 * n)秒
138{
139...... // 程序代码请参考程序清单3.9
140}
141// 定义验收滤波器的参数,接收所有帧
142unsigned char SJA_CAN_Filter[8] = {
1430x00, 0x00, 0x00, 0x00, // ACR0~ACR3
1440xff, 0xff, 0xff, 0xff // AMR0~AMR3
145};
146// CAN发送报文缓冲区
147unsigned char STD_SEND_BUFFER[11] = {
1480x08, // 帧信息,标准数据帧,数据长度=8 1490xEA, 0x60, // 帧ID=0x753
1500x55,0x55,0x55,0x55,0xaa,0xaa,0xaa,0xaa // 帧数据
151};
152
153void main(void) // 主函数,程序入口
154{
155timerInit(); // 初始化
156// D1 = 0;
157SJA1000_RST = 1; // 硬件复位SJA1000
158timerDelay(50); // 延时500ms
159SJA1000_RST = 0;
160SJA1000_Init(0x00,0x14,SJA_CAN_Filter); // 初始化SJA1000,设置波特率为1Mbps 161// 无限循环,main()函数不允许返回
162for (;;) {
163SJASendData(STD_SEND_BUFFER,0x0);
164timerDelay(100); // 延时1000ms
165}
166}
运行上面的程序后,使用CAN协议分析器CANScope可以获得如图3.4所示的CAN总线波形。波形图中各部分的含义如下:
①起始位:由单独的一个显性位表示;
②仲裁场波形:包括11位帧ID(这里帧ID为0x753)和一个RTR位(显性代表数据帧,隐性代表远程帧),帧ID表明了该帧的含义,RTR表明该帧的类型;
③控制场波形:图中代表数据长度为8字节;
④数据场波形:其对应的数据为0x55 0x55 0x55 0x55 0xaa 0xaa 0xaa 0xaa;
⑤CRC校验数据波形:CRC校验数据长度为15位,末尾跟随的隐性位是CRC界定符;
⑥应答位:由应答间隙和应答界定符组成,接收到正确CAN帧的节点会在应答间隙期间通过发送显性位的方式来应答;
⑦结束标志:由7个隐性位组成。
图3.4 1Mbps波特率的CAN总线波形图
图3.4是通过CAN总线协议分析器在一条并接了终端电阻,传输波特率高达1Mbps的CAN 总线上抓获的,虽然此时的信号波形不是理想的方波(有一点过冲,这种情况的发生与CAN
收发器自身电器特性和终端电阻都有关系),但是整个传输波形还是比较稳定和干净的,干扰
信号成分很少。如果不接终端电阻,并且传输波特率较高时,信号过冲现象将会非常严重,因为阻抗在线缆末端的突变会引起传输信号的大量反射,并叠加在正常传输的信号上,导致CAN 总线上信号传输质量大幅度降低,传输数据容易出错。
图3.5 250kbps波特率的CAN总线波形图
在测试过程中我们也截取了CAN总线上波特率为250kbps且不接终端电阻时的波形,详见图3.5。从图中可以看见总线上波形确实严重失真,信号过冲也非常明显,而且跳变沿时间较长,所以无法进行更高速率的数据传输(短距离传输测试时数据收发能达到的最高波特率仅为
250Kbps)。通过对图3.4和图3.5的比较和分析也很容易看出终端电阻在CAN总线通信中的重要性。