Matlab编程技巧:通过正则表达式解析DBC文件

本文通过Matlab函数regexp的正则表达式功能,解析DBC文件中的报文帧信息。DBC文件中的信号等其他信息都可以通过类似的方式解析出来。

1 DBC文件

DBC数据库文件是用来描述CAN网络节点间数据通讯的一种文件,做汽车CAN网络通信的话肯定是绕不开DBC文件的。

关于DBC文件格式的内容可以参考另一位博主的文章关于DBC文件的格式解析(DBC文件系列其二),本文后面也会引用到其中的一些知识内容。

DBC文件的格式有很强的规律性,所以可以很轻松地通过正则表达式函数regexp解析出其中地信息。实际上,Matlab提供了封装好的函数canDatabase(),可以更加快速准确地解析DBC中的帧和信号。本文没有使用封装的函数,是希望以DBC为例,训练正则表达式的应用技巧,以便在遇到其他需求没有现成的函数时,自己也能造出轮子。

2 正则表达式函数

在Matlab中,正则表达式函数是regexp。关于正则表达式的语法不用死记硬背,用到的时候去查Matlab帮助文档就行。

3 实例:解析报文帧信息

首先,自己新建一个DBC文件,一般是通过CANdb++这类DBC工具。不过这里为了方便就不去手动建立了,直接把如下内容拷贝进文本文件,并命名为DBC_Demo.dbc。

BS_:
BU_: AVNT ACU HUD
BO_996 HUD_1_B: 8 HUD
 SG_ HUD_OffSt : 0|1@0- (1,0) [-1|0] "" ACU,AVNT
 SG_ HUD_HeightLv : 7|7@0+ (1,0) [0|127] "lv" ACU,AVNT
 SG_ HUD_BrightnessLv : 15|4@0+ (1,0) [0|15] "lv" ACU,AVNT
CM_ BO_ 996 "The message of control hud status";
CM_ SG_ 996 HUD_BrightnessLv "Control hud brighness level";

其中,BO_开头的那一行就是报文帧,其格式定义如下:
在这里插入图片描述

4 Matlab脚本

1)首先要将DBC文件的文本内容导入到Matlab中。在Matlab命令行输入

DBCText = fileread('DBC_Demo.dbc')

就会将DBC文件内容作为字符串返回到DBCText变量中。
在这里插入图片描述
2)用正则表达式提取出报文帧那一行,也就是BO_开头的那一行。这里开始就要研究BO_这一行的模式了。

  1. BO关键字,在正则表达式模式中直接用BO_完全匹配即可;
  2. 报文ID,是若干个数字可以用\d+表示1个或以上的阿拉伯数字;
  3. 报文名称,一般是由字母、数字和下划线组成的字符串,所以可以用\w+表示;
  4. 报文数据域字节数,无符号整数,直接用\d+表示;
  5. 网络节点:一般是由字母、数字和下划线组成的字符串,所以可以用\w+表示;

所以报文帧这一行的模式可以写为:

>> pattern = 'BO_\s*\d+\s+\w+:\s+\d+\s+\w+';

中间的\s+或者\s*代表空格符。

然后就可以通过regexp解析出BO这一行了。注意加上’match’这个参数以返回字符串。

>> BO_Cell = regexp(DBCText,pattern,'match')

这里要注意,返回的的是一个字符串组成的元胞数组。由于demo中只有一个报文帧的定义,所以元胞数组中只有一个字符串。
在这里插入图片描述
3)解析元胞数组中的每一个报文帧行,提取出信息,这里只以一个报文帧为例。首先解析出报文ID,只要把之前的pattern改一下就行。

pattern_MessageId = '(?<=BO_\s*)\d+(?=\s+\w+:\s+\d+\s+\w+)';

在这里插入图片描述
这里用了正则表达式的上下文匹配,语法是*(?=test)(?<=test)*,其中test是上下文内容。这样就能根据上下文匹配出报文帧了。

再比如说报文名称也用上下文匹配:

>> MessageName_Cell = regexp(BO_Cell{1},'(?<=BO_\s*\d+\s+)\w+(?=:\s+\d+\s+\w+)','match')

MessageName_Cell =

  1×1 cell 数组

    {'HUD_1_B'}

5 总结

用正则表达式解析DBC文件比较容易,只是需要一点耐心慢慢地调。除了报文帧,其他信号、波特率、节点等信息都能通过类似的方式解析。这样的技术也可以迁移到其他文件,只要我们了解了该文件的书写规则就行。

>>返回个人博客总目录

附 完整代码

测试用的DBC文件如下:

BS_:
BU_: AVNT ACU HUD
BO_996 HUD_1_B: 8 HUD
 SG_ HUD_OffSt : 0|1@0- (1,0) [-1|0] "" ACU,AVNT
 SG_ HUD_HeightLv : 7|7@0+ (1,0) [0|127] "lv" ACU,AVNT
 SG_ HUD_BrightnessLv : 15|4@0+ (1,0) [0|15] "lv" ACU,AVNT
CM_ BO_ 996 "The message of control hud status";
CM_ SG_ 996 HUD_BrightnessLv "Control hud brighness level";

博主自己写的脚本,可能会有考虑不周的地方,不一定适用于任何DBC文件。但是思路和方法是这样的,可以在遇到问题时尝试修改脚本,不断迭代。脚本内容如下:

function Message_Struct = ImportDBC()
%% 导入DBC文本
DBCText = fileread('DBC_Demo.dbc');

