DBC_2_C上位机
前言
时隔半年,我又回来了,今天带来的是一款相对实用一点的工具------DBC_2_C。在汽车电子圈里混的各位佬都知道,主机厂定义信号矩阵,像lin啊,can啊这些,8个字节分成了十几个或者几十个信号,每个信号的意义也不同,如何从一帧数据截取对应的信号,如何将信号打包到一帧数据里边再发送到总线。本人之前使用到的是位域,相比移位,我更喜欢位域,清晰简单。第一次接触到位域也是刚进入行业的时候。当时很感叹位域设计的精妙,这不就是为解析lin报文而生的嘛!直到那次,我意识到位域的局限性,这是后话了,下面会介绍到。然后我又去咨询了两位同事,他们的之前做法是移位。"手写" 移位函数。虽然已经猜到他们是这么做的,但是还是对他们的回答很失望。没有让我眼前一亮的方案,一个个信号去移位,要累死个人啊,要是真得舍弃掉位域,移位运算我也是真不想要。有没有一个好的折中的简单的方案。有!工具生成。python提供了一个可以解析can报文的库cantools,搭建好环境,选择dbc,直接就给你生成两个全是移位函数的文件。是不是这个cantools的存在感比较低,好多人都没怎么听说,我也是deekseek推荐才知道的。好了。如果只是为了找到一个可以生成移位函数的工具,文章到这里就可以结束了。哈哈哈。但是我就不,死犟的我打算打造出属于我的工具!!!
位域的局限
好了,上面废话有点多了,进入正题。
c
#include"stdio.h"
typedef union
{
struct
{
unsigned char AS_NtcOpen_Fault : 3;
unsigned char DR_NtcOpen_Fault : 3;
unsigned char AS_DOOR_STA : 2;
unsigned char DR_DOOR_STA : 3;
unsigned char reserved1:5;
unsigned char reserved2:5;
unsigned char reserved3:5;
unsigned char reserved4:5;
unsigned char reserved5:5;
unsigned char reserved6:5;
unsigned char reserved7:5;
}Bit;
unsigned char Byte[8];
}BCM_LIN_DRVR_UNION;
int main()
{
BCM_LIN_DRVR_UNION look;
look.Bit.AS_NtcOpen_Fault = 7;
look.Bit.DR_NtcOpen_Fault = 2;
printf("0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",look.Byte[0],look.Byte[1],look.Byte[2],look.Byte[3],look.Byte[4],look.Byte[5],look.Byte[6],look.Byte[7]);
return 0;
}
拿这段代码来说,之前在芯旺微的芯片上跑,输出的是0xE8 0x00...
同样的代码,在nxp的s32k144上跑,却是
0x17 0x00...
看下边这个图片就可以一目了然是因为什么

deepseek给出的解释就是不同的编译器,位域在内存中的布局是不一样的,像gcc,iar都会有差别。在来看看下边两张图片,这个是c语言在线编译器,采用gcc的两个不同的版本,编译相同的代码,输出也存在差别。


