当前位置: 首页 > 工具软件 > srecord > 使用案例 >

[嵌入式开发模块]Motorola S-records(S19)解析模块

祁飞翰
2023-12-01

飞思卡尔系列单片机程序编译后生成的是S-records格式的文件(.s19)。在实现远程升级的时候,不可避免的需要传输S-record记录(或其转码后的数据),然后根据里头的数据来更新程序。这就需要对ASCII码表示的S-record数据进行解析。

于是就写了这个模块,或说工具包可能更准确点。
顺便一提。我已基于官方的版本基本实现了自定协议的bootloader,改成了软方式进入bootloader而不是官方的使用引脚的方式。代码不准备放出来,但后面会写篇文章介绍下自定的协议,原理,以及实现时遇到的坑等。

下面上代码:

/*
*********************************************************************************************************
*
*
*                              Motorola S-records Support package
*                                        S-records 支持包
*
* File    : SRecord.h
* By      : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date    : 2018/03/03
* Version : V1.0
* Note    : 1. This package provide the functions for converting between the S-record and the 
*                corresponding string.
*              这个包提供了S-record与字符串间的转换函数
*           2. The package supposed the letter in the string in upper case. user should make
*                sure for that.
*              工具包默认字符串中的字母都是大写的,用户需要保证这件事。
*           3. the first thing to do with this package is to check the TYPE DEFINE.
*              用这个包的第一步是检测下定义的类型对不对 
*
* the follow description of Motorola S-records Format is from 
*    http://www.amelek.gda.pl/avr/uisp/srecord.htm
* chinese version: http://blog.csdn.net/lin_strong/article/details/78521950
*
* NAME
*   srec - S-record file and record format
* DESCRIPTION
*
*   An S-record file consists of a sequence of specially formatted ASCII character strings. An S-record 
* will be less than or equal to 78 bytes in length.
*
*   The order of S-records within a file is of no significance and no particular order may be assumed.
*
*   The general format of an S-record follows:
*
*   +-------------------//------------------//-----------------------+
*   | type | count | address  |            data           | checksum |
*   +-------------------//------------------//-----------------------+
*
*   type -- A char[2] field. These characters describe the type of record (S0, S1, S2, S3, S5, S7, S8, or S9).
*
*   count -- A char[2] field. These characters when paired and interpreted as a hexadecimal value, display the 
* count of remaining character pairs in the record.
*
*   address -- A char[4,6, or 8] field. These characters grouped and interpreted as a hexadecimal value, display 
* the address at which the data field is to be loaded into memory. The length of the field depends on the number
* of bytes necessary to hold the address. A 2-byte address uses 4 characters, a 3-byte address uses 6 characters,
* and a 4-byte address uses 8 characters.
*
*   data -- A char [0-64] field. These characters when paired and interpreted as hexadecimal values represent 
* the memory loadable data or descriptive information.
*
*   checksum -- A char[2] field. These characters when paired and interpreted as a hexadecimal value display the
* least significant byte of the ones complement of the sum of the byte values represented by the pairs of 
* characters making up the count, the address, and the data fields.
*
*   Each record is terminated with a line feed. If any additional or different record terminator(s) or delay 
* characters are needed during transmission to the target system it is the responsibility of the transmitting 
* program to provide them.
*
*   S0 Record. The type of record is 'S0' (0x5330). The address field is unused and will be filled with zeros 
* (0x0000). The header information within the data field is divided into the following subfields.
*   
*     mname is char[20] and is the module name.
*     ver is char[2] and is the version number.
*     rev is char[2] and is the revision number.
*     description is char[0-36] and is a text comment.
*
*     Each of the subfields is composed of ASCII bytes whose associated characters, when paired, represent one 
* byte hexadecimal values in the case of the version and revision numbers, or represent the hexadecimal values 
* of the ASCII characters comprising the module name and description.
*
*   S1 Record. The type of record field is 'S1' (0x5331). The address field is intrepreted as a 2-byte address.
* The data field is composed of memory loadable data.
*
*   S2 Record. The type of record field is 'S2' (0x5332). The address field is intrepreted as a 3-byte address. 
* The data field is composed of memory loadable data.
*
*   S3 Record. The type of record field is 'S3' (0x5333). The address field is intrepreted as a 4-byte address. 
* The data field is composed of memory loadable data.
*
*   S5 Record. The type of record field is 'S5' (0x5335). The address field is intrepreted as a 2-byte value and 
* contains the count of S1, S2, and S3 records previously transmitted. There is no data field.
*
*   S7 Record. The type of record field is 'S7' (0x5337). The address field contains the starting execution 
* address and is intrepreted as 4-byte address. There is no data field.
*
*   S8 Record. The type of record field is 'S8' (0x5338). The address field contains the starting execution 
* address and is intrepreted as 3-byte address. There is no data field.
*
*   S9 Record. The type of record field is 'S9' (0x5339). The address field contains the starting execution 
* address and is intrepreted as 2-byte address. There is no data field.
*
* EXAMPLE
*
*   Shown below is a typical S-record format file.
*
*     S00600004844521B
*     S1130000285F245F2212226A000424290008237C2A
*     S11300100002000800082629001853812341001813
*     S113002041E900084E42234300182342000824A952
*     S107003000144ED492
*     S5030004F8
*     S9030000FC
*
*   The file consists of one S0 record, four S1 records, one S5 record and an S9 record.

*   The S0 record is comprised as follows:

*     S0 S-record type S0, indicating it is a header record.
*     06 Hexadecimal 06 (decimal 6), indicating that six character pairs (or ASCII bytes) follow.
*     00 00 Four character 2-byte address field, zeroes in this example.
*     48 44 52 ASCII H, D, and R - "HDR".
*     1B The checksum.
*
*   The first S1 record is comprised as follows:
*
*     S1 S-record type S1, indicating it is a data record to be loaded at a 2-byte address.
*     13 Hexadecimal 13 (decimal 19), indicating that nineteen character pairs, representing a 2 byte address,
*  16 bytes of binary data, and a 1 byte checksum, follow.
*     00 00 Four character 2-byte address field; hexidecimal address 0x0000, where the data which follows is 
*  to be loaded.
*     28 5F 24 5F 22 12 22 6A 00 04 24 29 00 08 23 7C Sixteen character pairs representing the actual binary
*  data.
*     2A The checksum.
*
*   The second and third S1 records each contain 0x13 (19) character pairs and are ended with checksums of 
* 13 and 52, respectively. The fourth S1 record contains 07 character pairs and has a checksum of 92.
*
*   The S5 record is comprised as follows:
*
*     S5 S-record type S5, indicating it is a count record indicating the number of S1 records
*     03 Hexadecimal 03 (decimal 3), indicating that three character pairs follow.
*     00 04 Hexadecimal 0004 (decimal 4), indicating that there are four data records previous to this record.
*     F8 The checksum.
*
*   The S9 record is comprised as follows:
*
*     S9 S-record type S9, indicating it is a termination record.
*     03 Hexadecimal 03 (decimal 3), indicating that three character pairs follow.
*     00 00 The address field, hexadecimal 0 (decimal 0) indicating the starting execution address.
*     FC The checksum.
*
*********************************************************************************************************
*/

