hex文件格式剖析,以及hex与bin文件互相转换

news2024/11/17 13:48:02

大家好,我是学电子的小白白。

熟悉单片机开发的朋友,应该经常见到*.hex后缀的文件,它是单片机和嵌入式工程编译输出的一种常见的目标文件格式(比如keil就能编译输出hex文件),通过烧写工具把它下载到单片机中,程序就能在芯片中运行。

有些时候,比如我们在自己实现IAP时,又需要把编译后的目标文件转成*.bin文件的格式,才能往单片机中传输,之后bootloader程序会将接收到的bin文件固化到芯片中。

那么,hex文件和bin文件有什么区别呢,为什么有时用hex格式有时用bin格式?本篇文章就带大家来了解一下。

1)hex文件格式

这里,我在keil中编写了一个极简的LED闪烁程序,在keil中需要勾选输出hex设置:

如果不勾选,只会输出默认的axf格式目标文件。

编译之后,就可以输出hex格式的目标文件了,我们用普通的文本编辑器就可以打开hex目标文件,可以看到如下内容:

我们解释一下文件的内容:

a)hex文件内部是以文本格式来存储内容的,每行以冒号(:)起始,后面每两个字母是一个8bit的16进制数;

b)每行的格式:BBAAAATTD……DCC;

BB表示本行数据的长度;AAAA表示本行数据存储的地址;TT表示数据类型;DD的长度可长可短,是实际的数据;CC是校验和;

我们以第一行为例:020000040800F2

02是数据长度,也就是后面的实际数据“0800”的长度,2字节;

0000表示地址0;

04是数据类型,后面在详细解释;

0800是实际的数据;

F2是校验和,计算方法是,本行所有字节累加和(累加和只用低8位)取反再加1,即计算过程为0xF2=0x01 + not(0x02+0x04+0x08);

c)这里需要补充说明的是TT表示的数据类型

数据类型一共有6种形式:

'00'数据记录:用来记录数据,HEX文件的大部分记录都是数据记录;

'01'文件结束记录:用来标识文件结束,放在文件的最后,标识HEX文件的结尾;

'02'扩展段地址记录:用来标识扩展段地址的记录;

'03'开始段地址记录:开始段地址记录;

'04'扩展线性地址记录:用来标识扩展线性地址的记录;

'05'开始线性地址记录:开始线性地址记录;

这里仍然举几个例子说明,第一行:020000040800F2的数据类型是04,即扩展线性地址记录,表示的是这样一行的内容是地址的高位,也即DD区域表示地址高位为0x0800;当地址长度超过16bit时,就需要扩展线性地址记录来声明高位地址;

第二行:10000000000400200900000800F009F800F01CF8C6,数据类型是00,也就是数据记录,那么它的DD区域就是数据;同时,它的地址区域是0000,那么就表示,这一行的数据应该存在0000地址中;再结合上一行的扩展线性地址记录,它实际存储的地址应该是0x08000000。熟悉stm32单片机的朋友可能清楚,flash起始地址就是0x08000000:

第三行:1000100000F026F800F01EF800F022F8F6E703B52D,格式与第二行一样,但是地址不同,表示这一行的数据存在0x08000010地址中;我们发现这两地址相隔0x10,刚好是第一行的数据内容实际长度。

倒数第三行,格式与第二、三行也是一样的,只是长度不足0x10,只有0x08,这是已经到了实际内容的末尾。

Hex文件内大部分都是这种数据记录。

d)最后两行

:0400000508000009E6

:00000001FF

我们先解释末尾一行,:00000001FF,这行是表示文件结尾,所有的hex文件最后一行都可以是这个。

倒数第二行,数据类型是05,开始线性地址记录,其实它表示的是一个函数入口地址,但是这个函数地址并不会影响实际烧写到flash中的内容,我们可以不管它,MDK官方的解释是大多数情况下可以忽略它:

The Start Linear Address specifies the address of the __main (pre-main) function but not the address of the startup code which usually calls __main after calling SystemInit(). An odd linear start address specifies that __main is compiled for the Thumb instruction set.

The Start Linear Address Record can appear anywhere in hex file. In most cases this record can be ignored because it does not contain information which is needed to program flash memory.

