【MATLAB-CAN协议】手把手学会Matlab脚本之DBC与Excel互转


前言

       车载领域必不可少的一部分通信协议便是CAN总线,诸如温控、中控、底盘等均需要CAN协议来监控与控制。但是,CAN协议是为单片机工作所分配的底层机制,对于工程师来说却并不是很直观。因此,为方便解读CAN数据,在为单片机分配机制的同时,也为工程师们提供了一种解析协议,这种协议在工程师们的交流中被称为DBC解析文件。

       但是,该项文件也需要工程师去制作,在信号量庞大时不太利于工作效率,因此,本文提供一种Excel与DBC互相转换的工具以便于日常工作。 

一、CAN是什么?

       只要是步入汽车行业的小伙伴,相比对于CAN总线都不陌生。但是对于初学者还是需要一定的时间去掌握,因此在介绍转换工具前,在这里先对CAN知识做个总结。

       CAN-Controller Area Network,控制器局域网络,是一种有效支持分布式控制系统的串行通信网络,CAN协议的特性包括完整性的串行数据通讯、提供实时支持、传输速率高达1Mb/s、同时具有11位的寻址以及检错能力。主要用于汽车电子通信。目前,大部分控制器做到物理层、数据链路层的充分开发。

 1.1 CAN的底层机制

由于本文的重点不在于解析CAN机制,因此仅以下图标准帧格式进行简单的描述。

一条正常的报文应符合以上格式,即包含

帧起始(SOF)+仲裁场(ID+RTR)+控制场(IDE+R+DLC)+数据场(data)+CRC场(CRC+DEL)+ACK场(ACK+DEL)+帧结束(EOF)+间歇场(ITM)+恢复空闲状态

CAN总线上,隐性电平为1,显性电平为0。

SOF:当从CAN空闲时检测到一个显性电平则开始解析该帧,同时各节点可以通过该位进行时钟同步

ID:即每一帧报文的标识符,一般由工程师自行定义

RTR:即判断该帧为数据帧还是远程帧,显性电平为数据帧

IDE:即判断该帧为标准帧还是拓展帧,显性电平为标准帧

r:工程师协会预留

DLC:即定义数据段传输的数据长度,一般由工程师自行定义

data:报文传输的数据,一般由工程师自行定义

CRC:循环冗余校验,数据帧的校验范围为帧起始+仲裁段+控制段+数据段

DEL:为界定符,主要分开某些特殊位

ACK:判断报文是否被正确接收,正常发送时为隐形电平,当被正确接收时回复显性电平即该位在总线上为显性电平说明无错误

EOF:帧结束标识符,一般为7个隐性电平

1.2 CAN数据场结构

       本文核心的内容便是解析CAN数据,一般而言,一帧报文长度为8个字节,即64位,每一位都有其特殊含义,因此在解析数据前必须先了解64位数据是如何分布的。

        如下图所示,最左边一列为对应的0-7字节,最上边一行表示每个字节8个位分别存储哪些信息。

        以图中信号为例,可知test1位于第0字节,使用了8位传递数据。test2位于第1字节,使用了7位传递数据。test3位于第1字节,使用了1位传递数据。假设现在有一帧报文信息如下:

,

那么根据以上描述可知0x21传递的就是test1的内容,0x11低7位传递的是test2的内容,0x11最高位传递的是test3的内容。那么我们则将蕴含总线报文信息的矩阵称为通信协议,打包成文件提供给其它工具进行解析的文件就叫做DBC文件。

二、通过matlab脚本实现Excel转DBC

2.1 DBC文件描述

      关于DBC文件的详细描述可以参考以下博客

DBC系列之DBC格式与属性说明[1]_dbc属性-CSDN博客

2.2 脚本设计

       根据DBC文件的格式,我们将以下需要人为定义的数据以Excel形式填充完整.然后通过字符拼接的方式将一些字段进行复制、粘贴、替换即可。由于工程量比较大,此处仅提供设计思路供学习。

第一步 设计Excel模板

      所选用的Excel模板是根据项目经验以及与同行交流后所采用的格式,其中包含报文名称、ID、周期等关键信息。

第二步 读取Excel表格