不同的编译器,对位域的实现是会有影响的。一方面不太方便移植到不同的平台上边去。另一方面把信号映射到位域上边去,还要根据存储规则去做运算,就太多此一举了,直接采用移位运算。可能有些芯片可以更改lsb还是msb,但是s32ds我没找到哪里可以设置,我太菜了。
cantools简介
根据上边的链接自己搭建一下python环境还有安装下cantools这个库。接下来,让我来介绍一下生成了些什么东西。
cantools生成.c文件
DBC_2_C简介
这个上位机界面很简单,几个按钮,几个文本框就完了,主要功能也仅是生成文件,所以就单线程了,一般不会导致界面卡死的。主要生成的就是pack、unpack、encode、decode、is_in_range函数,以及不同id的结构体包含的哪些信号。在cantools的基础上,对代码排布,代码功能做了点优化。
dbc_2_c生成.c文件
新建一个Generate_File_DBC2C_wby_dbc_mess_0x501_t结构体,然后将这个结构体调用pack函数,给到目标数组,底层根据这个目标数组,发送到总线;
底层接收到一帧数据,然后将缓存unpack到Generate_File_DBC2C_wby_dbc_mess_0x501_t就完成了信号的截取。
到这里其实以及够了,但是,怎么感觉有点膈应这一块,老感觉这一块会经常出问题。所以,我又做了优化,新增了.c和.h中/start / 到 /end/中间的那一块代码
新增代码部分
DBC_2_C生成文件使用
生成文件的使用
c
void DBC2C_Update_device(void)
{
uint8_t *data_buffer = NULL;
if(dbc_2_c_find_array(Generate_File_DBC2C_wby_dbc_mess_0x503_FRAME_ID , &data_buffer) != -1)
{
//wby_dbc_mess_0x503_pack(data_buffer,&wby_dbc_mess_0x503_w);
}
if(dbc_2_c_find_array(Generate_File_DBC2C_CANV_CCM_0x18000001_CAN14_FRAME_ID , &data_buffer) != -1)
{
CANV_CCM_0x18000001_CAN14_pack(data_buffer,&CANV_CCM_0x18000001_CAN14_w);
}
}
应应层把所有id的pack函数都统一写到这里,会查找dbc有无对应的id,把信号映射到对应的缓冲区。这个缓冲区驱动层同样要使用到,驱动层利用dbc_2_c_find_array找到对应的缓冲区。
c
/*main函数里边调用*/
native_CAN0_writge_tmp_extension(1,0x18000001);
native_CAN0_writge_tmp_standard(3,0x501,0x506,0x502);
/*写函数实现*/
void native_CAN0_writge_tmp_extension(int count, ...)
{
can_message_t Tx_msg_1;
Tx_msg_1.cs = 0U;
int8_t array_length = 0;
uint32_t canid;
uint8_t *data_buffer = NULL;
va_list args;
va_start(args, count);
for(int i=0;i<count;i++)
{
canid = va_arg(args, int);
array_length = dbc_2_c_find_array(canid,&data_buffer);
if(array_length == -1)
{
//警告
}
else
{
Tx_msg_1.id = canid;
memcpy(Tx_msg_1.data,data_buffer,array_length);
Tx_msg_1.length = array_length;
while(CAN_Send(&can_pal0_instance, TX_MAILBOX_CAN0, &Tx_msg_1) != STATUS_SUCCESS);
}
}
va_end(args);
}
/*读函数实现*/
void DBC2C_Update_standard_driver(uint32_t canid,uint8_t *data_buffer)
{
int8_t data_length;
data_length = dbc_2_c_find_array(recvMsg_CAN1.id,&data_buffer);
if(data_length != -1)
{
memcpy(data_buffer,recvMsg_CAN1.data,data_length);
if(recvMsg_CAN1.id == Generate_File_DBC2C_wby_dbc_mess_0x503_FRAME_ID)
{
wby_dbc_mess_0x503_unpack(&wby_dbc_mess_0x503_r,data_buffer);
}
}
}
这里使用到可变参数,第一个形参是参数的个数,后边跟随的是要发送的id,如果id在dbc中没有找到,就不会去发送。另外驱动层相对麻烦一点,哪个id放在哪个can口,通过哪个邮箱发送出去,从哪个邮箱接收,接收的can还是canfd。所以很难做到在Generate_File_DBC2C.c生成一个函数供底层调用。如果大佬们有好的想法也可以一起沟通改进。
DBC_2_C的局限性
视频中也讲到了关于这个上位机的一些不足
1、虽然qt的代码可以识别哪个些d是can,哪些id是canfd,但是生成代码是没有开放给底层去调用的。如果有需要后边会做一个接口。
2、上位机要求导入的dbc必须全是大端或者全是小端,如果一个id中大小端相间,解析会出错!一方面这种大端插小端的场景很少见,另一方面大端插小端实现的难度有点大,特别的pack和unpack函数,先这样子吧,后边心血来潮再来解决这个问题。
3、不对dbc文件进行检查。生成之前一定要确保dbc是正确的,DBC_2_C只会将错就错,不提供纠错功能。
尾声
视频录制时间有点晚,我都有点不知道在说什么了,大家凑合着看,虽然 "DBC_2_C" 看起来还是个毛坯房,但是精装修一下,应该还是有点价值的。就这样子吧,下课!