#ifndef SRECORD_H 
#define SRECORD_H

/*
*********************************************************************************************************
*                                      INCLUDE
*********************************************************************************************************
*/

#include <stddef.h>

/*
*********************************************************************************************************
*                                      TYPE DEFINE
*********************************************************************************************************
*/

typedef unsigned char INT8U;
typedef unsigned long INT32U;

/*
*********************************************************************************************************
*                                      CONSTANT
*********************************************************************************************************
*/
// S-record only has defined type S0,S1,S2,S3,S5,S7,S8,S9
enum {
  SRECORD_TYPE_S0 = 0,
  SRECORD_TYPE_S1 = 1,
  SRECORD_TYPE_S2 = 2,
  SRECORD_TYPE_S3 = 3,
  // SRECORD_TYPE_S4,
  SRECORD_TYPE_S5 = 5,
  // SRECORD_TYPE_S6,
  SRECORD_TYPE_S7 = 7,
  SRECORD_TYPE_S8 = 8,
  SRECORD_TYPE_S9 = 9,
};

#define SRECORD_DATA_MAXCHARCNT    64
#define SRECORD_DATA_MAXLENGTH    (SRECORD_DATA_MAXCHARCNT / 2)

/*
*********************************************************************************************************
*                                      ERROR CODE 
*********************************************************************************************************
*/