使用xlsread函数将Excel表格数据取出来存入数组中
[~,~,CAN_Matrix_Text] = xlsread('DemoCAN_Matrix_CAN0.xlsx');
运行结果如下

第三步 按行遍历数据

 [column_num,row_num] = size(CAN_Matrix_Text);
 for column_index = 3:column_num  %去除行1与行2,从第3行获取    
    Message_Text = CAN_Matrix_Text(column_num,1:row_num);
 end

第四步 按照DBC文件格式重新组合信息

% BU_:用于定义网络节点
MessageNode = '';
for NodeIdex = 1:numel(MsgNode)
    NodeNameStr = MsgNode{NodeIdex};
    MessageNode = strcat(MessageNode,32,NodeNameStr);
    end
BU_Content = [sprintf(strcat('BU_:',MessageNode)) eol]; 
...
...
...       
% 信息排序整合
Combine_content = [Header_content BU_Content];
Combine_content = [Combine_content eol];
Combine_content = [Combine_content BO_SG_Content];
Combine_content = [Combine_content eol];
Combine_content = [Combine_content CM_BO_SG_Content];
Combine_content = [Combine_content eol];
Combine_content = [Combine_content BA_DEF_Content];
Combine_content = [Combine_content BA_Content];
Combine_content = [Combine_content BA_BO_Content];
Combine_content = [Combine_content BA_SG_Content];

第五步 生成DBC文件

filePath = fullfile(xlsfile_path, [UserDBCName,'.dbc']);%用户定义DBC名称与路径
fid = fopen(filePath,'wt+','n','GB2312');%打开定义的DBC文件
    if -1 == fid
       error('Cannot open the file.');
    end
fprintf(fid, Combine_content);%向打开的目标DBC文件写入DBC信息
fclose(fid);
   

2.3 脚本最终演示

该转换工具最终分为4部分,一个是路径选择、一个是名称选择、一个是sheet表选择、还有一个是信息打印
工具在运行过程中,会显示当前流程状态,并且会打印存储路径等信息供参考

三、通过matlab脚本实现DBC转Excel

       此处的DBC转Excel本质上就是Excel转DBC的逆向操作,即通过遍历文本将相关信息写入Excel表格对应区域即可

3.1 脚本设计

第一步 通过脚本定义Excel

hExcel = actxserver('excel.application');     %获取EXCEL服务器,并返回句柄

hSheet.Range('A1').Value = [ 'Msg Name', newline, '报文名称' ];
hSheet.Range('A1').ColumnWidth = 12;
hSheet.Range('A1:A2').MergeCells = 1;
            
hSheet.Range('B1').Value = [ 'Msg Type', newline, '报文类型' ];
hSheet.Range('B1').ColumnWidth = 8.11;
hSheet.Range('B1:B2').MergeCells = 1;
            
hSheet.Range('C1').Value = [ 'Msg ID',newline,'(Hex)', newline, '报文标识符' ];
hSheet.Range('C1:C2').MergeCells = 1;

...
...
...

hSheet.Range('U1').Value = [ 'Invalid Value',newline,'(Hex)', newline, '无效值' ];
hSheet.Range('U1').ColumnWidth = 8.11;
hSheet.Range('U1:U2').MergeCells = 1;
            
hSheet.Range('V1').Value = [ 'Unit', newline, '单位'];
hSheet.Range('V1').ColumnWidth = 8.11;
hSheet.Range('V1:V2').MergeCells = 1;
            
hSheet.Range('W1').Value = [ 'Node', newline, '节点'];
            

第二步 读取DBC信息

%打开文件
dbc_fp = fopen( dbcName, 'r' );

