目录
前言
Hex文件结构分析
1.利用notepad++打开hex文件
2.hex行格式 :行开始 数据长度 地址 数据类型 数据 校验和
3.校验和
完整代码
总结
Intel HEX文件是由一行行符合Intel HEX文件格式的文本所构成的ASCII文本文件。在Intel HEX文件中,每一行包含一个HEX记录。这些记录由对应机器语言码和/或常量数据的十六进制编码数字组成。Intel HEX文件通常用于传输将被存于ROM或者EPROM中的程序和数据。
在单片机烧录升级时,hex文件分别记录了flash dirver程序和升级的固件.在进行烧录前,需要先对每条记录进行解析和校验和计算.
如图,每行数据的不同位置被不同颜色标识出来.最后一个字节为校验和,校验和正确时为绿色。
行格式 | 说明 | 长度(字节) |
例子 |
---|---|---|---|
行开始 | :代表行开始 | : | |
数据长度 | 代表Bytes,数据长度 | 1 | 02 |
地址 | 地址 | 2 | 0000 |
数据类型 | 00: '记录数据', 01: '文件结束', 02: '扩展段地址的记录', # <<4 03: "开始段地址记录", 04: "扩展线性地址的记录", # <<16 05: "开始线性地址记录", # 对于嵌入式HEX无用 |
1 | 04 |
数据 | 这个记录的有效数据 | n | 0002 |
校验和 | 它表示这个记录的校验和。校验和的计算是通过将记录当中所有十六进制编码数字对的值相加,以256为模进行以下补足。 | 1 | F8 |
"""
python实现的校验和公式如下
row_line : param str 为每行记录,最后一个字节为校验和,
计算记录的前面部分和校验和是否相等
"""
row_line = "020000040002f8"
check_num = hex(0x100 - (sum([int(row_line[i:i + 2], 16) for i in range(0, len(row_line[:-2]), 2)])) & 0xFF)
# f8 == f8 pass
print(check_num == hex(int(row_line[-2:], 16)))
对整个文件进行校验和的计算,并获取它的有效数据
import os
class HexHandle(object):
# 数据类型
data_type_dict = {
0: '记录数据',
1: '文件结束',
2: '扩展段地址的记录', # <<4
3: "开始段地址记录",
4: "扩展线性地址的记录", # <<16
5: "开始线性地址记录", # 对于嵌入式HEX无用
}
def __init__(self, path):
# path hex文件的路径
self.path = path
def _get_hex_data(self):
text_list = []
if os.path.exists(self.path) is False:
return text_list
with open(self.path, 'rb') as fp:
_text_list = fp.readlines()
text_list = [str(_, 'utf-8').replace("\r\n", "").replace(":", "") for index, _ in enumerate(_text_list) if
_]
return text_list
def func_parse_hex(self, row_line):
# 数据长度
data_length = int(row_line[:2], 16)
# 数据类型
type_int = int(row_line[7:8], 16)
data_type = self.data_type_dict.get(type_int)
_index = row_line[2:6]
# 有效数据
data = row_line[-2 - data_length * 2:-2]
jy_text = row_line[-2:]
check_num = hex(0x100 - (sum([int(row_line[i:i + 2], 16) for i in range(0, len(row_line[:-2]), 2)])) & 0xFF)
is_pass = check_num == hex(int(jy_text, 16))
_pars_res = {
"数据类型": data_type,
"数据长度": f"{data_length}",
"偏移地址": _index,
# "源数据": row_line,
"有效数据": data,
"校验和": f"{jy_text}<->{check_num}",
"实际计算地址": "",
"校验结果": is_pass,
}
return _pars_res
def get_res(self):
"""
物理地址为:基地址 X 16D(相当于在基地址后面添一个0)+ 偏移地址
当一个扩展线性地址记录被读取,存储于数据域的扩展线性地址被保存,它被应用于从 Intel HEX 文件读取来的随后的记录
线性地址保持有效,直到它被另外一个扩展地址记录所改变
通过把记录当中的地址域与被移位的来自扩展线性地址记录的地址数据相加获得数据记录的绝对存储器地址
:return: dict
"""
data_list = self._get_hex_data()
data = ""
start_index = ""
start_index1, start_index2 = "", ""
is_pass = True
for _index, row_line in enumerate(data_list):
row_dict = self.func_parse_hex(row_line)
if row_dict["数据类型"] == "记录数据":
data += row_dict["有效数据"]
elif row_dict["数据类型"] == "扩展线性地址的记录" and _index == 0:
start_index1 = f'{row_dict["有效数据"]}'
_is_pass = row_dict["校验结果"]
if _is_pass is False:
is_pass = False
if _index == 1:
start_index2 = f'{row_dict["偏移地址"]}'
# 起始地址为, 第一行的有效数据和 第二行的偏移地址相加
if start_index1 and start_index2:
logger.info(f"start_index {start_index1} + {start_index2}")
start_index = hex(int(start_index1, 16) + int(start_index2, 16))[2:]
# 有效数据长度
data_length = int(len(data) / 2)
logger.info(f"data_length={data_length} start_index={start_index} 校验结果{is_pass}")
data_dict = {
"data": data,
"data_length": hex(data_length)[2:].rjust(8, "0"), # 4个字节 不足4个 用0补齐
"start_index": start_index.rjust(8, "0"), # 4个字节 不足4个 用0补齐
"is_pass": is_pass,
}
return data_dict
了解hex文件的构造方式,方能提取里面的有效数据.
关键信息是,起始地址,数据长度以及有效数据。其中,起始地址(4个字节)由首行的有效数据和第一行的偏移地址相加得到;有效数据是由所有的记录数据类型的数据累加得到;数据长度(4个字节,不足4个字节,用0补足)就是所有有效数据的总长度。