STM32 IAP应用开发--bootloader升级程序

news2024/11/24 17:40:23

STM32 IAP应用开发--bootloader升级程序

  • Chapter1 STM32 IAP应用开发——通过串口/RS485实现固件升级(方式2)
    • 前言
      • 什么是IAP?
      • 什么是BootLoader?
    • 方案介绍:
      • 1)bootloader部分:
      • 2)APP部分:
    • 3 程序编写
      • 3.1 BootLoader部分
      • 3.2 APP的制作
    • 4 修改工程中的内存配置
      • 4.1 Bootloader工程内存配置
      • 4.2 APP工程内存配置
    • 5 烧录相关配置
      • 5.1 BootLoader部分
      • 5.2 APP部分
    • 6 运行测试
    • 结束语
  • Chapter2 STM32F1 IAP在线升级功能实现(使用串口)及心得
    • APP程序
  • Chapter3 STM32学习笔记之简易Bootloader串口升级设计
    • 概念简介
    • 复位执行流程
    • 实现要点
      • Bootloader程序实现
      • App程序实现
  • Chapter4 STM32 IAP应用开发——自制BootLoader
    • 1 环境搭建
    • 2 BootLoader工作原理以及常见分区介绍
    • 3 BootLoader的制作
    • 4 烧录下载配置
    • 5 运行测试
    • 结束语


Chapter1 STM32 IAP应用开发——通过串口/RS485实现固件升级(方式2)

原文链接:https://blog.csdn.net/ShenZhen_zixian/article/details/129424077

前言

什么是IAP?

IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。在应用编程(IAP)是用户的应用代码对片内Flash存储器进行擦除/编程的方法。这种方式的典型应用就是用一小段代码来实现程序的下载,实际上单片机的ISP功能就是通过IAP技术来实现的,即片子在出厂前就已经有一段小的boot程序在里面,片子上电后,开始运行这段程序,当检测到上位机有下载要求时,便和上位机通信,然后下载数据到数据存储区,从而实现固件升级。

什么是BootLoader?

百度百科:在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。

实际上,BootLoader不仅仅在操作系统上使用,在一些内存小,功能应用较为简单的单片机设备上面也可以通过BootLoader来完成固件升级。

我之前也有发过一些关于STM32远程升级的文章,实现的方式有很多种,感兴趣的同学可以去看一下。
STM32固件升级系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

那么这一期我来介绍一下如何自己制作一个BootLoader程序,并且通过串口或者RS485实现固件升级。

1 环境搭建
关于STM32以及Keil的环境这里就不具体介绍了,网上教程也很多,不懂的同学自行查阅资料。

2 功能描述
在做bootloader之前一定要先想好升级的途径和方式,这样才好规划分区以及制作bootloader。
关于bootloader详细的讲解,可以看下我之前发的博客:
STM32 IAP应用开发——自制BootLoader

分区介绍:
我用的是STM32F407,内存是512K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。
注:F4系列的MCU不像F1那样,内存扇区都很大(最少也是16K),而且同一块扇区只能一起擦除,所以就没办法分的那么细了。详细的内存分布可以参考下面的两个图。
STM32F4x扇区分布图如下:
在这里插入图片描述
STM32F1x扇区分布图如下:
在这里插入图片描述

那么我这里呢,就用一个512k的内存,分成3个区域,来实现一个升级的功能。
分区表如下:
在这里插入图片描述
在这里插入图片描述

方案介绍:

1)bootloader部分:

开始运行后先等待5s,在这个时间内如果收到串口2或者RS485的升级命令就进入升级模式,如果超时则跳转到用户程序(APP)。
在升级模式,可以通过串口2或者RS485传输要升级的固件,传输的数据协议我这里图方便就直接用Ymodem了,不知道Ymodem协议的可以先自行查阅一下资料。
在这里插入图片描述

2)APP部分:

APP部分修改一下中断向量表地址即可,其他的随便你做什么应用。
另外,我在分区的时候留了一块settimg区,在实际的应该中如果有需要记录一些掉电后还能保存的数据,那么这块区域就可以用得上了。

3 程序编写

3.1 BootLoader部分

不管用的是什么MCU,要实现固件升级都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现固件升级。
BootLoader的制作需要根据实际的需求来做,不同的运行方式或者升级方式在做法上都是有区别的,包括BootLoader所需要的内存空间也不尽相同。
不过不管是用什么方式,Bootloader都应该尽可能做的更小更简洁,这样的话内存的开销就更小,对于内存较小的MCU来说压力就没那么大了。

注:我这里是基于正点原子的工程模板改的,增加了自己的功能。

示例代码如下:
Bootloader分区定义:

#define FLASH_SECTOR_SIZE           1024
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))

//flash sector addr
#define ADDR_FLASH_SECTOR_0         ((uint32_t)0x08000000) 	//sector0 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1         ((uint32_t)0x08004000) 	//sector1 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2         ((uint32_t)0x08008000) 	//sector2 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3         ((uint32_t)0x0800C000) 	//sector3 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4         ((uint32_t)0x08010000) 	//sector4 addr, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5         ((uint32_t)0x08020000) 	//sector5 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6         ((uint32_t)0x08040000) 	//sector6 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7         ((uint32_t)0x08060000) 	//sector7 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8         ((uint32_t)0x08080000) 	//sector8 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9         ((uint32_t)0x080A0000) 	//sector9 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10        ((uint32_t)0x080C0000) 	//sector10 addr,128 Kbytes  
#define ADDR_FLASH_SECTOR_11        ((uint32_t)0x080E0000) 	//sector11 addr,128 Kbytes  