到这里,hex文件中常见的一些格式就介绍完了,如果需要研究更深入,可以通过http://www.keil.com/support/docs/1584/

找到keil官方的解析(这个链接好像已经404了,可以在文末关注公*众*号找到保存的原文档)。

我们在做有IAP功能的项目时,有时需要把boot和app两段代码合并以后烧写,这样可以大大简化操作步骤,此时,可以把两个hex文件手动合并。

操作方法是,把其中一个hex文件最后的两行(开始线性地址记录、文件结束记录)删除:

再把另一个hex文件的所有内容都复制粘贴到其后,就可以了。

但是要注意,两个文件的地址区不能有重叠!

2)bin文件格式

相比hex文件,bin文件格式就简单得多了,它直接就是保存的需要烧写的目标文件内容,是没有任何附加格式的原始二进制文件。

我们把上一个工程设置为输出bin文件:

再编译输出,可以同时输出bin格式的目标文件。

使用十六进制文件编辑器打开,可以看到如下内容:

对比之前的hex文件格式,发现这些内容正好对应了hex文件中所有“数据记录”行中的实际数据内容。没有地址、校验、文件结尾等等一些附加内容。

3)hex文件和bin文件的对比和转换

通过前述的解释,我们了解了,bin文件只有最原始的程序数据,它与保存在单片机flash中的原始值是一样的;hex文件中,则除了程序数据外,还包含了地址、校验等等一些信息。

所以,一般在自己设计IAP程序时,直接传输bin文件可以简化bootloader软件的设计,减少一些处理过程,缩小boot程序的体积,但是要注意需要事先规定写入的地址;而通过上位机软件和下载器烧写时,使用hex文件,可以简化操作、增加可靠性。

一般软件IDE都可以生成hex文件,有的也能生成bin文件,如上文中提到的keil;st公司出品的STM32CubeProgrammer也可以将hex转bin。

如果软件没有这个功能,我们也不用重复造轮子,有很多前辈大佬已经做了转换工具,我们可以使用现成的。

我试用了多种,选了比较好用的一种hex转bin文件工具推荐给大家:

这个软件由“不咸不要钱”大佬编写,该大佬的软件开源地址:GitHub - mian2018/CSharp_Hex2Bin: 嵌入式 hex转bin

如果打不开,我在网盘里保存了一份,可以在我的公众号里找到网盘下载地址。

想要把bin文件转换为hex文件,就需要增加一些内容了,因为hex文件中比bin文件多的内容主要就是地址信息。

这里我也找了一个软件来转换,需要敲命令行,输入地址和文件名:

源码如下,也可在我的公众号里找到网盘下载地址。

/*
     使用方法 : bin2hex -b adress filename output
     -b : 指示hex文件起始地址
     address : hex文件的起始地址(输入4字节十六进制地址)
     filename : 待转换的文件名
     output : 输出文件名
     示例 : bin2hex -b 0x08000000 rom.bin out.hex
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE *fp_read;               /* 待读取文件句柄 */
FILE *fp_write;              /* 待写入文件句柄 */


unsigned long start_adr;     /* 转换成Hex格式的起始地址 */
unsigned short cur_base;     /* 转换成Hex格式的当前地址高16位 */
unsigned short cur_offset;   /* 转换成Hex格式的当前地址低16位 */
unsigned char read_buf[16];
unsigned char write_buf[48];

void calc_start_adr (char *buf)
{
	unsigned int len,i;
	char *str;
	char c;

	len = strlen(buf);

	if ((buf[0] == '0') && ((buf[1] == 'x')||(buf[1] == 'X')))
	{
		buf[1] = '0';
	}
	for(i=0; i<len; i++)
	{
		c = buf[i];
		if ((c >= '0') && (c <= '9'))
			{}
		else if((c >= 'A') && (c <= 'F'))
			{}
		else if((c >= 'a') && (c <= 'f'))
			{}
		else
		{
			printf ("Invalid argument.\n");
			exit (-1);
		}
	}

	/*十六进制字符串转换为地址*/
	start_adr = strtol(buf, &str, 16);
	cur_base =   (unsigned short)(start_adr >> 16);
	cur_offset = (unsigned short)start_adr;
}