enum{
  SREC_ERR_NO,              // if success
  SREC_ERR_FORMAT,          // if error in format, e.g. first char is not 'S', char in DATA is not digit.
  SREC_ERR_TYPE,            // if type of record is invalid
  SREC_ERR_CHECKSUM,        // if the checksum is wrong.
  SREC_ERR_TOOSHORT,        // if the length of the record is too short
  SREC_ERR_TOOLONG,         // if the length of the record is too long
};
/*
*********************************************************************************************************
*                                      TYPE DEFINITION 
*********************************************************************************************************
*/

typedef struct {
  INT8U RecType;
  INT8U DataLen;
  INT32U LoadAddr;
  INT8U Data[SRECORD_DATA_MAXLENGTH];       // hold datas that has been converted from hex char
} SRECORD_STRUCT;

/*
*********************************************************************************************************
*                                         PUBLIC FUNCTION
*********************************************************************************************************
*/

INT8U SRecord_StrToRec(INT8U *buf,SRECORD_STRUCT *result);
INT8U SRecord_RecToStr(SRECORD_STRUCT *srec,INT8U *result);

#endif
/*
*********************************************************************************************************
*
*
*                              Motorola S-records Support packetage
*                                        S-records 支持包
*
* File    : SRecord.c
* By      : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date    : 2018/03/03
* Version : V1.0
* Note    : 
*
*********************************************************************************************************
*/

#include <ctype.h>
#include "SRecord.h"

/*
*********************************************************************************************************
*                                      LOCAL FUNCTION DECLARATION
*********************************************************************************************************
*/
// description: convert hex char pair to coresponding byte.
// argument:  buf      pointer to the char waiting for convert.
//            perr     return the err
//                     0    if success
//                     1    if find not xdigit char
// return:    the converted value.
static INT8U GetHexByte(INT8U *buf,INT8U *perr);
// description: convert the byte to coresponding hex char pair.
// argument:  val      the byte to convert
//            buf      pointer to the buffer to hold the char pair, buffer should have
//                     two place at least.
// return:    pointer to the buf+2
static INT8U* PutHexCharPair(INT8U val,INT8U *buf);

