[STM32F4]STM32F4深入底層學習


深入底層學習

1 STM32啟動過程分析

1.0 預備知識

1、RAM、FLASH

  • ram即隨機存儲器,掉電即丟失信息,常用於存放寄存器和運行中的數據。
  • flash是和rom類似的存儲器,掉電不會丟失信息,用於存放代碼。
  • 芯片手冊可以查到STM32F407ZGT6的Flash大小為1024Kbyte,SRAM的大小為192k

2、三種啟動方式

打開STM32F407中文手冊,找到2.4 自舉配置

STM32有三種啟動方式,分別是從FLASH(閃存)SRAM系統存儲器啟動,由BOOT0BOOT1設置

  • Flash啟動,將Flash地址0x08000000映射到0x00000000,一般我們所寫的程序都放在STM32的內部Flash中,是我們最常用的模式
  • SRAM啟動,將SRAM地址0x20000000映射到0x00000000,一般用於調試(PS:遇到過flash被鎖定,則可通過SRAM解鎖Flash,參考【解鎖FLASH】如何給STM32芯片解鎖)
  • 系統存儲器啟動,根據中文手冊2.4 自舉配置表4可得系統存儲器即存儲器地址0x1FFFF000起到0x1FFF77FF的這一段地址。再往上看,解釋這段空間存放着一段嵌入式自舉程序,這個程序出廠就燒錄在里面。可以理解為電腦的bios程序,這個程序提供了串口下載的功能,也就是串口下載要切換boot的原因。

3、編譯完后的信息和map文件分析

在keil中編譯完程序之后

  • Code:是程序中代碼所占字節大小;

  • RO-data:程序只讀(readonly)的變量,也就是帶const的,和已初始化的字符串等;

  • RW-data:已初始化的可讀寫(readwrite)全局/靜態變量;

  • ZI-data:未初始化(Zero-initialized)的可讀寫全局/靜態變量;

程序所占Flash空間大小=Code+RO data+RW-data=生成的bin文件大小。

程序固定占用RAM大小=RW data+ZI data

在生成文件中可以找到一個map文件,這個文件記錄了編譯鏈接以及內存分配等信息。keil可以設置map的輸出信息。

keilProject -> Options for Target -> Listing主要包含配置:

  • Memory Map:內存映射
  • Callgraph:圖像映射
  • Symbols:符號
  • Cross Reference:交叉引用
  • Size Info:大小信息
  • Totals Info:統計信息
  • Unused Section Info:未調用模塊信息
  • Veneers Info:裝飾信息

map文件主要結構

map文件里面內容大致分為五大類(按照map文件分類的順序)
1.Section Cross References:模塊、段(入口)交叉引用
2.Removing Unused input sections from the image:移除未使用的模塊
3.Image Symbol Table:映射符號表
4.Memory Map of the image:內存(映射)分布
5.Image component sizes:存儲組成大小

文件名詞
段(section):描述映像文件的代碼和數據塊
RO:Read-Only的縮寫,包括RO-data(只讀數據)和RO-code(代碼)
RW:Read-Write的縮寫,主要是RW-data,RW-data由程序初始化初始值
ZI:Zero-initialized的縮寫,主要是ZI-data,由編譯器初始化為0。
.text:與RO-code同義
.constdata:與RO-data同義
.bss:與ZI-data同義
.data:與RW-data同義

  • Section Cross References模塊、段(入口)交叉引用(需勾上Cross Reference)
    • 表達不同文件中函數的調用關系
    • 例如:main.o(.text) refers to delay.o(.text) for delay_init表達main文件里的一段語句,調用了delay文件delay_init函數

  • Removing Unused input sections from the image 移除未使用的模塊(需勾上Unuaed Sections Info)
    • 最后一句統計信息23 unused section(s) (total 104 bytes) removed from the image.表示總共23段沒有調用,沒有調用的大小為104字節

  • Image Symbol Table 映射符號表(需勾上Symbols)
    • 映射符號表,也就是各個段所存儲對應地址的表(這一項比較重要)
    • Symbols分為兩大類1.Local Symbols局部、2.Global Symbols全局
    • 1、Symbol Name:符號名
    • 2、Value:存儲對應的地址;(0x08開頭表示存儲在Flash中,0x2開頭表示存儲在SRAM中)
    • 3、Ov Type:符號對應的類型符號類型大概有幾種:NumberSectionThumb CodeData等;(全局、靜態變量等位於0x2000xxxx的內存RAM中)
    • 4、Size:存儲大小(懷疑內存溢出,可以查看代碼存儲大小來分析)
    • 5、.Object(Section):段目標(這里一般指所在模塊(所在源文件))

  • Memory Map of the image:內存(映射)分布(需勾上Memory Map)
    • 1、Base Addr:存儲地址(0x0800xxxxFLASH地址和0x2000xxxx內存RAM地址)
    • 2、Size:存儲大小
    • 3、Type:類型(Data:數據類型Code:代碼類型 Zero:未初始化變量類型 PAD:“補充類型”。)
    • 4、Attr:屬性(RO:存儲與ROM中的段 RW:存儲與RAM中的段)
    • 5、Section Name:段名這里也可以說為入口分類名,與第一章節“Section Cross References”指的模塊、段一樣。大概包含:RESET、.ARM、 .text、 i、 .data、 .bss、 HEAP、 STACK等。
    • 6、Object:目標模塊

  • Image component sizes:存儲組成大小(需勾上Size Info)
    • 對信息進行匯總,和開頭分析的那個信息是一樣的。

