基于CAPL的S19文件解析

news2024/9/20 6:20:47
  • 🍅 我是蚂蚁小兵,专注于车载诊断领域,尤其擅长于对CANoe工具的使用
  • 🍅 寻找组织 ,答疑解惑,摸鱼聊天,博客源码,点击加入👉【相亲相爱一家人】
  • 🍅 玩转CANoe,博客目录大全,点击跳转👉

📘前言

  • 🍅 车载测试必不可少的是刷写,行业内有很多格式的刷写文件,S19,HEX,BIN,还有一些主机厂自定义的比如Volvo/GeeaVBFCheryCBF

  • 🍅 本章节先了解S19文件

请添加图片描述

目录

  • 📘前言
  • 📙 S19 文件的格式简介
    • 🍅 标准解释
    • 🍅 实例说明
    • 🍅 HEX View 神器
  • 📙CAPL解析S19文件源码
  • 🌎总结

请添加图片描述


📙 S19 文件的格式简介

🍅 标准解释

  • Motorola S-record是由Motorola创建的一种文件格式,它以 ASCII十六进制文本形式传送二进制信息。这种文件格式也可以称为SRECORD、SREC、S19、S28、S37。它通常用于对微控制器、EPROM 和其他类型的可编程逻辑设备进行编程。

  • S-record格式是在1970年代中期为 Motorola 6800处理器创建的。该处理器和其他嵌入式处理器的软件开发工具将生成S-record格式的可执行代码和数据。程序员将读取S-record格式并将数据“刻录”到嵌入式系统中使用的PROM或EPROM中

