STM32速成笔记—串口IAP

news2024/11/18 17:33:16

本文涉及到串口通信和Flash知识,对于这部分知识不熟悉的小伙伴可以到博主STM32速成笔记专栏查看。

文章目录

  • 一、串口IAP简介
    • 1.1 什么是IAP
    • 1.2 STM32下载程序
  • 二、串口IAP有什么作用
  • 三、启动流程
    • 3.1 正常启动流程
    • 3.2 加入IAP后的启动流程
  • 四、必备知识
    • 4.1 修改程序运行起始地址
    • 4.2 设置中断向量表偏移
    • 4.3 生成.bin文件
  • 五、串口IAP实现
    • 5.1 串口中断服务函数
    • 5.2 Flash写入程序
    • 5.3 IAP程序
    • 5.4 main函数
  • 六、注意事项

一、串口IAP简介

1.1 什么是IAP

IAP,英文全程In Application Programming,在应用中编程。很好理解,就是在程序运行过程中我们进行程序的烧写,或者叫升级。

1.2 STM32下载程序

我们都知道,STM32可以利用串口下载程序,这是因为ST公司在产线上就在产品中内嵌了自举程序。所谓的自举程序,实际就是支持我们通过串口下载程序的代码。自举程序被存放在系统存储区,因此如果我们需要通过串口下载程序,需要将Boot0接高电平,Boot1接低电平,让程序从系统存储器开始运行,运行自举程序。下载完成后我们再将Boot0接地,让程序从主闪存存储器开始运行。自举程序是我们用户无法修改的。

二、串口IAP有什么作用

上面我们介绍了什么是IAP,那么这个IAP到底有什么作用呢?

首先介绍两个词——BootloaderApplication。Bootloader实际就是一段引导程序,单片机上电后先执行Bootloader程序,然后再执行用户编写的应用程序Application。介绍完这两个词,我们来介绍一下IAP有什么作用。比如我们生产了A,B两款产品。A产品是某个精密器件的一部分,B产品是一款物联网产品。我们的产品销售范围很广,远销海外。

某天A产品的某个客户反映了一个Bug,我们编写好了程序,需要进行程序更新,或者叫升级。利用IAP,我们可以在程序运行时,通过预留的通信接口直接烧写程序。而不需要再把整个设备拆开,像我们调试时那样下载程序。甚至我们可以直接给客户邮寄一个小设备,客户直接进行傻瓜式升级。

又过了一段时间,B产品的程序出现了一个Bug,风险等级比较低,但是依旧需要全体升级程序。我们总不能挨个产品派人去升级,成本极大。这时候又轮到我们的IAP出场了。它可以在所有设备在线运行的情况下,直接通过网络下发升级程序,实现在线升级,节约了大量的人力成本。

通过上面这两个例子,大家应该能够基本了解IAP的用处,使用IAP让我们不需要再使用调试器进行下载,甚至实现设备的在线升级。

三、启动流程

在介绍如何实现IAP之前,我们先来简单了解以下STM32的启动流程。

3.1 正常启动流程

这里的正常启动流程指的是,没有添加IAP的流程。

正常启动流程
程序启动时首先开辟栈空间,配置栈顶指针。然后配置堆空间。配置完成后,建立中断向量表,在中断向量表中找到复位中断,开始执行复位中断服务函数,然后跳转到main函数中,执行用户代码。当用户代码中有中断请求时,会回到中断向量表,根据中断源执行相应的中断服务函数。

3.2 加入IAP后的启动流程

下面是加入IAP之后的启动流程。

加入IAP启动流程

可以看到,与上面不同的是,加入IAP后,执行完复位中断服务函数后直接进入IAP的main函数。在执行完IAP之后,跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main 函数。

由上面的两个启动过程我们可以看出

  • 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始。
  • 必须将新程序的中断向量表相应的移动,移动的偏移量为 x。

四、必备知识

4.1 修改程序运行起始地址

点击魔术棒,选择“Target”,修改运行起始地址和代码大小。

修改App运行起始地址

4.2 设置中断向量表偏移