#define BOOT_SECTOR_ADDR            0x08000000     // BOOT sector start addres
#define BOOT_SECTOR_SIZE            0x4000         // BOOT sector size    
#define SETTING_SECTOR_ADDR         0x08004000     // SETTING sector start addres  
#define SETTING_SECTOR_SIZE         0x4000         // SETTING sector size     
#define APP_SECTOR_ADDR             0x08008000     // APP sector start address  
#define APP_SECTOR_SIZE             0x78000        // APP sector size    

#define BOOT_ERASE_SECTORS_NUM      1  // 16k
#define SETTING_ERASE_SECTORS_NUM   1  // 16k
#define APP_ERASE_SECTORS_NUM       6  // 16k + 16k + 64k + 128k + 128k + 128k

main函数:

#include "bootloader.h"
#include "usart.h"
#include "rs485.h"
#include "delay.h"
#include "ymodem.h"

#define WAIT_TIMEOUT   5

void print_boot_message(void)
{
    uart_log("---------- Enter BootLoader ----------\r\n");
    uart_log("\r\n");
    uart_log("======== flash pration table =========\r\n");
    uart_log("| name     | offset     | size       |\r\n");
    uart_log("--------------------------------------\r\n");
    uart_log("| boot     | 0x%08X | 0x%08X |\r\n", BOOT_SECTOR_ADDR, BOOT_SECTOR_SIZE);
    uart_log("| setting  | 0x%08X | 0x%08X |\r\n", SETTING_SECTOR_ADDR, SETTING_SECTOR_SIZE);
    uart_log("| app      | 0x%08X | 0x%08X |\r\n", APP_SECTOR_ADDR, APP_SECTOR_SIZE);
    uart_log("======================================\r\n");
}

void print_wait_message(void)
{
    uart_log("------- Please enter parameter -------\r\n");
	uart_log("[1].Start program\r\n");
	uart_log("[2].Update program\r\n");
    uart_log("--------------------------------------\r\n");
}

int main() 
{
    process_status process;
    uint16_t timerout = 0;

    delay_init(168);
    uart_init(115200);
    ymodem_init();
    print_boot_message();
    print_wait_message();

    while (1) 
    {
        process = get_ymodem_status();
        switch (process) 
        {
            case WAIT_START_PROGRAM:
                uart_log("wait start app...(%ds)\r\n", WAIT_TIMEOUT - timerout);
                delay_ms(1000);
                timerout ++;
                if(timerout >= WAIT_TIMEOUT)
                {
                    set_ymodem_status(START_PROGRAM);
                }
                break;
            case START_PROGRAM:
                uart_log("start app...\r\n");
                delay_ms(50);
                if (!jump_app(APP_SECTOR_ADDR)) 
                {
                    uart_log("start app failed: app no program\r\n");
                    delay_ms(1000);
                }
                break;
            case UPDATE_PROGRAM:
                ymodem_c();
                uart_log("update app program...\r\n");
                delay_ms(1000);
                break;
            case UPDATE_SUCCESS:
                uart_log("update success\r\n");
                uart_log("system reboot...\r\n");
                delay_ms(1000);
                system_reboot();
                break;
            default:
                break;
        }
    }
}

Ymodem协议处理:

#define YMODEM_SOH		0x01
#define YMODEM_STX		0x02
#define YMODEM_EOT		0x04
#define YMODEM_ACK		0x06
#define YMODEM_NAK		0x15
#define YMODEM_CA		0x18
#define YMODEM_C		0x43

#define MAX_QUEUE_SIZE  1200

typedef void (*ymodem_callback)(process_status);

typedef struct 
{
	process_status process;
	uint8_t status;
	uint8_t id;
	uint32_t addr;
	uint8_t sectors_size;
	ymodem_callback cb;
} ymodem_t;

//顺序循环队列的结构体定义如下:
typedef struct
{
	uint8_t queue[MAX_QUEUE_SIZE];
	int rear;  //队尾指针
	int front;  //队头指针
	int count;  //计数器
} seq_queue_t; 

typedef struct 
{
	uint8_t data[1200];
	uint16_t len;
} download_buf_t;

void ymodem_ack(void) 
{
    uint8_t buf[3];
    buf[0] = YMODEM_ACK;
    buf[1] = 0x0D;
    buf[2] = 0x0A;
    RS485_Send_Data(buf, 3);
}

void ymodem_nack(void) 
{
    uint8_t buf[3];
    buf[0] = YMODEM_NAK;
    buf[1] = 0x0D;
    buf[2] = 0x0A;
    RS485_Send_Data(buf, 3);
}

void ymodem_c(void) 
{
    uint8_t buf[3];
    buf[0] = YMODEM_C;
    buf[1] = 0x0D;
    buf[2] = 0x0A;
    RS485_Send_Data(buf, 3);
}

void set_ymodem_status(process_status process) 
{
    ymodem.process = process;
}

process_status get_ymodem_status(void) 
{
    process_status process = ymodem.process;
    return process;
}

void ymodem_start(ymodem_callback cb) 
{
    if (ymodem.status == 0) 
    {
        ymodem.cb = cb;
    }
}

