Intel HEX
Intel HEX 是一种用 ASCII 文本表达二进制文件信息的文件格式,对于我们玩嵌入式的人来说并不陌生。本文有两个目的:
- 阐明 Intel HEX 的文件格式;
- 用 C 语言解析和生成 HEX 文件。
## 文件格式
Intel HEX 是以记录( record )为单位的,文件的每行存放一条记录。记录的基本格式为:
!(https://file.mculoop.com/images/2022/11/intel-hex_1668589748.png)
<!--more-->
在一条记录中,除了起始标记( 1 字节的 ASCII 码 “ : ”),其他的部分均为 HEX 数字,两个字节的 HEX 数字表示一个字节的数据,所以后续的说明中若无特别说明,均表示转换后的数据。
**数据域长度**:1 字节,表示数据域的长度;
**地址**:2 字节,表示数据域的起始地址;
**记录类型**:1 字节,表示记录是何种类型的记录,含义如下:
- 00 数据记录
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">0B</span><span style="background-color: #92d050;">0010</span><span style="background-color: #00b050;">00</span><span style="background-color: #00b0f0;">6164647265737320676170</span><span style="background-color: #0070c0;">A7</span>
- 01 文件结束记录
用于表示 HEX 文件的结束,记录的数据域长度为 00 ,地址域为 0000 。
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">00</span><span style="background-color: #92d050;">0000</span><span style="background-color: #00b050;">01</span><span style="background-color: #0070c0;">FF</span>
- 02 扩展段地址记录
记录的数据域长度为 02 ,地址域为 0000 ,数据域为 2 字节的段基地址地址。由于数据记录( Type: 00 )的地址空间最大为 64 KB,再大就不能表达了。扩展段地址记录正是为了解决该问题,它将地址扩展至 20 位(扩展段地址表示绝对地址的 位),可达到 1 MB 的寻址空间。当扩展段地址为 0000 时可省略。
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">02</span><span style="background-color: #92d050;">0000</span><span style="background-color: #00b050;">02</span><span style="background-color: #00b0f0;">1200</span><span style="background-color: #0070c0;">EA</span>
表示绝对地址为 0x12000,数据的绝对地址 = 0x12000 + 数据记录地址。
- 03 起始段地址记录
记录的数据域长度为 04 ,地址域为 0000 ,数据域为 4 字节的地址,用于加载至 CS:IP 寄存器(前两字节加载至 CS 寄存器,后两字节加载至 IP 寄存器)。由于不具备烧写至 flash 的信息,一般忽略它。
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">04</span><span style="background-color: #92d050;">0000</span><span style="background-color: #00b050;">03</span><span style="background-color: #00b0f0;">00003800</span><span style="background-color: #0070c0;">C1</span>
- 04 扩展线性地址记录
记录的数据域长度为 02 ,地址域为 0000 ,数据域为 2 字节的扩展线性地址(绝对地址高 2 字节)。使用扩展线性地址可以将地址空间扩展到 32 位( 4 GB );当扩展线性地址为 0000 时可省略。显然比扩展段地址更强,更易用。
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">02</span><span style="background-color: #92d050;">0000</span><span style="background-color: #00b050;">04</span><span style="background-color: #00b0f0;">FFFF</span><span style="background-color: #0070c0;">FC</span>
表示后续的数据记录( Type:00,地址为 0xabcd )的绝对地址为 0xffffabcd 。
- 05 起始线性地址记录
记录的数据域长度为 04 ,地址域为 0000 ,数据域为 4 字节的地址(如 __main 的地址),用于加载至 EIP 寄存器。由于不具备烧写至 flash 的信息,一般忽略它。
<span style="background-color: #c00000;">:</span><span style="background-color: #ffc000;">04</span><span style="background-color: #92d050;">0000</span><span style="background-color: #00b050;">05</span><span style="background-color: #00b0f0;">000000CD</span><span style="background-color: #0070c0;">2A</span>
**数据域**:其长度由长度域定义,内容因记录的类型而异。
**校验**:1 字节,其计算方法是将冒号以后,校验域以前的内容按字节求和,再模 0x100 ,最后用 0x100 减去模运算结果的差即为校验。
```c
CS = 0x100 - sum % 0x100
或 CS = ~(sum & 0xff) + 1
或 CS = ((sum & 0xff) ^ 0xff) + 1
```
## C语言实现
注意:以下源码仅供参考!
```c
#define min(m, n) ((m) < (n) ? (m) : (n))
static const char digitc = "0123456789ABCDEF";
struct HexRecord
{
uint8_t type;
uint16_t address;
uint8_t data;
uint8_t dataLen;
};
/**
* 单字符转数值(支持到36进制)
* @paramc 待转换字符
* @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位
* @paramhex待转换的字符指针
* @parambyte 转换后的数值存储指针
* @return 0 出错1 转换了1个字符2转换了两个字符
*/
int hex2byte(const char *hex, uint8_t *byte)
{
uint8_t tmp;
if(hex)
{
tmp = char2int(hex);
if(tmp < 16)
{
*byte = tmp;
if(hex)
{
tmp = char2int(hex);
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 = digitc;
hex = digitc;
}
/**
* 将HEX字符串转换为数值
* 若出现非HEX字符将出错(不包括空格)
* 若有奇数个字符,则最后一个字符将转换为低4位
* @paramhex 待转换HEX字符串指针
* @parambin 转换后的数值存储指针
* @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)) != 0)
{
hex += j;
i++;
}
else
{
return -1;
}
}
return i;
}
/**
* 将数值转换为字符串
* @parambin 待转换的数值指针
* @paramlen 数值长度
* @paramhex 转换后的字符串的存储区指针
* @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;
}
/////////////////////////////////////////////////////////////
/**
* 计算记录的校验值
* @paramdata 记录的指针(指向长度域)
* @paramlen记录的长度(从长度域开始,不包含校验域)
* @return 校验值
*/
static uint8_t checksum(const uint8_t *data, int len)
{
uint8_t sum = 0;
while(len--)
{
sum += *data++;
}
return ~sum + 1;
}
/**
* 解析一条Intel HEX记录字符串
* @paramrecord 待解析记录字符串指针
* @paramrec 解析结构体存储指针
* @return 0 失败 1 成功
*/
int hexRecordAnalyse(const char *record, struct HexRecord *rec)
{
int len;
uint8_t bin;
if(record == ':')
{
if(strnlen(record + 1, 523) < 523)
{
len = hex2bin(record + 1, bin);
if(len >= 5)
{
if(bin + 5 == len)
{
if(bin == checksum(bin, len - 1))
{
rec->type = bin;
rec->address = (uint16_t)bin << 8 | bin;
memcpy(rec->data, bin + 4, bin);
rec->dataLen = bin;
return 1;
}
}
}
}
}
else if(record == '\0') //空行
{
rec->type = 0;
rec->address = 0;
rec->dataLen = 0;
return 1;
}
return 0;
}
/**
* Intel HEX记录转bin
* @paramstartAddr 起始地址,若HEX记录的地址小于此地址将出错
* @paramhex HEX记录的指针
* @parambin 解析后的数据存储指针
* @parammaxLen 存储区大小
* @paramfill 记录间的数据区填充值
* @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 << 12) | ((uint32_t)rec.data << 4);
}
else if(rec.type == 4) //扩展线性地址
{
extAddr = ((uint32_t)rec.data << 24) | ((uint32_t)rec.data << 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;
tmp = 2;
tmp = 0;
tmp = 0;
tmp = 4;
tmp = extAddr >> 8;
tmp = extAddr;
tmp = checksum(tmp, 4 + 2);
bin2hex(tmp, 7, record + 1);
record = ':';
record = '\r';
record = '\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;
tmp = dataLen;
tmp = addr >> 8;
tmp = addr;
tmp = 0;
memcpy(&tmp, data, dataLen);
tmp = checksum(tmp, 4 + dataLen);
bin2hex(tmp, 5 + dataLen, record + 1);
record = ':';
dataLen = (dataLen + 5) << 1;
record = '\r';
record = '\n';
return dataLen + 3;
}
/**
* add end-of-file record
* @paramrecord 记录存储指针
* @return 记录长度
*/
int addEndRec(char *record)
{
memcpy(record, ":00000001FF\r\n", 13);
return 13;
}
/**
* 将数据转换为Intel HEX记录
* @paramaddr 数据目标起始地址
* @parambin 数据的指针
* @paramlen 数据的长度
* @paramhex 转换记录的存储指针
* @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>
页:
[1]