【Bootloader学习理解学习--加强版】

笔者在接着聊一下bootloader,主要针对MCU的Bootloader。

笔者之前介绍过一篇Bootloader文章,主要是其概念、一些升级包的格式和升级流程,本次接着来说一下。

1、MCU代码运行方式

之前文章也介绍过,MCU的代码运行方式有两种,

  • XIP方式:(Excute In Place),在位置上执行,即在存储地址上执行代码,如果是这样,则对存储芯片有要求,则要求是支持memory map的方式,把存储空间当做是CPU的memory一样,可以通过总线获取到code指令,然后执行。
    在这里插入图片描述

内部ROM Code会跳转到Code中,然后Flash中的Code就会执行,接着把data段和bss段进行分散加载到对应的ram,然后就可以执行,STM32就是这种方式。

  • RAM方式:一般情况下,MCU Boot会把代码从存储空间搬到RAM上去运行,例如一些SPI Flash或者Nand Flash,无法直接执行指令,需要搬到RAM方式执行,然后再跳到RAM地址执行,RAM地址执行,还有个好处是速度执行更快,相对Flash上面执行。

下图以NXP的LPC54016为例进行展示,Boot将代码从SPI Flash搬到SRAMX,然后再将执行权交给SRAMX,至此代码就在Sramx上面执行,最后同样需要进行分散加载,将bss段和data段搬到Sram0-3上面执行。
在这里插入图片描述
分散加载可以参考笔者本篇文章。

2、NXP单片机运行方式修改

以NXP LPC54016单片机为例,笔者来介绍一下如何从RAM方式切到XIP方式。

2.1 代码启动流程

  1. 首先需要看一下手册,研究一下如何支持XIP方式,MCU的code都是rom code搬出来的,如果要执行XIP方式,则不搬code,所以需要看相关的配置

  2. 开始笔者想着是相关的寄存器设置,可以让ROM Code中的boot不搬代码,但是实际看手册没有找到相关的寄存器信息。

  3. 找到了相关的流程图,通过引脚来决定怎么启动
    在这里插入图片描述

  4. OTP BOOT SRC 引脚被设置,则会从外部flash(Parallel Mmeory)搬数据(512Byte)到SRAM,通过该512Byte来决定在怎么启动(XIP还是RAM方式)

  5. 如果OTP BOOT SRC没有被设置,则有ISP的引脚,来决定进入ISP模式下载数据到SRAM,然后BOOT,还是从外部Flash Boot(搬移)。

  6. 这里有个AUTO BOOT的方式,首先寻找有效的Image(0x0/0x20000000 within internal SRAM (SRAMX or SRAM0) or 0x10000000 for
    SPIFI XIP and 0x80000000 for parallel flash XIP image),如果地址都没找到,则进入ISP模式,等待命令下载代码。
    在这里插入图片描述

  7. 如何确定有效的Image呢,NXP巧妙的利用了中断向量表的信息,来装载NXP特有的Mark,从而确定是否有效的代码。

    1. 中断向量表中,有几个位置是保留,且中断向量表处于代码开始放置,方便确认,可参考笔者之前文章ARM学习(5) 异常模式学习(CortexM3/M4)
    2. 从0x20的位置确定是否是0xEDDC94BD
    3. 从0x24的位置,确定真正NXP的Image Header位置信息,从而跳转过去,找到Image Header决定走XIP还是SRAM等
      在这里插入图片描述
  8. NXP LPC单片机有自己的Image Header,可以设置一些属性,通过链接脚本的方式,包括分散加载的地址,也是链接脚本里面指定。
    在这里插入图片描述

    1. Image Type:选择ram方式还是xip方式
      在这里插入图片描述
    2. Load_adderess:加载地址,SRAMX还是SPIFI地址
    3. CRC check选择:设置是否进行CRC 校验
  9. 经过这些研究,就可以知道XIP的设置和RAM的设置表了,主要关心的还是 Image Type和Loadaddress。
    在这里插入图片描述
    在这里插入图片描述

2.2 链接脚本修改

