STM32内存分布,启动过程及bootloader

news/2024/9/20 17:45:48

STM32怎么说也用了好几年了,但是对于它的内存分布,启动过程,总是模棱两可;所以说决定写这篇文章做下梳理,水平有限,欢迎指正;
以下以F407为例
1. STM32地址空间分布

图片出自M3与M4权威指南

+-------------------------+----------------------------------+
Address Range Description
+-------------------------+----------------------------------+
0x00000000 - 0x03FFFFFF 内存别名映射区域
+-------------------------+----------------------------------+
0x08000000 - 0x080FFFFF 内部Flash存储器
+-------------------------+----------------------------------+
0x10000000 - 0x1000FFFF CCMRAM
+-------------------------+----------------------------------+
0x1FFF0000 - 0x1FFF77FF 系统存储器
+-------------------------+----------------------------------+
0x1FFF7800 - 0x1FFF7A0f opt
+-------------------------+----------------------------------+
0x1FFFC000 - 0x1FFFC00F 选项字节
+-------------------------+----------------------------------+
0x20000000 - 0x2001BFFF SRAM1 112KB
+-------------------------+----------------------------------+
0x20001C00 - 0x2001FFFF SRAM2 16KB
+-------------------------+----------------------------------+
0x20020000 - 0x2003FFFF SRAM3 64kB 407不存在
+-------------------------+----------------------------------+
0x40000000 - 0x5FFFFFFF 外设寄存器
+-------------------------+----------------------------------+
0xE0000000 - 0xE00FFFFF 系统控制空间
+-------------------------+----------------------------------+
  • [1] 内存别名映射区域
    搜了一大堆,不太清楚,可以理解为,程序其实还是从地址0开始执行,如果选择flash启动那么就是将FLASH(0x0800 0000)重映射或者芯片出厂自带的Bootloader(0x1FFF 0000)重映射到地址0,故而代码是下载到 0x80000000 往后的存储空间中,却说运行又是从 0x00000000地址运行的
  • [2] 内部Flash存储器
    程序这就不用说了,开始时是中断向量表
  • [3] CCMRAM
    额,其实我也没用过,CCMRAM由内核直接控制,可以使用__attribute__((section(".ccmram")))指定变量位置,总之就也是RAM,速度还更快
  • [4] 系统存储器
    这个主要用来存放 STM32F4 的 bootloader 代码用于下载代码;这个地址范围包含了一些特殊用途的存储器区域,主要用于存放一些系统配置信息、唯一设备标识号(Unique Device ID, UID)、Flash管理和保护设置等重要数据
  • [5] opt
    没用过加1 OTP 区域,即一次性可编程区域,共 528 字节,被分成两个部分,前面 512 字节(32 字节为 1 块,分成 16 块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面 16 字节,用于锁定对应块。
  • [6] 选项字节
    flash写保护读保护,flash编程用到
  • [7] SRAM
    内存区域,407ram大小为128KB,也就是上边的SRAM1和SRAM2,手册中把这里分为了两个区域,但是根据STM32407链接文件定义来看,是把这里SRAM1和SRAM2,还是统一作为整个RAM处理;直接作为128KB处理
    其中22000000->23FFFFFF的32MB大小是20000000-20100000的位带映射
MEMORY
{CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64KRAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128KFLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 512K
}
  • [8] 外设寄存器
    各种外设寄存器区域
    这还是看参考手册,存储器映射吧,其中42000000->43FFFFFF的32MB大小是40000000-40100000的位带映射,我们用来操控IO很方便;提醒一点并不是所有32都有位带操作,F7就不存在
  • [9] 系统存储器
    没了解过,以后有机会再去看吧

2.关于链接文件

点击查看代码

/* Entry Point */
ENTRY(Reset_Handler)  /* 定义程序的入口点为 Reset_Handler *//* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* 设置用户模式栈的最高地址 这个数据在启动文件中会用到,正常情况下是在0x08000000存放,也就是复位中断的前一个字节,表示初始堆栈指针向下增长*//* 定义堆和栈的最小尺寸 */
_Min_Heap_Size = 0x200; /* 最小堆大小 (512 bytes) */
_Min_Stack_Size = 0x400; /* 最小栈大小 (1024 bytes) *//* 内存区域定义 */
MEMORY
{CCMRAM (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K    /* 定义CCMRAM区域 */RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K   /* 定义SRAM区域 */FLASH  (rx)     : ORIGIN = 0x08000000,   LENGTH = 1024K  /* 定义Flash区域 */
}/* 段的定义 */
SECTIONS
{/* 中断向量表存放在Flash中 */.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* 保持中断向量表 */. = ALIGN(4);} >FLASH/* 程序代码和只读数据存放在Flash中 */.text :{. = ALIGN(4);*(.text)           /* .text段 (代码) */*(.text*)          /* .text*段 (代码) */*(.glue_7)         /* glue arm到thumb代码 */*(.glue_7t)        /* glue thumb到arm代码 */*(.eh_frame)KEEP (*(.init))    /* 保持初始化代码 */KEEP (*(.fini))    /* 保持结束代码 */. = ALIGN(4);_etext = .;        /* 在代码段结束处定义全局符号 */} >FLASH/* 常量数据存放在Flash中 */.rodata :{. = ALIGN(4);*(.rodata)         /* .rodata段 (常量, 字符串等) */*(.rodata*)        /* .rodata*段 (常量, 字符串等) */. = ALIGN(4);} >FLASH/* ARM特定段 */.ARM.extab   :{. = ALIGN(4);*(.ARM.extab* .gnu.linkonce.armextab.*). = ALIGN(4);} >FLASH.ARM :{. = ALIGN(4);__exidx_start = .;*(.ARM.exidx*)__exidx_end = .;. = ALIGN(4);} >FLASH/* 构造函数表 */.preinit_array :{. = ALIGN(4);PROVIDE_HIDDEN (__preinit_array_start = .);KEEP (*(.preinit_array*))PROVIDE_HIDDEN (__preinit_array_end = .);. = ALIGN(4);} >FLASH.init_array :{. = ALIGN(4);PROVIDE_HIDDEN (__init_array_start = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array*))PROVIDE_HIDDEN (__init_array_end = .);. = ALIGN(4);} >FLASH.fini_array :{. = ALIGN(4);PROVIDE_HIDDEN (__fini_array_start = .);KEEP (*(SORT(.fini_array.*)))KEEP (*(.fini_array*))PROVIDE_HIDDEN (__fini_array_end = .);. = ALIGN(4);} >FLASH/* 用于启动代码初始化数据 */_sidata = LOADADDR(.data);/* 初始化数据段存放在RAM中,其初始值在Flash中 */.data :{. = ALIGN(4);_sdata = .;        /* 在数据段开始处定义全局符号 */*(.data)           /* .data段 */*(.data*)          /* .data*段 */*(.RamFunc)        /* .RamFunc段 */*(.RamFunc*)       /* .RamFunc*段 */. = ALIGN(4);_edata = .;        /* 在数据段结束处定义全局符号 */} >RAM AT> FLASH/* CCMRAM段 */_siccmram = LOADADDR(.ccmram);.ccmram :{. = ALIGN(4);_sccmram = .;      /* 在CCMRAM段开始处定义全局符号 */*(.ccmram)*(.ccmram*). = ALIGN(4);_eccmram = .;      /* 在CCMRAM段结束处定义全局符号 */} >CCMRAM AT> FLASH/* 未初始化数据段存放在RAM中 */.bss :{_sbss = .;         /* 在bss段开始处定义全局符号 */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .;         /* 在bss段结束处定义全局符号 */__bss_end__ = _ebss;} >RAM/* 用户堆栈段,用于检查剩余的RAM */._user_heap_stack :{. = ALIGN(8);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;. = ALIGN(8);} >RAM/* 删除编译器库信息 *//DISCARD/ :{libc.a ( * )libm.a ( * )libgcc.a ( * )}/* ARM属性段 */.ARM.attributes 0 : { *(.ARM.attributes) }
}
如果要实现boot升级,首先要注意FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* 定义Flash区域 */的分配,这是代码的起始地址; 可以把APP设置成ORIGIN = 0x08008000,这个数值正常来说设置成某个页或块的起始地址,flash写入前要先擦除,擦除单位是页或者块; ._user_heap_stack是为了代码检查,防止所占用内存大于RAM大小,栈其实还是从高地址到低地址;
空闲空间
.bss
.data
.text
---- ---- ----