void ymodem_recv(download_buf_t *p) 
{
    uint8_t type = p->data[0];
    switch (ymodem.status) 
    {
        case 0:
            if (type == YMODEM_SOH) 
            {
                ymodem.process = BUSY;
                ymodem.addr = APP_SECTOR_ADDR;
                uart_log("erase flash: 0x%08X\r\n", APP_SECTOR_ADDR);
                mcu_flash_erase(ymodem.addr, APP_ERASE_SECTORS_NUM);
                uart_log("erase flash success\r\n");
                ymodem_ack();
                ymodem_c();
                ymodem.status++;
            }
            else if (type == '1') 
            {
                uart_log("start program now\r\n");
                ymodem.process = START_PROGRAM;
            }
            else if (type == '2') 
            {
                uart_log("enter update mode\r\n");
                ymodem.process = UPDATE_PROGRAM;
            }
            break;
        case 1:
            if (type == YMODEM_SOH || type == YMODEM_STX) 
            {
                if (type == YMODEM_SOH) 
                {
                    mcu_flash_write(ymodem.addr, &p->data[3], 128);
                    ymodem.addr += 128;
                }
                else 
                {
                    mcu_flash_write(ymodem.addr, &p->data[3], 1024);
                    ymodem.addr += 1024;
                }
                ymodem_ack();
            }
            else if (type == YMODEM_EOT) 
            {
                ymodem_nack();
                ymodem.status++;
            }
            else 
            {
                ymodem.status = 0;
            }
            break;
        case 2:
            if (type == YMODEM_EOT) 
            {
                ymodem_ack();
                ymodem_c();
                ymodem.status++;
            }
            break;
        case 3:
            if (type == YMODEM_SOH) 
            {
                ymodem_ack();
                ymodem.status = 0;
                ymodem.process = UPDATE_SUCCESS;
            }
    }
    p->len = 0;
}

void ymodem_init(void)
{
    RS485_Init(115200);
    timer_init();
    queue_initiate(&rx_queue);
}

关于bootloader详细的讲解,可以看下我之前发的博客:
STM32 IAP应用开发——自制BootLoader
完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

3.2 APP的制作

APP部分根据自己实际的功能来做,只要记得修改中断向量表地址即可。地址的值等于你APP区的起始地址。

示例代码如下:
main函数:

#include "main.h"
#include "usart.h"
#include "delay.h"

#define APP_VERSION          "V100"
#define NVIC_VTOR_MASK       0x3FFFFF80
#define APP_PART_ADDR        0x08008000

void ota_app_vtor_reconfig(void)
{
    /* Set the Vector Table base location by user application firmware definition */
    SCB->VTOR = APP_PART_ADDR & NVIC_VTOR_MASK;
}

void led_init(void)
{         
    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOF, &GPIO_InitStructure);
    GPIO_SetBits(GPIOF, GPIO_Pin_9);
}

void print_boot_message(void)
{
    uart_log("======================================\r\n");
    uart_log("-------------- Enter APP -------------\r\n");
    uart_log ("app version is: %s\r\n", APP_VERSION);
    uart_log("======================================\r\n");
}

int main(void)
{
    ota_app_vtor_reconfig();
    delay_init(168);
    uart_init(115200);
    print_boot_message();
    led_init();

    uart_log ("app init success\r\n");
    while (1)
    {
        GPIO_SetBits(GPIOF, GPIO_Pin_9);
        delay_ms(1000);
        GPIO_ResetBits(GPIOF, GPIO_Pin_9);
        delay_ms(1000);
    }
}

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

4 修改工程中的内存配置

因为我们对stm32的内存进行了分区,不同的代码要存放在不同的区域,因此,我们在编译工程之前需要先定义好各自的区域,以免出现内存越界。

4.1 Bootloader工程内存配置

Bootloader的起始地址不需要改,按flash默认地址即可,size需要改成实际分区大小。
在这里插入图片描述

4.2 APP工程内存配置

APP的起始地址和size都需要根据实际的分区来改。
在这里插入图片描述

5 烧录相关配置

我们的Bootloader做好以后需要烧录到MCU里面,可以直接用Keil uVison来下载,也可以用J-Flash或者其他,这个都没关系,但是要注意内存的分配,要把固件烧到对应的内存地址上。

5.1 BootLoader部分

1)使用Keil uVision下载
如果是用keil下载的话,需要注意flash的配置,具体如下:
在这里插入图片描述
2)使用其他下载工具
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08000000就好了。

5.2 APP部分

1)使用Keil uVision下载
跟BootLoader一样,我们按照前面分配好的空间配置APP的参数即可。
在这里插入图片描述
2)使用其他下载工具
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08008000就好了。

6 运行测试

用串口助手查看运行log(我这里用的是XShell,用其他的也是可以的)。

1)开始运行代码
等待5s,如果不需要升级就跳转到App区,如下图:
在这里插入图片描述
2)发送命令1
在等待的5s内通过串口2或者RS485发送一个’1’,直接跳转到APP。
注:我这里为了方便调试才用的这种方式,实际上可以根据自己的需求来做。
在这里插入图片描述
3)发送命令2,进入升级模式
在等待的5s内通过串口2或者RS485发送一个’2’,进入升级模式。
注:我这里为了方便调试才用的这种方式,实际上可以根据自己的需求来做。比如用按键进入,或者用其他串口,USB之类的,也可以在APP部分做这个功能。
串口调试窗口log如下图:
在这里插入图片描述
4)通过Ymodem传输新固件
调试工具我用的是XShell,实际上用其他工具也行,只要支持Ymodem方式传输文件即可。
在这里插入图片描述
在这里插入图片描述
5)升级固件
固件升级完成后自动重启,重新运行Bootloader和APP。
在这里插入图片描述
至此,整个升级流程就走完了。

结束语

好了,关于自制BootLoader并实现串口以及RS485升级固件的介绍就讲到这里,本文列举的例子其实只是升级的其中一种方式,只是提供一个思路,不是唯一的方法,实际上最好还是根据自己实际的需求来做。我之前也发给几篇升级相关的文章,用的都是不同的方式,各有各的优点和缺点,感兴趣的同学可以去看一下。

Chapter2 STM32F1 IAP在线升级功能实现(使用串口)及心得

原文链接:https://blog.csdn.net/qq_45625638/article/details/127121199