VTOR 寄存器存放的是中断向量表的起始地址。如果要设置中断向量表偏移,只需要在main函数最开始添加如下语句即可

SCB->VTOR = FLASH BASE | 偏移量:

4.3 生成.bin文件

点击魔术棒,选择“User”,按照如下配置,输入下面的内容

fromelf --bin -o "$L@L.bin" "#L"

生成.bin文件配置

点击编译,不报错就可以,去设置的输出文件夹中就可以找到对应的.bin文件。

编译提示

五、串口IAP实现

本次的目标是实现一个串口IAP,也就是编写Bootloader,在程序运行过程中实现程序的下载。Bootloader程序应该可以通过串口接收上位机发来的.bin文件(App程序),检查后将.bin文件写入到Flash特定位置,然后跳转到App程序运行。

5.1 串口中断服务函数

本次的串口中断服务函数与之前不同,这里单独贴出来。需要定义一个接收数组,接收数组的起始地址限制为0X20001000。接收数组最多可以接收55K字节,可以根据需要调整。但是需要注意的是,数组的大小需要比App程序要大,而且不能超过芯片的SRAM空间大小。

/*
 *==============================================================================
 *函数名称:USART1_IRQHandler
 *函数功能:USART1中断服务函数
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
 */
u32 gReceCount = 0;   // 接收计数变量
// 接收数组
// 限制起始地址为0X20001000
// 保证偏移量为 0X200的倍数
// 是为了给App留SRAM空间
u8 gReceFifo[USART_RECE_MAX_LEN]__attribute__ ((at(0X20001000)));

void USART1_IRQHandler(void)  
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)   //接收到一个字节  
	{
		if (gReceCount < USART_RECE_MAX_LEN)
		{
			gReceFifo[gReceCount++] = USART1->DR;
		}
		else
		{
			printf ("APP code out of memory!\r\n");
		}
	}
}

5.2 Flash写入程序

关于Flash程序,这里就不在赘述,只是贴一下带检查的写入程序。其他具体内容可以到博主STM32速成笔记专栏查看。

/*
 *==============================================================================
 *函数名称:Med_Flash_Write
 *函数功能:从指定地址开始写入指定长度的数据
 *输入参数:WriteAddr:写入起始地址;pBuffer:数据指针;
						NumToRead:写入(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */

// 根据中文参考手册,大容量产品的每一页是2K字节
#if STM32_FLASH_SIZE < 256
	#define STM32_SECTOR_SIZE   1024   // 字节
#else 
	#define STM32_SECTOR_SIZE   2048
#endif

// 一个扇区的内存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

void Med_Flash_Write (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
	u32 secpos;   // 扇区地址
	u16 secoff;   // 扇区内偏移地址(16位字计算)
	u16 secremain;   // 扇区内剩余地址(16位计算)	   
 	u16 i;    
	u32 offaddr;   // 去掉0X08000000后的地址
	
	// 判断写入地址是否在合法范围内
	if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
	{
		return;   // 非法地址
	}
	
	FLASH_Unlock();   // 解锁
	offaddr = WriteAddr - STM32_FLASH_BASE;   // 实际偏移地址
	secpos = offaddr / STM32_SECTOR_SIZE;   // 扇区地址
	secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇区内的偏移(2个字节为基本单位)
	secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇区剩余空间大小
	
	if (NumToWrite <= secremain)
	{
		secremain = NumToWrite;   // 不大于该扇区范围
	}
	
	while (1) 
	{
		// 读出整个扇区的内容
		Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		
		// 校验数据
		for (i = 0;i < secremain;i ++)
		{
			// 需要擦除 
			if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
			{
				break; 
			}				
		}
		// 需要擦除
		if (i < secremain)
		{
			FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除这个扇区
			
			// 复制
			for (i = 0;i < secremain;i ++)
			{
				STM32_FLASH_BUF[i + secoff] = pBuffer[i];	  
			}
			
			// 写入整个扇区
			Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		}
		else
		{
			// 写已经擦除了的,直接写入扇区剩余区间
			Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
		}
		
		if (NumToWrite == secremain)
		{
			break;   // 写入结束了
		}
		// 写入未结束
		else
		{
			secpos ++;   // 扇区地址增1
			secoff = 0;   // 偏移位置为0 	 
			pBuffer += secremain;   // 指针偏移
			WriteAddr += secremain;   // 写地址偏移	   
			NumToWrite -= secremain;   // 字节(16位)数递减
			if (NumToWrite > (STM32_SECTOR_SIZE / 2))
			{
				secremain = STM32_SECTOR_SIZE / 2;   // 下一个扇区还是写不完
			}
			else
			{
				secremain = NumToWrite;   // 下一个扇区可以写完了
			}
		}	 
	}	
	FLASH_Lock();   // 上锁
}