3. 启动文件
在启动文件的中断向量表中,开头如下所示,在我们程序地址默认0x08000000开始时,0x08000000地址的数据也就是_estack,对应链接文件中的值_estack = ORIGIN(RAM) + LENGTH(RAM)也就是ram最高地址;
0x08000004是复位中断,程序开始也就是进入这里;

点击查看代码
  .word  _estack.word  Reset_Handler.word  NMI_Handler.word  HardFault_Handler.word  MemManage_Handler.word  BusFault_Handler.word  UsageFault_Handler.word  0.word  0.word  0.word  0.word  SVC_Handler.word  DebugMon_Handler.word  0.word  PendSV_Handler.word  SysTick_Handler
复位中断完成什么工作呢,第一步就是将栈地址_estack放到SP中,在完成ram中变量初始化后,调用SystemInit,然后进入主函数; SystemInit,函数在system_stm32f4xx.c定义,里面有个寄存器值就是我们常说的中断向量表偏移,`SCB->VTOR`,这里指向中断向量表位置,正常程序等于0x0800000,但是app程序要指向app的中断向量表位置,程序在0x08008000那么SCB->VTOR=0x08008000;提醒stm32f0并没有VTOR寄存器,跳转APP后要将向量表复制到RAM并改变向量表映射为ram,
点击查看代码
.section  .text.Reset_Handler
.weak  Reset_Handler
.type  Reset_Handler, %functionReset_Handler:  ldr   sp, =_estack     /* 设置堆栈指针 *//* 将数据段的初始值从Flash复制到SRAM */movs  r1, #0           /* 初始化偏移量 */b  LoopCopyDataInit    /*跳转*/
/*完成将有初始化数据的变量,复制到ram,也就是.data段*/
/*r1是偏移量,每次复制后偏移量加4*/
CopyDataInit:ldr  r3, =_sidata      /* 数据段初始值在Flash中的起始地址 */ldr  r3, [r3, r1]      /* 从Flash中读取数据 */str  r3, [r0, r1]      /* 将数据写入SRAM */adds  r1, r1, #4       /* 更新偏移量 */b    LoopCopyDataInit
/*判断是否复制结束*/
LoopCopyDataInit:ldr  r0, =_sdata       /* 数据段起始地址 */ldr  r3, =_edata       /* 数据段结束地址 */adds  r2, r0, r1       /* 计算当前地址 */cmp  r2, r3            /* 比较当前地址和数据段结束地址 */bcc  CopyDataInit      /* 如果当前地址小于结束地址,继续复制 */ldr  r2, =_sbss        /* .bss段起始地址 */b  LoopFillZerobss
/*完成将有未初始化数据的变量,复制到ram,也就是.bss段*/
/* 清零.bss段 */
FillZerobss:movs  r3, #0           /* 初始化值为0 */str  r3, [r2], #4      /* 将0写入.bss段 */adds r2, r2, #4        /* 更新地址 */b    LoopFillZerobssLoopFillZerobss:ldr  r3, =_ebss        /* .bss段结束地址 */cmp  r2, r3            /* 比较当前地址和.bss段结束地址 */bcc  FillZerobss       /* 如果当前地址小于结束地址,继续清零 *//* 调用系统初始化函数 */bl  SystemInit/*搜的解释:调用静态构造函数 一般不用C语言没有构造函数的概念,程序进入 main 函数之前执行一些初始化代码。GCC提供了constructor和destructor属性,允许你定义在main函数之前和程序退出时运行的函数。/*完成C库的初始化 确保在程序开始执行其主要逻辑之前正确初始化了 C/C++ 对象*/*/bl  __libc_init_array/* 调用应用程序的入口点 */bl  mainbx  lr                 /* 返回 */
**4.程序跳转 ** 程序如何实现跳转呢,简单来说就是将app程序的地址,转换成函数指针指向的地址,直接当成函数跳转; 但是需要重新完成主堆栈地址的设置,和中断向量表的设置,__set_MSP设置sp,但其实启动文件也有sp的设置,向量表偏移就是VTOR寄存器; 注意STM32F0并不一样需要把向量表复制到RAM中,然后把向量表指向RAM
点击查看代码
	pFunction Jump_To_Application;//定义一个函数指针uint32_t JumpAddress;__disable_irq();  //关闭中断JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 0x00000004);//得到复位程序地址Jump_To_Application = (pFunction) JumpAddress; //强制转换为函数__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); //设置栈顶地址,ram完全交给app使用Jump_To_Application();

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

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

