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) }
}
棧 | ||
空閒空間 | ||
堆 | ||
.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
點選檢視程式碼
.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 /* 返回 */
點選檢視程式碼
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();