STM32記憶體分佈,啟動過程及bootloader

lpajsj發表於2024-06-24

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 = 64K
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH    (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, %function

Reset_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    LoopFillZerobss

LoopFillZerobss:
  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  main

  bx  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();

相關文章