1.1 啟動過程分析

1 啟動文件解讀

野火哥的啟動文件視頻講解,超詳細

  1. 初始化堆和棧

第一段定義了一個棧空間(用於存放局部變量,函數)

  • Stack_Size EQU 0x00000400;偽指令Stack_Size=1KB
  • AREA STACK, NOIIT, READWRITE, ALIGN=3;STACK新的棧段,NOIIT表示存放到SRAM,READWRITE可讀寫ALIGN=32^3字節即8字節對齊
  • Stack_Mem SPACE Stack_Size;分配空間
  • __initial_sp 表示棧的結束地址,即棧頂地址;棧是由高向低生長的

第二段定義了一個堆空間(動態內存分配)

  • Heap_Size EQU 0x00000200;Heap_Size=512字節
  • AREA HEAP, NOINIT, READWRITE, ALIGN=3同上
  • __heap_base;堆的起始地址
  • Heap_Mem SPACE Heap_Size;分配空間
  • __heap_limit;堆的結束地址;堆是由低向高生長的,和棧相反
  • PRESERVE8;指定當前文件的堆棧按照8 字節對齊。
  • THUMB;表示后面指令兼容THUMB 指令。THUBM是ARM以前的指令集
  1. 初始化向量表

  • AREA RESET, DATA, READONLY;定義一個數據段,名字為RESET,存放在flash,只讀。

  • 聲明 __Vectors__Vectors_End__Vectors_Size 這三個標號具有全局屬性,可供外部的文件調用。

    • EXPORT __Vectors
    • EXPORT __Vectors_End
    • EXPORT __Vectors_Size
    • EXPORT:聲明一個標號可被外部的文件使用,使標號具有全局屬性。如果是IAR 編譯器,則使用的是GLOBAL 這個指令。
  • DCD __initial_sp ;

    • DCD 簡單來說就是給后面的標號分配了一個地址,並存放在該位置
    • 第一個存放棧頂指針SP,第二個存放PC指針,指向復位RESET中斷子服務函數
  1. 復位中斷子服務函數及其他中斷子服務函數

  • EXPORT Reset_Handler [WEAK];WEAK:表示弱定義,如果外部文件優先定義了該標號則首先引用該標號,如果外部文件沒有聲明也不會出錯。這里表示復位子程序可以由用戶在其他文件重新實現,這里並不是唯一的。
  • IMPORT SystemInit;IMPORT:表示該標號來自外部文件,跟C 語言中的EXTERN 關鍵字類似。
  • 然后下面兩個分別進入外部的SystemInit函數和__main函數(__main集成在MDK自帶的庫里面了,主要的功能是軟件設置SP、加載.data.bss並初始化棧區)

下面是一些其他的中子服務函數,並且具有弱WEAK性,即如果外部有,優先采用外部的,不會產生沖突

  1. 用戶堆棧初始化

給用戶自己定義堆棧


2 內部Flash啟動過程