中断向量表
extern void (* const g_pfnVectors[])(void);
extern void * __Vectors attribute ((alias (“g_pfnVectors”)));
WEAK extern void __imghdr_loadaddress();
WEAK extern void __imghdr_imagetype();

void (* const g_pfnVectors[])(void) = {
    // Core Level - CM4
    &_vStackTop,                       // The initial stack pointer
    ResetISR,                          // The reset handler
    .......,
    0,                                 // ECRP
    (void (*)(void))0xEDDC94BD,        // Reserved
    (void (*)(void))0x160,             // Reserved
    .....,
    (void (*)(void))0xFEEDA5A5, // Header Marker,0x160
    __imghdr_imagetype,         // (0x04) Image Type
    __imghdr_loadaddress,       // (0x08) Load_address
    (void (*)(void))(((unsigned)_image_size) - 4),            // (0x0C) load_length, exclude 4 bytes CRC field.

从上面中断向量表中可以看出,NXP LPC单片机在中断向量表中做了手脚,设置了一些参数,和启动方式结合起来,包括还可以知道ImageSize等。

注意观察,其声明是一个虚函数的方式(如果链接脚本里面没指定,也可以链接过),符合中断向量表数组的定义,其实际是链接脚本里面指定的数据。

RAM方式的链接脚本:
memory 布局定义,外部Flash地址,SRAMX,SRAM0-3地址,在链接脚本指定布局时,可以指定其位置。

MEMORY
{
  /* Define each memory region */
  BOARD_FLASH(rwx) : ORIGIN = 0x10000000, LENGTH = 0x1000000 /* 16M bytes (alias RAM) */  
  SRAMX (rwx) : ORIGIN = 0x0, LENGTH = 0x30000 /* 192K bytes (alias RAM) */  
  SRAM_0_1_2_3 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x28000 /* 160K bytes (alias RAM2) */  
  USB_RAM (rwx) : ORIGIN = 0x40100000, LENGTH = 0x2000 /* 8K bytes (alias RAM3) */  
}

  /* Define a symbol for the top of each memory region */
  __base_SRAMX = 0x0  ; /* SRAMX */  
  __base_RAM = 0x0 ; /* RAM */  
  __top_SRAMX = 0x0 + 0x30000 ; /* 192K bytes */  
  __top_RAM = 0x0 + 0x30000 ; /* 192K bytes */  
  __base_SRAM_0_1_2_3 = 0x20000000  ; /* SRAM_0_1_2_3 */  
  __base_RAM2 = 0x20000000 ; /* RAM2 */  
  __top_SRAM_0_1_2_3 = 0x20000000 + 0x28000 ; /* 160K bytes */  
  __top_RAM2 = 0x20000000 + 0x28000 ; /* 160K bytes */  
  __base_USB_RAM = 0x40100000  ; /* USB_RAM */  
  __base_RAM3 = 0x40100000 ; /* RAM3 */  
  __top_USB_RAM = 0x40100000 + 0x2000 ; /* 8K bytes */  
  __top_RAM3 = 0x40100000 + 0x2000 ; /* 8K bytes */  

中断向量表定义,可看到在其前面定义了一些信息,data段的信息,data ram和data ram3都没有用到,我们就不考虑了,
“AT > xxxx”:加载地址
“> xxx”:执行地址
分散加载做的就是将加载地址数据搬到执行地址,如果本身两者地址一样,则无需处理。

     /* MAIN TEXT SECTION */
    .text : ALIGN(4)
    {
        FILL(0xff)
        __vectors_start__ = ABSOLUTE(.) ;
        KEEP(*(.isr_vector))
        /* Global Section Table */
        . = ALIGN(4) ;
        __section_table_start = .;
        __data_section_table = .;
        LONG((LOADADDR(.data_RAM) - LOADADDR(.text)) + __base_SRAMX);
        LONG(    ADDR(.data_RAM));
        LONG(  SIZEOF(.data_RAM));
        LONG((LOADADDR(.data) - LOADADDR(.text)) + __base_SRAMX);
        LONG(    ADDR(.data));
        LONG(  SIZEOF(.data));
        LONG((LOADADDR(.data_RAM3) - LOADADDR(.text)) + __base_SRAMX);
        LONG(    ADDR(.data_RAM3));
        LONG(  SIZEOF(.data_RAM3));
        __data_section_table_end = .;
        __bss_section_table = .;
        LONG(    ADDR(.bss_RAM));
        LONG(  SIZEOF(.bss_RAM));
        LONG(    ADDR(.bss));
        LONG(  SIZEOF(.bss));
        LONG(    ADDR(.bss_RAM3));
        LONG(  SIZEOF(.bss_RAM3));
        __bss_section_table_end = .;
        __section_table_end = . ;
        /* End of Global Section Table */
        *(.after_vectors*)
    } > SRAMX AT> SRAMX

从上面脚本来分析,目前代码段的加载地址就处于SRAMX,所以直接写LOADADDR(.data)也行,看图2,加载地址和图1是一样的,
加载地址其实就是存放的位置,相对代码段的位置,但是却不是执行的地址。
在这里插入图片描述
在这里插入图片描述
代码段的定义,其加载地址和执行地址一样,均是SRAMX,同时一些只读数据和const数据,也存放在代码段。

 .text : ALIGN(4)
    {
       *(.text*)
       *(.rodata .rodata.* .constdata .constdata.*)
       . = ALIGN(4);
    } > SRAMX AT> SRAMX
    _etext = .;

data段的定义,加载地址和执行地址不一样,前者是执行地址,后者是加载地址,说明其正式执行的时候,在SRAM0-3,加载的时候在SRAMX。

加载地址和存储地址也不是一个概念,存储地址是真正存放代码的位置,程序运行需要再特殊的地方,比如内存,所以肯定需要搬到对应的位置,BOOT做的就是这个事情。搬到对应的位置后,就是真正执行的地址吗,也不竟然,BOOT通常是做很简单的事情,整体都搬过去,包括代码和数据,但是代码和数据往往要分开,所以就有了分散加载。

    /* Main DATA section (SRAM_0_1_2_3) */
    .data : ALIGN(4)
    {
       FILL(0xff)
       _data = . ;
       PROVIDE(__start_data_RAM2 = .) ;
       PROVIDE(__start_data_SRAM_0_1_2_3 = .) ;
       *(vtable)
       *(.ramfunc*)
       KEEP(*(CodeQuickAccess))
       KEEP(*(DataQuickAccess))
       *(RamFunction)
       *(.data*)
       . = ALIGN(4) ;
       _edata = . ;
       PROVIDE(__end_data_RAM2 = .) ;
       PROVIDE(__end_data_SRAM_0_1_2_3 = .) ;
    } > SRAM_0_1_2_3 AT>SRAMX

BSS段又不一样,其不用存储,加载地址和执行地址一样,只要初始化成0就行。

 /* MAIN BSS SECTION */
    .bss : ALIGN(4)
    {
        _bss = .;
        PROVIDE(__start_bss_RAM2 = .) ;
        PROVIDE(__start_bss_SRAM_0_1_2_3 = .) ;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4) ;
        _ebss = .;
        PROVIDE(__end_bss_RAM2 = .) ;
        PROVIDE(__end_bss_SRAM_0_1_2_3 = .) ;
        PROVIDE(end = .);
    } > SRAM_0_1_2_3 AT> SRAM_0_1_2_3