/*
*********************************************************************************************************
*                                        SRecord_RecToStr()
*
* Description : try to convert the string to the S-record. 试着转换字符串为对应的S-record
*
* Arguments   : buf      pointer to the buffer that holds input.  指向要转换的字符串
*               result   return the result,if success;            返回成功转换后的S-record
*
* return      : SREC_ERR_NO         if success.
*               SREC_ERR_FORMAT     if error in format, e.g. first char is not 'S', char in DATA is 
*                                      not digit.
*               SREC_ERR_TYPE       if type of record is invalid(i.e. S4 or S6)
*               SREC_ERR_CHECKSUM   if the checksum is wrong.
*               SREC_ERR_TOOSHORT   if the length of the record is too short(according to the LENGTH field)
*               SREC_ERR_TOOLONG    if the length of the record is too long(according to the LENGTH field)
*
* Note(s): 1. for the S0 record ,this function just simply mark the result->RecType as S0, 
*               then return; with no further resolution.
*              对于S0记录,这个函数仅仅直接标记result->RecType为S0,然后就返回了,不做进一步解析。
*          2. there is no need for string to be ended with '\0', for the length of the S-record is
*               given in the LENGTH field, and thanks to the CKECKSUM, if anything is wrong, it's 
*               probobly been found.
*             实际上输入字符串没必要用'\0'来结尾,因为是根据S-record的LENGTH字段得知记录长度的,如果出了
*             任何问题,几乎都能通过校验码来发现。(除非正好让校验码通过了,这概率太小忽略不计)
*********************************************************************************************************
*/
INT8U SRecord_StrToRec(INT8U *buf,SRECORD_STRUCT *result){
  INT8U c,checksum,err;
  INT8U i,bytesLeftCnt,addrBytesCnt;
  INT8U *rstBuf;
  INT32U address;

  // TYPE field
  if(*(buf++) != 'S')
    return SREC_ERR_FORMAT;
  c = *(buf++) - '0';
  if(c > 9)
    return SREC_ERR_FORMAT;
  if(c == 4 || c == 6)
    return SREC_ERR_TYPE;
  result->RecType = c;
  if(c == SRECORD_TYPE_S0)
    return SREC_ERR_NO;

  // LENGTH field 
  bytesLeftCnt = GetHexByte(buf,&err);
  if(err != 0)
    return SREC_ERR_FORMAT;
  buf += 2;

  checksum = bytesLeftCnt;

  // here, c == TYPE, addrBytesCnt == length of ADDRESS
  if(c == SRECORD_TYPE_S1 ||c == SRECORD_TYPE_S5 || c== SRECORD_TYPE_S9){
    addrBytesCnt = 2;
  }else if (c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S8){
    addrBytesCnt = 3;
  }else{   //  if (c == SREC_TYPE_S3 || c == SREC_TYPE_S7)
    addrBytesCnt = 4;
  }

  if(bytesLeftCnt <= addrBytesCnt)   // at least a CHECKSUM field
    return SREC_ERR_TOOSHORT;
  bytesLeftCnt -= addrBytesCnt;

  // only S1,S2,S3 use Data field
  if (c > SRECORD_TYPE_S3){
    // only a CHECKSUM field
    if(bytesLeftCnt > 1)
      return SREC_ERR_TOOLONG;
  }else{
    // only S1,S2,S3 use Data field
    if(bytesLeftCnt > SRECORD_DATA_MAXLENGTH + 1)
      return SREC_ERR_TOOLONG;
  }

  address = 0;
  // ADDRESS field
  for(i = 0; i < addrBytesCnt; i++){
    c = GetHexByte(buf,&err);
    if(err != 0)
      return SREC_ERR_FORMAT;
    buf += 2;
    address = (address << 8) + c;
    checksum += c;
  }
  result->LoadAddr = address;

  result->DataLen = bytesLeftCnt - 1;
  rstBuf = result->Data;
  // DATA field
  for(i = 0; i < bytesLeftCnt - 1; i++){
    c = GetHexByte(buf,&err);
    if(err != 0)
      return SREC_ERR_FORMAT;
    buf += 2;
    *(rstBuf++) = c;
    checksum += c;
  }

  // CHECKSUM field
  c = ~GetHexByte(buf,&err);
  if(err != 0)
    return SREC_ERR_FORMAT;
  if(checksum != c)
    return SREC_ERR_CHECKSUM;//*/
  return SREC_ERR_NO;
}
/*
*********************************************************************************************************
*                                        SRecord_RecToStr()
*
* Description : Convert the S-record to the string(end with '\0').
*
* Arguments   : srec           the S-record to be converted;             要转换的S-record
*               result         pointer to the buffer that holds result.  指向存放结果的缓冲区
*
* return      : SREC_ERR_NO         if success.
*               SREC_ERR_TYPE       if type of record is invalid(i.e. S0,S4 or S6 and > 9)
*               SREC_ERR_TOOLONG    if the length of the record is too long(according to the LENGTH field)
*
* Note(s): 1. this function don't support S0, though it's defined in the standard.
*              虽然标准里有S0,但这个函数不支持转换它 
*          2. user should make sure that the result buffer is big enough.
*              用户需要自己确保保存结果的缓冲区足够大
*********************************************************************************************************
*/
INT8U SRecord_RecToStr(SRECORD_STRUCT *srec,INT8U *result){
  INT8U c,checksum = 0;
  INT8U len,i,addrBytesCnt;
  c = srec->RecType;
  // 不支持转换S0
  if( c > 9 || c == 4 || c == 6 || c == SRECORD_TYPE_S0)
    return SREC_ERR_TYPE;

  // TYPE field
  *result++ = 'S';
  *result++ = c + '0';

  if(c == SRECORD_TYPE_S1 ||c == SRECORD_TYPE_S5 || c== SRECORD_TYPE_S9){
    addrBytesCnt = 2;
  }else if (c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S8){
    addrBytesCnt = 3;
  }else{   //  if (c == SREC_TYPE_S3 || c == SREC_TYPE_S7)
    addrBytesCnt = 4;
  }
  if(srec->DataLen > SRECORD_DATA_MAXLENGTH)
    return SREC_ERR_TOOLONG;

  // 只有S1,S2,S3使用数据字段
  // LENGTH field
  if(c == SRECORD_TYPE_S1 || c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S3)
    len = srec->DataLen + addrBytesCnt + 1;   // ADDRESS + DATA + CHECKSUM
  else
    len = addrBytesCnt + 1;                   // ADDRESS + CHECKSUM
  result = PutHexCharPair(len,result);
  checksum += len;

  // ADDRESS field
  for(i = addrBytesCnt; i > 0 ; i--){
    c = (INT8U)(srec->LoadAddr >> ((i - 1) * 8));
    result = PutHexCharPair(c,result);
    checksum += c;
  }
  len -= addrBytesCnt;

  // DATA field
  for(i = 0;i < len - 1; i++){              // the least is the check sum
    c = srec->Data[i];
    result = PutHexCharPair(c,result);
    checksum += c;
  }

  // CHECKSUM field
  result = PutHexCharPair(~checksum,result);

  *result = '\0';
  return SREC_ERR_NO;
}


