阡陌 发表于 2023-12-19 01:09:48

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]
查看完整版本: Intel HEX