嵌入式MCU BootLoader开发配置详细笔记教程

目录

一、BootLoader基础

二、BootLoader原理及配置

三、BootLoader程序

bootloader.h

bootloader.c

四、Application1 用户程序

application1.h

application1.c

五、Application2 用户程序

application2.h

六、程序运行效果

七、工程文件Demo


一、BootLoader基础

        对于接触过嵌入式Linux系统开发的开发者们,想必对BootLoader是不陌生的,因为定制化移植Linux系统,最先接触的就是BootLoader程序。但如果是从单片机MCU起步的开发者,可能对BootLoader就不是那么熟悉了,因为单片机开发最先接触的往往是GPIO外设的驱动开发。但不管是嵌入式Linux的MPU开发,还是嵌入式单片机的MCU开发,BootLoader的功能都是一样的。它是嵌入式系统中一种特殊的软件程序,它在系统加电或复位后最先执行,负责初始化硬件设备、设置系统环境,并最终引导加载操作系统内核或用户指定的应用程序。BootLoader 起到了桥梁的作用,连接了硬件启动与高级软件运行之间的环节,确保系统能够从一个初始、裸机的状态过渡到一个完整的、可操作的运行环境。

        当然,除了嵌入式开发者们,对于喜欢DIY装机的极客们,对于BootLoader应该也是不陌生的,并且经常接触到它,如电脑在安装或开启Windows时的BIOS界面,本质上也是一个BootLoader引导程序。如下图为常见的BootLoader显示的GUI界面图。

如下例举的是一些BootLoader 的主要功能和作用:

①、硬件初始化:

  • 开启和配置基本的硬件模块,如CPU、时钟、内存控制器、中断控制器、串口、GPIO等,使它们进入工作状态。
  • 设置堆栈、中断向量表等关键数据结构,为后续软件执行做好准备。

②、内存管理:

  • 建立内存空间映射图,识别可用的RAM区域及其大小,为操作系统内核分配合适的运行空间。
  • 对于使用MMU(Memory Management Unit)的系统,可能还需要设置内存分页和映射规则。

③、引导加载操作系统:

  • 从非易失性存储器(如Flash、EEPROM、NAND/NOR Flash等)中读取并验证操作系统的内核映像。
  • 将内核映像加载到RAM中指定的位置,并按照内核所需的特定格式设置启动参数和环境变量。

④、固件升级:

  • 提供一种安全机制,允许在运行时通过网络、串口、USB等接口接收新的固件映像,并将其写入非易失性存储器,实现设备的远程或本地固件更新。

⑤、系统诊断与恢复:

  • 可能包含简单的故障检测和恢复机制,如硬件自检、低级别固件修复、安全模式启动等功能,帮助在系统启动失败时进行故障排查和恢复。

⑥、多重引导支持:

  • 在某些系统中,BootLoader 可能支持选择加载不同的操作系统版本或应用程序,提供多启动选项,增强系统的灵活性和可定制性。

        目前BootLoader 的应用广泛存在于各种嵌入式系统中,如消费电子领域中,随处可见的智能手机,智能电脑,智能手表、路由器等。在汽车电子领域的车载系统ADAS(Advanced Driver Assistance Systems)模块、ECU(Electronic Control Units)等,及医疗设备领域的便携式医疗仪器、监护设备、植入式医疗器械等。基本上有电子产品的地方,都能看到BootLoader的身影,因此掌握BootLoader是从事嵌入式开发的一项非常基本的技能。

二、BootLoader原理及配置

        本文主要是针对单片机MCU设备进行BootLoader的配置讲解,目标设备为STM32G431,开发平台是MDK KEIL V5以上

        如下图所示是FLASH中的数据分布图,可见FLASH的用户代码区域的起始程序为BootLoader引导程序,然后紧接着的是应用程序APP1和应用程序APP2。

        其中,BootLoader和APP1及APP2都是完整的用户代码程序,但因BootLoader只起一个引导跳转APP启动操作,所以占用的FLASH内存空间较小。

        提醒:App的个数可以根据实际需求进行设置,只要不超过FLASH的内存空间大小限制即可,为了效果展示,在本文中设置了一个BootLoader程序,两个APP应用程序进行切换。

        如下图所示的是,Bootloader、App1、App2在FLASH中的内存地址映射图。在BootLoader的程序配置好后,根据触发条件的不同,会自动跳转到不同的APP应用程序。

        BootLoader和APP应用程序的启动跳转切换,原理上就是内存地址的切换,当BootLoader程序接收到对应的操作触发条件时,会进行相应的地址跳转切换,及一些其它的附加操作,然后执行该地址空间上的用户程序。但一般来说,BootLoader中会进行CPU工作模式、配置内存控制器、初始化外设等工作,为后续程序运行创建一个稳定的硬件环境。所以在APP中可以节省掉BootLoader中已经进行过的硬件环境配置。

        如下图所示为《STM32G4系列微控制器参考手册》官方文件中截图下来的,STM32的FLASH中是按块进行读写操作的,所以内存空间配置时,必须以块为最小单元分配

