草庐IT

STM32实战总结:HAL之IAP

路溪非溪 2023-10-03 原文

我们学习单片机一般都是从51开始的,51单片机烧录程序通常是使用烧录软件如STC-ISP。这种方式,通过串口连接单片机,选择一个合适的波特率就可以烧录了。

后来学习STM32,编程时使用KEIL软件自带的下载按钮就能下载程序,方便了不少,但需要额外使用J-Link等下载器。

再后来,接触到产品研发,给已经发布出的产品升级,都是要靠远程无线升级的。

……

ICP全称是In Circuit Programming,即在电路中编程

ICP是最早的一种程序升级方式。

首先需要明确的是,单片机程序下载的本质就是将由0和1组成的hex文件写入到掉电数据不会消失的EEPROM(Electrically Erasable Programmable Read Only Memory,电可擦除可编程只读存储器)中。

最早使用的烧录程序的方式是使用单独的编程器,据说价格比较昂贵,而且每次编程时都需要把可编程芯片取下来放在编程器上,然后再写入程序。【似乎知道为什么现在普遍使用的51单片机最小系统板是用一个夹紧的绿色底座而不是接触更好的双列直插的芯片插座,估计就是历史遗留问题】

这里的编程器是电擦除的,据说更早还有紫外线擦除的,应该是匹配EPROM。

显然,这种烧录程序的方式一个是价格昂贵,意味着你每开发一款芯片,都需要先买一个可能比单片机还贵的编程器;另一方面就是这种编程方式意味着你每改动一次程序都需要拆装一次,不仅麻烦还会对电路板造成损伤,而且如果是成型的产品需要升级程序,还需要返厂或者让技术人员到现场解决,非常不便。于是后来就有了ISP。

ISP (In System Programming, 在系统编程) 

所谓ISP,即In System Programming,有些人翻译成“在系统中编程”,确实也有道理,因为原来的编程方式需要将芯片取下,即离开系统,而ISP不需要编程器即可完成程序烧录,此时单片机芯片可以焊在电路板上,调试完即是成品。这里的“系统”应该理解成SoC,即板载系统或者片上系统,而不是我们常说的软件系统。

ISP基本是目前单片机烧录程序的主要方式。它的实现方式就是通过电脑端的上位机软件,通过某种数据传输协议,将程序编译产生的二进制文件烧录到单片机的EEPROM中。一般电路板上还需要添加少量的外围电路辅助程序的烧录。因此调试单片机程序时,只需要将相关的接口留出即可,而不需要来回取下芯片。


其中,因为对烧录速度和质量的要求,人们在 “某种数据传输协议” 上不断更新,因此也就有了各种不同的烧录方式。如STC的51单片机基于的就是串口协议,即程序通过串口写入到FLASH(EEPROM的一种)中;Atmel的AT89S51,AT89S52等系列单片机基于SPI协议将程序写入到FLASH中;STM32系列的芯片采用ST-Link和J-Link等设备来下载程序,其基于的协议为SWD和JTAG,当然,STM32也可以基于串口协议下载程序;Arduino单片机可以通过串口协议下载程序,也可以通过SPI协议下载(算是AVR单片机一个独有的特点吧)。
 

不过虽然大家都是ISP,但各自还是有区别的。
其中最为特殊的莫过于串口协议,因为其他的各种协议都是借助外界设备(如ST-Link、USBasp)来直接操作单片机的FLASH,而通过串口下载程序时,虽然也需要使用外部设备(一般是一个USB转TTL模块),但是其本质还是靠芯片内部已固化的一段程序来写入FALSH。

这也就是为什么网上很多资料都把串口下载称之为ISP,而基于其他协议下载的都不叫ISP。
另外,对于某些单片机,支持ISP模式烧写程序的协议可能不止串口协议,或者说不止一个通讯接口。如STM32F4支持多个串口写入和CAN协议。

用来写入FLASH的这部分程序是芯片出厂就已经固化到芯片当中的,称为引导程序,也叫自举程序(自己能举起自己嘛),英文名叫BootLoader。这部分程序是对用户保密的,也就是说用户无法知道这段程序到底是怎么写入FLASH的,只知道它能这么做。

因此,为实现这种功能,芯片内部ROM就可以分为两部分,一部分是系统存储区 (System Flash),一般在低地址,用来存放引导程序;另一部分是用户存储区(User Flash),有些也称为应用程序区(Application Flash),一般在高地址,用来存放用户编写的程序(主要执行的部分)。其示意图如下所示。