void start_convert (void)
{
	unsigned char cnt;
	unsigned char read_num;
	unsigned char cksum, highc, lowc;

	/* 设置当前地址高16位 */
	highc = cur_base >> 8;
	lowc = (unsigned char)cur_base;
	cksum = 2 + 4 + highc + lowc;
	cksum = 0xFF - cksum;
	cksum = cksum + 1;
	sprintf (write_buf, ":02000004%04X%02X", cur_base, cksum);
	write_buf[15] = 0x0D;
	write_buf[16] = 0x0A;
	fwrite (write_buf, 1, 17, fp_write);

	read_num = fread (read_buf, 1, 16, fp_read);
	while (read_num == 16)
	{
		/* 写入读取的16字节 */
		highc = cur_offset >> 8;
		lowc = (unsigned char)cur_offset;
		cksum = 0x10 + highc + lowc;
		for (cnt=0; cnt<16; cnt++)
		{
			cksum += read_buf[cnt];
		}
		cksum = 0xFF - cksum;
		cksum = cksum + 1;

		sprintf (write_buf, ":10%02X%02X00%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
		         highc, lowc,
		         read_buf[0], read_buf[1], read_buf[2], read_buf[3],
		         read_buf[4], read_buf[5], read_buf[6], read_buf[7],
		         read_buf[8], read_buf[9], read_buf[10], read_buf[11],
		         read_buf[12], read_buf[13], read_buf[14], read_buf[15],
		         cksum);
		write_buf[43] = 0x0D;
		write_buf[44] = 0x0A;
		fwrite (write_buf, 1, 45, fp_write);

		/* 计算当前地址低16位,当越限时写入当前地址高16位 */
		if (cur_offset == 65520)
		{
			cur_offset = 0;
			cur_base ++;
			highc = cur_base >> 8;
			lowc = (unsigned char)cur_base;
			cksum = 2 + 4 + highc + lowc;
			cksum = 0xFF - cksum;
			cksum = cksum + 1;
			sprintf (write_buf, ":02000004%04X%02X", cur_base, cksum);
			write_buf[15] = 0x0D;
			write_buf[16] = 0x0A;
			fwrite (write_buf, 1, 17, fp_write);
		}
		else
		{
			cur_offset += 16;
		}

		read_num = fread (read_buf,1,16,fp_read);
	}

	/* 写入剩余的字节 */
	if (read_num)
	{
		highc = cur_offset >> 8;
		lowc = (unsigned char)cur_offset;
		cksum = read_num + highc + lowc;
		for (cnt=0; cnt<read_num; cnt++)
		{
			cksum += read_buf[cnt];
		}
		cksum = 0xFF - cksum;
		cksum = cksum + 1;

		sprintf (write_buf, ":%02X%02X%02X00", read_num, highc, lowc);
		for (cnt=0; cnt<read_num; cnt++)
		{
			sprintf (&write_buf[9 + cnt * 2], "%02X", read_buf[cnt]);
		}
		sprintf (&write_buf[9 + cnt * 2], "%02X", cksum);
		write_buf[11 + read_num * 2] = 0x0D;
		write_buf[12 + read_num * 2] = 0x0A;
		fwrite (write_buf, 1, 13 + read_num * 2, fp_write);
	}

	/* 写入终止序列 */
	sprintf (write_buf, ":00000001FF");
	write_buf[11] = 0x0D;
	write_buf[12] = 0x0A;
	fwrite (write_buf, 1, 13, fp_write);
}

int main (int argc, char *argv[])
{
	if (argc != 5)
	{
		printf ("Usage : %s -b address filename output\n", argv[0]);
		printf ("-b : indicate the starting address convert to.\n");
		printf ("address : starting address.\n");
		printf ("filename : file to be converted.\n");
		printf ("output :   file to output\n");
		printf ("example : %s -b 0x08003000 input.bin output.hex\n", argv[0]);
		return -1;
	}

	if (strcmp (argv[1], "-b"))
	{
		printf ("Invalid argument.\n");
		return -1;
	};

	fp_read = fopen (argv[3], "rb");
	if (fp_read == NULL)
	{
		printf ("Can't open file %s", argv[3]);
		return -1;
	}
	fp_write = fopen (argv[4], "wb");
	if (fp_write == NULL)
	{
		printf ("Can't create file %s",argv[4]);
		return -1;
	}

	calc_start_adr (argv[2]);
	start_convert ();

	fclose (fp_read);
	fclose (fp_write);

	printf("Convert Seccessfully!\n");

	return 0;
}

