基于蓝牙的STM32 IAP在线升级

最近开发的一个小项目需要支持蓝牙在线升级,今天便详细地了解一番。蓝牙在线升级的方式,流程如图
在这里插入图片描述
流程解释: 产品的最新程序放在云端的服务器上,并将程序更新的提醒通过手机APP推送给用户,当用户点击程序更新时,APP将程序下载至手机上,并通过蓝牙传输到STM32上,这时单片机解析到的指令为程序更新,便触发IAP在线刷新程序。

要实现这一功能,必须通过单片机的串口IAP在线升级功能。
1.什么是IAP?

IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。

所以要实现IAP功能,固件程序须分为两个代码,即引导程序(BootLoader)和用户程序(APP)。
1.引导程序(BootLoader):只执行串口数据的接收、烧写程序并将程序执行地址跳转至用户程序段(此代码只能通过JTAG或SWD烧写)
2.用户程序(APP):执行用户所要实现的程序(此代码通过串口接收,IAP烧写入单片机flash中)

2.STM32程序的启动方式
为什么能将程序分成两个程序分别下载,要理解这个,我们有必要了解一下STM32的启动方式,因为我用的是STM32F103C8T6,所以就以这个型号为例:
STM32上电或者复位后,代码区始终从0x00000000开始,三种启动模式其实就是将各自存储空间的地址映射到0x00000000中。其启动模式由BOOT0和BOOT1引脚的电平高低来控制。
在这里插入图片描述
1、BOOT1=x BOOT0=0:从Flash启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始,这是正常的工作模式。
2、BOOT1=0 BOOT0=1:从系统存储器启动。首先控制BOOT0 BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码,这种模式启动的程序功能由厂家设置。
3、BOOT1=1 BOOT0=1: 从RAM启动,将RAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始,这种模式可以用于调试。
这里,我默认使用的模式是主闪存控制器启动,也就是从Flash启动。

3. Flash在STM32内存空间的定义
STM32单片机内存空间有明确的定义,
在这里插入图片描述
查看STM32F103C8T6可知,其Flash是分配在空间从0x0800 0000到0x0801FFFF,最大的空间为127KByte。所以在主闪存控制器启动模式下,STM32一上电,单片机先将0x0800 0000映射到代码区,然后从0x0800 0000开始执行程序。
在进入main函数前,单片机还做了以下处理(不需要自己编写代码,由单片机内部自动执行)
在这里插入图片描述
ortex-M3上电后来到复位中断(已将前4个字节的值存入MSP堆栈指针),转到__main标号,完成RW段的移动、ZI段的初始化,建立堆栈,初始化库函数,然后跳转到main函数,自此就开始执行我们编写的C程序。

4.引导程序和用户程序内存空间的划分
我们知道,单片机默认是从0x0800 0000开始执行的,其过程:
在这里插入图片描述
1.STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳
转到复位中断服务程序,如图标号①所示;
2.在复位中断服务程序执行完之后,会跳转到我们的main 函数,如图标号②所示;
3.而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;
4.然后,根据中断源进入相应的中断服务程序,如图标号④所示;
5.在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。