以STM32为例,由于它既支持SWD,JTAG这种直接操作FLASH的协议,也支持基于串口协议利用引导程序写入FLASH,因此它需要支持多种启动模式。如下图所示。

这里的“主闪存存储器”即上面提到的用户存储区,用来存放用户编写的程序。

所以,显而易见,如果手边有ST-Link或J-Link,就可以考虑从用户存储区开始运行,这样上位机就直接将数据写入到用户存储区的FLASH中;如果要采用串口下载,就需要从系统存储区开始启动,当芯片在这个部分开始执行程序时,会不断检测串口是否有写入FLASH的指令,如果没有,则开始执行后面的用户程序,如果有,则开始写入用户存储区的FLASH。

但是,一般串口下载要更慢,而且ST-Link或J-Link除了下载程序外,还支持硬件仿真,这也就是为什么用串口下载的比较少。

为了更好地了解这个过程,下面以STC单片机为例来展示这个过程。【图片来自官网手册】

这里值得注意的是,STC单片机对于从系统存储区启动有一个特别的要求,那就是冷启动,即单片机彻底没电(给2-3V供电也不行)。这个时候上电单片机才会执行ISP程序(一般按下RST单片机不会执行ISP程序,只是从头开始执行用户程序),检测是否有烧入程序的需求。

还有一种单独给STC单片机烧录的设备,不用冷启动就能烧录程序,我只知道它大概是通过软件复位到系统存储区去执行ISP程序(这个功能STC已提供,而且应该是独有的),但具体原理还不太明白。(为什么那个下载器能在芯片运行过程中修改芯片内部寄存器的数值呢?)

IAP (In Application Programming,应用在线编程)

说了这么多ISP,感觉基本够用了,为什么还会有IAP呢?这个主要是用于一些特殊的情况,比如一个产品内程序的远程升级。

和上面的ISP一样,IAP也有翻译成“在应用中编程”,这个也有其合理性,但是个人感觉“应用在线编程”会更形象点,这个“线”就不是指系统了,而是指芯片正在执行应用程序,在这个过程中实现程序的自我更新,此即IAP的原理。也正是这种特殊操作,能够实现对一个已开发的产品进行远程的程序升级。

其实,IAP并不局限于用什么样的接口来实现IAP。

一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给用户一个代码文件即可。比较典型的就是从SD卡启动。

我有个疑惑:既然可以将程序直接下载到Flash中,那为什么不直接下载到起始地址处然后启动,还得通过IAP来间接实现呢?再简单化点,为什么串口不能直接把代码下载到Flash中。

思考总结:

IAP主要是想实现远程升级。那么通常就需要通过网络来更新程序。

先想一下串口是怎么升级程序的:通过串口下载程序,中间有一段引导程序可以将应用程序加载到Flash中,从而实现升级。

那么, 对比思考下IAP,通过网络传输数据,那么,是不是就得先有一段类似于ISP引导程序的IAP引导程序呢?

我们一开始先将IAP引导程序烧录入芯片,之后通过网络传输数据,IAP可以将网络传输过来的程序加载到应用程序所处的位置,从而实现远程升级。

引导程序就是我们常说的BootLoader

实现原理

直接参考:IAP程序升级(全网最全)__果果小师弟的博客-CSDN博客

要解决的问题有:
1、如何进行对STM32的Flash进行擦除和写入操作。

HAL有专门操作内部Flash的函数

包括读写擦除等,具体见文件stm32f1xx_hal_flash.h以及stm32f1xx_hal_flash_ex.h
2、中断向量表偏移如何设置。


3、如何改变代码存放的地址空间(因为BootLoader要存放在0x08000000处,而默认的代码存放的地址空间为0x08000000)。

程序中项目设置


4、怎么进行PC指针的强制跳转,跳转时需要做些什么。
5、串口接收的用户代码数据是什么样的代码数据,是一种什么样的文件。

bin文件

Bin文件和Hex文件

BIN文件和HEX文件差异_bin文件和hex文件的区别_duhui75的博客-CSDN博客

Hex文件是以ASCII文本形式保存编译后的二进制文件信息。Hex文件使用ASCII文本的形式保存Bin文件的内容和Bin文件的一些配置信息。Hex文件可以由下载器(比如jlink)烧写到MCU的ROM中。