如何配置BootLoader及APP应用程序的下载烧录?

①、单击魔术棒

②、选择Target

③、修改IROM1中的Start地址数据及Size数据

        修改Size空间的大小时,需要先确定程序编译后的内存大小是多少,如果内存空间配置不够,会导致编译及下载报错。如下图所示为查看程序编译后需要的内存空间大小的方式。     

三、BootLoader程序

bootloader.h

#ifndef __BOOTLOADER_H
#define __BOOTLOADER_H

#include "main.h"

#define FLASH_BASE_ADDR    (uint32_t)(0x08000000)

//BootLoader   预留10KB的FLASH空间 --- (0x0800 0000 --> 0x0800 27FF)
//Application1 预留20KB的FLASH空间 --- (0x0800 2800 --> 0x0800 77FF)
//Application2 预留20KB的FLASH空间 --- (0x0800 7800 --> 0x0800 C7FF)

#define BOOT_BASE_ADDR    FLASH_BASE_ADDR
#define APP1_BASE_ADDR    (uint32_t)(0x08002800)
#define APP2_BASE_ADDR    (uint32_t)(0x08007800)


#define KEY_DOWN    GPIO_PIN_RESET
#define KEY_UP        GPIO_PIN_SET

#define LED1    LED1_LCD8_Pin
#define LED2    LED2_LCD9_Pin
#define LED3    LED3_LCD10_Pin
#define LED4    LED4_LCD11_Pin
#define LED5    LED5_LCD12_Pin
#define LED6    LED6_LCD13_Pin
#define LED7    LED7_LCD14_Pin
#define LED8    LED8_LCD15_Pin

#define LED_ON    GPIO_PIN_RESET
#define LED_OFF    GPIO_PIN_SET

extern volatile uint8_t key1_flag;
extern volatile uint8_t key2_flag;

void BootLoader_Code(void);

void Key_San(void);
void LED_Control(int led, int state);
void LED_Close_All(void);

#endif

bootloader.c

#include "bootloader.h"



typedef  void (*pFunction)(void);

pFunction Boot_Jump_to_App;

uint32_t jump_addr;


#if 0
//初始化用户栈指针汇编程序
__ASM void __set_MSP(uint32_t mainStackPointer)
{
    msr msp, r0
    bx lr
}
#endif

/**
  * @brief  BootLoader程序
  * @param  None
  * @retval None
  */
void BootLoader_Code(void)
{
    LED_Close_All();
    
    printf("------------Hello BootLoader V 1.0---------\r\n");
    printf("------------Editor:牛马大师兄--------------\r\n\r\n");
    printf("------------Press KEY1 Boot APP1-----------\r\n");
    printf("------------Press KEY2 Boot APP2-----------\r\n");
        
    //扫描按键状态,根据按键跳转到相应的APP程序
    while(1)
    {
        Key_San();
        if(key1_flag==1||key2_flag==1)
        {
            break;
        }
    }
    
    if(key1_flag == 1)
    {
        //检查用户代码1的栈顶地址,是否位于0x20000000~0x2001ffff内。
        if (((*(volatile uint32_t*)APP1_BASE_ADDR) & 0x2FFE0000 ) == 0x20000000)
        {
            printf("\r\n-------------- APP1 Starting --------------\r\n");
            //屏蔽所有中断,防止跳转过程中,中断干扰
            __disable_irq();
            
            //用户代码的第二个字,为程序开始地址(复位地址)
            jump_addr = *(volatile uint32_t*)(APP1_BASE_ADDR+4);
            Boot_Jump_to_App = (pFunction)jump_addr;
            //初始化用户栈指针
            __set_MSP(*(volatile uint32_t*) APP1_BASE_ADDR);
            //用户程序跳转
            Boot_Jump_to_App();
        }
    }else if(key2_flag == 1)
    {
        //检查用户代码2的栈顶地址,是否位于0x20000000~0x2001ffff内。
        if (((*(volatile uint32_t*)APP2_BASE_ADDR) & 0x2FFE0000 ) == 0x20000000)
        {
            printf("\r\n-------------- APP2 Starting --------------\r\n");
            //屏蔽所有中断,防止跳转过程中,中断干扰
            __disable_irq();
            
            //用户代码的第二个字,为程序开始地址(复位地址)
            jump_addr = *(volatile uint32_t*)(APP2_BASE_ADDR+4);
            Boot_Jump_to_App = (pFunction)jump_addr;
            //初始化用户栈指针
            __set_MSP(*(volatile uint32_t*) APP2_BASE_ADDR);
            //用户程序跳转
            Boot_Jump_to_App();
        }
    }
}