在这里插入图片描述


  • S19文件的每一行数据由下列五个部分组成:
    在这里插入图片描述

  • type(记录类型):用来描述记录的类型 (S0,S1,S2,S3,S5,S7,S8,S9)。

  • count(字节计数):2个字符,表示记录的其余部分(address + data + checksum)的字节数,该字段的最小值为 3,最大值为 255 。通常记录有 0x20 个数据字节。

  • address(地址):4或6或8个字符。由记录类型 type(记录类型)` 决定。对于S1和S9类型(S19),地址字段为 4 个十六进制数字(2 个字节)。对于S2和S8 record(S28),地址字段为 6 个十六进制数字(3个字节),对于S3和S7 record(S37),地址字段为 8 个十六进制数字(4 个字节)。地址首先以 MSB 发送。地址字节以大端格式排列。

  • data(数据):0—64字符。用来组成和说明一个代表了内存载入数据或者描述信息的16进制的值。

  • checksum(校验和):一个字节。Checksum = 取补码( (uint8_t)(Byte count + Address + Data) )。


🍅 实例说明

  • Notepad++ 打开选择语言,自动识别,根据颜色可以看出S19的记录格式
  • 看到的是16进制显示的ASCII文本格式

在这里插入图片描述
在这里插入图片描述


  • 重点说下S19的Type类型
  • S0是文件的第一行,头信息
  • 如果数据是S1开头,则地址占2个字节(文本字符串用4个字节表示),则文件最后一行的尾信息的开头就是用S9表示
  • 如果数据是S2开头,则地址占3个字节(文本字符串用6个字节表示),则文件最后一行的尾信息的开头就是用S8表示
  • 如果数据是S3开头,则地址占4个字节(文本字符串用8个字节表示),则文件最后一行的尾信息的开头就是用S7表示

再看下面的一个type 是S2的小实例 :地址占6个字节(注意这是ASCII文本表示的,实际地址是0x708000 )
S2 24 708000 18A718A718A718A718A718A718A718A718A718A718A718A718A718A718A718A7 FB

再看下面的一个type 是S1的小实例 ,地址占4个字节(注意这是ASCII文本表示的,实际地址是0x0020)
S1 23 0020 1A6416AE01DA6426AE024A6E01B6AE01EEDE029A6416AE01FA6406AE025A6436 21


🍅 HEX View 神器

  • HEX View 是一款专业的解析S19文件,HEX文件的工具,可以很方便的看出打开文件的Block块,起始地址和地址块的长度等信息
    在这里插入图片描述

📙CAPL解析S19文件源码


  • 核心变量解释下:
  • F_SegmentInfor[10]:用来存放解析S19文件的Block的信息,一般情况下,刷写文件的地址是不连续的,那么就会分成几个Block块,每个Block块我们要记录下该Block起止地址数据大小,因为这是我们UDS 34服务下载的必须数据,这里定义数组大小为10,足够大了,具体有多少个Block块,由变量SegCounter记录
  • FlsData_BufferArr[0x1FFFFFF]: 这个数组是记录是s19文件的所有字节,数组大小可由刷写文件具体大小设置,因为这个数组存放的是所有Block块的数据,那么当我下载的时候我怎么区分,去取数据呢,那就是 上面说的结构体中的 dword data_offset;这个变量来控制的,
  • AllDataBytes:该文件所有的数据字节数。
  • 我为什么没有这样定义 结构体呢?这样分段信息不是更加明确,刷写时取数据也更加方便直观吗?想一想为什么? struct FlsData_Segment
    {
    byte seg_index;
    dword start_address;
    dword data_size;
    byte FlsData_BufferArr[0x1FFFFFF];
    } F_SegmentInfor[10];//暂时定数组为10
/*@!Encoding:936*/

variables
{ 
  dword fileHandle;
  const dword  text_module = 0;
  const dword  binary_module = 1;
  enum File_Type { file_header, file_data,file_tail };
   
  struct  FlsData_Segment 
  {
  byte  seg_index;
  dword start_address;
  dword data_size;
  dword data_offset;     
  } F_SegmentInfor[10];//暂时定数组为10

  long   SegCounter;       //记录文件被分成多少个段
  long   AllDataBytes;     // 文件所有的数据字节数
  byte  FlsData_BufferArr[0x1FFFFFF]; //文件中的所有字节被解析后放在该数组  
}
 

on key 'a'
{
 
 Flash_Parse_S19("E:\\demo.s19");

}

long Flash_Parse_S19(char f_path[])
{
  long i;
  char tmpBuffer[5000];
  char temStr[255];
 
  byte AddressSize;          // s19文件的地址占多少个字节,S1是2个;S2是3个;S3是4个
  dword DataSize_PerLine_0;  // 两个地址之间的插值,是个固定值,比如0x20
  dword DataSize_PerLine_1;  // 每行数据的第2-3,两个字节,是改行的数据长度(包含地址,数据,和校验)
  dword DataSize_PerLine_2;  // 每行纯数据段的长度
  byte  RawData_Line[255];   // 解析后每行数据内容

  dword FlsData_CurrentAddress;
  dword FlsData_PreviousAddress;  
  
  long   file_line_num;    // 文件有多少行
  long   SegmentDataSize;  //记录当前段的字节数,临时变量

  
 
  /*初始化变量*/
  file_line_num = 0;
  SegCounter = 0;
  AllDataBytes = 0; 
  SegmentDataSize = 0;  
  FlsData_CurrentAddress = 0x00000000;
   
  fileHandle = OpenFileRead(f_path,text_module);
  
  if (fileHandle == 0 ) 
  {
    write("Failed to open File %s !",f_path);
    return 0 ;
  }

  write("File:%s Opened",f_path);
  
  while ( fileGetString(tmpBuffer,elcount(tmpBuffer),fileHandle) != 0)
  {
     file_line_num++; //数据行计数器
    
     if(S19_Record_Type(tmpBuffer[1],AddressSize) == file_header)
    {
       write("文件头数据:%s",tmpBuffer);
    }
        
    else if(S19_Record_Type(tmpBuffer[1],AddressSize) == file_data)
    {
  
      FlsData_PreviousAddress = FlsData_CurrentAddress;

      substr_cpy_off(temStr, 0, tmpBuffer, 2, 2, elcount(tmpBuffer));        // 截取字符串,取出数据长度
      snprintf(temStr, elcount(temStr), "0x%s",temStr);       
      strtoul(temStr,DataSize_PerLine_1);  //将数据长度 字符串转为数值

      substr_cpy_off(temStr, 0, tmpBuffer, 4, AddressSize, elcount(tmpBuffer)); // 截取字符串,取出当前行的地址
      snprintf(temStr, elcount(temStr), "0x%s",temStr);       
      strtoul(temStr,FlsData_CurrentAddress);  //将当前行的地址 字符串转为数值
      
      
      if (file_line_num == 2) //只执行一次,获取S19文件 第一段的刷写地址,和数据长度
      {
        DataSize_PerLine_0 = DataSize_PerLine_1; //DataSize_PerLine_0  是标准数据长度,DataSize_PerLine_1最后一行数据可能不等于标准数据长度
        F_SegmentInfor[SegCounter].start_address = FlsData_CurrentAddress;
        F_SegmentInfor[SegCounter].data_offset = 0; 
      }
      
      //分段处理
      if(((FlsData_CurrentAddress - FlsData_PreviousAddress) > DataSize_PerLine_0) && (FlsData_PreviousAddress != 0x00000000)) 
      {   
          write("Previous Address:0x%2X,Current Addresses:0x%2X ",FlsData_PreviousAddress,FlsData_CurrentAddress);
          F_SegmentInfor[SegCounter].data_size   = SegmentDataSize;  //上一段结束,保存上一段的字节数                                          
                
          SegCounter++;            
          F_SegmentInfor[SegCounter].start_address = FlsData_CurrentAddress; //保存下一段的首地址  
          F_SegmentInfor[SegCounter].data_offset += SegmentDataSize; //相对数组FlsData_BufferArr的开始索引地址 
        
         SegmentDataSize = 0;     // 重置计数变量,为这一段字节计数准备
      }

      // DataSize_PerLine_0 - AddressSize -1  : Byte count: 一个字节,表示后面其余部分(地址+数据+校验和)的字节数
      DataSize_PerLine_2 =  DataSize_PerLine_1*2 - AddressSize - 2 ;
      
      substr_cpy_off(temStr, 0, tmpBuffer, 4+AddressSize,DataSize_PerLine_2 , elcount(tmpBuffer)); // 截取字符串,取出当前行的数据    
      HexStrToByteArr(temStr,RawData_Line);
        
      for(i=0;i<DataSize_PerLine_2/2;i++)
      {
          FlsData_BufferArr[AllDataBytes] = RawData_Line[i];
          AllDataBytes ++ ; 
          SegmentDataSize ++ ;
      }  
        
    }
      else if(S19_Record_Type(tmpBuffer[1],AddressSize) == file_tail)
    {
       write("文件尾数据:%s\n",tmpBuffer);
           //最后一段的数据长度
      F_SegmentInfor[SegCounter].data_size = SegmentDataSize;
      F_SegmentInfor[SegCounter].data_offset = AllDataBytes - SegmentDataSize; //相对数组FlsData_BufferArr的开始索引地址   
      fileClose(fileHandle);
      
      SegCounter++;
      
      write("文件总行数:%d",file_line_num);
      write("文件总字节数:%d",AllDataBytes);
      write("文件总分段数:%d",SegCounter);

      
      for(i = 0; i < SegCounter; i++)
      {
      	write("数据块:%d,  起始地址:0x%X, 结束地址:0x%X, 数据长度:%6d字节\r\n", i+1, F_SegmentInfor[i].start_address, F_SegmentInfor[i].start_address + F_SegmentInfor[i].data_size - 1, F_SegmentInfor[i].data_size);
      }
      
    }
    else
    {
      write("S19为定义格式!");
      fileClose(fileHandle);
      return 0 ;
    }
    
  }
    
    return 1 ;
}



  
long S19_Record_Type(byte type ,byte &address_size)   
{
  
  
  if(type == '0') //s19文件的头部
  {
    return file_header; 
  }
  else if((type == '7') || (type == '8') || (type == '9') ) //s19文件的结束
  {
     return file_tail;  
  }
  else if((type == '1') || (type == '2') || (type == '3') ) //s19文件的数据
  {
    if(type== '1')
    {
      address_size = 4 ;  //地址占 4个字节      
    }
    else if(type == '2')
    {
       address_size = 6 ; //地址占6个字节    
    }
     else if(type == '3')
    {
       address_size = 8 ; //地址占 8个字节   
    }

    
    return file_data;  
  }
  else
  {
     write("Not Defined!"); 
  }
  
  return -1;  
}


byte HexStrToByteArr(char hexRawData[], byte outByteArr[])
{
   word i;
   word offset;
   word hexLength;
   word byteIndex;
   byte tmpVal;
   byte retVal;  
   for (i = 0; i < elcount(outByteArr); i++)
   {
      outByteArr[i] = 0;
   }
   
   // get the hex length
   hexLength = elcount(hexRawData);
   
   if( hexRawData[0] == '0' && hexRawData[1] == 'x' )
      offset = 2;   
   else
    offset = 0; 

   if ( elcount(outByteArr) <  (hexLength - offset)/2 )
   {
    write("Out Arrary too Small.");
    return 0;
   }
   else 
   {  
      //All checks went fine, convert data
      for (i = offset; i < hexLength; i++)
      {
         // The byte index to use for accessing output array
         byteIndex = (i - offset) / 2 ;         
         // convert the Hex data and validity check it
         tmpVal = (byte)hexRawData[i];
         if (tmpVal >= 0x30 && tmpVal <= 0x39)
            tmpVal = tmpVal - 0x30;
         else if(tmpVal >= 'A' && tmpVal <= 'F')
            tmpVal = tmpVal - 0x37;
         else if (tmpVal >= 'a' && tmpVal <= 'f')
            tmpVal = tmpVal - 0x57;
         else
         {
            return 0;
         }       
         
         if (0 == (i % 2))
         {
            outByteArr[byteIndex] = tmpVal << 4;
         }
         else
         {
            outByteArr[byteIndex] = outByteArr[byteIndex] | tmpVal;
         }
      }
   }
   
   return 1;
}




  • CAPL脚本跑出来的结果和HexView对比,结果一致,说明我们解析没问题

在这里插入图片描述

在这里插入图片描述

🌎总结

23

  • 🍅 有需要演示中所用demo工程的,可以关注下方公众号网盘自取啦,感谢阅读。

7

  • 🚩要有最朴素的生活,最遥远的梦想,即使明天天寒地冻,路遥马亡!

  • 🚩如果这篇博客对你有帮助,请 “点赞” “评论”“收藏”一键三连 哦!码字不易,大家的支持就是我坚持下去的动力。
    18

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1617573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Qt 学习笔记】Qt常用控件 | 按钮类控件 | Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件 | Check Box的使用及说明 文章编号&#xff…

js的算法-交换排序(快速排序)

快速排序 基本思想 快速排序的基本思想是基于分治法的&#xff1a;在待排序表L【1...n】中任意取一个元素p 作为枢轴&#xff08;或基准&#xff0c;通常取首元素&#xff09;。通过一趟排序将待排序表划分为独立的两部分L【1...k-1】和L【k1...n】;这样的话&#xff0c;L【1…

低代码技术与仓储管理的新纪元:革命性的供应链变革

引言 在当今数字化时代&#xff0c;企业对于创新和效率的追求越发迫切。在这样的背景下&#xff0c;低代码技术应运而生&#xff0c;成为企业数字化转型的重要工具之一。低代码技术的崛起为企业提供了一种快速、灵活、成本效益高的开发方式&#xff0c;大大缩短了软件开发周期…

如何把视频压缩变小?你应该知道的三个压缩视频的方法

一&#xff0c;视频压缩的基本原理 视频压缩的基本原理是通过去除视频中的冗余信息&#xff0c;减少视频数据的大小&#xff0c;从而达到压缩的目的。视频压缩的方法有很多种&#xff0c;其中最常见的是有损压缩和无损压缩。 二&#xff0c;视频压缩的必要性 1&#xff0c;视…

【智能算法】回溯搜索算法(BSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2013年&#xff0c;P Civicioglu等人受到当前种群与历史种群之间的差分向量的引导启发&#xff0c;提出了回溯搜索算法&#xff08;Backtracking Search Algorithm, BSA&#xff09;。 2.算法原理…

甄美天使1+3退休模式开发|最新升级模式

我是新零售商业架构师肖琳&#xff0c;易创客社交新零售创始‌‌‌‌‌人&#xff0c;擅长品牌从0-1、1-10起盘全过程。易创客新零售&#xff0c;提供商业模式解决方案系统&#xff0c;包括分销系统、微商系统、新零售系统&#xff1b;提供社交新零售陪跑代运营&#xff0c;提供…

Mybatis 一级缓存和二级缓存

文章目录 前言查询缓存一级缓存应用场景生效的条件测试一级缓存原理工作流程源码分析 一级缓存总结 二级缓存二级缓存配置源码分析 为什么 MyBatis 默认不开启二级缓存&#xff1f; 前言 MyBatis是常见的Java数据库访问层框架。在日常工作中&#xff0c;开发人员多数情况下是使…

宁盾LDAP统一用户认证与单点登录:构建高效安全的企业身份认证

在信息化时代&#xff0c;企业面临着众多的应用系统和数据资源&#xff0c;如何有效地管理和保护这些资源&#xff0c;确保信息安全和高效利用&#xff0c;成为了企业信息化建设的核心问题。LDAP统一用户认证和单点登录&#xff08;SSO&#xff09;作为一种高效、安全的身份验证…

学习笔记记录ensp中防火墙配置(trust,unstrus,dmz 资源下载可用)

实验目的&#xff0c;通过配置防火墙控制相互之间的访问&#xff0c;拓扑图如下 资源已上传&#xff0c;注意lsw1和ar2的路由表到各个网段的路由表配置&#xff0c;通过防火墙来控制各个区域能否访问成功。 防火墙通过cloud2链接&#xff0c;方便登录网页配置防火墙策略。防火…

构建智慧银行:现代化系统架构的探索与实践

在数字化时代&#xff0c;银行业正面临着前所未有的挑战和机遇。随着科技的不断发展&#xff0c;传统银行业务已经难以满足客户日益增长的需求&#xff0c;智慧银行系统的建设成为了行业的必然选择。本文将探讨智慧银行系统的架构设计与实践&#xff0c;以期为银行业的数字化转…

eCharts 折线图 一段是实线,一段是虚线的实现效果

在lineStyle里写了不生效的话&#xff0c;可以尝试数据拼接 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [150, 230, 224,218 ,,,],type: line},{data: [,,, 218, 135, 147, 260],type: line,lineStyl…

ESP32学习第一天-ESP32点亮LED,按键控制LED状态,LED流水灯

第一天使用到的函数: 函数第一个参数设置哪一个引脚&#xff0c;第二个参数设置引脚模式。 pinMode(led_pin,OUTPUT); //设置引脚模式 函数的第一个参数设置哪一个引脚&#xff0c;第二个参数设置是高电平还是低电平。 digitalWrite(led_pin,HIGH);//将引脚电平拉高 #incl…

Docker部署Nginx和SpringBoot项目,一遍把坑全踩完!

一.下载Docker 理论上来说&#xff0c;直接跟官网就行了&#xff0c;不过这里有个小坑。 官网默认推荐的是Docker Desktop&#xff08;一个docker图形化工具&#xff09;的安装方式&#xff0c;而不是Docker Engine(docker核心组件&#xff09;的安装。 正确查看安装文档的方式…

电脑怎么拖动文件到想要的位置?电脑上拖拽没了的文件怎么找回

在日常的办公和学习中&#xff0c;电脑文件拖拽操作是每位用户都不可或缺的技能。然而&#xff0c;有时在拖动文件时&#xff0c;可能会因为误操作或其他原因&#xff0c;导致文件消失或移至未知位置。本文将详细解析如何在电脑上轻松拖动文件到指定位置&#xff0c;并为您提供…

HarmonyOS开发案例:【音乐播放器】

介绍 使用ArkTS语言实现了一个简易的音乐播放器应用&#xff0c;主要包含以下功能&#xff1a; 播放应用中的音频资源文件&#xff0c;并可进行上一曲、下一曲、播放、暂停、切换播放模式&#xff08;顺序播放、单曲循环、随机播放&#xff09;等操作。结合后台任务管理模块&…

多因素不同水平的正交表设计(并列法)

文章目录 一、问题提出二、举例说明 一、问题提出 参考高等教育课本《实验设计与数据处理》 很多时候&#xff0c;我们要考察的因素水平数不尽相同&#xff0c;这时候一般采用混合水平正交表或者对普通的正交表作修改&#xff0c;其中&#xff0c;混合水平正交表由于水平数不规…

某翻译平台翻译接口逆向之加解密参数刨析

上文链接 某翻译平台翻译接口逆向之webpack学习 分析参数 加密参数&#xff1a; ${t} function S(e, t) {return _(client${u}&mysticTime${e}&product${d}&key${t}) } function k(e, t) {const n (new Date).getTime();return {sign: S(n, e),client: u,produc…

STM32 软件I2C方式读取MT6701磁编码器获取角度例程

STM32 软件I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《STM32 软件I2C方式读取AS5600磁编码器获取角度例程》&#x1f33f;《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f530;MT6701芯片和AS5600从软件读取对比&#xff0c;只是读取的寄存器和…

掉落回弹问题(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;float b 100;float sum 0;int i 0;//运算&#xff1b;for (i 1; i < 10; i){//运算&…