公司产品要求,需要做一个能远程升级程序的功能,找了很多例程,大多都是需要按键来完成操作的,而我需要的是通过串口发送指令来完成,于是东拼西凑最后还是用了四天的时间勉强做出来

整个功能需要的程序是两个部分。一个是IAP程序,一个是APP程序。对于IAP程序和APP原理方面的内容就不再过多赘述。直接从操作开始吧。

IAP程序
写IAP程序之前首先得配置程序的起始地址和大小。这里根据个人情况而定,我这里单片机flash大小是512k,所以IAP程序选择分配的大小是64k。
在这里插入图片描述
点击魔术棒,然后在选择target,设置起始地址(start)和大小(size),IAP程序起始地址都是从0x8000000开始的,大小就是0x10000也就是64k啦。
在这里我参考了原子哥的源码和一位大神的分享,原子的资料一搜一大堆,这里就仅贴出大佬的链接
stm32 IAP 程序编写心得
有了前车之鉴,做起来也稍显得心应手,这里的flash操作的函数和IAP功能函数都是拿来主义了,原子IAP例程里拿来就可以用。主要工作还是针对main函数,这里直接贴出源码:

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "stmflash.h"
#include "iap.h"
 
#define ADDR_CodeWriteFlag  0X08070000		//设置跳转标志位保存地址
#define ADDR_JumpToIAPFlag  0X08070001
int main(void)
{	
	u16 IAPFlagBuf[2];
	u16 RxDataCount=0;				//串口接收到的数据计数
	u16 RxDataLength=0;				//串口接收到的数据长度
	u16 AppCodeLength = 0;		//接收到的app代码长度
	u8  RxCmdFlag = 0;				
	u8  AppRunFlag = 0;				//应用程序运行标志
	u16 JumpToAPPFlag;									//跳转至APP程序标志位
	u16 JumpToIAPFlag;									//跳转回IAP标志位
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	JumpToAPPFlag = STMFLASH_ReadHalfWord(ADDR_CodeWriteFlag); //读取APP写入标志位,判断是否已有程序
	JumpToIAPFlag = STMFLASH_ReadHalfWord(ADDR_JumpToIAPFlag);
	uart_init(9600);					//串口初始化为9600
	delay_init();	   	 				//延时初始化 
	IAPFlagBuf[0] = 0x11;
	IAPFlagBuf[1] = 0x00;
	
	printf("提示:输入send发送bin文件!\r\n");
	while(1)
	{
		if(JumpToAPPFlag != 0x11)			//判断是否已有APP程序,如果已有,跳转至APP程序运行
		{
			if(JumpToIAPFlag == 0x11)		//判断是否从APP程序跳转回来,如果是擦除已有APP程序
			{			
				JumpToIAPFlag = 0x00;		
			}
			if(USART_RX_CNT)			//如果有数据进来
			{
				if(RxDataCount == USART_RX_CNT)				//串口没有再收到新数据
				{
					RxDataLength = USART_RX_CNT;
					if(RxCmdFlag == 0 && RxDataLength == 4)	//接收到IAP指令
					{
						if(USART_RX_BUF[0] == 's' && USART_RX_BUF[1] == 'e' && USART_RX_BUF[2] == 'n' && USART_RX_BUF[3] == 'd')//判断是否为IAP指令
						{
							RxCmdFlag = 1;							//接收到更新APP代码指令,标志位置1
							RxDataLength = 0;						//清空指令长度,防止影响后面计算APP代码大小
							printf("准备接收app程序,请添加bin文件!\r\n"); //准备好接收bin文件,等待用户添加
						}
						else
						{
							CodeUpdateFlag = 0;
							AppCodeLength = 0;
							printf("指令错误!\r\n");	//未接收到IAP更新指令,其他任何串口发送数据都认为指令错误
						}
					}
					
					else if(RxCmdFlag == 1 && RxDataLength > 10)//接收APP程序
					{
						CodeUpdateFlag = 1;												//代码更新标志位置位,用于应用程序代码接收完成后写FLASH
						RxCmdFlag = 0;
						AppCodeLength = USART_RX_CNT;
						printf("APP程序接收完成!\r\n");
						printf("程序大小:%dBytes\r\n",AppCodeLength);
					}
					
					else
					{
						RxDataLength = 0;
						printf("文件或指令错误!\r\n"); //如果代码大小不足10Bytes,认为没有正确添加bin文件
					}
					RxDataCount = 0;
					USART_RX_CNT = 0;
				}
				else 
				{
					RxDataCount = USART_RX_CNT;
				}
			}
			
			delay_ms(10);											//给以串口中断的时间,判断是否接收完成
			
			if(CodeUpdateFlag)								//代码更新标志位置位
			{
				CodeUpdateFlag = 0;
				if(AppCodeLength)
				{
					printf("程序更新中...\r\n");
					if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)					//判断代码合法性
					{	
						printf("正在下载程序!\r\n");
						iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,AppCodeLength);	//新代码写入FLASH  
						AppRunFlag = 1;
					}
					else 
					{
						printf("程序更新失败,请检查bin文件是否正确!\r\n");
						printf("跳转到原有应用程序!\r\n");
						iap_load_app(FLASH_APP1_ADDR);								         //执行FLASH APP代码
					}
				}
				else 
				{
					printf("没有程序可以更新!\r\n");
				}								 
			}
			
			if(AppRunFlag)																//App运行标志置位
			{
				printf("开始运行程序!\r\n");
				delay_ms(10);                        
				if(((*(vu32*)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)		//判断代码合法性
				{	 
					AppRunFlag = 0;
					STMFLASH_Write(ADDR_CodeWriteFlag,IAPFlagBuf,2);		   //写入APP代码标志
					//RCC_DeInit(); //关闭外设
					//__disable_irq();
					iap_load_app(FLASH_APP1_ADDR);								         //执行FLASH APP代码
				}
				else 
				{
					printf("应用程序错误!\r\n");  
				}									   
			}
		}
		else
		{
			printf("已有一个应用程序!\r\n");
			printf("开始运行程序!\r\n");
			delay_ms(10);
			iap_load_app(FLASH_APP1_ADDR);										//执行FLASH APP代码
		}
	}
} 
  1. main函数中,选择一块不使用的flash区域来保存跳转标志位。
    在这里插入图片描述