5.3 IAP程序

IAP程序包含两部分,一部分是将接收到的.bin文件写入Flash,另一部分是跳转到App执行。

/*
 *==============================================================================
 *函数名称:iap_write_appbin
 *函数功能:写入.bin文件
 *输入参数:appxaddr:App程序起始地址;appbuf:缓存App程序的数组;
						appsize:App程序大小
 *返回值:无
 *备  注:无
 *==============================================================================
 */
 // 对Flash操作的最小单位是16位
 u16 iapbuf[1024];   // 要写入Flash的内容
 
void iap_write_appbin (u32 appxaddr,u8 *appbuf,u32 appsize)
{
	u16 t;   // 临时循环变量
	u16 i = 0;   // 自增索引
	u16 temp;   // 临时计算变量
	
	u32 fwaddr = appxaddr;   // 当前写入的地址
	u8 *dfu = appbuf;   // 指向App代码数组首地址的指针
	
	// for循环,2K为单位进行写入
	for(t = 0;t < appsize;t += 2)
	{		
		temp = (u16)dfu[1] << 8;
		temp += (u16)dfu[0];	  
		dfu += 2;   // 偏移2个字节
		iapbuf[i++] = temp;	    
		if(i == 1024)
		{
			i = 0;
			Med_Flash_Write (fwaddr,iapbuf,1024);	
			fwaddr += 2048;   // 偏移2048  16=2*8所以要乘以2
		}
	}
	if(i)
	{
		Med_Flash_Write (fwaddr,iapbuf,i);   // 将最后的一些内容字节写进去
	}
}
/*
 *==============================================================================
 *函数名称:iap_load_app
 *函数功能:跳转到App
 *输入参数:appxaddr:App程序起始地址
 *返回值:无
 *备  注:无
 *==============================================================================
 */
iapfun jump2app;

void iap_load_app (u32 appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)   // 检查栈顶地址是否合法
	{ 
		jump2app=(iapfun)*(vu32*)(appxaddr+4);   // 用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);   // 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();   // 跳转到APP
	}
}

5.4 main函数

main函数设计如下

int main(void)
{
	u32 curRecCunt = 0;   // 当前接收数量
	
	// 设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	delay_init();   // 延时初始化
	uart_init(115200);   // 串口初始化
	
	printf ("Enter the Bootloader Code!\r\n");
	
	while(1)
  {
		// 如果接收到内容且传输完成
		if (curRecCunt == gReceCount && gReceCount != 0)
		{
			printf ("App Code reception complete!\r\n");
			printf ("The length of the received App code is %d!\r\n",gReceCount);
			
			// 开始准备写入Flash
			if(((*(vu32*)(0X20001000+4)) & 0xFF000000) == 0x08000000)   // 判断是否为0X08XXXXXX.
			{
				iap_write_appbin(FLASH_APP1_ADDR,gReceFifo,gReceCount);   // 更新FLASH代码  
				gReceCount = 0;   // 清零接收计数变量
				printf("Update complete!\r\n");
			}
			else 
			{ 
				printf("Update error!\r\n");
			}
			
			// 准备跳转App执行
			if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)   //判断是否为0X08XXXXXX.
			{
				printf("Enter App!\r\n");
				iap_load_app(FLASH_APP1_ADDR);   //执行FLASH APP代码
			}else 
			{
				printf("Enter error!\r\n");
			}	
		}
		else   // 未传输完成
		{
			curRecCunt = gReceCount;   // 更新当前接收数量
			// 延时一定要有,给串口中断留出时间
			// 如果没有会导致接收不完全
			delay_ms(10);
		}
	}
}

