一、单片机中hex、bin文件的区别
单片机程序编译之后,除了生成hex文件之外还生成了bin文件,实际它们都是单片机的下载文件
Hex
Hex文件包含地址信息。
在用ISP方式烧写程序时,有这样的经验:
- 选择单片机型号
- 选择串口号
- 设置波特率(或者默认)
- 选择下载的文件
- 点击下载按钮下载
在串口工具中,操作如下图红框所示。
经过这几步后,程序下载工作就完成了,在以上的步骤中我们并没有选择要把程序下载到单片机的哪块内存中,即不需要设置地址。因为HEX文件内部的信息已经包括了地址。
BIN
BIN文件格式只包括了数据本身,没有包含地址。烧写BIN文件的时候,用户是一定需要指定地址信息的。
所以在下载bin文件时需要选择内存的起始地址和终止地址,即要把bin文件下载到指定的内存空间。
通常需要指定程序内存地址的芯片为ARM芯片和DSP芯片。
文件大小
对于bin文件,通过右键属性查看到的文件的大小就是数据的实际大小。
而对HEX文件而言,你看到的文件大小并不是实际的数据的大小。一是因为HEX文件是用ASCII来表示数据,二是因为HEX文件本身还包括别的附加信息。
#STM32单片机中Hex、Bin文件的区别与应用
STM32、51等单片机程序经过编译后,生成的hex文件、bin文件,它们都是单片机烧写文件,本文介绍它们的区别与应用。
Hex文件
Keil5中生成hex文件的配置
如上图,分别点击“魔术棒”-“Output选项卡”,勾选“Create HEX File”选项,确认即可。
STM32CubeIDE中生成hex文件的配置
如上图,先用鼠标点击选中项目名,之后点击菜单栏“File”-“Properties”。
如上图红框处,依次点击“C/C++ Build”-“MCU Post build outputs”,勾选“Convert to Intel Hex file”,应用并关闭窗口。
hex烧写
用ISP方式烧写程序,首先找来ISP烧写软件,之后进行如下步骤:
- 选择芯片型号
- 选择串口号
- 设置波特率,可以默认为115200
- “打开文件”,选择要下载的hex文件
- 点击“程序下载”,开始烧写程序
带ISP下载功能的串口工具如下图所示。
调试单片机程序时,通常下载的是Hex文件。由于在Hex文件中已经包含了地址信息,在上述下载步骤中不需要设置内存地址。
BIN文件
Keil5中生成Bin文件配置
如上图,点击魔术棒,在“User”选项卡中勾选“After Build/Rebuild”下的“Run #1”。
在后面“User Command”一栏中填写如下用户自定义命令:
fromelf.exe --bin -o .\lcd1602a\lcd1602a.bin .\lcd1602a\lcd1602a.axf
这个自定义命令在编译生成Hex文件之后执行,通过“formelf.exe”工具生成基于.axf文件的.bin文件。如下图,按修改时间排序也可以知道.bin文件是在.axf文件之后生成的。Hex文件是用ASCII来表示数据,而且附加了地址信息,相对Bin文件要大一些。
STM32CubeIDE中生成Bin的配置
如上图,只需要勾选“Convert to binary file”即可。
平时调试STM32单片机程序用的是Hex文件,而在IAP升级固件时要用到Bin文件,IAP升级固件
二、STM32单片机的启动过程
STM32启动流程
STM32三种启动模式
下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存,这就是所谓的启动过程。
STM32上电或者复位后,代码区始终从0x00000000开始,其实就是将存储空间的地址映射到0x00000000中。三种启动模式如下:
- 从主闪存存储器启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。主闪存存储器是STM32内置的Flash,作为芯片内置的Flash,是正常的工作模式。一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。
- 从系统存储器启动。首先控制BOOT0、BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码。系统存储器是芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM区。启动的程序功能由厂家设置。系统存储器存储的其实就是STM32自带的bootloader代码。
- 从内置SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始。内置SRAM,也就是STM32的内存,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。假如我只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码,用于快速的程序调试,等程序调试完成后,在将程序下载到SRAM中。
用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。STM32三种启动模式对应的存储介质均是芯片内置的,如下图:
串口下载程序原理
从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序,这是一块ROM,出厂后无法修改。
一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中
这个下载方式需要以下步骤:
- 将BOOT0设置为1,BOOT1设置为0,然后按下复位键,这样才能从系统存储器启动BootLoader;
- 在BootLoader的帮助下,通过串口下载程序到Flash中;
- 程序下载完成后,又有需要将BOOT0设置为GND,手动复位,这样,STM32才可以从Flash中启动。
从汇编代码分析STM32启动过程
STM32的启动文件与编译器有关,不同编译器,它的启动文件不同。虽然启动文件(汇编)代码各有不同,但它们原理类似,都属于汇编程序,相关文章:详解STM32启动文件。拿基于MDK-ARM的启动文件来举例,说一下要点内容。在基于MDK的启动文件开始,有一段汇编代码是分配堆栈大小的。
这里重点知道堆栈数值大小就行。还有一段AREA(区域),表示分配一段堆栈数据段。可以使用STM32CubeMX对上面的数值大小进行配置:
在IAR中,是通过工程配置堆栈大小:
看下面的汇编代码,程序上电之后,是跳到Reset_Handler这个位置。
知道代码是从Reset_Handler开始执行,再来看如下Reset_Handler汇编代码。在启动的时候,执行了SystemInit这个函数。
执行完SystemInit函数,初始化了系统时钟,之后跳转到main函数执行。
三、SMT32的HEX文件里加入固件版本的方法
使用MDK编译器,让STM32程序HEX文件中加入固件版本信息。
代码
代码如下:
//------------------------------------------------------------------------------
#include <absacc.h>
//------------------------------------------------------------------------------
#define VERINFO_ADDR_BASE (0x8009F00) // 版本信息在FLASH中的存放地址
const char Hardware_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00))) = "Hardware: 1.0.0";
const char Firmware_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x20))) = "Firmware: 1.0.0";
const char Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Date: "__DATE__;
const char Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Time: "__TIME__;
//------------------------------------------------------------------------------
写入到程序中:
选项配置中:Flash地址与大小不用做任何修改!
HEX文件:
串口打印输出:
上述方法的缺点
上述操作可行, 但是有一个缺点:就是生成的bin文件都是满flash大小的, 造成每次烧录都是整个flash读写。
其实这个可以把存放地址放到前面,比如偏移1K的地方,都不用改指定地址。
按照上述操作,程序末尾到VERINFO_ADDR_BASE地址这一段会被填充成0x00。根据需要可以修改VERINFO_ADDR_BASE减小地址,或者说不强制指定地址,由编译器自动分配,但这样就要去找相应的版本标识字符串了。
优化方法
不想前面这一段被大量填充0x00,让HEX文件体积小一点的话, 可以把选项配置中Flash的Size改小一点,把VERINFO_ADDR_BASE设置成从FlashSize后面的空间开始,这样生成的HEX文件就小了,且未用空间就不会被大量填充0x00了。
方法如下:
四、STM32嵌入式开发中的RTOS
1 FreeRTOS
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。
相对于C/OS-II、 embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为6.0版。
作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。
FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。
FreeRT0S内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。
FreeRTOS的不足:相对于常见的μC/OS—II操作系统,FreeRTOS操作系统既有优点也存在不足。其不足之处, 一方面体现在系统的服务功能上,如FreeRTOS只提供了消息队列和信号量的实现,无法以后进先出的顺序向消息队列发送消息;另一方 面,FreeRTOS只是一个操作系统内核,需外扩第三方的GUI(图形用户界面)、TCP/IP协议栈、FS(文件系统)等才能实现一个较复杂的系统, 不像μC/OS-II可以和μC/GUI、μC/FS、μC/TCP-IP等无缝结合。
在STM32CubeIDE中直接可以配置FreeRTOS
FreeRTOS资料多、生态活跃,在Cube中通过配置界面,三两下上手这款操作系统,推荐拿来入门。
基于不同的需求,下文再介绍下其他RTOS。
2 μClinux
μClinux是一种优秀的嵌入式Linux版本,其全称为micro-control Linux,从字面意思看是指微控制Linux。
同标准的Linux相比,μClinux的内核非常小,但是它仍然继承了Linux操作系统的主要特性,包括良好的稳定性和移植性、强大的网络功能、出色的文件系统支持、标准丰富的API,以及TCP/IP网络协议等。因为没有MMU内存管理单元,所以其多任务的实现需要一定技巧。
μClinux在结构上继承了标准Linux的多任务实现方式,分为实时进程和普通进程,分别采用先来先服务和时间片轮转调度,仅针对中低档嵌入式CPU特点进行改良,且不支持内核抢占,实时性一般。
综上可知,μClinux最大特点在于针对无MMU处理器设计,这对于没有MMU功能的STM32F103来说是合适的,但移植此系统需要至少512KB的RAM空间,1MB的ROM/FLASH空间,而STM32F103拥有256K的FLASH,需要外接存储器,这就增加了硬件设计的成本。
μClinux结构复杂,移植相对困难,内核也较大,其实时性也差一些,若开发的嵌入式产品注重文件系统和与网络应用则μClinux是一个不错的选择。
3 μC/OS-II
μC/OS-II是在μC/OS的基础上发展起来的,是用C语言编写的一个结构小巧、抢占式的多任务实时内核。μC/OS-II能管理64个任务,并提供任务调度与管理、内存管理、任务间同步与通信、时间管理和中断服务等功能,具有执行效率高、占用空间小、实时性能优良和扩展性强等特点。
在文件系统的支持方面,由于μC/OS-II是面向中小型嵌入式系统的,即使包含全部功能,编译后内核也不到10 KB,所以系统本身并没有提供对文件系统的支持。但是μC/OS-II具有良好的扩展性能,如果需要也可自行加入文件系统的内容。
在对硬件的支持上,μC/OS-II能够支持当前流行的大部分CPU,μC/OS-II由于本身内核就很小,经过裁剪后的代码最小可以为2KB,所需的最小数据RAM空间为4 KB,μC/OS-II的移植相对比较简单,只需要修改与处理器相关的代码就可以。
综上可知,μC/OS-II是一个结构简单、功能完备和实时性很强的嵌入式操作系统内核,针对于没有MMU功能的CPU,它是非常合适的。它需要很少的内核代码空间和数据存储空间,拥有良好的实时性,良好的可扩展性能,并且是开源的,网上拥有很多的资料和实例,所以很适合向STM32F103这款CPU上移植。
4 eCos
eCos(embedded Configurable operating system),即嵌入式可配置操作系统。它是一个源代码开放的可配置、可移植、面向深度嵌入式应用的实时操作系统。
最大特点是配置灵活,采用模块化设计,核心部分由小同的组件构成,包括内核、C语言库和底层运行包等。
每个组件可提供大量的配置选项(实时内核也可作为可选配置),使用eCos提供的配置工具可以很方便地配置,并通过不同的配置使得eCos能够满足不同的嵌入式应用要求。
eCos操作系统的可配置性非常强大,用户可以自己加入所需的文件系统。eCos操作系统同样支持当前流行的大部分嵌入式CPU,eCos操作系统可以在16位、32位和64位等不同体系结构之间移植。
eCos由于本身内核就很小,经过裁剪后的代码最小可以为10 KB,所需的最小数据RAM空间为10 KB。
在系统移植方面 eCos操作系统的可移植性很好,要比μC/OS-II和μClinux容易。
综上所述,eCos最大特点是配置灵活,并且支持无MMU的CPU的移植,开源且具有很好的移植性,也比较合适于移植到STM32平台的CPU上。但eCOS的应用还不是太广泛,还没有像μC/OS-II那样普遍,并且资料也没有μC/OS-II多。eCos适合用于一些商业级或工业级对成本敏感的嵌入式系统,例如消费电子领域中的一些应用。
5 mbed OS
开源嵌入式操作系统,ARM公司将mbed OS免费提供给所有厂商使用,mbed提供了一个相对更加系统和更加全面的智能硬件开发环境。
主要功能:
提供用于开发物联网设备的通用操作系统基础,以解决嵌入式设计的碎片化问题。支持所有重要的连接性与设备管理开放标准,以实现面向未来的设计。使安全可升级的边缘设备支持新增处理能力与功能。通过自动电源管理解决复杂的能耗问题。
主要特点:
开发速度快,功能强大,安全性高,为了量产化而设计,可离线开发,也可以在网页上编辑。
6 RTX
是ARM公司的一款嵌入式实时操作系统,使用标准的C结构编写,运用RealView编译器进行编译。不仅仅是一个实时内核,还具备丰富的中间层组件,不但免费,而且代码也是开放的。
主要功能:
开始和停止任务(进程),除此之外还支持进程通信,例如任务的同步、共享资源(外设或内存)的管理、任务之间消息的传递。开发者可以使用基本函数去开启实时运行器,去开始和终结任务,以及去传递任务间的控制(轮转调度)。开发者可以赋予任务优先级。
主要特点:
支持时间片,抢占式和合作式调度。不限制数量的任务,每个任务都具有254的优先级。不限制数量的信号量,互斥信号量,消息邮箱和软定时器。支持多线程和线程安全操作。使用MDK基于对话框的配置向导,可以很方便的完成MDK的配置。
7 都江堰
都江堰操作系统,简称djyos,得名于一个伟大的水利工程:都江堰。
与传统操作系统不同,djyos不是以线程而是以事件为调度核心,这种调度算法使程序员摆脱模拟计算机执行过程编写程序的思维方式,而是按人类认知世界的方式编写应用程序,就如同在嵌入式编程中引入了VC似的。
djyos的调度算法使程序员可以摆脱线程和进程的束缚,djyos没有有关线程的api,一个完全不懂线程知识的程序员也可以顺利地在djyos下编写应用程序。
djyos 操作系统是以事件为核心进行调度的,这种调度策略使程序员可以按人类认知事物的习惯而不是计算机的习惯来编程。
8 RT-Thread
嵌入式操作系统RTOS介绍,RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。
RT-Thread具备一个IoT OS平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过两千万台,成为国人自主开发、国内最成熟稳定和装机量最大的开源RTOS。
国内最有可能成为Top 1,优势在于丰富的组件,中立立场!赶上了时机,得到诸多芯片厂商的支持,也挺受开发者喜欢的。缺点在于本身的教程文档和freertos等之类的比还是很弱。
五、STM32低功耗模式
低功耗模式
本文讨论下STM32低功耗模式,先看如下手册。
对比了 STM32F0 和 STM32F1 两者进入低功耗是一样的,低功耗模式有三种:
- 睡眠模式,CM3 内核停止,外设仍然运行,此功耗是最高的
- 停止模式,所有时钟都停止,此功耗较低,典型大概在20uA左右
- 待机模式,1.8V 内核电源关闭,此功耗最低,典型大概在2uA左右
一般做开发大多都是选择停机模式,因为停机模式功耗较低,而且任一中断或事件都能唤醒。待机模式虽然功耗最低,电流只差10个微安,但是只有特定的事件和引脚可以唤醒,实时性不是很好。
先来看下官方库进入低功耗的方式。
void PWR_EnterSleepMode(uint8_t PWR_SLEEPEntry); //睡眠模式
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry); //停机模式
void PWR_EnterSTANDBYMode(void); //待机模式
这里我们用到停机模式,有两个参数 。
第一个PWR_Regulator是选择电源是否进入低功耗。
#define PWR_Regulator_ON //电源不进低功耗 唤醒基本没延迟
#define PWR_Regulator_LowPower //电源进去低功耗 不过唤醒启动有一点延迟
第二个参数PWR_STOPEntry选择唤醒的方式。
#define PWR_STOPEntry_WFI //中断唤醒
#define PWR_STOPEntry_WFE //事件唤醒
停机模式唤醒后自动选择系统内部时钟,看自己的应用是否需要重新配置。如果你的系统时钟是HSI或者HSE是要重新配置,一般都需要重新配置,直接调用系统时钟配置函数。
另外停机模式唤醒后,flash程序是从中断或事件开始执行的。
如何做到停机模式更低功耗
代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //使能时钟
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);
上面代码第一个参数PWR_Regulator_LowPower是配置电源低功耗模式。第二个参数PWR_STOPEntry_WFI用来确定是中断唤醒还是事件唤醒,或者两者都要。
第二步你要把所有引脚IO口释放,全部配置成模拟输入状态,此时IO口几乎0消耗,具体见手册说明。
我们只要在进入低功耗之前把IO口配置一下就行了(根据自己应用需要配置IO),但是唤醒之后就要重新配置IO口了。
注意,在配置IO模拟输入之前,一定不要锁定IO口。我之前就踩了这个坑,在配置成模拟输入之前我们串口两个引脚锁定了导致我的功耗一直在90uA左右下不去。
正确配置的功耗在10uA左右,这功耗已经相当低了,用四节5号电池够你用至少1年了。