Bin文件是MCU固件烧写的最终形式,也就是说MCU的ROM中烧写的内容完全就是Bin文件的内容。

Hex文件和Bin文件的关系:Hex文件可以说是MCU固件的中间形式,由下载器的软件根据Hex文件生成Bin文件再烧写到MCU的ROM中。

Hex文件有更好的可读性,最重要的是hex文件能够保证固件在保存与传输时的完整性。因此hex文件更适用于保存与传输。而Bin文件是纯二进制文件,内部只包含程序编译后的机器码和变量数据。当文件损坏时,我们也无法知道文件已损坏。不过Bin文件作为固件的最终形式,在使用串口下载程序或者远程升级时,是不可替代的。

程序编写

回想ISP方式,要么需要串口,要么需要JTAG或者SWD接口,都需要直连,是没有办法实现远程升级的。另一方面,ISP方式,不管是串口还是JTAG,都需要复位后才能运行新的程序代码,如果是远程,是没有办法再过去手动复位的。而且,有时候产品都装好了,你再给拆下来升级程序显然是不现实的。

那么,IAP就能解决这个问题吗?

IAP可以通过wifi、GPRS、4G等无线技术将程序发送给目标,然后让程序自己实现升级。

关键引导代码如下:

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
static uint16_t ulPage_DataBuf[STM32_FlASH_Page_SIZE / 2]; //页面数据缓存
	
/* Private function prototypes------------------------------------------------*/ 
static void IAP_Write_App_Bin(uint32_t, uint8_t *, uint32_t); //写入APP的bin文件
static void IAP_ExecuteApp(uint32_t);	                        //跳转APP应用程序

/* Public variables-----------------------------------------------------------*/
IAP_t IAP = 
{
	0,
  {0},
	0,
	
	IAP_Write_App_Bin,
	IAP_ExecuteApp
};
/* Private function prototypes------------------------------------------------*/      

/*
	* @name   IAP_ExecuteApp
	* @brief  通过IAP写入应用程序BIN文件
	* @param  ulStartAddr :起始地址(起始地址必须与Page页面地址对齐)
  *         pBin_DataBuf:数据指针
  *         ulBufLength :应用程序长度(写入的16位数据的个数)
	* @retval None      
*/
void IAP_Write_App_Bin (uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength)
{
	
	uint16_t usCnt = 0;                    //计数
	uint32_t ulIndex,ulAppWriteAddr = ulStartAddr; //索引,APP数据写入地址
	uint8_t* pBinData = pBin_DataBuf;      //APP数据指针
	
	//起始地址与Page页面地址对齐校验
	if(((ulStartAddr - FLASH_BASE) % STM32_FlASH_Page_SIZE) != 0)
	{
		printf("错误:FLASH写入初始地址没有与Page页面地址对齐!");
		System.Error_Handler();
	}

	//按页写入FLASH
	for(ulIndex=0; ulIndex < ulBufLength; ulIndex += 2)
	{
		ulPage_DataBuf[usCnt++] = (uint16_t)(*(pBinData+1) << 8) + (uint16_t)(*pBinData); //数据处理,按半字发送
		pBinData += 2; //指针偏移2个字节
		
		//数据达到1页
		if(usCnt == STM32_FlASH_Page_SIZE / 2)
		{
			usCnt = 0; //计数清零
			STM32_FlASH.Write(ulAppWriteAddr,ulPage_DataBuf,STM32_FlASH_Page_SIZE / 2); //发送一页
			ulAppWriteAddr += STM32_FlASH_Page_SIZE; //写入地址偏移一页
		}
	}
	
	//写入最后不到一页的内容
	if(usCnt > 0)
	{
		STM32_FlASH.Write(ulAppWriteAddr,ulPage_DataBuf,usCnt);
	}
}

__asm void MSR_MSP ( uint32_t ulAddr ) 
{
    MSR MSP, r0  //set Main Stack value
    BX r14
}