%% 解析BO_和SG_
pattern = 'BO_\s*\d+\s+\w+:\s+\d+\s+\w+\s*\r\n(\s*SG_.*?\r\n)*';
BO_Cell = regexp(DBCText,pattern,'match');

for i = 1:length(BO_Cell)
    BO_Str = BO_Cell{i};
    Message_Struct = Parse_Message(BO_Str);
    SG_Cell = regexp(BO_Str,'SG.*?\r\n','match');
    Signal_Cell = {};
    for j = 1:length(SG_Cell)
        SG_Str = SG_Cell{j};
        Signal_Struct = Parse_Signal(SG_Str);
        Signal_Cell{end+1} = Signal_Struct;
    end
    Message_Struct = setfield(Message_Struct,'Signal',Signal_Cell);
end
end

%解析信号属性
function Signal_Struct = Parse_Signal(SG_Str)
%pattern:SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n
%SignalName
Temp = regexp(SG_Str,'(?<=SG_\s*)\w+(?=\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
SignalName = Temp{1};
%StartBit
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*)\d+(?=\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
StartBit = Temp{1};
%SignalSize
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|)\d+(?=@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
SignalSize = Temp{1};
%ByteOrder
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@)(0|1)(?=\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
if(strcmp(Temp{1},'0'))
    ByteOrder = 'Motorola';
else
    ByteOrder = 'Intel';
end
%ValueType
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*)(\+|\-)(?=\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
if(strcmp(Temp{1},'+'))
    ValueType = 'Unsigned';
else
    ValueType = 'Signed';
end
%Factor
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\()-*\d+(?=,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
Factor = Temp{1};
%Offset
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,)-*\d+(?=\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
Offset = Temp{1};
%Min
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[)-*\d+(?=\|-*\d+\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
Min = Temp{1};
%Max
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|)-*\d+(?=\]\s*"\w*"\s*(\w|\,)+\r\n)','match');
Max = Temp{1};
%Unit
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*)"\w*"(?=\s*(\w|\,)+\r\n)','match');
Unit = Temp{1};
%Receiver
Temp = regexp(SG_Str,'(?<=SG_\s*\w+\s*:\s*\d+\|\d+@(0|1)\s*(\+|\-)\s*\(-*\d+,-*\d+\)\s*\[-*\d+\|-*\d+\]\s*"\w*"\s*)(\w|\,)+(?=\r\n)','match');
Receiver = Temp{1};
%Signal_Struct
Signal_Struct = struct('SignalName',SignalName,'StartBit',StartBit,'SignalSize',SignalSize,'ByteOrder',ByteOrder,'ValueType',ValueType,...
    'Factor',Factor,'Offset',Offset,'Min',Min,'Max',Max,'Unit',Unit,'Receiver',Receiver);
end

% 解析报文属性
function Message_Struct = Parse_Message(BO_Str)
%pattern:'BO_\s*\d+\s+\w+:\s+\d+\s+\w+'
%MessageId
Temp = regexp(BO_Str,'(?<=BO_\s*)\d+(?=\s+\w+:\s+\d+\s+\w+)','match');
MessageId = Temp{1};
%MessageName
Temp = regexp(BO_Str,'(?<=BO_\s*\d+\s+)\w+(?=:\s+\d+\s+\w+)','match');
MessageName = Temp{1};
%MessageSize
Temp = regexp(BO_Str,'(?<=BO_\s*\d+\s+\w+:\s+)\d+(?=\s+\w+)','match');
MessageSize = Temp{1};
%Transmitter
Temp = regexp(BO_Str,'(?<=BO_\s*\d+\s+\w+:\s+\d+\s+)\w+','match');
Transmitter = Temp{1};
%Message_Struct
Message_Struct = struct('MessageId',MessageId,'MessageName',MessageName,'MessageSize',MessageSize,'Transmitter',Transmitter,'Signal','');
end
解析一个 dbc 文件,可以使用 Java 编程语言的各种库和工具。DBC 文件是一种用于描述 Controller Area Network (CAN) 消息的文件格式,它包含有关 CAN 消息、信号和节点的定义。 首先,我们可以使用 Apache POI 或其他类似的库来读取 DBC 文件的内容。这些库提供了用于读取 Excel 文件的 API,我们可以使用这些 API 来打开和读取 DBC 文件中的工作表、行和单元格。 接下来,我们需要解析 DBC 文件中的各个部分。通常,DBC 文件由多个节(section)组成,每个节都包含有关 CAN 网络中的不同方面的信息。常见的节包括: 1. `VERSION` 节:它包含有关 DBC 文件版本和创建日期的信息。 2. `BU_` 节:它定义了 CAN 网络中的节点。 3. `BO_` 节:它定义了 CAN 消息。 4. `SG_` 节:它定义了信号。 5. `VAL_` 节:它定义了信号的值。 我们需要遍历 DBC 文件中的每个节,并提取所需的信息。可以使用正则表达式或字符串操作来提取每个节的名称和内容。 一旦我们获取了节的名称和内容,我们可以进一步解析每个节。例如,对于 `BU_` 节,我们可以解析节点的名称和属性,并将其存储在相应的对象中。对于 `BO_` 节,我们可以解析消息的名称、ID 和长度。对于 `SG_` 节,我们可以解析信号的名称、起始位、长度和属性。 最后,我们可以将解析后的信息用于任何我们想要的用途,例如生成代码、执行数据转换或进行数据分析。 需要注意的是,解析 DBC 文件可能会涉及到一些复杂的逻辑和处理,这取决于 DBC 文件的结构和复杂性。因此,我们需要仔细阅读 DBC 文件的规范,并可根据需要进行适当的调整和扩展。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值