六、注意事项

需要注意的是,上面的程序中App程序是从0x8010000开始,需要配置好程序起始地址和中断向量表偏移

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

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

相关文章

日常开发中Git遇到的问题-记录贴

日常开发中Git遇到的问题 前言场景一&#xff1a;clone代码并进行开发完毕后&#xff0c;进行代码合并时要求在新分支下提交代码场景二&#xff1a;远程分支被删除后&#xff0c;本地分支缓存需要更新&#xff0c;防止提交错误 前言 本篇博客只是用来记录平时开发过程之中用gi…

从头开始:数据结构和算法入门(时间复杂度、空间复杂度)

目录 文章目录 前言 1.算法效率 1.1 如何衡量一个算法的好坏 1.2 算法的复杂度 2.时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3常见时间复杂度计算 3.空间复杂度 4.常见复杂度对比 总结 前言 C语言的学习篇已经结束&#xff0c;今天开启新的篇章——数据结构和算…

Hive视图

hive的视图 简介 hive的视图简单理解为逻辑上的表hive只支持逻辑视图&#xff0c;不支持物化视图视图存在的意义 对数据进行局部暴露&#xff08;涉及隐私的数据不暴露&#xff09;简化复杂查询 创建视图&#xff1a; create view if not exists v_1 as select uid,movie f…

国内十大精准的现货黄金价格走势图软件最新排名(综合版)

选择国内现货黄金价格走势图软件时&#xff0c;需要考虑几个因素。首先&#xff0c;软件的稳定性和可靠性至关重要。应选择有良好声誉和长期稳定运行的平台&#xff0c;以确保价格数据的准确性和及时性。其次&#xff0c;要选择功能齐全的软件。较为优秀的软件应该提供多种技术…

C++学习——类和对象(三)

接着我们就继续学习我们C当中的相关的知识。 一&#xff1a;初始化列表 还记得我们之前讲过的构造函数吗&#xff1f;我们在构造函数的函数体里面可以对对象当中的属性进行初始化。但是作为我们的构造函数来说&#xff0c;初始化的方式并不只是在构造函数体当中进行赋值。我们…

Junit4+MultiThreadedTestRunner 并发测试

目录 前言&#xff1a; 具体步骤&#xff1a; 前言&#xff1a; 在进行软件测试时&#xff0c;我们需要确保应用程序在不同的并发情况下仍能正常运行。 最近要对一个类里的方法&#xff0c;进行压力测试。下面讲一下写出的 Junit4 的并发测试代码吧。如果要复用的话&#x…

通达信一看就懂的成交量指标公式_通达信公式

用法说明&#xff1a;黄色为立桩量&#xff0c;绿色长竖条为逃顶提示&#xff0c;蓝色与红箭头为突破&#xff0c;红色为牛&#xff0c;紫色为立桩三天法则成立&#xff0c;笑脸为寻底&#xff0c;棕色与绿箭头为破位&#xff0c;淡红色黑中线涨停&#xff0c;深绿色黑中线跌停…

yolov7裂缝检测

B站视频笔记。 1.首先到Github上找RoboFlow的仓库地址 该教程提供了传统算法比如Resnet、YOLO等&#xff0c;还有包含一些较新的算法。 2.通过Colab打开例程 可以直接通过Colab打开,还支持其他的打开方式&#xff0c;这里提供三种方式。 提示&#xff1a;点击授权即可。 …

dy设备deviceid iid注册分析

清楚缓存&#xff0c;重新打开app, 点击同意按钮&#xff0c;会触发设备注册&#xff1b; 很明显是一个post包&#xff0c;device_register 可以看到请求体加密了 那么 请求体是什么呢&#xff1f; 很老版本思路&#xff1a;都是直接明文注册 较老版本思路&#xff1a;在反编译…

二、SQL-5.DQL-8).案例练习