volatile uint8_t key1_flag = 0;
volatile uint8_t key2_flag = 0;

/**
  * @brief  按键扫描程序
  * @param  None
  * @retval None
  */
void Key_San(void)
{
    if(HAL_GPIO_ReadPin(GPIOB, KEY1_Pin) == KEY_DOWN)
    {
        HAL_Delay(10);
        if(HAL_GPIO_ReadPin(GPIOC, KEY1_Pin) == KEY_DOWN)
        {
            key1_flag = 1;
            printf("\r\n--------------- KEY 1 PRESS ---------------\r\n");
            LED_Control(LED1, LED_ON);
        }
    }
    
    if(HAL_GPIO_ReadPin(GPIOB, KEY2_Pin) == KEY_DOWN)
    {
        HAL_Delay(10);
        if(HAL_GPIO_ReadPin(GPIOC, KEY1_Pin) == KEY_DOWN)
        {
            key2_flag = 1;
            printf("\r\n--------------- KEY 2 PRESS ---------------\r\n");
            LED_Control(LED2, LED_ON);
        }
    }
}
/**
  * @brief  LED控制程序
  * @param  led:操作的LED灯
  * @param  state:LED的状态
  * @retval None
  */
void LED_Control(int led, int state)
{
    HAL_GPIO_WritePin(GPIOC, led, state);    
    HAL_GPIO_WritePin(LED_LOCK_GPIO_Port, LED_LOCK_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_LOCK_GPIO_Port, LED_LOCK_Pin, GPIO_PIN_RESET);
}
/**
  * @brief  关闭全部的LED指示灯
  * @param  None
  * @retval None
  */
void LED_Close_All(void)
{
    HAL_GPIO_WritePin(GPIOC, LED6_LCD13_Pin|LED7_LCD14_Pin|LED8_LCD15_Pin|LED1_LCD8_Pin
          |LED2_LCD9_Pin|LED3_LCD10_Pin|LED4_LCD11_Pin|LED5_LCD12_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_LOCK_GPIO_Port, LED_LOCK_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_LOCK_GPIO_Port, LED_LOCK_Pin, GPIO_PIN_RESET);
}

        主函数中对BootLoader程序进行调用

        修改BootLoader程序烧录地址及内存空间大小

四、Application1 用户程序

application1.h

#ifndef __APPLICATION_H
#define __APPLICATION_H

#include "main.h"



#define APP1_VECT_ADDR_OFFSET    0x2800

void App1_Code(void);


#endif

application1.c

#include "application1.h"

/**
  * @brief  App1应用程序
  * @param  None
  * @retval None
  */
void App1_Code(void)
{
    //设置中断向量偏移表
    SCB->VTOR = FLASH_BASE | APP1_VECT_ADDR_OFFSET;
    //使能全局中断--不使能会出现异常
    __enable_irq();
    
    //APP1中的业务代码程序
    printf("--------- Welcome to Application 1 --------\r\n");
    
    LCD_Init();
    
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LCD_Clear(Black);
    HAL_Delay(200);
    LCD_DisplayStringLine(Line4, (unsigned char *)"    Application 1    ");    

}

         主函数中对App1程序进行调用

        修改App1程序烧录地址及内存空间大小

五、Application2 用户程序

application2.h

#ifndef __APPLICATION_H
#define __APPLICATION_H

#include "main.h"


#define APP2_VECT_ADDR_OFFSET    0x7800

void App2_Code(void);



#endif

application2.c

#include "application2.h"

/**
  * @brief  App2应用程序
  * @param  None
  * @retval None
  */