堆栈区域,存放于SRAMX区域,堆后面预留了栈的4K空间,如果空间最后不够,链接脚本会报错。
.stack区域指明了栈的起始地址,没预留空间,由上面那个空间预留保证,很巧妙解决了栈递减的空间逻辑。

SECTIONS
{
    /* Reserve and place Heap within memory map */
    _HeapSize = 0x1000;
    .heap :  ALIGN(4)
    {
        _pvHeapStart = .;
        . += _HeapSize;
        . = ALIGN(4);
        _pvHeapLimit = .;
    } > SRAMX

     _StackSize = 0x1000;
     /* Reserve space in memory for Stack */
    .heap2stackfill  :
    {
        . += _StackSize;
    } > SRAMX
    /* Locate actual Stack in memory map */
    .stack ORIGIN(SRAMX) + LENGTH(SRAMX) - _StackSize - 0:  ALIGN(4)
    {
        _vStackBase = .;
        . = ALIGN(4);
        _vStackTop = . + _StackSize;
    } > SRAMX

最后这些都是符号,不占用任何空间size,如果程序用到了,直接将对于的数据连接进入,而不是变量进行加载。
如果确实是运行态的数据,就需要占用空间size。

    /* ## Create checksum value (used in startup) ## */
    PROVIDE(__valid_user_code_checksum = 0 - 
                                         (_vStackTop 
                                         + (ResetISR + 1) 
                                         + (NMI_Handler + 1) 
                                         + (HardFault_Handler + 1) 
                                         + (( DEFINED(MemManage_Handler) ? MemManage_Handler : 0 ) + 1)   /* MemManage_Handler may not be defined */
                                         + (( DEFINED(BusFault_Handler) ? BusFault_Handler : 0 ) + 1)     /* BusFault_Handler may not be defined */
                                         + (( DEFINED(UsageFault_Handler) ? UsageFault_Handler : 0 ) + 1) /* UsageFault_Handler may not be defined */
                                         ) );

    /* Provide basic symbols giving location and size of main text
     * block, including initial values of RW data sections. Note that
     * these will need extending to give a complete picture with
     * complex images (e.g multiple Flash banks).
     */
    _image_start = LOADADDR(.text);
    _image_end = LOADADDR(.data) + SIZEOF(.data);
    _image_size = _image_end - _image_start;

    /* Provide symbols for LPC540xx parts for startup code to use
     * to set image to be plain load image or XIP.
     * Config : Plain load image = true
     */
    __imghdr_loadaddress = ADDR(.text);
    __imghdr_imagetype = 1;
}

