本文通过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_这一行的模式了。
- BO关键字,在正则表达式模式中直接用BO_完全匹配即可;
- 报文ID,是若干个数字可以用\d+表示1个或以上的阿拉伯数字;
- 报文名称,一般是由字母、数字和下划线组成的字符串,所以可以用\w+表示;
- 报文数据域字节数,无符号整数,直接用\d+表示;
- 网络节点:一般是由字母、数字和下划线组成的字符串,所以可以用\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