void App2_Code(void)
{
    //设置中断向量偏移表
    SCB->VTOR = FLASH_BASE | APP2_VECT_ADDR_OFFSET;
    //使能全局中断--不使能会出现异常
    __enable_irq();
    
    //APP2中的业务代码程序
    printf("--------- Welcome to Application 2 --------\r\n");
    
    LCD_Init();
    
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LCD_Clear(Black);
    HAL_Delay(200);
    LCD_DisplayStringLine(Line4, (unsigned char *)"    Application 2    ");    
}

         主函数中对App2程序进行调用

        修改App2程序烧录地址及内存空间大小

六、程序运行效果

开发板实物演示图,按下KEY1按键,启动APP1;按下KEY2按键,启动APP2。

        

上位机串口输出数据演示图

七、工程文件Demo

        本文关于BootLoader讲解演示的3个工程文件可查阅下面的链接访问,文件已上传至CSDN平台的文件资源仓库。

【免费】嵌入式MCUBootLoader开发配置工程Demo资源-CSDN文库

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ | Leetcode C++题解之第29题两数相除

题目: 题解: class Solution { public:int divide(int dividend, int divisor) {// 考虑被除数为最小值的情况if (dividend INT_MIN) {if (divisor 1) {return INT_MIN;}if (divisor -1) {return INT_MAX;}}// 考虑除数为最小值的情况if (divisor I…

C++ 之 【类与对象】从入门到精通一条龙服务 最终篇(static成员、友元、匿名对象。。。)

💴到用时方恨早,白首方悔挣的少 车到山前没有路,悬崖勒马勒不住 一、再谈构造函数 1.构造函数体赋值 2.初始化列表 3.explicit关键字 二、Static成员 1.概念 2.特性 三、友元 1.友元函数 2.友元类 四、内部类 五、匿名对象 六、…

项目5-博客系统5+程序部署(Linux基本使用)

1.Linux的基本使用 1.1 Linux 是什么 Linux 是⼀个操作系统. 和 Windows 是 "并列" 的关系. 1.2 Unix & Linux 发展历程图 1.1969−1970 年, ⻉尔实验室的 Dennis Ritchie (左) 和 Ken Tompson (右) 开发了 Unix 操作系统. 2. Unix ⽕了之后, 衍⽣出很多的分⽀…

jvm参数介绍

JVM参数是指在Java应用程序启动时,通过命令行或者配置文件等方式传递给JVM的一些参数和选项。这些参数主要用于配置JVM的内存、垃圾回收、线程等相关参数。下面是一些常见的JVM参数简介: 1. -Xmx:设置应用程序可使用的最大内存量,…

C++ | Leetcode C++题解之第30题串联所有单词的子串

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> findSubstring(string &s, vector<string> &words) {vector<int> res;int m words.size(), n words[0].size(), ls s.size();for (int i 0; i < n && i m …

文献阅读:Viv:在 web 上多尺度可视化高分辨率多重生物成像数据

文献介绍 「文献题目」 Viv: multiscale visualization of high-resolution multiplexed bioimaging data on the web 「研究团队」 Nils Gehlenborg&#xff08;美国哈佛医学院&#xff09; 「发表时间」 2022-05-11 「发表期刊」 Nature Methods 「影响因子」 47.9 「DOI…

GitHub repository - Code - Issues - Pull Requests - Wiki

GitHub repository - Code - Issues - Pull Requests - Wiki 1. Code2. Issues3. Pull Requests4. WikiReferences 1. Code 显示该仓库中的文件列表。仓库名下方是该仓库的简单说明和 URL. 2. Issues 用于 BUG 报告、功能添加、方向性讨论等&#xff0c;将这些以 Issue 形式进…

初识DOM

目录 前言: 1.初识DOM: 1.1DOM树: 1.2节点&#xff08;Node&#xff09;: 1.2.1元素节点&#xff1a; 1.2.2属性节点&#xff1a; 1.2.3文本节点&#xff1a; 1.3Document对象: 2.操作网页元素: 2.1找出元素&#xff1a; 2.1.1document.getElementById(id)&#xff1…

【入门】时钟旋转

时间限制 : 1 秒 内存限制 : 128 MB 时钟上面的时针从m时走到n时旋转了多少度&#xff1f;&#xff08;m<n&#xff0c;且m和n都是1~12之间的整数&#xff09; 输入 2个整数m和n 输出 一个整数代表时针旋转的度数 样例 输入 1 4 输出 90 提示 基础问题 #includ…

2011年认证杯SPSSPRO杯数学建模A题(第二阶段)客机水面迫降时的姿态全过程文档及程序

2011年认证杯SPSSPRO杯数学建模 A题 客机水面迫降时的姿态 原题再现&#xff1a; 2009 年 1 月 15 日下午&#xff08;美国东部时间&#xff09;&#xff0c;US Airways 所属第 1549 航班&#xff08;空中客车 A320 客机&#xff09;在起飞后不久在纽约哈德逊河紧急迫降。经及…

Android MVVM架构学习——ViewModel DataBinding

关于MVVM架构&#xff0c;我并不想花篇幅去做重复性的描述&#xff0c;网上一搜都是一堆讲解&#xff0c;大家可以自行了解&#xff0c;我所做的只是以最简单的例子&#xff0c;最有效的步骤&#xff0c;从零开始&#xff0c;去实现一个相对有点学习参考价值的项目。 先来看本…

鸿蒙开发快速入门

基本概念 ArkTS 因为ArkTS是基于Type Script扩展而来&#xff0c;是Type Script的超集&#xff0c;所以也可以关注一下Type Script的语法来理解ArkTS的语法 ArkUI HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。方舟开发框架…

Cohere Command R+:企业级可扩展大模型

Command R 是一种最先进的 RAG 优化模型&#xff0c;旨在处理企业级工作负载&#xff0c;并首先在 Microsoft Azure 上提供 今天&#xff0c;我们推出了 Command R&#xff0c;这是我们最强大、可扩展的大型语言模型 &#xff08;LLM&#xff09;&#xff0c;专为在实际企业用…

Go语言图像处理深入指南:探索 `image/draw` 标准库

Go语言图像处理深入指南&#xff1a;探索 image/draw 标准库 引言基础知识图像类型颜色模型draw 包概览 image/draw 的基本用法创建和操作图像复制和粘贴图像遮罩和图像合成 高级技巧自定义图像处理性能优化多线程图像处理 实战案例图像滤镜实现动态图像生成图像处理工具开发 总…

2024年MathorCup妈妈杯C题终极资料分享(微调后可直接提交的数据结果+多种可视化呈现)

【腾讯文档】2024年妈杯认证杯资料汇总说明 https://docs.qq.com/doc/DSExyRGhNUm1pTHh4 我们直接将我们认为最为合适数据结果以及最好用的运行代码进行了整理以便大家可以在最后一天无法运行代码急需结果或者不会写代码时&#xff0c;提供帮助。、 % 数据加载 data readtab…

Vivado编译常见错误合集(一)

引言&#xff1a;本文对Vivado编译时常见的错误或者关键警告做一些梳理汇总&#xff0c;便于日后归纳总结。 1. 普通IO引脚约束为时钟时报错。 原因&#xff1a;Xilinx Vivado开发环境编译HDL时&#xff0c;对时钟信号设置了编译规则&#xff0c;如果时钟由于硬件设计原因分配…

Ceph [OSDI‘06]论文阅读笔记

原论文&#xff1a;Ceph: A Scalable, High-Performance Distributed File System (OSDI’06) Ceph简介及关键技术要点 Ceph是一个高性能、可扩展的分布式文件系统&#xff0c;旨在提供出色的性能、可靠性和可扩展性。为了最大化数据和元数据管理的分离&#xff0c;它使用了一…

MYSQL执行过程和顺序详解

一、前言 1.1、说明 就MySQL在执行过程、sql执行顺序&#xff0c;以及一些相关关键字的注意点方面的学习分享内容。 在参考文章的基础上&#xff0c;会增加自己的理解、看法&#xff0c;希望本文章能够在您的学习中提供帮助。 如有错误的地方&#xff0c;欢迎指出纠错&…

vue中使用axios获取不到响应头Content-Disposition的解决办法

项目中&#xff0c;后端返回的文件流; 前端需要拿到响应头里的Content-Disposition字段的值&#xff0c;从中获取文件名 在控制台Headers中可以看到相关的字段和文件名&#xff0c;但是在axios里面却获取不到 如果想要让客户端访问到相关信息&#xff0c;服务器不仅要在head…

嵌入式单片机 TTL电平、232电平、485电平的区别和联系

一、简介 TTL、232和485是常见的串口通信标准&#xff0c;它们在电平和通信方式上有所不同&#xff0c; ①一般情况下TTL电平应用于单片机外设&#xff0c;属于MCU/CPU等片外外设&#xff1b; ②232/485电平应用于产品整体对外的接口&#xff0c;一般是片外TTL串口转232/485…