XIP方式链接脚本


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

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

相关文章

Nacos源码本地搭建流程及目录结构解读

下载地址 https://github.com/alibaba/nacos 目录结构 本地单机启动 首先maven编译完成之后在console下面找到Nacos 这个就是主启动类 然后再vm中配置参数-Dnacos.standalonetrue表示单机启动 当控制台没有报错 访问 http://localhost:8848/nacos 控制台界面登录进来之后显…

27.0/多态/对象向上转型/向下转型/抽象类/抽象方法。

目录 27.1为什么使用多态? 27.1.2什么是多态 27.1.3对象多态 27.1.4多态的使用前提 27.2 向上转型 27.3向下转型 (面试题) 27.4抽象类和抽象方法 特点(面试题): 27.1为什么使用多态? 需求1:动物园让我们实现一个功能: 创建一个狗类 ,狗…

cpu飙高问题,案例分析(二)——批处理数据过大引起的应用服务CPU飙高

上接cpu飙高问题,案例分析(一) 一、批处理数据过大引起的应用服务CPU飙高 1.1 问题场景 某定时任务job 收到cpu连续(配置的时间是180s)使用超过90%的报警; 1.2 问题定位 观察报警中的jvm监控,发现周期…

软件测试测试文档编写

在软件测试中的流程中,测试文档也是一个重要的流程,所以测试人员也需要学习测试文档的编写和阅读。 一、定义:   测试文档(Testing Documentation)记录和描述了整个测试流程,它是整个测试活动中非常重要…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 上一节把数据库与相关基础数据字典准备好,下面就来实现相应的功能,目前先针对自定义…

大学招聘平台既然存在逻辑漏

找到一个学校的就业信息网, 随便点击一个招聘会,并且抓包查看返回包 注意返回包中的dwmc参数,这个是公司名称,zplxr参数这个是招聘人员姓名,lxdh参数是电话号码,这几个参数后面有用 在第一张图点击单位登…

Python 爬虫 案例 之 豆瓣Top250电影数据

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 课程亮点: 1、动态数据抓包演示 2、csv文件保存 3、requests模块的使用 4、parsel解析数据的使用 环境介绍: python 3.8 pycharm 模块…

一种方便、优美的使用Python调用fofa API的方法