好了,本节内容就分享到这里了。

如果觉得有用,欢迎大家关注我,下载相关的学习资源和软件代码:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot项目实战】图片压缩包上传、解压、存储等等一套流程教学

【SpringBoot项目实战】图片压缩包上传、解压、存储等等一套流程教学前言一、压缩包上传1、接口实现2、获取压缩包的文件名和文件路径二、压缩包解压并保存1、处理压缩包文件方法解压缩步骤&#xff1a;2、接口中实现处理压缩包三、总结前言 之前一直用的zip4j来对压缩包进行操…

开源项目推荐 | 中科院自动化所历时9年打造的类脑认知智能引擎“智脉”正式开源部署至OpenI启智社区

​人脑能够自组织地协同数百项认知功能&#xff0c;灵活适应复杂多变的环境。如何整合多尺度生物可塑性法则来构建具有生物合理性和计算高效性的神经网络模型是类脑人工智能和计算神经科学领域共同关注和面临的重要挑战。 中国科学院自动化研究所类脑认知智能研究组历时9年&am…

通关算法题之 ⌈动态规划⌋

动态规划 动态规划是什么&#xff1f;解决动态规划问题有什么技巧&#xff1f;如何学习动态规划&#xff1f; 首先&#xff0c;动态规划问题的一般形式就是求最值。动态规划其实是运筹学的一种最优化方法&#xff0c;只不过在计算机问题上应用比较多&#xff0c;比如说让你求…

蓝桥杯寒假集训第二天(分巧克力)

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 题目描述&#xff1a; 有很多的巧克力块&#xff0c;需要设计一个程序&#xff0c;让手艺师傅切出来的巧克力既满足切出来的巧克力的份数达到客户要求的份数&#xff0c;并且切出来的巧克力块尽…

硬盘图片丢失怎么办?看看这常见的三种恢复方法

电脑硬盘能够帮助我们存储各种各样的数据&#xff0c;比如图片数据&#xff0c;而随着这些充满回忆的图片积累的越来越多&#xff0c;难免会手误删除或其他原因导致丢失&#xff0c;那么对于电脑硬盘上丢失的图片如何恢复呢&#xff1f;在这里你可以了解到硬盘图片丢失常见原因…

python—subprocess模块常用方法介绍

目录 subprocess.run() subprocess.Popen() subprocess.call() subprocess.check_call() subprocess.getstatusoutput() subprocess.getoutput() subprocess.check_output() subprocess是子流程&#xff0c;即进程的意思&#xff0c;该模块可以启动一个新进程&#xff0…

印度电线标准IS 694(R2020),印度插头标准IS 1293(R2020)

印度电线标准IS 694&#xff08;R2020) https://download.csdn.net/download/std86021/87328675POLYVINYLCHLORIDEINSULATEDUNSHEATHEDANDSHEATH更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/std86021/87328675 补丁一&#xff1a;IS694Amd…

已获1000 万美元战略融资,Web3链游#Delysium 有何魔力?

这周社区推文关注了AIGC在游戏领域中的创新应用&#xff0c;在Web3领域中&#xff0c;有哪些值得关注的链游项目呢&#xff1f;Mixlab小杜AIGC 与游戏机制创新玩法结合已成为潮流趋势&#xff0c;如游戏内容资产生成、智能NPC设计与自定义游戏玩法等都有非常大的探索空间。Web3…

【ACWING】【BFS】【844走迷宫】【845八数码】

一、走迷宫 给定一个 nm的二维整数数组&#xff0c;用来表示一个迷宫&#xff0c;数组中只包含 0 或 1&#xff0c;其中 0表示可以走的路&#xff0c;1 表示不可通过的墙壁。 最初&#xff0c;有一个人位于左上角 (1,1)处&#xff0c;已知该人每次可以向上、下、左、右任意一…

CVE复现1

