CAN编写完分帧发送, 分帧接收,J1939位域型结构体心得
关于由多个不同的C文件构成的工程,我采用以下方法
以为400Hz数字电源程序为例
假设工程由以下文件组成
DC_Comm.c 主要完成串口通讯部分
DC_Config.c 主要完成时钟,外设 中断初始化
DC_Control.c 主要完成电源数字化SPWM控制,以及串口接收中断的处理
DC_Memory.c 主要完成FM33256 的SPI时序的软件实现。故障记录与操作记录的写入与读取操作。
DC_Timing.h 主要完成与CPLD配合的一些时序。
响应的在include 中我还用到了一些头文件
DC_Comm.h 主要用来对DC_Comm.c中用到的数据类型进行声明,以及函数进行声明。这些函数都在DC_Comm.c中定义
DC_Control.h 主要用来对DC_Control.c中用到的数据类型进行声明,以及函数进行声明。这些函数在DC_Control.c 中定义
DC_Types.h 中宏定义了 一些Q格式常量 ,以及一些函数的声明。
总之:假设在DC_Comm.c中 定义了函数SCIRXProcess ()
则在DC_Comm.h中声明了 extern void SCIRXProcess ()
那么我在main.c 文件中调用 SCIRXProcess()的时候, 直接在main.c的前方将DC_Comm.h 包含进来就ok .
总结 就是 一个工程假设有A,B,C,main.c 4个文件组成, 假设在main.c 中定义了一些变量p,q,m 若A文件要使用p , 则需要在A文件的开头 用extern 关键字进行声明。
抛砖引玉:开始进入基于ican协议的CAN开发,该平台单片机采用STC89C52
该工程由两个文件组成SJA.C 和ican.c
SJA1000.h 中 定义了寄存器的硬件地址
基本地址 #define SJA_BaseAdr 0X7F00 由外部电路的硬件地址决定 单片机的那一个引脚连接在SJA1000的CS引脚上
内部控制寄存器 #define REG_CONTROL SJA_BaseAdr+0x00
命令寄存器 #define REG_COMMAND SJA_BaseAdr+0x01
状态此存器 #define REG_STATUS SJA_BaseAdr+0x02
…….
发送缓冲区寄存器
#define REG_TXBuffer1 SJA_BaseAdr+0x10 //发送缓冲区1
#define REG_TXBuffer2 SJA_BaseAdr+0x11 //
#define REG_TXBuffer3 SJA_BaseAdr+0x12 //
#define REG_TXBuffer4 SJA_BaseAdr+0x13 //
#define REG_TXBuffer5 SJA_BaseAdr+0x14 //
#define REG_TXBuffer6 SJA_BaseAdr+0x15 //
#define REG_TXBuffer7 SJA_BaseAdr+0x16 //
#define REG_TXBuffer8 SJA_BaseAdr+0x17 //
#define REG_TXBuffer9 SJA_BaseAdr+0x18 //
#define REG_TXBuffer10 SJA_BaseAdr+0x19 //
#define REG_TXBuffer11 SJA_BaseAdr+0x1A //
#define REG_TXBuffer12 SJA_BaseAdr+0x1B //
#define REG_TXBuffer13 SJA_BaseAdr+0x1C //发送缓冲区13
接收缓冲区寄存器
#define REG_RXBuffer1 SJA_BaseAdr+0x10 //接收缓冲区1
#define REG_RXBuffer2 SJA_BaseAdr+0x11 //
#define REG_RXBuffer3 SJA_BaseAdr+0x12 //
#define REG_RXBuffer4 SJA_BaseAdr+0x13 //
#define REG_RXBuffer5 SJA_BaseAdr+0x14 //
#define REG_RXBuffer6 SJA_BaseAdr+0x15 //
#define REG_RXBuffer7 SJA_BaseAdr+0x16 //
#define REG_RXBuffer8 SJA_BaseAdr+0x17 //
#define REG_RXBuffer9 SJA_BaseAdr+0x18 //
#define REG_RXBuffer10 SJA_BaseAdr+0x19 //
#define REG_RXBuffer11 SJA_BaseAdr+0x1A //
#define REG_RXBuffer12 SJA_BaseAdr+0x1B //
#define REG_RXBuffer13 SJA_BaseAdr+0x1C //接收缓冲区13
SJA1000.h中声明了若干函数 包括:
CAN总线发送数据的流程:
发送数据还有一种写法:
if ((ReadSJAReg(REG_CAN_SR) & (TBS_BIT|TCS_BIT)) != (TBS_BIT|TCS_BIT))
{ status = 0;}
查看SJA1000资料 有以下要点:
-
SJA1000 的peilican模式的发送是单次发送
(2)与发送有关的状态寄存器的各位定义
位 |
符号 |
名称 |
值 |
功能 |
SR.5 |
Ts |
发送状态 注3 |
1 |
发送 sja1000 在传送信息 |
0 |
空闲 没有要发送的信息 |
|||
SR.3 |
Tcs |
发送完毕状态 注4 |
1 |
完毕 最近一次发送请求被成功处理 |
0 |
未完毕 当前发送请求未处理完毕 |
|||
SR.2 |
Tbs |
发送缓冲区状态 注5 |
1 |
释放:CPU可以向发送缓存器写数据 |
0 |
锁定:CPU不能访问发送缓冲器,有信息正在等待 发送或者正在发送 |
注3:如果接收状态位和发送状态位 都是0 ,则CAN总线是空闲的。
注4:无论何时发送请求位被置为1,发送完毕位(Tcs)都会被置为0,发送完毕位会一直保持到消息被成功发送。
注5:如果CPU在发送缓冲器状态为是0时(锁定)试图写发送缓冲器,则写入的字节被拒绝接收且会在无任何提示的情况下丢失。
与485通讯比较,485发送出去的数据 若接收方没有安装,主机依然显示发送成功,相比较CAN,CAN发送数据给另一个节点,则CAN节点在应答场会给主机CAN节点一个信号,表示主节点的CAN发送成功。
关于ican.c 中的应用
首先 我用结构体定义 iCANMSG 数据类型
在SJA.C 中定义了 icanmsg 数据类型的变量
iCANMSG message1 ;
iCANMSG * pcan ;
iCANMSG msg_readonly_s;
此外:对于ican协议我专门定义了指针 pcan 并用宏定义去进行处理,这样很方便的与29位ID号所对应的标识符号对应上。
然后在ican.c中因为 用到了这些变量 全部在前面加上 extern
在main.c 里 我用
至于为什么要在main () 文件 的开头 定义 msg_readonly_s
iCANMSG msg_readonly_s; //保存副本
是因为 如果出现如下情况 相当于是一个临时变量,
关于使用位阈型结构体的总结:
ICAN协议:
遵循原则 第 条我认为不一定对,因为在ican下 我infoID定义8位
但是ican下 我的低3位 是没有被定义的 undef
Ican 协议的格式定义如下:
帧结构信息
位 |
BIT7 |
BIT6 |
BIT5 |
BIT4 |
BIT3 |
BIT2 |
BIT1 |
BIT0 |
说明 |
FF |
RTR |
X |
X |
DLC.3 |
DLC.2 |
DLC.1 |
DLC.0 |
帧标识符信息
帧 标 识 符 |
ID28 |
ID27 |
ID26 |
ID25 |
ID24 |
ID23 |
ID22 |
ID21 |
|
00 |
SRCMACID(资源节点编号) |
||||||||
ID20 |
ID19 |
ID18 |
ID17 |
ID16 |
ID15 |
ID14 |
ID13 |
||
00 |
DestMACID(目标节点编号) |
||||||||
ID12 |
ID11 |
ID10 |
ID9 |
ID8 |
ID7 |
ID6 |
ID5 |
||
ACK |
FUNCID(功能码) |
SourceID(资源节点编号) |
|||||||
ID4 |
ID3 |
ID2 |
ID1 |
ID0 |
X |
X |
X |
||
SourceID(资源节点编号) |
未使用(忽略) |
我定义的方法如下:
但是 我在应用j1939协议的时候
J1939协议 所定义的帧信息ID结构如下 (29位扩展)
ID28 |
ID27 |
ID26 |
ID25 |
ID24 |
ID23 |
ID22 |
ID21 |
优先级 |
保留位 |
数据页 |
PDU格式 |
||||
ID20 |
ID19 |
ID18 |
ID17 |
ID16 |
ID15 |
ID14 |
ID13 |
PDU格式 |
特定PDU |
||||||
ID12 |
ID11 |
ID10 |
ID9 |
ID8 |
ID7 |
ID6 |
ID5 |
特定PDU |
源地址 |
||||||
ID4 |
ID3 |
ID2 |
ID1 |
ID0 |
X |
X |
X |
源地址 |
我的定义方法如下: 从低位到高位定义
假设 节点2 和节点3正在通讯,某一时刻节点1要设置节点3 。给节点3发数据,
节点1不用等到节点2和节点3不通讯了。节点1直接发送数据,节点1 的CAN硬件会自动控制给节点1发数据。不用人为控制,由CAN控制器的硬件来完成。
基于51单片机的CAN通讯试验:
方法: 51单片机程序中不断的往上位机(CANtest)发送数据 ,然后在某一任意时刻, 我用周立功的(CANtest)发送建立连接命令,看单片机是否可以正常响应。 并且记录示波器的波形图。
在CANTEST 上 点击 发送消息帧 如下图 第2个较短的时间间隔内的帧
该消息的ID号是 0x0023e4fe 数据场是 00 ee 0a
51单片机接收到消息以后,往上传送消息 该消息的数据场 00 01 02 03 04
如下图所示: 左侧第一个较短的帧 就是 51单片机上传的响应帧。数据场为00 01 02 03 04
响应场 的数据 (该数据我先不解析)
为了验证我用kavaser 捕捉以下时间间隔 。
看两个时间间隔
第一 就是上位机 发送建立连接命令 到收到51单片机 返回的响应帧的时间间隔
第二 就是51 单片机 返回响应帧 到 51单片机继续往上位机传送计数值的时间间隔
第三 测试 看一下 默认情况下 上位机不发送连接命令,51单片机上传数据的时间间隔
第一个时间 我用示波器测试是: 约为200ms
第二个时间 我用示波器测试是: 约为1.5ms
第三个时间 我用示波器测试是: 约为12.4ms
我用kavaser 在 20190423 的 9点32 和 9点33 分左右的时候分别用cantest 发送建立连接命令 接收的时间间隔是 6041-4021=2021
2021*百分之一毫秒 约等于 200ms 与示波器测试一致
接下来 我用kavaser 的logging 功能测试
时刻 9点32 的数据
9点33时刻的数据
接下来 我的想法是 你新找一个51单片机 ,然后替换 周立功上位机的功能,进行连接命令的发送
试验平台大家如下:
试验平台照片
实际上 CAN 网络是不分主机和从机的,不像485网络。这里我设计的主机的功能就是:
按下:靠4个数码管一侧的按键, 按一下 数码管的显示增加1 然后并发送一帧
发送的消息帧 为 ID号 0x0023e4fe 数据场是00 ee 20 (16进制的20代表十进制32
计数 32次,认为握手时间是32秒,超过32秒可以认为连接断开)
做这个事情的目的是:消息帧的发送我在用嵌入式编程的时候,用can_send_anylength()函数就可以搞定。这种情况使用于网络中一直有数据通讯存在的情况。
试验现象:
在时间10点24
我用主机(51单片机)的按键 发送消息帧 ID号0x0023e4fe 数据 00 ee 2 0
从机51单片机 在1秒以后 反馈给我响应帧 ID号0X3E034EE 数据是 00 01 02 03 04
在这个1秒的时间间隔内,CAN数据线上 还有一帧消息在传递 如下图所示:
若在32秒内,主机再次发送连接命令,。从机将给主机反馈 已经在连接中的提示消息
该消息 的ID号是 0X3E02FFE 00 03
在时间:10:30:10:7323 我又用主机发送了 建立连接的消息
ID号是 0x023e4fe 00 ee 20
此时在10:30:10:8433 时刻 从机就给主机回复了消息帧 在这个时间间隔内,无其他帧在传递。 如下图:
重要:与上面的那个中间有一帧的情况的截图进行对比:可以知道:从机在接收到主机的连接,命令后,会判断CAN线上是否空闲,如果当前有数据发送或接收 从机就等该数据发送完毕以后,在发送响应帧, 如果CAN线上空闲,则从机便可以直接发送给主机器响应帧。发送程序的时候 从机程序仅仅检测 是不是上一帧数据是不是发送完成,并不检测总线上空闲,这一块是CAN控制器硬件自动完成的,我暂且先这么认为。
在时间 10时30分 13秒 在32秒的计时时间内, 我再次发送建立连接命令,此时 从机
便会给我回复响应的消息帧 帧ID号 0x 3e02ffe 00 03 如下图所示:
Word 源文件在百度网盘