相关文章

花样玩转“所见即所得”的可视化开发UI

随着技术的发展,用户对软件的界面美观度和交互体验的要求越来越高。在这样的背景下,可视化开发UI(User Interface)成为了提升用户体验和开发效率的重要工具。 通过图形界面来设计和构建用户界面的方法,可视化开发UI可以说改变了软便开发的生态,与传统的代码编写相比,它允…

UNIQUE VISION Programming Contest 2024 Summer (AtCoder Beginner Contest 359)

A - Count Takahashi 数 Takahashi 字符串的数量。 模拟。点击查看代码 #include<bits/stdc++.h> using namespace std; #define int long long const int maxn=2e5+3; char s[maxn]; int n,cnt; signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>s;cnt…

【内网渗透】全网最详细的端口转发教程

场景介绍最近在真实环境测试和编写靶场实战教程时,总会遇到内网主机无法直接访问,需要通过转发端口、建立代理隧道等方式去访问,以方便后续进一步的横向渗透测试。本文章主要介绍一些端口转发工具的使用。测试场景拓扑图中所标注的IP地址为本地测试地址Kali服务只能ping通19…

好用的HAI-GPU:我将腾讯云HAI的AI绘画接入小程序

前言 感觉已经进入全面AIGC的时代了,从刚开始的ChatGPT的生成文本,到GPT-4文本到图片的发展,深刻感受到了技术的日新月异。但是GPT-4一直是付费模式,我才开始接触stable diffusion,在自己的电脑上学习AI绘画。 AI绘画的文生图还没研究透彻呢,文生视频sora又来了。对于我来…