1 CVE-2022-22980 spring Data MongoDB SpEL 表达式注入,Spring Data MongoDB应用程序在对包含查询参数占位符的SpEL表达式使用Query或Aggregation注解的查询方法进行值绑定时&#xff0c;如果输入未被过滤&#xff0c;则容易受到SpEL注入攻击。 影响范围 Spring Data Mongo…

190:vue+openlayers 调整卫星运动的播放速度,展示运动轨迹

第190个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中利用两行根数计算卫星在某一时刻的位置点,利用时间循环,定位出不同的位置,呈现动画的效果。文中利用slider的改变来确定当前时间为开始时间,slider的值为加速倍数,每个setTimeout为10毫秒,更新…

【博客系统】前端页面

博客系统 实现一个简单的博客系统。 当前先完成页面设计部分&#xff0c;通过学习的前端知识来构建出网页。 主要分成四个部分&#xff1a; 博客列表页博客正文页博客登陆页博客编辑页 预期效果 博客列表页效果 博客详情页效果 博客登陆页效果 博客编辑页效果 实现博客列…

C++数据结构--跳表的思想--手把手教你实现跳表--0721

1、 跳表--skiplist skiplist本质上是一种查找结构&#xff0c;跟平衡搜索树和哈希表的价值是一样的。跳表首先是一个链表&#xff0c;它是在链表的基础上发展的。但一般的链表进行查找数据只能全部遍历&#xff0c;时间复杂度为O(n)。 William Pugh的优化&#xff1a; 假如每…

Kafka一次线上问题

线上问题&#xff1a; Kafka: 客户说Broker不时会发生些错误日志&#xff0c;也看到topic的tps下降很快很明显&#xff0c; 日志看是ISR在不断的伸缩&#xff0c; 监控发现发生问题时的CPU、IO、磁盘都没有瓶颈 再查看堆栈信息&#xff1a;可看到关键信息&#xff1a; 有锁…

R语言raster包批量读取单一或大量栅格图像

本文介绍基于R语言中的raster包&#xff0c;读取单张或批量读取多张栅格图像&#xff0c;并对栅格图像数据加以基本处理的方法。 1 包的安装与导入 首先&#xff0c;我们需要配置好对应的R语言包&#xff1b;前面也提到&#xff0c;我们这里选择基于raster包来实现栅格图像数据…

TestStand-单执行界面

文章目录用户界面运行流程例程创建前面板设置用户界面主窗口前面板控件配置用户事件用户界面运行流程 用户界面设计的基本元素&#xff1a;管理控件、可视化控件、连接、应用程序启动及关闭、注册事件、处理事件。 LabVIEW中通过Regesiter Event Callback注册事件。 LabVIEW-Te…

flink内存管理, 增加Task内存大小,减少ManageMemory, network内存的方法

问题描述 flink默认分配的内存&#xff0c;不合理&#xff0c;jvm 堆内存太小&#xff0c;其他内存太大。向yarn申请8G内存&#xff0c;最后分配到heap的大小才3.2G&#xff0c;不是让人抓狂吗&#xff1f; 以上是&#xff0c;向yarn申请8G内存&#xff0c;实时分配的内存是上…

“破壁者”氚云,打破低代码之困

互联网云大厂的“火”已经烧红了低代码领域的半边天。 自低代码在国内盛行以来&#xff0c;尤其是时至2022年末&#xff0c;阿里、腾讯、华为等云大厂的跑马圈地仍如火如荼&#xff0c;动作密集程度堪比机关枪。 面对日益增长的企业数字化业务需求&#xff0c;产品经理只需少…

编译器设计(十三)——指令调度

一、简介 对程序块或过程中的操作进行排序以有效利用处理器资源的任务称为指令调度&#xff08;instruction scheduling&#xff09;。调度器的输入是由目标机汇编语言操作组成的一个部分有序的列表&#xff0c;输出是同一列表的一个有序版本。 一组指令的执行时间严重依赖于…

什么是云存储?有什么优势?

在云计算中&#xff0c;用户将数据保存在远程位置。它可以通过互联网连接访问&#xff0c;而不是在本地或物理上(在硬盘上)访问。而云存储成为最实用有效的方式之一。它有助于在线存储数据。 什么是云存储? 云存储是指安全、全局和可扩展的数据存储。它用于存储不可变数据&…