飞思卡尔系列单片机程序编译后生成的是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。