我选择的是0x8070000开始的区域,整个flash大小是512k也就是0x8080000,所以最后我又留了64k的大小来存放,所以留给APP程序的就是0x08010000到0x08070000的地址,也是就384k大小空间,记住这个0x08010000到0x08070000,后面APP程序要考。

  1. 每次进入main函数都要先读这个地址的值,若等于0x11则直接跳转到APP,因为在跳转APP之前是要对这个地址进行写0x11的,所以跳转到APP后这个地址是0x11,开机启动会自动进入APP。
    在这里插入图片描述
    到此似乎思路就清晰了,原子通过按键来操作接收,下载。我这里通过指令来接收下载,其他的判断栈顶地址的合法性,看两遍代码也能明白了。

APP程序

接下来就是APP程序的操作部分了。
要从IAP跳转到APP,APP的程序也需要修改。首先就是程序的起始地址和大小。
在这里插入图片描述
前面的IAP从0x8000000到0x8010000,所以我们的APP程序只能从0x8010000开始,而0x8070000到0x8080000又要保存跳转标志位,那么APP的地址就只能是0x8010000到0x8070000了,既然这样,那就拉满吧,所以大小设置我选择0x60000。
然后就是输出bin文件了,要用串口传输不能用hex文件,所以在USER界面
选择如图所示操作,方框里填的是E:\keil5\ARM\ARMCC\bin\fromelf.exe --bin -o …\output\Project.bin …\output\Project.axf 注意有空格,当然这个也得根据自己的文件位置更改。
在这里插入图片描述
下一步就是更改偏移地址了,这里只需要在主函数里加
SCB->VTOR=FLASH_BASE|0x10000; 即可,我这里偏移0x10000
在这里插入图片描述
到此就能编译生成bin文件,通过IAP程序,使用串口来完成升级了。
但是后续要升级怎么办,我们还得从APP跳转到IAP来。所以在APP程序中还需要接收指令来跳转到IAP去。
在这里插入图片描述
这里Receive_Data_Point3是接收到的字节长度,我设置的跳转指令是APPTOIAP!共9位。
然后就是跳转前还需将存放标志位得地址写入0x00,前面IAP中会对该地址得值进行判断是否是0x11,是的话就又跳回来了。然后有大佬踩坑后,得知跳转到IAP直接用NVIC_SystemReset(); 即可。
在这里插入图片描述
到此就基本完成了。
第一次写博客,也是为了记录和学习。语言不通顺还请多谅解,如有错误的地方也请多加指正。同时有疑问的同学也可以评论留言,欢迎讨论交流。

可能出现的问题解决方法:
在后续的测试中发现,串口接收bin文件时还没接收完就进入到了写入flash的动作,导致有很大的概率程序升级失败。分析了半天原因可能是,接收缓存在10ms内没收到数据就默认接收完毕进入写入跳转了,我使用的9600波特率,在将下图中的延时增加到100ms后,没有再出现问题了。
在这里插入图片描述

Chapter3 STM32学习笔记之简易Bootloader串口升级设计

原文链接:https://blog.csdn.net/xinghuanmeiying/article/details/79573065

概念简介

在学习制作串口升级 Bootloader 之前,我们先了解一下STM32的 IAP (In Application Programming)即在应用编程,IAP是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级.
设计这样的功能需要有两个项目代码:

Bootloader 程序: 该部分代码用于实现通过某种通信方式(如 USB,USART)接收程序或数据,执行对第二部分代码的更新,通常存储于 Flash 的起始地址(0x08000000);
App 程序: 该部分代码是产品实现业务逻辑正常运行的代码,该部分代码需要在存储的 Flash 中进行相对的地址偏移.

复位执行流程

当芯片上电或者复位后,首先是Bootloader代码开始运行,它作如下操作:

(1) 检查是否需要对第二部分代码进行更新;
(2) 如果不需要更新则转到(4);
(3) 执行更新操作;
(4) 跳转到第二部分代码执行.

加了 Bootloader 程序后,程序运行流程图:
在这里插入图片描述

实现要点

Bootloader程序实现

(1) 完成 USART 串口数据接收传输功能,并将接收到的数据绝对定位到 SRAM 的设定地址中,地址的设定根据实际情况而定,应保证设定的地址大于 Bootloader 执行需要的 RAM 的空间;
(2) 需要实现对 STM32 Flash 读写操作相关的驱动,并将绝对定位 SRAM 处的固件数据写入到 Flash 中;
(3) 完成固件数据接收和写入 Flash 后,需要对 PC 指针进行程序跳转,跳转完成后,即运行固件中的程序.

注: 程序跳转之前需要关闭所有的中断.

App程序实现

(1) 在 main 函数最开头处设置 VTOR 寄存器,实现固件中断向量表的重定向.例如:

SCB->VTOR = FLASH_BASE | 0X10000; /*中断向量表偏移量设置*/