/*
	* @name   IAP_ExecuteApp
	* @brief  跳转到应用程序段
	* @param  ulAddr_App: 应用程序段起始地址
	* @retval None      
*/
void IAP_ExecuteApp(uint32_t ulAddr_App)
{
	pIapFun_TypeDef pJump2App;
	
	//检查栈顶地址是否合法
	if(((*(__IO uint32_t*)ulAddr_App) & 0x2FFE0000) == 0x20000000)	  
	{
		printf("栈顶合法,运行APP\r\n");
		pJump2App = (pIapFun_TypeDef) *(__IO uint32_t *)(ulAddr_App + 4);	//用户代码区第二个字为程序开始地址(复位地址)
		MSR_MSP(*(__IO uint32_t *)ulAddr_App );					                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		pJump2App ();								                                    	//跳转到APP
	}
	else
	{
		printf("错误:栈顶地址不合法!");
		HAL_ResumeTick();
		System.Error_Handler();
	}
}
/********************************************************
  End Of File
********************************************************/

有关STM32实战总结:HAL之IAP的更多相关文章

  1. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  2. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  3. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  4. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  5. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

  6. 你真正了解什么是接口测试么?接口实战一“篇”入魂 - 2

    最近在工作中,看到一些新手测试同学,对接口测试存在很多疑问,甚至包括一些从事软件测试3,5年的同学,在聊到接口时,也是一知半解;今天借着这个机会,对接口测试做个实战教学,顺便总结一下经验,分享给大家。计划拆分成4个模块跟大家做一个分享,(接口测试、接口基础知识、接口自动化、接口进阶)感兴趣的小伙伴记得关注,希望对你的日常工作和求职面试,带来一些帮助。注:文章较长有5000多字,希望小伙伴们认真看完,当然有些内容对小白同学不是太友好,如果你需要详细了解其中的一些概念或者名词,请在文章之后留言,后续我将针对大家的疑问,整理输出一些大家感兴趣的文章。随着开发模式的迭代更新,前后端分离已不是新的概念,

  7. ESP32学习入门:WiFi连接网络 - 2

    目录一、ESP32简单介绍二、ESP32Wi-Fi模块介绍三、ESP32Wi-Fi编程模型四、ESP32Wi-Fi事件处理流程 五、ESP32Wi-Fi开发环境六、ESP32Wi-Fi具体代码七、ESP32Wi-Fi代码解读6.1主程序app_main7.2自定义代码wifi_init_sta()八、ESP32Wi-Fi连接验证8.1测试方法8.2服务器模拟工具sscom58.3测试代码8.4测试结果前言为了开发一款亚马逊物联网产品,开始入手ESP32模块。为了能够记录自己的学习过程,特记录如下操作过程。一、ESP32简单介绍ESP32是一套Wi-Fi(2.4GHz)和蓝牙(4.2)双模解决方

  8. Spring Security 6.0系列【32】授权服务器篇之默认过滤器 - 2

    有道无术,术尚可求,有术无道,止于术。本系列SpringBoot版本3.0.4本系列SpringSecurity版本6.0.2本系列SpringAuthorizationServer版本1.0.2源码地址:https://gitee.com/pearl-organization/study-spring-security-demo文章目录前言1.OAuth2AuthorizationServerMetadataEndpointFilter2.OAuth2AuthorizationEndpointFilter3.OidcProviderConfigurationEndpointFilter4.N

  9. ruby - 摘要::CRC32 与 Zlib - 2

    在我的代码中,我需要使用各种算法(包括CRC32)对文件进行哈希处理。因为我还在Digest系列中使用其他加密哈希函数,所以我认为为它们维护一个一致的接口(interface)会很好。为了记录,我确实找到了digest-crc,一颗完全符合我要求的gem。问题是,Zlib是标准库的一部分,并且有一个我想重用的CRC32工作实现。此外,它是用C编写的,因此它应该提供与digest-crc相关的卓越性能,后者是纯ruby​​实现。实现Digest::CRC32一开始看起来非常简单:%w(digestzlib).each{|f|requiref}classDigest::CRC32一切正常:

  10. ruby - 安装gem : Couldn't reserve space for cygwin's heap, Win32错误487错误 - 2

    我正在尝试在我的机器上安装win32-apigem,但在构建native扩展时我遇到了一些问题:$geminstallwin32-api--no-ri--rdocTemporarilyenhancingPATHtoincludeDevKit...Buildingnativeextensions.Thiscouldtakeawhile...C:\Programs\dev_kit\bin\make.exe:***Couldn'treservespaceforcygwin'sheap,Win32error0ERROR:Errorinstallingwin32-api:ERROR:Failed

随机推荐