基于Chan-Vese算法的图像边缘提取matlab仿真

1.算法运行效果图预览2.算法运行软件版本 matlab2022a3.部分核心程序% 迭代更新水平集函数 err=[]; for i = 1:Iterssubplot(132) imshow(I1,[])hold on;contour(corn, [0.5 0.5],g);title([边缘提取效果,num2str(i), iterations]);hold off;corn = func_evolution(corn, I1, …

C++获取商店应用(msix应用)桌面快捷方式的安装目录

传统应用的快捷方式目标指向可执行文件的路径,但是对于商店应用(也叫msix打包应用),则指向一个奇怪的字符串,使用IShellLink::GetPath获取路径时,则得到的是空字符串,而我们的最终目的是要拿到应用的安装路径,那该怎么办呢? 首先解释一下,那个奇怪的字符串叫AUMID(App …

原生鸿蒙的成长史中,书写着无数鸿蒙先锋的故事

千帆并进破风云,浩瀚星河耀苍穹。 2024年6月21日,是属于鸿蒙的:HarmonyOS NEXT鸿蒙星河版Beta测试开启,5000多个鸿蒙原生应用已全部启动开发,其中超1500家已完成上架,鸿蒙生态设备数量超9亿,已有254万HarmonyOS开发者投入到鸿蒙世界的开发中,开发者服务调用次数827亿次…

鸿蒙先锋共筑星河 |科技浪潮中的教育革新,看南京大学教授的HarmonyOS教学之路

在科技的浩渺海洋中,每一次技术革新都如同激起的浪潮,推动着社会不断前进。HarmonyOS的崛起,不仅引领移动应用开发领域的新方向,也为高校教育带来了前所未有的机遇。高校老师们通过应用与实践,开展教学和研究工作,培养出一大批具备创新能力的人才。 南京大学教授刘钦,以其敏锐的…