# 前言
为什么写了这个东西,导师画饼。能不能用,暂时不知道,反正我写了,用到之时自会有用,这里只记录思路。
# 算法步骤
- 从制定好的 excel 表中读取数据
Data
. - 对
Data
进行数据预处理得到自己想要的数据结构T
. - 根据
T
去解析一个 DID 的 DCM 函数定义. - 根据
T
初始化一个 DID 对应的所有信号和存储大小. - 根据
T
中的 DID 的功能解析信号填充. - 生成注释和 C 函数及信号填充内容并写入 C 文件.
- 持续第 3~6 步直至
T
中 DID 全部被解析完并生成相应内容填充至 C 文件.
# Data
和 T
Data
是 excel 表中你自己定义的数据,里面可以包含一个 DID 标识符、DID 功能、对应的所有信号名和信号长度和信号位置,你可以在 excel 表中自己定义想要被读取的数据。T
则是你从 excel 表导入的数据预处理,毕竟一个 DID 可以包含多个信号,你需要构建成一个 DID 包含多个信号及对应信号存储信息的数据结构,方便你后续进行 DCM 函数的自动生成,你可以自己定义想要存的数据。
# DCM 函数定义
我不清楚其它对于 DCM 函数名是怎么定义的,起码在我这里 DCM 函数名不是标准的 C 语言的函数,而是 vector 对 C 语言封装后的函数声明,所以你需要对标准 C 函数的返回值,参数值进行转换适合 vector 函数的规范。
# 信号填充
这是最重要的步骤,可能会很长
# 信号长度和位置解析
假设一个 DBC 信号展示如图,同时你只有一个 DBC 信号的长度和位置,你怎么确定这个信号的 MSB、LSB 坐标以及是否跨字节?
只有知道了信号在 DBC 中的 MSB 和 LSB 位置,才能进行信号填充中的移位填充操作,即所谓的 CAN 报文通信会话层的拆包与组包。
MSB 坐标可以有位置值除 8 和取余 8 求得,剩下的就是 LSB 坐标。
对于信号是否在一个字节内的,只需判断 MSB 是否大于信号长度即可 (#1),在不满足 #1 的条件下,判断信号长度是否大于等于 10 或者信号长度减去 MSB 所在的字节长度的值是否大于 8,否则跨两个字节,是则跨两个及以上字节。
以下是计算 MSB 和 LSB 坐标的伪代码
MSB byte index = poistion / 8 | |
MSB bit index = poistion % 8 | |
// one byte | |
if MSB bit index + 1 >= Length | |
LSB byte index = MSB byte index | |
LSB bit index = MSB bit index - (Length - 1) | |
// two bytes | |
else if Length - (MSB bit index + 1) > 8 or Length >= 10 | |
LSB byte index = MSB byte index + 1 | |
LSB bit index = 8 - (Length - (MSB bit index + 1)) | |
// three bytes and more | |
else | |
subLength(no msb byte index) = Length - (MSB bit index + 1) | |
last row = (subLength / 8) + 1 | |
number in LSB byte index = subLength % 8 | |
LSB byte index = MSB byte index + last row | |
LSB bit index = 8 - number in LSB byte index |
# 移位填充操作
移位填充遵从的 Motorola 格式,即计算机科学里面所谓的大端模式,高位数据存低位,低位数据存高位。
注:这里说的移位填充操作指的是 DID 中的读数据功能,即把信号读进 DBC 信号矩阵中,还有一种是 DID 的写数据功能,即把 DBC 信号矩阵写进信号中,方式和读数据功能相反,但写法类似,不过数据类型转换不再是 uint8,而是根据每个信号类型不同进行进行强制类型转换 (uint8、uint16、uint32、Ascll、Hex)。
# 两种移位填充方式
将信号按 Motorola 格式填充至 DBC 中一共有两种方式,分别是
- 先清除信号不必要的 bit 位,再移位至合适的位置填充至 DBC ($1)
Data[x] |= (uint8)( (singal & mask) << shift) |
- 先再移位至合适的位置,再清除信号不必要的 bit 位填充至 DBC ($2)
Data[x] |= (uint8)( (singal >> shift) & mask) |
上述两种方式之后我会统一用 $1 和 $2 表达。
移位填充可分为三种情况,信号在一个字节内、两个字节内,三个字节及以上的。
# 信号在一个字节内
信号在一个字节内很简单,只需要执行 $1 即可。
Data[Start_Byte] |= (uint8)( (singal & (8 - Start_Bit)) << Start_Bit) |
注:这里的 Start_xx,End_xx 指的是 MSB 和 LSB 的坐标位置。
# 信号在两个字节内
两个字节的则需要先执行 $1 把信号高位数据移至 DBC 低位,再执行 $2 把信号低位数据移至 DBC 高位。
len1 = (8 - Start_Bit) | |
Data[Start_Byte] |= (uint8)( (singal & len1 ) << Start_Bit) | |
Data[End_Byte] |= (uint8)( (singal >> len1 ) & (len - len1)) |
# 信号在三个字节及以上的
三个字节其实信号在两个字节的特化版,首先看 DBC 最低位,先执行 $1, 之后全部执行 $2,但是由于除了最高位可能不是满字节的,所以 DBC 最高位的掩码不是 8,其余全是 8,需要注意的是,从第二步开始的每一次移位都是很上一次信号对应位置移位的累加,假设有四次移位操作,除第一次和第四次不是满字节,其余都是满字节,则有这样的可能,3,11 (8+3),19 (8+8+3),23 (4+8+8+3)。
len1 = (8 - Start_Bit) | |
Data[Start_Byte] |= (uint8)( (singal & len1 ) << Start_Bit) | |
Data[Start_Byte - 1 ] |= (uint8)( (singal >> len1 ) & 0xFF) | |
len1 = len1 + 8 | |
Data[Start_Byte - 2 ] |= (uint8)( (singal >> len1 ) & 0xFF) | |
.... | |
len1 = len1 + 8 | |
Data[Start_Byte - n ] |= (uint8)( (singal >> len1 ) & 0xFF) | |
Data[End_Byte] |= (uint8)( (singal >> len1 ) & (len - len1)) |
# 信号类型为 ASCII 或 HEX
ASCII 和 HEX 实际是一个 uint8 的数组,所以全程执行 $1 或 $2 都没问题,因为实际信号都完整填进 DBC 矩阵里了,这里以 $2 为例。
Data[Start_Byte] |= (uint8)( (singal[0] >> 0 ) << & 0xFF) | |
Data[Start_Byte - 1 ] |= (uint8)( (singal[1] >> 0 ) & 0xFF) | |
Data[Start_Byte - 2 ] |= (uint8)( (singal[2] >> 0 ) & 0xFF) | |
.... | |
Data[Start_Byte - n ] |= (uint8)( (singal[n-1] >> 0 ) & 0xFF) | |
Data[End_Byte] |= (uint8)( (singal[n] >> 0 ) & 0xFF) |