/*
*********************************************************************************************************
*                                      LOCAL FUNCTION DEFINITION
*********************************************************************************************************
*/

// description: convert hex char pair to coresponding byte.
// argument:  buf      pointer to the char waiting for convert.
//            perr     return the err
//                     0    if success
//                     1    if find not xdigit char
// return:    the converted value.
static INT8U GetHexByte(INT8U *buf,INT8U *perr)
{
  INT8U c,rst;

  *perr = 0;
  c = *(buf++);       //get an ASCII hex byte
  if (!isxdigit(c)){  //is it a valid hex digit
    *perr = 1;        //no. return an error
    return 0;
  }

  //convert the ASCII character
  rst = isdigit(c) ? (c - '0') : (c - 'A' + 10);

  c = *buf;           //get next ASCII hex byte
  if (!isxdigit(c)){  //is it a valid hex digit
    *perr = 1;        //no. return an error
    return rst;
  }

  //convert the ASCII character
  rst = (isdigit(c) ? (c - '0') : (c - 'A' + 10)) + (rst << 4);

  return(rst);         //return 'no error'
}

static INT8U* PutHexCharPair(INT8U val,INT8U *buf)
{
  INT8U c;
  c = val >> 4;
  *buf++ = (c > 9) ? (c - 10 + 'A') : (c + '0') ;
  c = val & 0x0F;
  *buf++ = (c > 9) ? (c - 10 + 'A') : (c + '0') ;
  return buf;
}

总共就两个函数,注释的也很清楚了,就不示例了。
要是使用中发现了什么bug或有什么建议意见,欢迎联系我,谢谢。

另附上S-record格式的说明http://blog.csdn.net/lin_strong/article/details/78521950

 类似资料: