STM32中OTA介绍及使用

1.OTA的介绍

OTA(Over-The-Air)是⼀项通过⽆线⽹络远程更新设备软件、固件或配置的技术,它让设备升级像⼿机系统 更新⼀样⽅便,⽆需连接电脑或前往服务中⼼。对于⼚商⽽⾔,它可以快速修复系统缺陷,避免⼤规模线下召回,显著降低售后成本; 对于⽤户⽽⾔,设备可以持续迭代更新,获得新功能和安全补丁。⼀次完整的OTA升级通常包含三个步骤: 1. ⽣成更新包:⼚商制作包含新功能或修复补丁的软件包,常采⽤"差分升级"技术,只⽣成新旧版本间的差异部分,⼤⼤减⼩下载体积。 2. 传输更新包:加密后的更新包被推送到云端服务器,设备在合适的时机⾃动或经⽤⼾同意后下载。 3. 安装与验证:设备安装更新,并严格验证其完整性和正确性。成功后,新版本即开始运⾏。

2.OTA在STM32中使用思路

为了再stm32中使用好ota我们要来先了解一下bootloader,BootLoader 可以理解成是引导程序, 它的作⽤是启动正式的App应⽤程序。Bootloader是在应⽤程序开始前运⾏的⼀个⼩程序,⾥⾯可以进⾏⼀些初始化操作,升级引⽤程序等,在嵌⼊式设备中很常⻅。了解了bootloader后我们就要在stm32的内存中对其进行分区处理,BootLoader区存放启动代码 ,App1区存放应⽤代码, App2区 存放暂存的升级代码。然后执行流畅大体如下:先执⾏ BootLoader 程序, 先去检查 APP2 区有没有程序, 如果有就将App2区(备份区)的程序拷⻉到 App1区 , 然后再跳转去执⾏ App1 的 程序. 因为 BootLoader 和 App1 这两个程序的向量表不⼀样, 所以跳转到 App1 之后第⼀步是先去更改程序的向量表然后再去执⾏其他的应⽤程序. 在应⽤程序⾥⾯会加⼊程序升级的部分, 这部分主要⼯作是拿到升级程序, 然后将他们放到 App2区(备份区) , 以便下次启动的时候通过 BootLoader 更新 App1 的程序。将 App2区 的最后4个字节( 0x0801FFFC )⽤来表⽰ App2区 是否有升级程序,STM32在擦除之后Flash的数据存放的都是 0xFFFFFFFF ,如果有,我们将这个地址存放为0xAAAAAAAA 。

3.程序及具体实现

首先我们先编写引导程序即boot程序,boot源文件程序及详解如下:

cpp 复制代码
#include "main.h"
#include <stdio.h>
#include "boot.h"

/**
 * @bieaf 擦除页
 *
 * @param pageaddr  起始地址	
 * @param num       擦除的页数
 * @return 1
 */
static int Erase_page(uint32_t pageaddr, uint32_t num)
{
	HAL_FLASH_Unlock();
	
	/* 擦除FLASH*/
	FLASH_EraseInitTypeDef FlashSet;
	FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;
	FlashSet.PageAddress = pageaddr;
	FlashSet.NbPages = num;
	
	/*设置PageError,调用擦除函数*/
	uint32_t PageError = 0;
	HAL_FLASHEx_Erase(&FlashSet, &PageError);
	
	HAL_FLASH_Lock();
	return 1;
}


/**
 * @bieaf 写若干个数据
 *
 * @param addr       写入的地址
 * @param buff       写入数据的起始地址
 * @param word_size  长度
 * @return 
 */
static void WriteFlash(uint32_t addr, uint32_t * buff, int word_size)
{	
	/* 1/4解锁FLASH*/
	HAL_FLASH_Unlock();
	
	for(int i = 0; i < word_size; i++)	
	{
		/* 3/4对FLASH烧写*/
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + 4 * i, buff[i]);	
	}

	/* 4/4锁住FLASH*/
	HAL_FLASH_Lock();
}



/**
 * @bieaf 读若干个数据
 *
 * @param addr       读数据的地址
 * @param buff       读出数据的数组指针
 * @param word_size  长度
 * @return 
 */
static void ReadFlash(uint32_t addr, uint32_t * buff, uint16_t word_size)
{
	for(int i =0; i < word_size; i++)
	{
		buff[i] = *(__IO uint32_t*)(addr + 4 * i);
	}
	return;
}


/* 读取启动模式 */
unsigned int Read_Start_Mode(void)
{
	unsigned int mode = 0;
	ReadFlash((Application_2_Addr + Application_Size - 4), &mode, 1);
	return mode;
}

/**
 * @bieaf 进行程序的覆盖
 * @detail 1.擦除目的地址
 *         2.源地址的代码拷贝到目的地址
 *         3.擦除源地址
 *
 * @param  搬运的源地址
 * @param  搬运的目的地址
 * @return 搬运的程序大小
 */
void MoveCode(uint32_t src_addr, uint32_t des_addr, uint32_t byte_size)
{
	/*1.擦除目的地址*/
	printf("> Start erase des flash......\r\n");
	Erase_page(des_addr, (byte_size/PageSize));
	printf("> Erase des flash down......\r\n");
	
	/*2.开始拷贝*/	
	unsigned int temp[256];
	
	printf("> Start copy......\r\n");
	for(int i = 0; i < byte_size/1024; i++)
	{
		ReadFlash((src_addr + i*1024), temp, 256);
		WriteFlash((des_addr + i*1024), temp, 256);
	}
	printf("> Copy down......\r\n");
	
	/*3.擦除源地址*/
	printf("> Start erase src flash......\r\n");
	Erase_page(src_addr, (byte_size/PageSize));
	printf("> Erase src flash down......\r\n");
}



/* 采用汇编设置栈的值 */
__asm void MSR_MSP (uint32_t ulAddr) 
{
    MSR MSP, r0 			                   //set Main Stack value
    BX r14
}



/* 程序跳转函数 */
typedef void (*Jump_Fun)(void);
void IAP_ExecuteApp (uint32_t App_Addr)
{
	Jump_Fun JumpToApp; 
    
	if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )	//检查栈顶地址是否合法.
	{ 
		JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);	//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP( * ( __IO uint32_t * ) App_Addr );	//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		JumpToApp();		//跳转到APP.
	}
}



/**
 * @bieaf 进行BootLoader的启动
 *
 * @param none
 * @return none
 */
void Start_BootLoader(void)
{
	/*==========打印消息==========*/  
	 printf("\r\n");
	 printf("***********************************\r\n");
	 printf("*        BootLoader               *\r\n");
	 printf("***********************************\r\n");
	
	//printf("> Choose a startup method......\r\n");	
	switch(Read_Start_Mode())										//  读取是否启动应用程序 
	{
		case Startup_Normol:										//  正常启动 
		{
			printf("> Normal start...\n");
			break;
		}
		case Startup_Update:										// 升级再启动 
		{
			printf("> Start update...\n");		
			MoveCode(Application_2_Addr, Application_1_Addr, Application_Size);
			printf("> Update down......\r\n");
			break;
		}
		default:													//  启动失败
		{
			printf("> Error:%X!!!......\r\n", Read_Start_Mode());
			return;			
		}
	}
	
	/* 跳转到应用程序 */
	printf("> Start APP1...\r\n");	
	IAP_ExecuteApp(Application_1_Addr);
}

boot头文件内容如下:

cpp 复制代码
#ifndef __BOOT_H_
#define __BOOT_H_

#define PageSize FLASH_PAGE_SIZE // 1K

/*=====用户配置(根据自己的分区进行配置)=====*/
// 0x400    1K 
// 0x800    2K 
// 0x1000   4K  
// 0x2000   8K  
// 0x7000   4K*7 = 28K   
#define BootLoader_Size         0x2000U         // BootLoader的大小 8K
#define Application_Size        0x7000U         // APP 应用程序的大小 28K

#define Application_1_Addr      0x08002000U     // 应用程序1的首地址 大小 28K 0x08002000 + 0x7000
#define Application_2_Addr      0x08009000U     // 应用程序2的首地址 大小 28K 0x08009000 + 0x7000
/*==========================================*/

/* 启动的步骤 */
#define Startup_Normol 0xFFFFFFFF   //  正常启动
#define Startup_Update 0xAAAAAAAA   //  升级再启动

void Start_BootLoader(void);

#endif

在main函数的循环中我们调用如下:

cpp 复制代码
 while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		Start_BootLoader();
		HAL_Delay(1000*10);
  }

然后编写我们的app程序,首先在app程序中我们要更改中断地址表,程序如下:

cpp 复制代码
SCB->VTOR = FLASH_BASE | Application_1_Addr ; /* 更改中断向量表地址 偏移到APP1 地址 	*/

然后将你要实现的功能写入到app文件中即可,需要进行ota更新的app文件要将前面说到的标志位进行一个更改。

相关推荐
进击的小头2 小时前
01_嵌入式C与控制理论入门:从原理到MCU实战落地
c语言·单片机·算法
xincan08182 小时前
MacOS安装Java+mvn+mvnd+jenv多环境丝滑切换
java·开发语言·macos
会编程是什么感觉...2 小时前
单片机 - STM32CubeMX HAL库开发部分
stm32·单片机·嵌入式硬件
派大鑫wink2 小时前
【Day13】集合框架(一):List 接口(ArrayList vs LinkedList)实战
java·开发语言·windows
日更嵌入式的打工仔2 小时前
两种核心消息队列:环形队列与RTOS消息队列解析
笔记·单片机
TheNextByte12 小时前
如何轻松地将音乐从Mac传输到Android ?
android·stm32·macos
石马马户2 小时前
keil使用Jlink下载时出现No Cortex-M SW Device Found 解决方法
单片机·嵌入式硬件
快乐的划水a2 小时前
嵌入式时间测量方法总结
c++·stm32·单片机
@小张在努力2 小时前
Javascript中的闭包
开发语言·javascript·ecmascript