(2) 设置 App 程序的存储偏移地址,这里以 STM32F103ZE系列的单片机为例,Flash为512k,SRAM为64k.这里设置Flash前64k用于存储 Bootloader 程序,后448k为固件程序空间,SRAM地址不需要设置偏移.
在这里插入图片描述
可以用jflash查看生成的HEX文件,查看HEX起始地址是否为设置的偏移地址,如下图所示:
在这里插入图片描述
若程序跳转时,发生 HardFault 异常中断,可能是设置的地址偏移量没有生效,应该勾选 MDK 的相关配置.
在这里插入图片描述
(3) 将编译生成的 .axf 文件 (要勾选生成HEX文件) 通过 MDK 安装时自带的 fromelf.exe 程序转成可以通过串口升级的 bin文件.

语法格式为: [MDK安装目录/fromelf.exe] –bin -o [bin文件生成目录] [axf文件目录]
点击编译即可生成可以通过串口升级的 bin 文件.
在这里插入图片描述
总结: App 程序在代码上除了需要设置中断向量表偏移和程序存储地址偏移以外,其余和不加 Bootloader 的程序代码并无差别,难点在于理解 Flash 偏移地址的设置.

Chapter4 STM32 IAP应用开发——自制BootLoader

原文链接:https://blog.csdn.net/ShenZhen_zixian/article/details/129064681

实际上,BootLoader不仅仅在操作系统上使用,在一些内存小,功能应用较为简单的单片机设备上面也可以通过BootLoader来完成固件升级。

我之前也有发过一些关于STM32远程升级的文章,但用的是第三方BootLoader,而且是基于操作系统实现的。BootLoader占用的内存也比较大,而且不开源。
所以这一讲我就来介绍一下如何自己制作一个简单的BootLoader程序。

1 环境搭建

关于STM32以及Keil的环境这里就不具体介绍了,网上教程也很多,不懂的同学自行查阅资料。

2 BootLoader工作原理以及常见分区介绍

不管用的是什么MCU,要实现固件升级都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现固件升级。
在这里插入图片描述

常见分区方式介绍:
1.Application
没有加入Bootloader之前,我们单片机内部的flash就是一整块的,所有的应用代码都放在这。
在这里插入图片描述
2.Bootloader + Application
在原有的flash区域里面划分出两个区域,Bootloader和Application,这种分区方式的好处在于既可以实现升级功能,App区又可以分到较大的空间,缺点是没有存放新固件的区域,需要从外部导入进来,而且一旦传输的过程被异常打断,那么原有的App代码也无法正常运行了,也就是传说中的“变砖”。
在这里插入图片描述
3.Bootloader + Application + Download
这种分区方式是比较万能的一种,优点是新固件是先存放到Download区的,哪怕搬运的过程中出现异常中断的情况,也不会“变砖”,缺点是需要单独划分一块内存跟APP区差不多的区域用来存放新固件,变相的减少了APP区的空间,对于内存较小的单片机来说压力就比较大了。
在这里插入图片描述
4.Bootloader + Application1 + Application2
这种方式可以同时存在两套App,优点在于升级了新固件以后,还保留了原来的旧版固件,必要的时候还可以进行版本的回退。
在这里插入图片描述

5.Bootloader + Setting + Application + Download
这种方式跟第3种基本一样,只是增加了一个区域用来存放升级相关的一些参数以及用户的一些配置。
在这里插入图片描述

3 BootLoader的制作

BootLoader的制作需要根据实际的需求来做,不同的运行方式或者升级方式在做法上都是有区别的,包括BootLoader所需要的内存空间也不尽相同。
不过不管是用什么方式,Bootloader都应该尽可能做的更小更简洁,这样的话内存的开销就更小,对于内存较小的MCU来说压力就没那么大了。

我下面要做的这个bootloader是上面讲的常见分区方式里面的第5种。
分区介绍:
我用的是STM32F103,内存是128K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。

分区表如下:
在这里插入图片描述
功能描述:
运行bootloader的时候先从setting里面读一些参数,确定是否需要升级,如果需要,则把download分区的固件搬运到app分区,如果不需要升级则直接跳转到app分区.
至于新固件的下载传输过程,我放到App里面去处理了,这跟我的项目实际需求有关系,App部分这里就先不往下拓展了,后面我会专门写一篇博客来介绍。

各个功能模块的具体讲解:
1、分区定义
先把各个分区的内存地址以及大小定义好,方便后面使用。

#define FLASH_SECTOR_SIZE       1024
#define FLASH_SECTOR_NUM        128    // 128K
#define FLASH_START_ADDR        ((uint32_t)0x8000000)
#define FLASH_END_ADDR          ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))

#define BOOT_SECTOR_ADDR        0x08000000     // BOOT sector start address 
#define BOOT_SECTOR_SIZE        0x3000         // BOOT sector size
#define SETTING_SECTOR_ADDR     0x08003000     // SETTING sector start address 
#define SETTING_SECTOR_SIZE     0x1000         // SETTING sector size
#define APP_SECTOR_ADDR         0x08004000     // APP sector start address  
#define APP_SECTOR_SIZE         0xE000         // APP sector size
#define DOWNLOAD_SECTOR_ADDR    0x08012000     // Download sector start address
#define DOWNLOAD_SECTOR_SIZE    0xE000         // Download sector size   

2、程序跳转
Bootloader作为引导程序,最重要的工作之一就是通过内存跳转进入用户程序,下面这段代码可以跳转到任何一个内存地址。

uint8_t jump_app(uint32_t app_addr) 
{
    uint32_t jump_addr;
    jump_callback cb;
    if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000) 
    {  
        jump_addr = *(__IO uint32_t*) (app_addr + 4);  
        cb = (jump_callback)jump_addr;  
        __set_MSP(*(__IO uint32_t*)app_addr);  
        cb();
        return 1;
    } 
    return 0;
}

3、处理函数
从setting区里面读取process状态值,然后进行对应的处理,如果需要升级则把download区的固件搬运到app区,然后再运行新APP,如果不需要升级则直接跳转到APP。

