Intel HEX 是一种用 ASCII 文本表达二进制文件信息的文件格式,对于我们玩嵌入式的人来说并不陌生。本文有两个目的:
- 阐明 Intel HEX 的文件格式;
- 用 C 语言解析和生成 HEX 文件。
文件格式
Intel HEX 是以记录( record )为单位的,文件的每行存放一条记录。记录的基本格式为:
![](https://file.mculoop.com/images/2022/11/intel-hex_1668589748.png)
在一条记录中,除了起始标记( 1 字节的 ASCII 码 “ : ”),其他的部分均为 HEX 数字,两个字节的 HEX 数字表示一个字节的数据,所以后续的说明中若无特别说明,均表示转换后的数据。
数据域长度:1 字节,表示数据域的长度;
地址:2 字节,表示数据域的起始地址;
记录类型:1 字节,表示记录是何种类型的记录,含义如下:
-
00 数据记录
:0B0010006164647265737320676170A7
-
01 文件结束记录
用于表示 HEX 文件的结束,记录的数据域长度为 00 ,地址域为 0000 。
:00000001FF
-
02 扩展段地址记录
记录的数据域长度为 02 ,地址域为 0000 ,数据域为 2 字节的段基地址地址。由于数据记录( Type: 00 )的地址空间最大为 64 KB,再大就不能表达了。扩展段地址记录正是为了解决该问题,它将地址扩展至 20 位(扩展段地址表示绝对地址的 [19..4] 位),可达到 1 MB 的寻址空间。当扩展段地址为 0000 时可省略。
:020000021200EA
表示绝对地址为 0x12000,数据的绝对地址 = 0x12000 + 数据记录地址。
-
03 起始段地址记录
记录的数据域长度为 04 ,地址域为 0000 ,数据域为 4 字节的地址,用于加载至 CS:IP 寄存器(前两字节加载至 CS 寄存器,后两字节加载至 IP 寄存器)。由于不具备烧写至 flash 的信息,一般忽略它。
:0400000300003800C1
-
04 扩展线性地址记录
记录的数据域长度为 02 ,地址域为 0000 ,数据域为 2 字节的扩展线性地址(绝对地址高 2 字节)。使用扩展线性地址可以将地址空间扩展到 32 位( 4 GB );当扩展线性地址为 0000 时可省略。显然比扩展段地址更强,更易用。
:02000004FFFFFC
表示后续的数据记录( Type:00,地址为 0xabcd )的绝对地址为 0xffffabcd 。
-
05 起始线性地址记录
记录的数据域长度为 04 ,地址域为 0000 ,数据域为 4 字节的地址(如 __main 的地址),用于加载至 EIP 寄存器。由于不具备烧写至 flash 的信息,一般忽略它。
:04000005000000CD2A
数据域:其长度由长度域定义,内容因记录的类型而异。
校验:1 字节,其计算方法是将冒号以后,校验域以前的内容按字节求和,再模 0x100 ,最后用 0x100 减去模运算结果的差即为校验。
CS = 0x100 - sum % 0x100
或 CS = ~(sum & 0xff) + 1
或 CS = ((sum & 0xff) ^ 0xff) + 1
C语言实现
注意:以下源码仅供参考!
#define min(m, n) ((m) < (n) ? (m) : (n))
static const char digitc[16] = "0123456789ABCDEF";
struct HexRecord
{
uint8_t type;
uint16_t address;
uint8_t data[256];
uint8_t dataLen;
};
/**
* 单字符转数值(支持到36进制)
* @param c 待转换字符
* @return 0xff 转换错误, 0 ~ 35 转换结果
*/
uint8_t char2int(char c)
{
uint8_t ret = 0xff;
if(c >= '0' && c <= '9')
{
ret = c - '0';
}
else if(c >= 'A' && c <= 'Z')
{
c -= 'A';
ret = c + 10;
}
else if(c >= 'a' && c <= 'z')
{
c -= 'a';
ret = c + 10;
}
return ret;
}
/**
* 将2个HEX字符转换为数值
* 若出现非HEX字符或没有输入字符将出错
* 若只有一个字符将转换为低4位
* @param hex 待转换的字符指针
* @param byte 转换后的数值存储指针
* @return 0 出错 1 转换了1个字符 2转换了两个字符
*/
int hex2byte(const char *hex, uint8_t *byte)
{
uint8_t tmp;
if(hex[0])
{
tmp = char2int(hex[0]);
if(tmp < 16)
{
*byte = tmp;
if(hex[1])
{
tmp = char2int(hex[1]);
if(tmp < 16)
{
*byte = (*byte << 4) | tmp;
return 2;
}
}
else
{
return 1;
}
}
}
return 0;
}
/**
* 将1字节的数值转换为2字节的HEX字符
* @param byte 待转换数值
* @param hex 转换后的字符存储指针
*/
void byte2hex(uint8_t byte, char *hex)
{
hex[0] = digitc[byte >> 4];
hex[1] = digitc[byte & 0x0f];
}
/**
* 将HEX字符串转换为数值
* 若出现非HEX字符将出错(不包括空格)
* 若有奇数个字符,则最后一个字符将转换为低4位
* @param hex 待转换HEX字符串指针
* @param bin 转换后的数值存储指针
* @return -1 转换错误 大于或等于0 数值长度
*/
int hex2bin(const char *hex, uint8_t *bin)
{
int i, j;
for(i = 0; *hex;)
{
if(*hex == ' ')
{
hex++;
}
else if((j = hex2byte(hex, &bin[i])) != 0)
{
hex += j;
i++;
}
else
{
return -1;
}
}
return i;
}
/**
* 将数值转换为字符串
* @param bin 待转换的数值指针
* @param len 数值长度
* @param hex 转换后的字符串的存储区指针
* @return 转换后的字符串的存储区指针
*/
char *bin2hex(const uint8_t *bin, int len, char *hex)
{
char *ret = hex;
while(len--)
{
byte2hex(*bin++, hex);
hex += 2;
}
*hex = '\0';
return ret;
}
/////////////////////////////////////////////////////////////
/**
* 计算记录的校验值
* @param data 记录的指针(指向长度域)
* @param len 记录的长度(从长度域开始,不包含校验域)
* @return 校验值
*/
static uint8_t checksum(const uint8_t *data, int len)
{
uint8_t sum = 0;
while(len--)
{
sum += *data++;
}
return ~sum + 1;
}
/**
* 解析一条Intel HEX记录字符串
* @param record 待解析记录字符串指针
* @param rec 解析结构体存储指针
* @return 0 失败 1 成功
*/
int hexRecordAnalyse(const char *record, struct HexRecord *rec)
{
int len;
uint8_t bin[262];
if(record[0] == ':')
{
if(strnlen(record + 1, 523) < 523)
{
len = hex2bin(record + 1, bin);
if(len >= 5)
{
if(bin[0] + 5 == len)
{
if(bin[len - 1] == checksum(bin, len - 1))
{
rec->type = bin[3];
rec->address = (uint16_t)bin[1] << 8 | bin[2];
memcpy(rec->data, bin + 4, bin[0]);
rec->dataLen = bin[0];
return 1;
}
}
}
}
}
else if(record[0] == '\0') //空行
{
rec->type = 0;
rec->address = 0;
rec->dataLen = 0;
return 1;
}
return 0;
}
/**
* Intel HEX记录转bin
* @param startAddr 起始地址,若HEX记录的地址小于此地址将出错
* @param hex HEX记录的指针
* @param bin 解析后的数据存储指针
* @param maxLen 存储区大小
* @param fill 记录间的数据区填充值
* @return -1 超出范围 大于或等于0 解析后的数据大小(从bin处开始计,到最后一个有效的数据记录止)
*/
int hexRecord2bin(uint32_t startAddr, char *hex, uint8_t *bin, uint32_t maxLen, uint8_t fill)
{
char *pos = NULL;
struct HexRecord rec;
uint32_t len, addr, extAddr = 0, binLen = 0;
memset(bin, fill, maxLen);
while(*hex)
{
if((pos = strstr(hex, "\n")) != NULL)
{
if(*(pos - 1) == '\r') *(pos - 1) = '\0';
else *pos = '\0';
}
if(hexRecordAnalyse(hex, &rec))
{
if(rec.type == 0) //data
{
addr = extAddr + rec.address;
if(startAddr > addr || startAddr + maxLen < addr + rec.dataLen) return -1; //超出范围
len = addr - startAddr + rec.dataLen;
memcpy(bin + addr - startAddr, rec.data, rec.dataLen);
binLen = binLen < len ? len : binLen;
}
else if(rec.type == 1) //end
{
break;
}
else if(rec.type == 2) //扩展段地址
{
extAddr = ((uint32_t)rec.data[0] << 12) | ((uint32_t)rec.data[1] << 4);
}
else if(rec.type == 4) //扩展线性地址
{
extAddr = ((uint32_t)rec.data[0] << 24) | ((uint32_t)rec.data[1] << 16);
}
}
if(pos) hex = pos + 1;
else break;
}
return binLen;
}
/**
* add extended linear address record
* @param extAddr 线性扩展地址
* @param record 记录存储指针
* @return 记录长度
*/
int addExtLinAddrRec(uint16_t extAddr, char *record)
{
uint8_t tmp[8];
tmp[0] = 2;
tmp[1] = 0;
tmp[2] = 0;
tmp[3] = 4;
tmp[4] = extAddr >> 8;
tmp[5] = extAddr;
tmp[6] = checksum(tmp, 4 + 2);
bin2hex(tmp, 7, record + 1);
record[0] = ':';
record[15] = '\r';
record[16] = '\n';
return 17;
}
/**
* add data record
* @param addr 数据目标起始地址
* @param data 数据指针
* @param dataLen 数据长度
* @param record 记录存储指针
* @return 记录长度
*/
int addDataRec(uint16_t addr, const uint8_t *data, uint8_t dataLen, char *record)
{
uint8_t tmp[21];
tmp[0] = dataLen;
tmp[1] = addr >> 8;
tmp[2] = addr;
tmp[3] = 0;
memcpy(&tmp[4], data, dataLen);
tmp[4 + dataLen] = checksum(tmp, 4 + dataLen);
bin2hex(tmp, 5 + dataLen, record + 1);
record[0] = ':';
dataLen = (dataLen + 5) << 1;
record[dataLen + 1] = '\r';
record[dataLen + 2] = '\n';
return dataLen + 3;
}
/**
* add end-of-file record
* @param record 记录存储指针
* @return 记录长度
*/
int addEndRec(char *record)
{
memcpy(record, ":00000001FF\r\n", 13);
return 13;
}
/**
* 将数据转换为Intel HEX记录
* @param addr 数据目标起始地址
* @param bin 数据的指针
* @param len 数据的长度
* @param hex 转换记录的存储指针
* @return 0 转换失败 1 转换成功
*/
int bin2HexRecord(uint32_t addr, const uint8_t *bin, uint32_t len, char *hex)
{
uint32_t m, n;
if(0 - addr < len) //32位地址空间剩余空间不足够存储
{
return 0;
}
m = 0;
while(len)
{
if((addr & 0xffff0000) > (m & 0xffff0000))
{
m = addr;
hex += addExtLinAddrRec(addr >> 16, hex);
}
n = min(len, 16 - (addr % 16));
hex += addDataRec(addr, bin, n, hex);
bin += n;
addr += n;
len -= n;
}
hex += addEndRec(hex);
*hex = '\0';
return 1;
}
参考资料
GENERAL: INTEL HEX FILE FORMAT http://www.keil.com/support/docs/1584.htm
Intel HEX https://en.wikipedia.org/wiki/Intel_HEX