之前已经讲过如何生成DBC文件了,程序中该如何解析DBC呢?
其中包括接收CAN报文解析和发送CAN报文组包??
一、Motorola和Intel格式
dbc里的信号Signals,其中里面有两种数据格式 Motorola和Intel格式。
之前C语言里,讲过无数遍的大小端,排上用场了。
参看:C语言再学习-- 大端小端详解(转)
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
MSB和LSB:
MSB: MoST Significant Bit ------- 最高有效位
LSB: Least Significant Bit ------- 最低有效位
先看一下,Motorola和Intel格式的区别。
Intel 格式 layout:
Motorola 格式 layout:
关于CAN报文,用Motorola,还是Intel格式,只在信号数据跨字节解析时,才有区别。单个字节数据没有区别。
当信号在一个字节内实现(信号不跨字节)时,Intel模式和Motorola模式的信号字节顺序,完全一样:
信号的高位(MSB)放在该字节的高位,信号的低位(LSB)放在该字节的低位。
如果是信号数据跨字节解析,才有区别。
Motorola格式即大端,信号的高位(MSB)放在低字节的高位,信号的低位(LSB)放在高字节的低位。反映到矩阵图中就是以起始位为原点,自下而上填充。Motorola格式,MSB在LSB上面。
Intel格式即小端,信号的高位(MSB)放在高字节的高位,信号的低位(LSB)放在低字节的低位;,反映到矩阵图中就是以起始位为原点,自上而下填充。Intel格式,MSB在LSB下面。
总结就一句话:
Intel格式(小端模式 ): “高位在后,低位在前”;
Motorola格式(大端模式): “高位在前,低位在后”。
解析报文和接收报文都是分为两种方式的,一种是使用位操作、一种是使用位域。
二、CAN接收报文解析
1、Intel格式 CAN接收报文解析
VehLength = ((((uint32_t)paraData[1] & 0x07) << 8)
| ((uint32_t)paraData[0]));
VehWidth = ((((uint32_t)paraData[1] & 0xF8) >> 3)
| (((uint32_t)paraData[2] & 0x0F) << 5));
VehWheelBase = ((((uint32_t)paraData[2] & 0xF0) >> 4)
| (((uint32_t)paraData[3] & 0x7F) << 4));
VehRearOverhang = ((((uint32_t)paraData[3] & 0x80) >> 7)
| (((uint32_t)paraData[4]) << 1)
| (((uint32_t)paraData[5] & 0x01) << 9));
VehLeftToSRR = ((((uint32_t)paraData[5] & 0xFE) >> 1)
| (((uint32_t)paraData[6] & 0x0F) << 7));
VehInstallAngle = ((((uint32_t)paraData[6] & 0xF0) >> 4)
| (((uint32_t)paraData[7] & 0x1F) << 4));
2、Motorola格式 CAN接收报文解析
tmp_VehicleSpdUint = ((((uint16_t)paraData[1])&0x01) << 14) + ((((uint16_t)paraData[2])&0xFF) << 6) + (((uint16_t)paraData[3]) >> 2);
3、最后,乘以factor加上offset
tmpYawRateF = (((float)tmpYawRateU) * 0.01f) - 81.91f;
三、CAN发送报文组包
1、首先,减去offset除以factor
tmpYawRateF = (((uint16_t)tmpYawRateU) +81.91) /0.01;
2、Intel格式 CAN发送报文组包
messageBuffer[0] |= (uint8_t)(CIPVVehPara.VehLength & 0x00FF);
messageBuffer[1] |= (uint8_t)((CIPVVehPara.VehLength & 0x0700) >> 8);
messageBuffer[1] |= (uint8_t)((CIPVVehPara.VehWidth & 0x001F) << 3);
messageBuffer[2] |= (uint8_t)((CIPVVehPara.VehWidth & 0x01E0) >> 5);
messageBuffer[2] |= (uint8_t)((CIPVVehPara.VehWheelBase & 0x000F) << 4);
messageBuffer[3] |= (uint8_t)((CIPVVehPara.VehWheelBase & 0x07F0) >> 4);
messageBuffer[3] |= (uint8_t)((CIPVVehPara.VehRearOverhang & 0x0001) << 7);
messageBuffer[4] |= (uint8_t)((CIPVVehPara.VehRearOverhang & 0x01FE) >> 1);
messageBuffer[5] |= (uint8_t)((CIPVVehPara.VehRearOverhang & 0x0200) >> 9);
messageBuffer[5] |= (uint8_t)((CIPVVehPara.VehLeftToSRR & 0x007F) << 1);
messageBuffer[6] |= (uint8_t)((CIPVVehPara.VehLeftToSRR & 0x0780) >> 7);
messageBuffer[6] |= (uint8_t)(((CIPVVehPara.VehInstallAngle+VEH_INSTALL_ANGLE_OFFSET) & 0x000F) << 4);
messageBuffer[7] |= (uint8_t)(((CIPVVehPara.VehInstallAngle+VEH_INSTALL_ANGLE_OFFSET) & 0x01F0) >> 4);
3、Motorola格式 CAN发送报文组包
messageBuffer[0] |= (uint8_t)((POS_X & 0x7F80) >> 7);
messageBuffer[1] |= (uint8_t)((POS_X & 0x007F) << 1);
messageBuffer[1] |= (uint8_t)((POS_Y & 0x4000) >> 14);
messageBuffer[2] |= (uint8_t)((POS_Y & 0x3FC0) >> 6);
messageBuffer[3] |= (uint8_t)((POS_Y & 0x003F) << 2);
messageBuffer[3] |= (uint8_t)((SPD_X & 0x0C00) >> 10);
messageBuffer[4] |= (uint8_t)((SPD_X & 0x03FC) >> 2);
messageBuffer[5] |= (uint8_t)((SPD_X & 0x0003) << 6);
messageBuffer[5] |= (uint8_t)((SPD_Y & 0x3F00) >> 8);
messageBuffer[6] |= (uint8_t)(SPD_Y & 0x00FF);
messageBuffer[7] |= (uint8_t)((TargetMovingFlag & 0x01) << 7);
messageBuffer[7] |= (uint8_t)(gFrameOrder & 0x7F);
四、位域
上面那种位操作的方法是可以的,不过太麻烦了点。
再简单一点的方法是使用位域。
1、CAN报文发送组包
位域:
typedef struct
{
uint64_t objNum :8;
uint64_t roadNum :4;
uint64_t res1 :4;
uint64_t objReportCnt :16;
uint64_t objReportItf :4;
uint64_t res2 :16;
uint64_t FMRHdInfoCounter :4;
uint64_t FMRHdInfoChecksum :8;
}FMR_Obj_Hd_Info;
CAN报文发送组包:
uint8_t SendData[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
FMR_Obj_Hd_Info *pFMR_Obj_Hd_Info = (FMR_Obj_Hd_Info *)SendData;
pFMR_Obj_Hd_Info->objNum = 0;
pFMR_Obj_Hd_Info->roadNum = 0;
pFMR_Obj_Hd_Info->objReportCnt = 0;
pFMR_Obj_Hd_Info->objReportItf = 0;
pFMR_Obj_Hd_Info->FMRHdInfoCounter = 0;
pFMR_Obj_Hd_Info->FMRHdInfoChecksum = 0;
//CAN 发送SendData
2、CAN报文接收解析
位域:
typedef struct
{
uint64_t CType :2;
uint64_t Type :4;
uint64_t Activity :1;
uint64_t Validity :1;
uint64_t RESERVED1 :16;
uint64_t Distance :16;
uint64_t RESERVED2 :24;
}Command_t;
CAN报文接收解析:
void MsgParse(uint32_t FrameID, uint8_t *pData, uint8_t len)
{
Command_t *sCommand = (Command_t *)(pData);
//使用解析的CAN报文
if(sCommand->Type != 0x00)
{
return;
}
}
不过使用位域需要注意的是,处理器的大小端要和CAN报文的一致。否则还是用位操作合适。
例如:
下面这个CPU Endianness(字节顺序)为 Little(小端)。那么就不能使用位域接收解析和发送组包 Motorola格式(大端模式)的报文。
五、CAN报文打包解包公用函数
其实更简单的方法,是使用CAN报文打包解包函数。
不过,因CAN报文存在motorola和Intel不同字序,整型、浮点型,有无符号等不同设计,打包解包的代码非常容易出错,而且很难检查验证。
1、代码
public.h
#ifndef __PUBLIC__H_
#define __PUBLIC__H_
#if 1
typedef signed char sint8;
typedef unsigned char uint8;
typedef signed short sint16;
typedef unsigned short uint16;
//typedef signed long sint32; //32位系统和64位系统,long字节不同,32位系统=4字书,
//64位系统=8字节
//typedef unsigned long uint32; //不同平台,int字节不同
typedef signed int sint32; //当前因simulink生成的代码,int是4字节,故保持一致
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef signed long long sint64;
typedef float float32;
typedef double float64;
#endif
//获得U32的n位的值
#define GetBitNValue(dword, n, offset) (((uint32)(dword) >> (offset)) & \
((1UL << (n)) - 1))
/*
************************************************************************
* Description : 获得64位数值中的N位的值
* Parameters : ldword,64位数
n,想获取的位数
byOffset,偏移值
* Returns : 想要获得的结果
* Notes : n和byOffset均不能超过63
************************************************************************
*/
#define GetNBitValueOfU64(ldword, n, byOffset) (((uint64)(ldword) >> (byOffset)) & \
(((uint64)1 << (n)) - 1))
#define TestBit(b,offset) (1U & ((b)>>(offset))) // 检查状态字中的某一位是否置位
#define SetBit(b,offset) ((b) |= (1U<<(offset))) // 置位状态字中的某一位
#define ResetBit(b,offset) ((b) &= (~(1U<<(offset)))) // 复位状态字中的某一位
#define ArrCountOf(a) (sizeof(a) / sizeof(a[0]))
extern void SetBitOfU32Value(uint32 *pdwData, uint8 n, uint8 byOffset, uint32 dwValue);
extern void SetBitOfU8Value(uint8 *pbyData, uint8 n, uint8 byOffset, uint8 byValue);
extern void SetNBitValueOfU64(uint64 *pldwData, uint8 n, uint8 byOffset, uint64 ldwValue);
#endif
public.c
#include "public.h"
/*
************************************************************************
* Description : 设置32位值中的N位的值
* Parameters : pdwData,需要改的值指针
n,修改的位数
byOffset,偏移
dwValue,设置值
* Returns : none
* Notes :
************************************************************************
*/
extern void SetBitOfU32Value(uint32 *pdwData, uint8 n, uint8 byOffset, uint32 dwValue)
{
uint32 tmp = *pdwData;
uint32 mask;
mask = (((uint32)1u << n) - 1) << byOffset;
tmp &= ~mask; //先清0
tmp |= (dwValue << byOffset) & mask; //值也需要保证没有更多的位
*pdwData = tmp;
}
/*
************************************************************************
* Description : 设置8位值中的N位的值
* Parameters : pbyData,需要改的值指针
n,修改的位数
byOffset,偏移
byValue,设置值
* Returns : none
* Notes :
************************************************************************
*/
extern void SetBitOfU8Value(uint8 *pbyData, uint8 n, uint8 byOffset, uint8 byValue)
{
uint8 tmp = *pbyData;
uint8 mask;
mask = (((uint8)1u << n) - 1) << byOffset;
tmp &= ~mask; //先清0
tmp |= (byValue << byOffset) & mask; //值也需要保证没有更多的位
*pbyData = tmp;
}
/*
************************************************************************
* Description : 设置64位值中的N位的值
* Parameters : pldwData,需要设置的值的指针
n,修改的位数
byOffset,偏移值
ldwValue,设置值
* Returns : none
* Notes : n和byOffset均不能超过63
************************************************************************
*/
extern void SetNBitValueOfU64(uint64 *pldwData, uint8 n, uint8 byOffset, uint64 ldwValue)
{
uint64 tmp;
uint64 mask;
if (pldwData == NULL)
{
return;
}
mask = (((uint64)1 << n) - 1) << byOffset;
tmp = *pldwData;
tmp &= ~mask; //先清0
tmp |= (ldwValue << byOffset) & mask; //值也需要保证没有更多的位;
*pldwData = tmp;
}
CanProc.h
#ifndef _CAN_PROC_H_
#define _CAN_PROC_H_
//#include "public.h" //某些情况下需要
/*
************************************************************************
* Description : CAN打包解包的字节序和数据类型整合
* Parameters : (都是DBC相关的信息)
littleEndian,大小端 0-大端motorola 1-小端intel
isSigned,有无符号 unsigned - 0 signed- 1 float - 1 double - 1
ValueType,数据类型 Integer(unsigned/signed) - 0, float - 1, double - 2
* Returns : uint32, 集合
* Notes :
************************************************************************
*/
#define GetCanSigMsg(littleEndian, isSigned, ValueType) ((((littleEndian)&0xFFUL) << 16) | \
(((isSigned)&0xFFUL) << 8) | \
((ValueType)&0xFFUL))
extern void CanPack(float32 value, uint8 *data, uint8 startBit, uint8 BitLength,
uint32 ValueMsg, float32 factor, float32 offset);
extern float32 CanUnpack(const uint8 *data, uint8 startBit, uint8 BitLength,
uint32 ValueMsg, float32 factor, float32 offset);
#endif
CanProc.c
#include "CanProc.h"
#include "public.h"
/*
************************************************************************
* Description : CAN报文打包 发送的前置
* Parameters : value, 值
data, CAN发送报文数据buf首地址
ValueMsg,字节序和数据类型等信息的一个集合 见GetCanSigMsg()
isSigned,有无符号 unsigned - 0 signed- 1 Single - 1 double - 1
ValueType,数据类型 Integer(unsigned/signed) - 0, Single - 1, double - 2
factor,分辨率
offset,偏移量
* Returns :
* Notes : 1)只支持8字节数据 有符号/无符号(整型)、浮点型;大小端
另外 嵌入式中不支持double,将其处理成float,若移植为
其他计算机用途,注意修改
2)因运行效率问题,取消使用sint64(BinaryValue、mask、signedMask)
改为sint32,若有需要使用double,则需要改回
//另外 负浮点型直接强制转化为unsigned,是未定义的行为
************************************************************************
*/
extern void CanPack(float32 value, uint8 *data, uint8 startBit, uint8 BitLength,
uint32 ValueMsg, float32 factor, float32 offset)
{
float32 tmp;
sint32 BinaryValue = 0;
sint8 BytePos; // 当前操作的data[BytePos]
sint8 BitOffset; //在data[BytePos]中的起始bit
sint8 ValuePos; //当前处理的在BinaryValue的bit位置
sint8 DealLen; //处理的数据位数
sint8 RemainLen; //剩余未处理的数据位数
uint8 byValue;
if (data == NULL)
{
return;
}
tmp = (value - offset) / factor;
// 获得数据的二进制码
switch(ValueMsg & 0xFF)
{
case 0: //interge
{
if (!((ValueMsg>>8) & 0xFF)) //无符号型
{
if (tmp < 0)
{
tmp = 0;
}
}
BinaryValue = (sint32)tmp;
}
break;
case 1: //float
{
if (BitLength != 32)
{
BinaryValue = 0;
}
else
{
BinaryValue = *(sint32*)&tmp;
}
}
break;
case 2: //double //
default:
{
/* //double的预留代码
if (BitLength != 64)
{
BinaryValue = 0;
}
else
{
float64 dtmp = tmp;
BinaryValue = *(sint64*)&dtmp;
}
*/
BinaryValue = 0;
}
break;
}
//buff填充 从signal的低字节处理,逐字节处理
BytePos = startBit >> 3; //除8
RemainLen = BitLength;
BitOffset = startBit - (BytePos<<3);
ValuePos = 0;
if ((ValueMsg>>16) & 0xFF) //Intel little endian
{
do
{
DealLen = (RemainLen < 8-BitOffset) ? RemainLen : (8-BitOffset);
byValue = (uint8)(BinaryValue >> ValuePos);
SetBitOfU8Value(data + BytePos, DealLen, BitOffset, byValue);
BytePos++;
ValuePos += DealLen;
RemainLen -= DealLen;
BitOffset = 0;
}while (RemainLen);
}
else //motorola / big endian mode
{
do
{
DealLen = (RemainLen < 8-BitOffset) ? RemainLen : (8-BitOffset);
byValue = (uint8)(BinaryValue >> ValuePos);
SetBitOfU8Value(data + BytePos, DealLen, BitOffset, byValue);
BytePos--;
ValuePos += DealLen;
RemainLen -= DealLen;
BitOffset = 0;
}while (RemainLen);
}
}
/*
************************************************************************
* Description : CAN报文解包
* Parameters : data, CAN报文数据buf首地址
startBit,起始位
BitLength,位长度
ValueMsg,字节序和数据类型等信息的一个集合 见GetCanSigMsg()
factor,分辨率
offset,偏移量
* Returns : 解析结果
* Notes : 1)只支持8字节数据 有符号/无符号(整型)、浮点型;大小端
另外 嵌入式中不支持double,将其处理成float,若移植为
其他计算机用途,注意修改
此函数直接根据dbc做处理
2)因运行效率问题,取消使用sint64(BinaryValue、mask、signedMask)
改为sint32,若有需要使用double,则需要改回
************************************************************************
*/
extern float32 CanUnpack(const uint8 *data, uint8 startBit, uint8 BitLength,
uint32 ValueMsg, float32 factor, float32 offset)
{
float32 rt = 0;
sint32 tmp;
sint32 BinaryValue = 0;
sint32 mask;
uint32 signedMask;
sint8 BytePos; // 当前操作的data[BytePos]
sint8 ValuePos; //当前处理的在BinaryValue的bit位置
sint8 BitOffset; //在data[BytePos]中的起始bit
sint8 DealLen; //处理的数据位数
sint8 RemainLen; //剩余未处理的数据位数
if (data == NULL)
{
return 0;
}
// 从signal的低字节处理,逐字节处理
BytePos = startBit >> 3; //除8
RemainLen = BitLength;
BitOffset = startBit - (BytePos<<3);
ValuePos = 0;
if ((ValueMsg>>16) & 0xFF) //Intel little endian
{
do
{
DealLen = (RemainLen < 8-BitOffset) ? RemainLen : (8-BitOffset);
tmp = GetBitNValue(data[BytePos], DealLen, BitOffset);
BinaryValue += (tmp << ValuePos);
BytePos++;
RemainLen -= DealLen;
BitOffset = 0;
ValuePos += DealLen;
}while (RemainLen);
}
else //motorola / big endian mode
{
do
{
DealLen = (RemainLen < 8-BitOffset) ? RemainLen : (8-BitOffset);
tmp = GetBitNValue(data[BytePos], DealLen, BitOffset);
BinaryValue += (tmp << ValuePos);
BytePos--;
RemainLen -= DealLen;
BitOffset = 0;
ValuePos += DealLen;
}while (RemainLen);
}
if ((ValueMsg>>8) & 0xFF)
{
mask = (1UL << (BitLength - 1));
if ((BinaryValue & mask) == mask)
{
signedMask = ~((1UL << BitLength) - 1);
BinaryValue |= (-1L & signedMask); //高位填充,扩展为32位/64位
}
}
switch(ValueMsg & 0xFF)
{
case 0: //interge
{
rt = (float32)BinaryValue * factor + offset;
}
break;
case 1: //float
{
if (BitLength != 32)
{
rt = 0;
}
else
{
rt = *(float32*)(&BinaryValue) * factor + offset;
}
}
break;
case 2: //double //嵌入式不支持
default:
{
/* //double的预留代码
if (BitLength != 64)
{
rt = 0;
}
else
{
rt = (float32)(*(float64*)(&BinaryValue) * factor + offset);
}
*/
rt = 0;
}
break;
}
return rt;
}
2、接收CAN报文:
3、发送CAN报文
这个我自己测试了一下是可用的。
不过不太清楚会不会有什么bug,能不能都适用。
到此,CAN报文部分讲完了。