而我们将FLash的内存空间分为引导程序和用户程序,其0x0800 0000~0x0800 2000,8KByte的空间作为BootLoader,将0x0800 2000 ~0x0801 FFFF,共120KByte作为用户空间(STM32F103C8T6实际只有64KByte Flash,用户空间为56KByte)。
在这里插入图片描述
通过此种方式后,STM32执行程序的流程变为:
在这里插入图片描述

  1. STM32 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示。
  2. 此部分同图 47.1.1 一样;在执行完 IAP 以后(即将新的 APP 代码写入 STM32的 FLASH,灰底部分。
  3. 新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,
  4. 同样 main 函数为一个死循环,并且注意到此时 STM32 的 FLASH,在不同位置上,共有两个中断向量表。

5.引导程序的设计
(1)首先,实现串口中断函数,蓝牙通过串口将数据传输到单片机上,因为STM32F103C8T6的RAM只有8KByte,所以上位机每次发送的数据为1KByte,单片机烧写完后再发送下1KByte数据。

extern uint8_t usart_buf[1024+8];
//数据长度(2B)  数据(1KB)  序号(2B)  [CRC(4B)]
extern uint16_t buf_cnt;
extern uint8_t bootStatus;

void USART1_IRQHandler(void)
{
	uint8_t temp = 0;
	if( (USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET) )
	{
		temp = (uint8_t)USART_ReceiveData(USART1);
		if(++buf_cnt < (1024+8)  && ((buf_cnt & 0x8000) != 0x8000))
		{
			usart_buf[buf_cnt - 1] = temp;
		}
		else buf_cnt |= 0x8000;
	}
}

(2)在main函数中,对接收到的数据进行判断

if(flag)      //是否串口是否有接收到数据
{
	printf("开始更新固件r\n");	
	// 判断APP程序的起始地址是否为0X08XXXXXX
 	if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
	{	 
		iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新	FLASHFLASH代码	
		printf("固件更新成功\r\n");		
	}
	else 
	{
		printf("非固件程序\r\n");
	}
	flag = 0;

(3)如果检测到APP程序,进入FLASH烧写函数

扫描二维码关注公众号,回复: 4515633 查看本文章
// 从addr起烧录程序

void Iap_Write(uint32_t addr)
{
	uint16_t temp = 0;
	uint16_t data_size = 0;    //烧录大小
	uint16_t data_len = 0;	   //数据长度
	uint16_t index = 0;         //数据块索引
	uint32_t addr_now = addr;	    //写入地址
	uint8_t data_write[1024] = {0};	//数据缓冲
	//准备烧录,数据大小为1K
	while(1)
	{
		while((buf_cnt & 0x8000) == 0);		//等待数据接收完毕
		//解析数据
		data_len = (uint16_t)usart_buf[1] << 8 | usart_buf[0];	//获取data有效长度
		for(temp = 2; temp < data_len + 2; temp++)	//从第二位开始拷贝数据
		{
			data_write[temp - 2] = usart_buf[temp];
		}
		index = usart_buf[1024 + 3] << 8 + usart_buf[1024 + 2];			//获取索引
		//开始写数据
		if(data_len < 1024)	//写入剩下的数据
		{
			if(data_len % 2 != 0)data_len += 1;
			STMFLASH_Write(addr_now, (uint16_t *)data_write, data_len / 2);		//
			data_size += 1;
			STMFLASH_Write(IAP_INFO, &data_len, 1);
			putString("OK\n");
			break;
		}
		else
		{
			STMFLASH_Write(addr_now, (uint16_t*)data_write, data_len / 2);
			data_size += 1;
			addr_now += 1024;	//下一个1K
			buf_cnt = 0;
			for(temp = 0; temp < USART_BUF_SIZE; temp++)usart_buf[temp] = 0;
			putString("Next\n");		
		}
	}		//烧录完成
	//清空串口缓存
	buf_cnt = 0;
	for(temp = 0; temp < USART_BUF_SIZE; temp++)usart_buf[temp] = 0;
}

(4)烧录好APP程序后,引导程序将PC指针地址指向APP程序的起始地址,即0x0800 2000;要对PC指针进行操作,需使用MSR汇编指令来操作。

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr) 
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}

然后执行PC指针跳转函数

void Iap_load(uint32_t addr)
{
	if(((*(vu32*)addr) & 0x2FFE0000) == 0x20000000)		
	{
		jump = (iapfun) *(vu32*)(addr + 4);					//强制转化为函数
		MSR_MSP(*(vu32*)addr);
		jump();
	}
	else 
	{
		printf("Error\n");
		while(1);
	}
}

(5)最后,main函数的实现就很简单了,以轮询的方式读取串口数据,然后烧写FLASH,最后跳转至APP程序

while(1)
{
	while((readBootTime() != 0)  && (flag == 0))			//以轮询的方式读取串口
	{
		if(Iap_wait() == 8)
		{
			USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);	
			flag = 1;
		}
	}
	if(flag)
	{
		IAP_WRITE();
		flag = 0;
	}
	else 
		IAP_LOAD();       //跳转至APP程序
	}

6.用户程序APP的实现
用户程序实现比较简单,只需要对地址进行设计一下就可以,所以我就使用点亮LED来作为APP程序
在这里插入图片描述
图中 IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X10000,
即从 0X08000000 开始的64K 空间为我们的程序存储(因为我们STM32F103C8T6 的 FLASH大小是 64K)。而图中,我们设置起始地址(Start)为 0X0800 2000,即偏移量为 0X2000字节),因而,留给 APP 用的 FLASH 空间(Size)只有0X8000(56K 字节)大小了。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。

(2) 设置APP程序的中断向量表的偏移量
在 systemInit 函数中的,设置中断向量表的偏移量

SCB->VTOR = FLASH_BASE | 0x2000;

以上设置完成之后,点击rebuild按钮重新编译,生成LED.hex
(3)生成APP程序bin文件
我们通过 MDK 自带的格式转换工具 fromelf.exe,来实现.axf 文件到.bin 文件的转换。该工具在 MDK 的安装目录\ARM\BIN40 文件夹里面
在这里插入图片描述
我们就可以在 MDK 编译成功之后,调用 fromelf.exe,根据当前工程的 LED.axf,生成一个 LED.bin 的文件。
7.APP程序的蓝牙在线升级
由于公司云端服务器还没搭好,我就先自己的电脑蓝牙与产品蓝牙连接,然后通过用QT写的串口调试助手发送至STM32中,完成APP程序的升级。
在这里插入图片描述
至此,基于蓝牙的STM32 IAP在线升级就完成了!

猜你喜欢

转载自blog.csdn.net/lin5103151/article/details/84960677