process = get_boot_state();
switch (process) 
{
    case START_PROGRAM:
        printf("start app...\r\n");
        delay_ms(50);
        if (!jump_app(APP_SECTOR_ADDR)) 
        {
            printf("no program\r\n");
            delay_ms(1000);
        }
        printf("start app failed\r\n");
        break;
    case UPDATE_PROGRAM:
        printf("update app program...\r\n");
        app_addr = APP_SECTOR_ADDR;
        down_addr = DOWNLOAD_SECTOR_ADDR;

        printf("app addr: 0x%08X \r\n", app_addr);
        printf("down addr: 0x%08X \r\n", down_addr);

        printf("erase mcu flash...\r\n");
        mcu_flash_erase(app_addr, APP_ERASE_SECTORS);  
        printf("mcu flash erase success\r\n");
    
        printf("write mcu flash...\r\n");
        // memset(down_buf, 0, sizeof(down_buf));
        for (i = 0; i < APP_ERASE_SECTORS * 8; i++)
        {
            mcu_flash_read(down_addr, &down_buf[0], 128);
            delay_ms(5);
            mcu_flash_write(app_addr, &down_buf[0], 128);
            delay_ms(5);
            down_addr += 128;
            app_addr += 128;
        }
        printf("mcu flash write success\r\n");

        set_boot_state(UPDATE_SUCCESS);
        break;
    case UPDATE_SUCCESS:
        printf("update success\r\n");
        boot_state = UPDATE_SUCCESS_STATE;
        write_setting_boot_state(boot_state);
        set_boot_state(START_PROGRAM);
        break;
    default:
        break;
}

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87462312

4 烧录下载配置

我们的Bootloader做好以后需要烧录到MCU里面,可以直接用Keil uVison来下载,也可以用J-Flash或者其他,这个都没关系,但是要注意内存的分配,要把固件烧到对应的内存地址上。
我这里做出来的bootloader bin只有8K,不过为了方便后续在这部分增加新功能,我实际分配了12K的空间,地址区间是0x08000000-0x08003000。

如果是用keil下载的话,需要注意flash的配置,具体如下:
在这里插入图片描述

在这里插入图片描述
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08000000就好了。

5 运行测试

注:这里我没讲解App部分代码,你们只看Bootloader部分的log就好了,不影响的,想看APP部分可以看我另外一篇文章,或者下载完整的代码实际跑一下也行。APP部分讲解:STM32 IAP应用开发——通过USB实现固件升级

运行结果:
不需要升级时直接跳转到App区,如下图:
在这里插入图片描述
需要升级时先从download区搬运新固件到app区,然后再跳转到App区,如下图:
在这里插入图片描述

结束语

好了,关于自制BootLoader的介绍就讲到这里,本文只是提供一个思路,不是唯一的方法,关键还是看你自己实际的需求。
还有App那部分这里没详细讲,我单独写了一篇文章,链接在下方,合到一起看就比较清晰了。或者你也可以下载完整的源码自己去跑一下,下面的源码我把BootLoader和APP都上传了。

APP部分讲解:STM32 IAP应用开发——通过USB实现固件升级
完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87462312

如果你有什么问题或者有更好的方法,欢迎在评论区留言。

更多相关文章:
STM32固件升级系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

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

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

相关文章

verilog 每日一练- 移位寄存器

module shift_1x64 (clk, shift,sr_in,sr_out,);input clk, shift;input sr_in;output sr_out;reg [63:0] sr;always(posedge clk)beginif (shift 1b1)beginsr[63:1] < sr[62:0];sr[0] < sr_in;endendassign sr_out sr[63];endmodule 这个Verilog模块 shift_1x64 实现了…

《算法通关村—迭代实现二叉树的前序遍历》

《算法通关村—迭代实现二叉树的前序遍历》 利用递归进行二叉树的前序遍历是非常容易的几行代码就能解决。但是你知道如何用迭代实现吗&#xff1f; 理论上来说能够用递归解决的都能用迭代解决&#xff0c;我们就来试试用迭代解决二叉树的前序遍历问题吧。 什么是前序遍历&a…

Photoshop 2023 v24.7

Photoshop是一款强大的图像编辑软件&#xff0c;被广泛应用于图像处理、图形设计、数字绘画等领域。它提供了丰富的图像编辑功能&#xff0c;可以用于调整图像的色彩、亮度、对比度等&#xff0c;添加特效、滤镜&#xff0c;以及进行复杂的图像合成和修复。 以下是Adobe Photo…

内存学习(2):内存分类与常用概念2(SDRAM与DDR)

SDRAM与DDR的简单概念介绍 SDRAM 定义&#xff1a; 同步动态随机存取内存&#xff08;Synchronous Dynamic Random-Access Memory&#xff0c;简称SDRAM&#xff09;是有一个同步接口的动态随机存取内存DRAM&#xff08;可以参考前文&#xff09;。可以理解为是一种利用同步…

Linux网络编程03

select的缺陷 &#xff08;1&#xff09;fd,set的本质是一个位图&#xff0c;容量是固定的1024&#xff0c;因此最大只能监听1024个连接 &#xff08;可以扩容&#xff09; &#xff08;2&#xff09;监听和就绪用的是同一个数据结构&#xff0c;使用困难 &#xff08;3&#x…

Nacos2.2.3版本运行startup.cmd出现闪退,无错误信息解决方法

Nacos2.2.3版本运行startup.cmd出现闪退&#xff0c;无错误信息解决方法 一、问题描述二、解决方法 一、问题描述 当我下载好nacos2.2.3版解压之后&#xff0c;直接双击startup.cmd出现闪退&#xff0c;而且 没有错误提示信息。后来经过一番搜索尝试&#xff0c;终于解决了自己…

