首页 / 资讯中心 / 文章详情

MATLAB解析asc文件:CAN报文信号提取与数据分析实战

MATLAB解析asc文件:CAN报文信号提取与数据分析实战 ★ FEATURED ARTICLE
简介这份资源是一套面向汽车电子与CAN总线数据分析人员的Matlab脚本工具用于解析asc格式的CAN报文并筛选指定信号最终输出为csv文件适合需要批量处理路测数据、做信号级分析的工程师或学习者。压缩包共10个文件约9KB包含7个m脚本、2个pl脚本和1个txt配置文件m文件承担主流程与信号查找、周期计算等逻辑pl脚本用于报文预过滤以提升效率txt集中存放可配置参数。脚本支持同时解析多个asc文件按0.asc、1.asc依次命名和多个CAN信号信号名称需与dbc定义一致且asc、dbc与脚本须置于同一路径配置完成后运行AnalysisTask.m即可。该版本已做部分优化采用m与perl混合编程效率较初版明显提升源码附有部分注释便于二次修改与排错。目前已有2546人学习下载可作为CAN信号提取与批量转换的实用参考。1. 拿到一份 CAN 报文却打不开这个 MATLAB 脚本把 asc 文件拆成了能用的信号做汽车电子测试的同行大概率遇到过这种场景台架上跑完一轮路试标定工程师甩过来一个.asc文件说“里面是 CAN 信号你帮我看看车速和扭矩对得上不”。结果用记事本打开一看全是带时间戳的十六进制报文一行行密密麻麻肉眼根本没法分析。这个基于 MATLAB 2020a 的脚本就是冲着这个痛点来的——它把 Vector 工具链导出的 asc 日志解析成结构化的信号矩阵让你能在 MATLAB 里直接画曲线、做统计、跑批处理。适合谁用做 CAN 总线数据分析、ECU 标定验证、整车网络测试的工程师以及需要把 asc 日志接入自己算法流水线的开发者。源码是脚本形式不依赖额外工具箱拿到就能改。2. 先搞清楚 asc 文件里到底装了什么格式拆解与解析思路2.1 asc 文件的三种行类型asc 是 Vector CANalyzer/CANoe 的通用日志格式纯文本按行组织。解析之前必须先把行分类否则后面全是玄学 bug。常见的有三类第一类是头部信息行以date、base、timestamps开头记录日志生成时间、进制模式和时钟精度。第二类是报文行格式类似0.123456 1 123 Rx d 8 01 02 03 04 05 06 07 08依次是时间戳、通道号、CAN ID、方向、数据长度、8 字节数据。第三类是错误帧或注释行以//或ErrorFrame开头解析时要跳过。我一般会在脚本开头写一个正则把这三类行一次性分流避免在主循环里反复判断字符串前缀那样跑几万行日志会明显变慢。% 读取 asc 文件全部内容 fid fopen(ascFilePath, r); rawLines textscan(fid, %s, Delimiter, \n, Whitespace, ); fclose(fid); rawLines rawLines{1}; % 用正则区分报文行时间戳 通道 ID 方向 数据 msgPattern ^\s*([\d.])\s(\d)\s([0-9A-Fa-f])\s(Rx|Tx)\sd\s(\d)\s(.*)$; isMsg ~cellfun(isempty, regexp(rawLines, msgPattern, once)); msgLines rawLines(isMsg);这段代码的关键点在于textscan的Whitespace参数设为空保证每行原样读入不被截断。msgPattern里的捕获组分别对应时间戳、通道、CAN ID、方向、DLC 和数据段。isMsg是一个逻辑索引比逐行strncmp快得多十万行日志大概能省一半时间。2.2 时间戳与数据段的提取逻辑报文行拿到之后下一步是把字符串转成数值。时间戳直接str2double就行但数据段要小心不同工具导出的分隔符可能是空格也可能是制表符而且 DLC 可能小于 8不能无脑按 8 字节切。tokens regexp(msgLines, msgPattern, tokens, once); n numel(tokens); timeStamp zeros(n, 1); canId zeros(n, 1); dataBytes zeros(n, 8); % 统一按 8 字节存不足补 0 for k 1:n t tokens{k}; timeStamp(k) str2double(t{1}); canId(k) hex2dec(t{3}); dlc str2double(t{5}); payload sscanf(t{6}, %x); % 十六进制转十进制 dataBytes(k, 1:numel(payload)) payload; endhex2dec处理 CAN ID 时要注意扩展帧和标准帧的区别标准帧 11 位、扩展帧 29 位如果日志里混着两种建议额外存一个标志位。sscanf用%x格式直接把十六进制字符串转成数值数组比split再hex2dec每个字节快不少。dataBytes预分配成n×8的矩阵不足的位补 0这样后面做矩阵运算时不会因为维度不一致翻车。2.3 按 CAN ID 分组从报文流到信号矩阵原始报文是按时间顺序排列的但分析时通常关心的是“某个 ID 的信号随时间怎么变”。所以解析完要按 CAN ID 做一次分组把每个 ID 的时间戳和数据单独抽出来。uniqueIds unique(canId); signalMap struct(); for i 1:numel(uniqueIds) id uniqueIds(i); idx canId id; signalMap(i).id id; signalMap(i).time timeStamp(idx); signalMap(i).data dataBytes(idx, :); signalMap(i).count sum(idx); endunique返回排序后的 ID 列表canId id生成逻辑索引这样每个 ID 对应一个结构体包含时间序列和数据矩阵。后续要画某个信号的曲线直接plot(signalMap(i).time, signalMap(i).data(:,1))就行。如果日志里有几十个 ID这个结构体数组就是你的信号字典。3. 把脚本跑起来环境配置、文件读取与第一个信号曲线3.1 MATLAB 2020a 的环境准备这个脚本对环境的依赖很低MATLAB 2020a 基础安装即可不需要 Vehicle Network Toolbox也不需要 Simulink。唯一要注意的是文件编码asc 文件通常是 ASCII 或 UTF-8如果日志里有中文注释用fopen时要指定编码否则读出来是乱码。% 推荐用这种方式打开避免编码问题 fid fopen(ascFilePath, r, n, UTF-8);fopen的第三个参数n表示使用系统默认字节序第四个参数指定编码。如果你的日志是纯英文可以省略第四个参数。另外建议把脚本和 asc 文件放在同一个目录下用相对路径读取这样换台机器也能跑。3.2 主脚本的调用流程整个脚本的入口很直接指定文件路径调用解析函数拿到结构体数组然后做后续分析。我一般会把解析部分封装成一个独立函数parseAscFile.m主脚本只负责调用和可视化。% main_parse_asc.m ascFile test_log.asc; signalMap parseAscFile(ascFile); % 打印解析结果概览 fprintf(共解析 %d 个 CAN ID\n, numel(signalMap)); for i 1:numel(signalMap) fprintf(ID: 0x%X, 报文数: %d\n, signalMap(i).id, signalMap(i).count); end % 画第一个 ID 的第一个字节随时间的变化 figure; plot(signalMap(1).time, signalMap(1).data(:,1), b-); xlabel(时间 (s)); ylabel(字节值); title(sprintf(CAN ID 0x%X 字节1, signalMap(1).id)); grid on;parseAscFile内部就是前面讲的读取、正则匹配、循环提取、分组四步。主脚本拿到signalMap后你可以按自己的需求改可视化部分。比如想看车速就找到对应的 CAN ID 和字节位置把data(:,1)换成data(:,3)之类的。3.3 验证解析结果是否正确跑通之后别急着分析先做一次交叉验证。最简单的办法是拿 CANalyzer 或 CANoe 打开同一个 asc 文件对比报文总数和某个 ID 的时间戳范围。如果数量对不上大概率是正则漏掉了某些行比如带ErrorFrame的行或者通道号不是 1 的行。% 统计原始文件中的报文行数与解析结果对比 rawMsgCount sum(isMsg); parsedCount sum([signalMap.count]); fprintf(原始报文行数: %d, 解析后总数: %d\n, rawMsgCount, parsedCount); assert(rawMsgCount parsedCount, 解析数量不一致检查正则表达式);这个assert是我血泪经验换来的有一次日志里混了 CAN FD 报文DLC 是 12 而不是 8正则里的d\s(\d)虽然能匹配但后面按 8 字节存就丢了数据。后来加了 DLC 判断超过 8 的单独处理。所以验证这一步不能省。4. 避坑与排查解析 asc 文件时最容易翻车的五个地方4.1 现象解析出来的时间戳全是 0 或者顺序错乱原因asc 文件里的时间戳可能是相对时间也可能是绝对时间取决于导出设置。有些日志的时间戳单位是毫秒而不是秒直接str2double会得到很大的数。另外如果日志跨天时间戳会重置。解决先看文件头部的timestamps行确认单位。如果是毫秒解析后除以 1000。跨天的情况建议用datetime处理或者手动加偏移量。我一般会在解析函数里加一个timeUnit参数默认按秒处理遇到毫秒日志手动传参。4.2 现象某些 CAN ID 的数据字节全为 0原因正则里的数据段捕获组(.*)$匹配到了空字符串或者sscanf遇到非十六进制字符比如xx占位符直接返回空。解决在sscanf之前加一个判断如果数据段包含非十六进制字符跳过该行或者用 0 填充。另外有些日志用-表示无数据也要过滤掉。if isempty(regexp(t{6}, [^0-9A-Fa-f\s], once)) payload sscanf(t{6}, %x); else payload []; end4.3 现象脚本跑大文件时内存爆了原因textscan一次性读入整个文件如果 asc 文件几百 MB内存直接吃满。另外dataBytes预分配成n×8n 是报文总数大日志下也是内存杀手。解决分块读取每次读 10000 行解析完存到临时文件或者只保留需要的 ID。如果只需要特定几个 ID可以在正则匹配后先过滤再提取数据减少中间变量。4.4 现象扩展帧和标准帧的 ID 混淆原因标准帧 11 位 ID 范围是 0x000 到 0x7FF扩展帧 29 位 ID 范围更大。如果日志里两种帧混在一起hex2dec出来的数值没法区分。解决在报文行里扩展帧的 ID 通常带x后缀或者长度不同。解析时额外存一个isExtended标志后续分析时分开处理。4.5 现象中文注释导致读取中断原因fopen默认用系统编码Windows 下是 GBK而 asc 文件可能是 UTF-8遇到中文注释就乱码甚至textscan直接报错。解决统一用fopen(ascFilePath, r, n, UTF-8)如果文件确实是 GBK改成GBK。不确定的话可以用detectImportOptions自动检测编码。5. 二次开发把解析脚本改造成你自己的信号分析流水线5.1 封装成函数并支持批量处理原始脚本是单文件跑一次的实际项目里往往要批量处理几十个日志。改造思路很简单把解析逻辑封装成函数外层加一个循环遍历文件夹。function allResults batchParseAsc(folderPath) files dir(fullfile(folderPath, *.asc)); allResults cell(numel(files), 1); for i 1:numel(files) fpath fullfile(folderPath, files(i).name); allResults{i} parseAscFile(fpath); fprintf(已处理: %s\n, files(i).name); end endbatchParseAsc返回一个 cell 数组每个元素对应一个日志的解析结果。后续要做跨日志统计直接遍历这个 cell 数组就行。如果日志特别多可以加parfor并行但要注意parseAscFile里不能有共享状态。5.2 按信号名映射字节位置解析出来的signalMap是按 CAN ID 组织的但分析时你关心的是“车速”“扭矩”这些物理信号。常见做法是维护一个 DBC 文件或者一个映射表把 CAN ID 字节位置映射到信号名。% 简化的信号映射表ID, 起始字节, 长度, 信号名 signalDefs { 0x123, 1, 2, VehicleSpeed; 0x123, 3, 2, EngineTorque; 0x456, 1, 1, BrakePressure; }; for i 1:size(signalDefs, 1) id signalDefs{i, 1}; startByte signalDefs{i, 2}; len signalDefs{i, 3}; name signalDefs{i, 4}; idx find([signalMap.id] id, 1); if ~isempty(idx) raw signalMap(idx).data(:, startByte:startBytelen-1); % 这里可以加物理值转换比如乘以系数加上偏移 assignin(base, name, raw); end end这个映射表可以手动维护也可以从 DBC 文件自动生成。assignin把信号存到工作区方便后续直接调用。物理值转换那一步根据实际标定参数来比如车速可能是raw * 0.01。5.3 加一个简单的 GUI 或者 App如果想让不写代码的同事也能用可以用 MATLAB App Designer 包一层界面一个按钮选文件一个下拉框选 CAN ID一个坐标轴显示曲线。核心逻辑还是调用parseAscFile只是把输入输出接到 UI 组件上。% App Designer 里的回调函数示例 function LoadButtonPushed(app, ~) [file, path] uigetfile(*.asc); if isequal(file, 0) return; end app.signalMap parseAscFile(fullfile(path, file)); ids arrayfun((s) sprintf(0x%X, s.id), app.signalMap, UniformOutput, false); app.CanIdDropDown.Items ids; end这个改造不复杂但能显著降低使用门槛。我一般会先跑通脚本确认解析逻辑没问题再花半天时间包个界面。5.4 一个我踩过的坑别在循环里动态扩容早期版本我在解析循环里用dataBytes [dataBytes; payload]动态扩容小文件没事十万行日志直接卡死。MATLAB 的数组扩容是重新分配内存加拷贝复杂度 O(n²)。后来改成预分配zeros(n, 8)速度提升了几十倍。从那以后我每次写循环都强制走一遍预分配检查哪怕多写一行代码。希望这个习惯能帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站