免责声明:由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失,均由使用者本人负责, 文章作者不为此承担任何责任。 学习网络安全的过程中,绕不开fofa搜索,我的需求是使用fofa获取互联网所…

C++不同平台下的RTTI实现

给定一个含有虚函数的对象的地址&#xff0c;找到对应的类名&#xff0c;不同平台下方法也不同&#xff0c;这是由于RTTI实现并没有统一的标准。 Linux&#xff1a; #include <iostream> #include <typeinfo>class Person { public:virtual void func(){std::cout…

[算法总结] - 蓄水池采样算法

问题描述 在长度为N的数组中&#xff0c;随机等概率选取K个元素&#xff0c;如何实现这个随机算法。 思路很简单&#xff0c;生成一个[0, N]的随机数index&#xff0c;然后返回index上的数值即可。 但是&#xff0c;如果输入是一个长度未知的数组比如stream&#xff0c;先遍历…

【Jmeter】什么是BeanShell?

一、什么是BeanShell&#xff1f; BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器&#xff0c;JMeter性能测试工具也充分接纳了BeanShell解释器&#xff0c;封装成了可配置的BeanShell前置和后置处理器&#xff0c;分别是 BeanShell Prep…

中科大蒋彬课题组开发 FIREANN,分析原子对外界场的响应

内容一览&#xff1a; 使用传统方法分析化学系统与外场的相互作用&#xff0c;具有效率低、成本高等劣势。中国科学技术大学的蒋彬课题组&#xff0c;在原子环境的描述中引入了场相关特征&#xff0c;开发了 FIREANN&#xff0c;借助机器学习对系统的场相关性进行了很好的描述。…

盘点72个Android系统源码安卓爱好者不容错过

盘点72个Android系统源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1qiWeLjF2i4dlgmTYgPPSvw?pwd8888 提取码&#xff1a;8888 项目名称 A keyboardlisten…

【链接MySQL】教你用VBA链接MySQL数据库

hi&#xff0c;大家好呀&#xff01; 之前呢&#xff0c;给大家分享过一个自制链接表管理器的文章&#xff0c;文章中有链接SQL Server数据库的代码&#xff0c;大家对这一段代码比较有兴趣&#xff0c;既然大家有兴趣&#xff0c;那我们今天就来讲一下链接数据库的代码。 这…

Vue路由嵌套和携带参数的几种方法

1、路由嵌套 路由嵌套逻辑&#xff1a; router.index.js中使用children嵌套子路由 //该文件专门用于创建整个文件的路由器 import VueRouter from vue-routerimport About from "/pages/About"; import Home from "/pages/Home"; import News from "…

基础课12——深度学习

深度学习技术是机器学习领域中的一个新的研究方向&#xff0c;它被引入机器学习使其更接近于最初的目标——人工智能。深度学习的最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字、图像和声音等数据。 深度学习的核心思想是通过学习样本数据的内在规律…

控价是什么意思

对价格进行控制&#xff0c;使其在一个目标范围内的行为被称为控价&#xff0c;那为什么要做控价&#xff0c;控价的前提是价格乱了&#xff0c;而品牌会对渠道中的低价进行控制&#xff0c;这就是品牌进行控价的目标&#xff0c;控制低价。 品牌可以选择自己去控价&#xff0c…

python计算概率分布

目录 1、泊松分布 2、卡方分布 3、正态分布 4、t分布 5、F分布 1、泊松分布 泊松分布是一种离散概率分布&#xff0c;描述了在固定时间或空间范围内&#xff0c;某个事件发生的次数的概率分布。该分布以法国数学家西蒙德尼泊松的名字命名&#xff0c;他在19世纪早期对这种…

深入了解MySQL数据库管理与应用

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 当涉及MySQL数据库管理与应用时&#xff0c;深…

智慧工地管理系统加快推进工程建设项目全生命周期数字化

智慧工地管系统是一种利用人工智能和物联网技术来监测和管理建筑工地的系统。它可以通过感知设备、数据处理和分析、智能控制等技术手段&#xff0c;实现对工地施工、设备状态、人员安全等方面的实时监控和管理。 智慧工地以物联网、移动互联网技术为基础&#xff0c;充分应用大…