InnoDB中Buffer Pool详解

1. 概念及特点 Buffer Pool 是 MySQL 中 InnoDB 存储引擎用来缓存表数据和索引数据的内存区域。这个内存区域被用来存储磁盘上的数据页的副本&#xff0c;这样常用的数据可以在内存中快速被访问&#xff0c;而不必每次都从磁盘中读取。 以下是 Buffer Pool 的一些重要特点&a…

kali Linux中更换为阿里镜像源

准备&#xff1a; kali Linux 阿里源链接 deb kali安装包下载_开源镜像站-阿里云 kali-rolling main non-free contrib deb-src kali安装包下载_开源镜像站-阿里云 kali-rolling main non-free contrib 配置&#xff1a; 打开kali 终端输入&#xff1a;sudo nano /etc/apt…

2023.11.5 关于 Spring 创建 和 使用

目录 创建 Spring 项目 1.创建 Maven 项目 2.添加 Spring 依赖 将 Bean 对象存储到 Spring 容器中 创建 Bean 存储 Bean ApplicationContext 获取 Bean BeanFactory 获取 Bean ApplicationContext 和 BeanFactory 的区别 获取 Bean 的三种方式 根据 Bean id 获取…

基于8086电压表系统仿真系统设计

**单片机设计介绍&#xff0c;1665基于8051单片机与1601LCD的计算器设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 一个基于8086的电压表系统仿真系统可以分为硬件和软件两部分。 硬件部分包括输入设备&#xff08;例如模拟…

什么是缓冲区溢出?

缓冲区溢出 1. 什么是缓冲区溢出2. 缓冲区溢出攻击的类型3. 攻击者如何利用缓冲区溢出4. 如何防止缓冲区溢出攻击 1. 什么是缓冲区溢出 &#xff08;1&#xff09;缓冲区 缓冲区是一块连续的计算机内存区域&#xff0c;用于在将数据从一个位置移到另一位置时临时存储数据。这…

[动态规划] (七) 路径问题:LCR 166.剑指offer 47. 珠宝的最高价值

[动态规划] (七) 路径问题&#xff1a;LCR 166./剑指offer 47. 珠宝的最高价值 文章目录 [动态规划] (七) 路径问题&#xff1a;LCR 166./剑指offer 47. 珠宝的最高价值题目解析解题思路状态表示状态转移方程初始化和填表顺序 返回值代码实现总结 LCR 166. 珠宝的最高价值 题目…

一致性哈希在分库分表的应用

文章目录 前言分库分表方法一致性哈希介绍分库分表的应用 总结 前言 大家应该都知道一些哈希算法&#xff0c;比如MD5、SHA-1、SHA-256等&#xff0c;通常被用于唯一标识、安全加密、数据校验等场景。除此之外&#xff0c;还有一种应用是对某个数据进行哈希取模映射到一个有限…

【数据开发】大数据平台架构,Hive / THive介绍

1、大数据引擎 大数据引擎是用于处理大规模数据的软件系统&#xff0c; 常用的大数据引擎包括Hadoop、Spark、Hive、Pig、Flink、Storm等。 其中&#xff0c;Hive是一种基于Hadoop的数据仓库工具&#xff0c;可以将结构化的数据映射到Hadoop的分布式文件系统上&#xff0c;并提…

什么是防火墙?详解三种常见的防火墙及各自的优缺点

目录 防火墙的定义 防火墙的功能 防火墙的特性 防火墙的必要性 防火墙的优点 防火墙的局限性 防火墙的分类 分组过滤防火墙 优点&#xff1a; 缺点&#xff1a; 应用代理防火墙 优点 缺点 状态检测防火墙 优点 缺点 防火墙的定义 防火墙的本义原是指古代人们…

【蓝桥杯选拔赛真题11】C++求平方 青少年组蓝桥杯C++选拔赛真题 STEMA比赛真题解析

目录 C/C++求平方 一、题目要求 1、编程实现 2、输入输出 二、算法分析

基于.NET、Uni-App开发支持多平台的小程序商城系统 - CoreShop

前言 小程序商城系统是当前备受追捧的开发领域&#xff0c;它可以为用户提供一个更加便捷、流畅、直观的购物体验&#xff0c;无需下载和安装&#xff0c;随时随地轻松使用。今天给大家推荐一个基于.NET、Uni-App开发支持多平台的小程序商城系统&#xff08;该商城系统完整开源…

前端框架Vue学习 ——(二)Vue常用指令

文章目录 常用指令 常用指令 指令: HTML 标签上带有 “v-” 前缀的特殊属性&#xff0c;不同指令具有不同含义。例如: v-if, v-for… 常用指令&#xff1a; v-bind&#xff1a;为 HTML 标签绑定属性值&#xff0c;如设置 href&#xff0c;css 样式等 <a v-bind:href"…

NLP入门——基础知识

生成式AI和AIGC&#xff1a; 生成式AI所生成的内容就是AIGC AI的Venn图&#xff1a; 注意&#xff1a; 监督学习&#xff08;训练数据带标签&#xff09;&#xff1a; 经典的监督学习任务包括分类&#xff08;划分类别&#xff09;和回归&#xff08;预测&#xff09;&#x…

基于葡萄串的采摘点定位方法

文章目录 概要所需设备方法基于RGB图像的YOLOV8目标检测基于深度图的区域种子生长利用峰值定位法来确定竖向位置核心代码演示效果概要 这里将介绍如何用图像识别方法来定位葡萄串采摘点,用于机器人自动采摘操作。 所需设备 深度相机,这里我用的是realsense-L515 方法 主…