1、查询年龄为20,21,22,23岁的员工信息 select * from emp where age in(20, 21, 22, 23) and gender 女; 2、查询性别为男&#xff0c;并且年龄在20-40岁&#xff08;含&#xff09;以内的姓名为三个字的员工 select * from emp where gender 男 && age between 2…

如何从任何地方远程解决电脑问题?

​如何远程解决电脑问题&#xff1f; “嗨&#xff01;我有一台Windows 10家用电脑。我外出旅行&#xff0c;但我的家人告诉我我的电脑有一段时间无法正常工作。我该如何远程检查电脑并解决相应的问题&#xff1f;提前谢谢&#xff01;” 您是否正在寻找远程解决电…

重庆市北斗新型智慧城市政府项目

技术栈&#xff1a;使用vue2JavaScriptElementUIvuexaxiosmapboxcesium 项目描述&#xff1a;重庆市北斗新型智慧城市政府项目是基于千寻孪界开发的一款智慧城市项目&#xff0c;包含车辆实时位置定位&#xff0c;智能设备的报警&#xff0c;基础设施的部设等等功能 工作内容&a…

javascript 7种继承-- 寄生式继承分析(5)

文章目录 概要继承的进化史技术名词解释寄生式继承案列分析源代码解析效果图小结 概要 这阵子在整理JS的7种继承方式&#xff0c;发现很多文章跟视频&#xff0c;讲解后都不能让自己理解清晰&#xff0c;索性自己记录一下&#xff0c;希望个位发表需要修改的意见&#xff0c;共…

C++多线程编程(第一章 多线程基本使用)

C 11&#xff1b; C 14; C 17; C 20&#xff1b; 1、为什么要多线程 任务分解 耗时的操作&#xff0c;任务分解&#xff0c;实时响应 数据分解 充分利用多核CPU处理数据 数据流分解 读写分离&#xff0c;解耦合设计 2、相关代码 1、初步&#xff1a; join(),detach() …

【数学建模】时间序列分析

文章目录 1. 条件2. 模型分类3. SPSS处理时间序列 1. 条件 1.使用于具有时间、数值两种要素 2.数据具有周期性可以使用时间序列分解 2. 模型分类 叠加模型【YTSCI】 序列的季节波动变化越来越大&#xff0c;反映变动之间的关系发生变化乘积序列【YTSC*I】 时间序列波动保持恒…

JavaSwing+MySQL的学生选课系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88101629?spm1001.2014.3001.5503 Jdk&#xff1a;1.8 MySQL&#xff1a;5.7 功能&#xff1a;可以进行选课与查看学生基本资料 在这里插入图片描述

Jmeter GET 请求 参数为 Json 串且参数中存在变量的转化

目录 前言&#xff1a; 1.在 HTTP 请求下添加 BeanShell PreProcessor 前置处理器&#xff1a; 2.在 BeanShell PreProcessor 的实现&#xff1a; 3.在 HTTP 请求中的使用方式&#xff1a; 4.参数化的数据方式&#xff1a; 5.请求结果&#xff1a; 前言&#xff1a; 在 A…

网络流量监视器vnStat

什么是 vnStat &#xff1f; vnStat 是一个网络流量监视器&#xff0c;它使用内核提供的网络接口统计信息作为信息源。这意味着 vnStat 实际上不会嗅探任何流量&#xff0c;并且无论网络流量速率如何&#xff0c;都可以确保系统资源的轻度使用。 安装 在群晖上以 Docker 方式安…

Hive内部表和外部表

表类型详解 表分类 在Hive中,表类型主要分为两种 第一种&#xff1a;内部表 也叫管理表表目录会创建在集群上的{hive.metastore.warehouse.dir}下的相应的库对应的目录中。默认创建的表就是内部表 第二种&#xff1a;外部表 外部表需要使用关键字"external"&#xff…

勘探开发人工智能应用:地震层位解释

1 地震层位解释 层位解释是地震构造解释的重要内容&#xff0c;是根据目标层位的地震反射特征如振幅、相位、形态、连续性、特征组合等信息在地震数据体上进行追踪解释获得地震层位数据的方法。 1.1 地震信号、层位与断层 图1.1 所示为地震信号采集的过程&#xff0c;地面炮…