%定义正则表达式
rexpMsgHeader = '^\s*BO_\s+(\d+)\s+(\w+)\s*:\s*(\d{1,1})\s*(\w*)';
rexpSignal = '^\s+SG_\s+(\w+)\s+(\w*)\s*:\s*(\d{1,2})\|(\d{1,2})@(0|1)(+|-)\s*\(\s*([0-9+\-.eE]+)\s*,\s*([0-9+\-.eE]+)\s*\)\s*\[\s*([0-9+\-.eE]+)\|\s*([0-9+\-.eE]+)\s*\]\s*"([^"]*)"\s*(.+)$';
rexpMsgCmt = '^\s*CM_\s+BO_\s+(\d+)\s+"(.*)(";$)?';
rexpSigCmt = '^\s*CM_\s+SG_\s+(\d+)\s+(\w+)\s*"(.*)(";$)?';
rexpAttrDeclare = '^BA_DEF_\s+(\w+)?\s+"(\w+)"\s+(\w+)\s+';
rexpAttrDefault = '^BA_DEF_DEF_\s+"(\w+)"\s+"?(\w+)"?;$';
rexpAttrSetting = '^BA_\s+"(\w+)"\s+(.+);$';
rexpSigVal = '^\s*VAL_\s+(\d+)\s+(\w+)\s*(.*);$';

% 信号值定义获取
status = fseek( dbc_fp, 0, 'bof' );

while ( ~feof( dbc_fp ) )
    line = fgetl( dbc_fp );
 
    tok = regexp( line, rexpSigVal, 'tokens' );
    if ( ~isempty( tok ) )
       tok = tok{ 1 }';
       findVal = 0;
       for k = 1:length( msgInfoList )
           for m = 1:length( msgInfoList( k ).sigInfoList )
               if ( strcmp( msgInfoList( k ).sigInfoList( m ).name, tok{ 2 } ) )
                  findVal = 1;
                  tok = regexp( tok{ 3 }, '[""]', 'split' );
                  sig_val = '';
                  for n = 1:2:length( tok ) - 1
                      tok{ n } = strrep( tok{ n }, ' ', '' );      
                      tok{ n + 1 } = strtrim( tok{ n + 1 } );
        
                      if isempty( regexp( tok{ n + 1 }, ';$', 'match' ) )
                         tok{ n + 1 } = [ tok{ n + 1 }, ';' ];
                      end 
                      sig_val = [ sig_val, tok{ n }, ':', tok{ n + 1 }, eol ];
                  end 
                  msgInfoList( k ).sigInfoList( m ).sigVal = sig_val;
                  break ;
                end 
           end 
        
          if findVal == 1
             break;
          end 
       end 
  end 
end

% 信号名称定义获取
...
...
...

第三步 写入Excel数据

...
...                       
 % 数据类型
if ( msgInfoList( 1, j ).sigInfoList( 1, jj ).isSigned )
    hSheet.Range(['O',CurrRowNumStr]).Value = 'Signed';
else 
    hSheet.Range(['O',CurrRowNumStr]).Value = 'Unsigned';
end
                        
sigFactor = msgInfoList( 1, j ).sigInfoList( 1, jj ).factor;
sigOffset = msgInfoList( 1, j ).sigInfoList( 1, jj ).offset;
% 精度
hSheet.Range(['P',CurrRowNumStr]).Value = sigFactor;
% 偏移量
hSheet.Range(['Q',CurrRowNumStr]).Value = sigOffset;
% 物理最小值
hSheet.Range(['R',CurrRowNumStr]).Value = msgInfoList( 1, j ).sigInfoList( 1, jj ).min;
% 物理最大值
hSheet.Range(['S',CurrRowNumStr]).Value = msgInfoList( 1, j ).sigInfoList( 1, jj ).max;
% 信号初始值
initVal_Hex = dec2hex(msgInfoList( 1, j ).sigInfoList( 1, jj ).initVal * sigFactor + sigOffset);
hSheet.Range(['T',CurrRowNumStr]).Value = [ '0x', initVal_Hex ];
% 单位
sigUnit = msgInfoList( 1, j ).sigInfoList( 1, jj ).unit;
sigUnit = strrep( sigUnit, '1/min', 'rpm' );        
hSheet.Range(['V',CurrRowNumStr]).Value = sigUnit;
...
...

hWorkbooks.SaveAs([ ExcelPath, ExcelFile ]);
Quit(hExcel);
delete(hExcel);

3.2 脚本最终演示--逻辑与Excel转DBC一致

总结

        在各界大佬的资源协助下,完成了本次工具的开发,本着互助学习的传承精神,该项工具开放给大家供学习使用。以上便是文章全部内容。

GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。https://gitcode.com/SYW-OPEN/Open-source.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值