啟動過程主要為:
1、初始化堆棧指針SP=_initial_sp和PC指針=Reset_Handler
2、配置系統時鍾,調用C 庫函數_main 初始化用戶堆棧調用main

1、初始化堆棧指針SP=_initial_spPC指針=Reset_Handler、初始化中斷向量表

上電后默認將0x0000 0000的位置讀出為SP指針,將0x0000 0004的位置數據讀出為PC指針

根據前面,我們知道以flash啟動時0x0000 0000被映射到0x0800 0000的位置,所以根據啟動文件可以得知,向量表也被映射到了0x0800 0000,由此可以讀出棧頂地址復位中斷地址

這里用軟件STM32 ST-LINK Utility 下載地址1(官網) 下載地址2

打開hex文件,可以查到0x0800 0000起頭兩個的數據,第一個0x20000758是分配給SP的地址(棧頂地址),第二個0x080004A1是分配給PC指針的地址(復位中斷地址)

0x080004A14字節對齊0x080004A0,在.map文件中找到地址0x080004A0對應得模塊(復位中斷函數就放在這個啟動文件里)

PC指針跳轉到復位中斷子服務函數Reset_Handler

2、配置系統時鍾,調用C 庫函數_main 初始化用戶堆棧調用main

復位中斷子服務函數里完成這些事


參考資料


[應用]IAP、ISP

見專欄IAP

2 SysTick系統定時器

系統定時器非常重要,在非操作系統里,系統定時器用來做延時使用,在操作系統里,系統定時器用來產生任務調度的定時。

SysTick系統定時器是Cortex M4架構里都有的外設,所以我們查看Cortex M4內核編程手冊

打開Cortex M4內核編程手冊,找到4 Core peripherals(內核特性)-4.5 SysTick Timer

它告訴我們SysTick是個24位的系統定時器,從預裝載值向下計數到0,相關寄存器有控制狀態寄存器STK_CTRL裝載值寄存器STK_LOAD當前值寄存器STK_VAL校准數值寄存器STK_CALIB

  • 控制狀態寄存器STK_CTRL

    • COUNTFLAG:當定時器數值為0時,此位為1
    • CLKSOURCE:選擇時鍾源,0:AHB/8 1:AHB
    • TICKINT:1使能定時器異常請求,即定時器回到0不會重裝載
    • ENABLE:使能定時器重裝載,同時受TICKINT控制
  • 裝載值寄存器STK_LOAD

    • RELOAD:重裝值為真實值N-1
  • 當前值寄存器STK_VAL

    • CURRENT:可手動清0

固件庫中的Systick相關函數

  • SysTick_CLKSourceConfig() Systick時鍾源選擇(在misc.c文件中)

  • SysTick_Config(uint32_t ticks) 初始化systick,時鍾為HCLK,並開啟中斷 (在core_cm3.h中)

  • void SysTick_Handler(void); SysTick中斷函數

  • SysTick_CLKSourceConfig() 選擇時鍾源,0:AHB/8 1:AHB

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}
  • SysTick_Config(uint32_t ticks)
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */

  SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}


  • if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); 預裝載值不能超過最大值
  • SysTick->LOAD = ticks - 1; 設置重裝載寄存器的值
  • NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);設置中斷寄存器
  • SysTick->VAL = 0; 當前值為0
  • SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;設置控制寄存器

應用:延時delay函數

  • 初始化延遲函數delay_init
//初始化延遲函數
//當使用OS的時候,此函數會初始化OS的時鍾節拍
//SYSTICK的時鍾固定為AHB時鍾的1/8
//SYSCLK:系統時鍾頻率
void delay_init(u8 SYSCLK)
{

 	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
	fac_us=SYSCLK/8;						//不論是否使用OS,fac_us都需要使用
	fac_ms=(u16)fac_us*1000;				//非OS下,代表每個ms需要的systick時鍾數   

}			

指定時鍾源為AHB/8

  • us延時delay_us

void delay_us(u32 nus)
{		
	u32 temp;
	SysTick->LOAD=nus*fac_us; 				//時間加載
	SysTick->VAL=0x00;        				//清空計數器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數 	 
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待時間到達   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器
	SysTick->VAL =0X00;       				//清空計數器 
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM