雜談
工作了一天,腦袋比較亂。一直想把底層的知識寫成一個系列,希望可以堅持下去。為什么要寫底層的東西呢?首先,工作用到了這部分內容,最近和內部Flash打交道比較多,自然而然會接觸到一些底層的東西;第二,近些年來Cortex-M陣營各廠商(ST、Nordic、ATMEL……)對新產品的迭代速度越來越快,以及微控制器應用普及程度的加深,越來越多的開發者把更多精力投注在應用層開發上,花在對底層技術上的時間越來越少,更深層次的原因是走嵌入式底層沒有做互聯網上層賺錢。希望自己可以把嵌入式ARM Cortex-M(M0/M0+/M3/M4/M7/M23/M33)底層技術寫下去,加油!(長文,慎入,謝謝)
一. STM32的啟動代碼分析
當前,STM32因其豐富的學習資料,已經成為了80%嵌入式工作者入門學習的首選,當然我也不例外,主要是因為在學生時代,沒錢買更好的開發板。工作之后,你會發現老板更摳門,產品的核心芯片一代比一代便宜。廢話不多說,直接上知識點。
1.1 啟動代碼的概念
問題1. 什么是啟動代碼
啟動代碼是系統上電或者復位后運行的第一段代碼,是進入C 語言的main 函數之前需要執行的那段匯編代碼。或者說用戶程序運行之前對系統硬件及軟件環境進行必要的初始化並在最后使程序跳轉到用戶程序。
問題2. 啟動代碼主要干了什么
啟動代碼直接面對ARM 處理器內核及硬件控制器進行編程,所執行的操作與具體的目標系統緊密相關。C語言程序的運行需要具備一定的條件,如分配好外部數據空間、設置初始堆棧指針、配置時鍾、設置中斷向量入口、設置初始程序計數器(指向main())等。對於 Cortex-M系列的芯片而言,啟動代碼大同小異,故我挑選其中一個進行分析。ARM Cortex-M系列MCU的啟動代碼的主要做3件事:
- 初始化並正確放置異常/中斷向量表;
- 分散加載;
- 初始化C語言運行環境(初始化堆棧以及C Library、浮點等)
1.2 啟動代碼詳解
匯編基礎1:
1. 偽指令: EQU
語法格式:名稱 EQU 表達式{,類型}
EQU 偽指令用於為程序中的常量、標號等定義一個等效的字符名稱,類似於 C 語言的#define,所以這下能理解了吧。
2. 偽指令: AREA
語法格式: AREA 段名{, 屬性 1}{, 屬性 2}……
AREA 命令指示匯編程序匯編一個新的代碼段或數據段。理解:段是獨立的、指定的、不可見的代碼或數據塊,它們由鏈接程序處理。
段名: 可以為段選擇任何段名。但是,以一個數字開始的名稱必須包含在豎杠號內,否則會產生一個缺失段名錯誤。例如, |1_DataArea|。
有些名稱是習慣性的名稱。例如: |.text|用於表示由 C 編譯程序產生的代碼段,或用於以某種方式與 C 庫關聯的代碼段。
屬性字段表示該代碼段(或數據段)的相關屬性,多個屬性用逗號分隔。 常用的屬性如下:
——CODE 屬性:用於定義代碼段,默認為 READONLY。
——DATA 屬性:用於定義數據段,默認為 READWRITE。
——READONLY 屬性:指定本段為只讀,代碼段默認為 READONLY。
——READWRITE 屬性:指定本段為可讀可寫,數據段的默認屬性為 READWRITE。
——ALIGN 屬性:使用方式為 ALIGN 表達式。在默認時, ELF(可執行連接文件)的代碼段和數據段是按字對齊的,表達式的取值范圍為 0~31,相應的對齊方式為 2 表達式次方。
如:ALIGN=3表示8字節對齊。
——NOINIT 屬性: 表示數據段是未初始化的或初始化為零。
一個匯編語言程序至少要包含一個段,當程序太長時,也可以將程序分為多個代碼段和數據段。
3. 偽指令: SPACE 用於分配一片連續的存儲單元
第一部分 定義棧段,不初始化
1 Stack_Size EQU 0x00000400 2 3 AREA STACK, NOINIT, READWRITE, ALIGN=3 4 Stack_Mem SPACE Stack_Size 5 __initial_sp
上面的程序這樣理解,定義了一個棧,棧名為STACK (AREA STACK),大小為Stack_Size(EQU理解為#define),只分配空間不做初始化或者初始化為 0;NOINIT,可讀可寫READWRITE;按 8 字節對齊: ALIGN=3;棧頂地址: __initial_sp ,SPACE表示分配一塊連續的區域。
第二部分 定義堆段,不初始化
1 Heap_Size EQU 0x00000200 2 3 AREA HEAP, NOINIT, READWRITE, ALIGN=3 4 __heap_base 5 Heap_Mem SPACE Heap_Size 6 __heap_limit
堆名: HEAP
大小: Heap_Size
只分配空間不做初始化或者初始化為 0: NOINIT
可讀可寫: READWRITE:
按 8 字節對齊: ALIGN=3
堆起始地址: __heap_base
堆終止地址: __heap_limit
1 PRESERVE8 ;指示編譯器 8 字節對齊(keil 編譯器時需要加上) 2 THUMB ;指示編譯器為 THUMB 指令
匯編基礎2:
4. 偽指令: EXPORT
語法格式: EXPORT 標號{[WEAK]}
EXPORT 偽指令用於在程序中聲明一個全局的標號,該標號可在其他的文件中引用。
EXPORT 可用 GLOBAL 代替。標號在程序中區分大小寫, [WEAK]選項聲明其他的同名標號優先於該標號被引用。
5. 偽指令: DCD
語法格式: DCD 表達式
DCD(或 DCDU) 偽指令用於分配一個或多個連續的字(32bit)存儲單元並用偽指令中指定的表達式初始化。其中,表達式可以為程序標號或數字表達式。用 DCD 分配的字存儲單元是字對齊的。(一片是指多少?我並沒有查到相關資料,但是我看了公司大神們寫的啟動文件,備注的地址只占了4個字節,所以我理解成分配一個字的存儲單元)
第三部分 定義復位段(中斷向量表),並初始化
1 AREA RESET, DATA, READONLY 2 EXPORT __Vectors 3 EXPORT __Vectors_End 4 EXPORT __Vectors_Size 5 6 __Vectors DCD __initial_sp ; Top of Stack 7 DCD Reset_Handler ; Reset Handler 8 DCD NMI_Handler ; NMI Handler 9 DCD HardFault_Handler ; Hard Fault Handler 10 DCD MemManage_Handler ; MPU Fault Handler 11 DCD BusFault_Handler ; Bus Fault Handler 12 DCD UsageFault_Handler ; Usage Fault Handler 13 DCD 0 ; Reserved 14 DCD 0 ; Reserved 15 DCD 0 ; Reserved 16 DCD 0 ; Reserved 17 DCD SVC_Handler ; SVCall Handler 18 DCD DebugMon_Handler ; Debug Monitor Handler 19 DCD 0 ; Reserved 20 DCD PendSV_Handler ; PendSV Handler 21 DCD SysTick_Handler ; SysTick Handler 22 23 ; External Interrupts 24 DCD WWDG_IRQHandler ; Window Watchdog 25 DCD PVD_IRQHandler ; PVD through EXTI Line detect 26 DCD TAMPER_IRQHandler ; Tamper 27 DCD RTC_IRQHandler ; RTC 28 DCD FLASH_IRQHandler ; Flash 29 DCD RCC_IRQHandler ; RCC 30 DCD EXTI0_IRQHandler ; EXTI Line 0 31 DCD EXTI1_IRQHandler ; EXTI Line 1 32 DCD EXTI2_IRQHandler ; EXTI Line 2 33 DCD EXTI3_IRQHandler ; EXTI Line 3 34 DCD EXTI4_IRQHandler ; EXTI Line 4 35 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 36 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 37 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 38 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 39 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 40 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 41 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 42 DCD ADC1_2_IRQHandler ; ADC1 & ADC2 43 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX 44 DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 45 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 46 DCD CAN1_SCE_IRQHandler ; CAN1 SCE 47 DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 48 DCD TIM1_BRK_IRQHandler ; TIM1 Break 49 DCD TIM1_UP_IRQHandler ; TIM1 Update 50 DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation 51 DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare 52 DCD TIM2_IRQHandler ; TIM2 53 DCD TIM3_IRQHandler ; TIM3 54 DCD TIM4_IRQHandler ; TIM4 55 DCD I2C1_EV_IRQHandler ; I2C1 Event 56 DCD I2C1_ER_IRQHandler ; I2C1 Error 57 DCD I2C2_EV_IRQHandler ; I2C2 Event 58 DCD I2C2_ER_IRQHandler ; I2C2 Error 59 DCD SPI1_IRQHandler ; SPI1 60 DCD SPI2_IRQHandler ; SPI2 61 DCD USART1_IRQHandler ; USART1 62 DCD USART2_IRQHandler ; USART2 63 DCD USART3_IRQHandler ; USART3 64 DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 65 DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line 66 DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend 67 DCD TIM8_BRK_IRQHandler ; TIM8 Break 68 DCD TIM8_UP_IRQHandler ; TIM8 Update 69 DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation 70 DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare 71 DCD ADC3_IRQHandler ; ADC3 72 DCD FSMC_IRQHandler ; FSMC 73 DCD SDIO_IRQHandler ; SDIO 74 DCD TIM5_IRQHandler ; TIM5 75 DCD SPI3_IRQHandler ; SPI3 76 DCD UART4_IRQHandler ; UART4 77 DCD UART5_IRQHandler ; UART5 78 DCD TIM6_IRQHandler ; TIM6 79 DCD TIM7_IRQHandler ; TIM7 80 DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1 81 DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2 82 DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3 83 DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5 84 __Vectors_End 85 86 __Vectors_Size EQU __Vectors_End - __Vectors
段名: RESET(根據前面的套路,發現AREA的第一個屬性表示段名)
大小: __Vectors_Size(大小肯定要表示出來,其實堆棧是直接給出的,RESET段是先分配,后計算得到的)
數據段: DATA
只讀: READONLY
按字節對齊: 默認 ALIGN
向量表起始地址: __Vectors(標號)
向量表終止地址: __Vectors_End
注意:SPACE 和 DCD有什么區別?
1. SPACE和DCD的功能類似,SPACE申請一片內存空間,DCD申請一個或多個字(32bit)的內存空間。
2. SPACE和DCD的區別在於,SPACE申請空間但不賦初值,DCD申請一個字的空間,並賦初值。
參考資料:https://blog.csdn.net/inurlcn/article/details/20691233#reply
匯編基礎3:
6. 過程定義偽指令: PROC、 ENDP
語法格式: <過程名> PROC [類型]
……
RET
<過程名> ENDP
過程就是子程序,即定義一個子程序。一個過程可以被其它程序所調用(用 CALL 指令),過程的最后一條指令一般是返回指令(RET)。
7. 偽指令: IMPORT
語法格式: IMPORT 標號 {[WEAK]}
IMPORT 偽指令用於通知編譯器要使用的標號在其他源文件中定義。
[WEAK]選項表示弱定義,如果編譯器發現在別處定義了同名的函數,則在鏈接時用別處的地址進行鏈接,如果其它地方沒有定義,編譯器也不報錯,以此處地址進行鏈接。
例如:對NMI_Handler的定義有兩處,如下圖,首先使用的是C語言的定義的,而不是匯編定義的。
1,匯編定義,后面加【weak】
2, C語言定義 在stm32f10x_it.c中
8. 偽指令: LDR
語法格式: LDR{執行條件,如 EQ、 NE 等} register,=expr/label_expr
大范圍的地址讀取偽指令 LDR 用於加載 32 位的立即數或一個地址值到指定寄存器,在匯編編譯源程序時, LDR 偽指令被編譯器替換成一條合適的指令。
9. Thumb 跳轉指令: B、 BL、 BX
語法格式: B{執行條件,如 EQ、 NE 等} label
帶鏈接 BL{執行條件,如 EQ、 NE 等} label
帶狀態切換 BX{執行條件,如 EQ、 NE 等} label
1 AREA |.text|, CODE, READONLY 2 3 ; Reset handler 4 Reset_Handler PROC 5 EXPORT Reset_Handler [WEAK] 6 IMPORT __main 7 IMPORT SystemInit 8 LDR R0, =SystemInit 9 BLX R0 10 LDR R0, =__main 11 BX R0 12 ENDP
段名: .text
代碼段: CODE
只讀: READONLY
按字節對齊: 默認 ALIGN
代碼段起始地址: Reset_Handler
更詳細的來說一下這段代碼,
這部分可以稱作Reset_Handler實體,是芯片上電經過廠商BOOTROM后,用戶最開始可控的地方。
- 第一行,申請一個名為.text的代碼段,該代碼段的屬性是只讀的,其他沒寫,認為認為是默認的;
- 第三行,注釋
- 第四行,Reset_Handler是標號,定義同一個名為Reset_Handler的子程序(代碼段)
- 第五行,聲明一下Reset_Handler程序可以在外部使用,[WEAK]表示沒有找到其他地方的定義時,然后連接器使用此處定義的Reset_Handler程序
- 第六行和第七行,在Reset_Handler函數中導入SystemInit 和__main ,這兩個標號在其他文件,在鏈接的時候需要到其他文件去尋找
- 第八行,把SystemInit 的地址加載到寄存器R0
- 第九行,程序跳轉到R0 中的地址執行程序,如果在SystemInit中配置了時鍾,之后系統的時鍾就被設置成我們配置的了。
- 第十行把_main 的地址加載到寄存器R0。
-
第十一行程序跳轉到R0 中的地址執行程序,執行完畢之后就去到我們熟知的C 世界。
- 第十二行表示子程序的結束。
因為默認的標准的啟動代碼主要工作是在Reset_Handler里面完成的,調用函數一般也會再這里。我們可以發現,在啟動代碼的匯編語言里調用C語言函數都可以使用以上兩步:
- 導入函數標號
- 調用這個函數
例如:
1 IMPORT SystemInit ;導入函數標號 2 LDR R0, =SystemInit ;2行和3行合起來,是調用函數的功能 3 BLX R0
當然這里有幾點注意事項,這里不是所有函數都可以在匯編語言中調用的,因為此時__main還沒有運行,C語言運行環境還沒有被完整搭建起來,堆棧也沒有初始化完成,所以要注意:
(1)調用的C函數參數不能超過4個,不用可以,但用的話不能超過4個參數,原因是在Cortex-M體系MCU中,函數的1-4個形參會壓進R0-R3這4個通用寄存器(Cortex-M系列MCU,M0也好、M3也好、M4也好都只有16個通用寄存器,內部寄存器結構去參照ARM官方的白皮書)如果有第五個參數,這個參數會被壓棧,但因為此時__main還沒有運行,堆棧沒有被初始化所以此時如果函數有超過4個以上的參數,會導致程序跑飛;
(2)不要把需要調用的函數寫到__main之后,因為沒有意義,程序不會跑到那里;
參考網址:https://blog.csdn.net/weixin_39118482/article/details/79632734
匯編基礎4:
10. 內置變量: {PC} 或“.” 當前指令地址
11. 匯編語句格式規范:
ARM 匯編中,所有標號必須在一行的頂格書寫,其后面不要添加“:”,但所有指令均不能頂格書寫。
ARM 匯編器對標識符大小寫敏感,書寫標號及指令時字母大小寫要一致, 在 ARM 匯編程序中,一個 ARM 指令、偽指令、寄存器名可以全部為大寫字母,也可以全部為小寫字母,但不要大小寫混合使用。
異常處理函數 1
1 NMI_Handler PROC ;定義一個名為NMI_Handler的子程序 2 EXPORT NMI_Handler [WEAK] ;外部聲明 3 B . ;跳轉到子程序的地址(這個函數里面通常寫的是死循環,所以當出現異常時,就會卡死) 4 ENDP ;結束 5 HardFault_Handler\ 6 PROC 7 EXPORT HardFault_Handler [WEAK] 8 B . 9 ENDP 10 MemManage_Handler\ 11 PROC 12 EXPORT MemManage_Handler [WEAK] 13 B . 14 ENDP 15 BusFault_Handler\ 16 PROC 17 EXPORT BusFault_Handler [WEAK] 18 B . 19 ENDP 20 UsageFault_Handler\ 21 PROC 22 EXPORT UsageFault_Handler [WEAK] 23 B . 24 ENDP 25 SVC_Handler PROC 26 EXPORT SVC_Handler [WEAK] 27 B . 28 ENDP 29 DebugMon_Handler\ 30 PROC 31 EXPORT DebugMon_Handler [WEAK] 32 B . 33 ENDP 34 PendSV_Handler PROC 35 EXPORT PendSV_Handler [WEAK] 36 B . 37 ENDP 38 SysTick_Handler PROC 39 EXPORT SysTick_Handler [WEAK] 40 B . 41 ENDP
異常處理函數2
這個默認的異常處理函數處理所有外部中斷 。
1 Default_Handler PROC 2 3 EXPORT WWDG_IRQHandler [WEAK] 4 EXPORT PVD_IRQHandler [WEAK] 5 EXPORT TAMPER_IRQHandler [WEAK] 6 EXPORT RTC_IRQHandler [WEAK] 7 EXPORT FLASH_IRQHandler [WEAK] 8 EXPORT RCC_IRQHandler [WEAK] 9 EXPORT EXTI0_IRQHandler [WEAK] 10 EXPORT EXTI1_IRQHandler [WEAK] 11 EXPORT EXTI2_IRQHandler [WEAK] 12 EXPORT EXTI3_IRQHandler [WEAK] 13 EXPORT EXTI4_IRQHandler [WEAK] 14 EXPORT DMA1_Channel1_IRQHandler [WEAK] 15 EXPORT DMA1_Channel2_IRQHandler [WEAK] 16 EXPORT DMA1_Channel3_IRQHandler [WEAK] 17 EXPORT DMA1_Channel4_IRQHandler [WEAK] 18 EXPORT DMA1_Channel5_IRQHandler [WEAK] 19 EXPORT DMA1_Channel6_IRQHandler [WEAK] 20 EXPORT DMA1_Channel7_IRQHandler [WEAK] 21 EXPORT ADC1_2_IRQHandler [WEAK] 22 EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK] 23 EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK] 24 EXPORT CAN1_RX1_IRQHandler [WEAK] 25 EXPORT CAN1_SCE_IRQHandler [WEAK] 26 EXPORT EXTI9_5_IRQHandler [WEAK] 27 EXPORT TIM1_BRK_IRQHandler [WEAK] 28 EXPORT TIM1_UP_IRQHandler [WEAK] 29 EXPORT TIM1_TRG_COM_IRQHandler [WEAK] 30 EXPORT TIM1_CC_IRQHandler [WEAK] 31 EXPORT TIM2_IRQHandler [WEAK] 32 EXPORT TIM3_IRQHandler [WEAK] 33 EXPORT TIM4_IRQHandler [WEAK] 34 EXPORT I2C1_EV_IRQHandler [WEAK] 35 EXPORT I2C1_ER_IRQHandler [WEAK] 36 EXPORT I2C2_EV_IRQHandler [WEAK] 37 EXPORT I2C2_ER_IRQHandler [WEAK] 38 EXPORT SPI1_IRQHandler [WEAK] 39 EXPORT SPI2_IRQHandler [WEAK] 40 EXPORT USART1_IRQHandler [WEAK] 41 EXPORT USART2_IRQHandler [WEAK] 42 EXPORT USART3_IRQHandler [WEAK] 43 EXPORT EXTI15_10_IRQHandler [WEAK] 44 EXPORT RTCAlarm_IRQHandler [WEAK] 45 EXPORT USBWakeUp_IRQHandler [WEAK] 46 EXPORT TIM8_BRK_IRQHandler [WEAK] 47 EXPORT TIM8_UP_IRQHandler [WEAK] 48 EXPORT TIM8_TRG_COM_IRQHandler [WEAK] 49 EXPORT TIM8_CC_IRQHandler [WEAK] 50 EXPORT ADC3_IRQHandler [WEAK] 51 EXPORT FSMC_IRQHandler [WEAK] 52 EXPORT SDIO_IRQHandler [WEAK] 53 EXPORT TIM5_IRQHandler [WEAK] 54 EXPORT SPI3_IRQHandler [WEAK] 55 EXPORT UART4_IRQHandler [WEAK] 56 EXPORT UART5_IRQHandler [WEAK] 57 EXPORT TIM6_IRQHandler [WEAK] 58 EXPORT TIM7_IRQHandler [WEAK] 59 EXPORT DMA2_Channel1_IRQHandler [WEAK] 60 EXPORT DMA2_Channel2_IRQHandler [WEAK] 61 EXPORT DMA2_Channel3_IRQHandler [WEAK] 62 EXPORT DMA2_Channel4_5_IRQHandler [WEAK] 63 ;下面的全部異常處理函數標號都對應同一個地址, 這個地址也是 Default_Handler 的地址
64 WWDG_IRQHandler 65 PVD_IRQHandler 66 TAMPER_IRQHandler 67 RTC_IRQHandler 68 FLASH_IRQHandler 69 RCC_IRQHandler 70 EXTI0_IRQHandler 71 EXTI1_IRQHandler 72 EXTI2_IRQHandler 73 EXTI3_IRQHandler 74 EXTI4_IRQHandler 75 DMA1_Channel1_IRQHandler 76 DMA1_Channel2_IRQHandler 77 DMA1_Channel3_IRQHandler 78 DMA1_Channel4_IRQHandler 79 DMA1_Channel5_IRQHandler 80 DMA1_Channel6_IRQHandler 81 DMA1_Channel7_IRQHandler 82 ADC1_2_IRQHandler 83 USB_HP_CAN1_TX_IRQHandler 84 USB_LP_CAN1_RX0_IRQHandler 85 CAN1_RX1_IRQHandler 86 CAN1_SCE_IRQHandler 87 EXTI9_5_IRQHandler 88 TIM1_BRK_IRQHandler 89 TIM1_UP_IRQHandler 90 TIM1_TRG_COM_IRQHandler 91 TIM1_CC_IRQHandler 92 TIM2_IRQHandler 93 TIM3_IRQHandler 94 TIM4_IRQHandler 95 I2C1_EV_IRQHandler 96 I2C1_ER_IRQHandler 97 I2C2_EV_IRQHandler 98 I2C2_ER_IRQHandler 99 SPI1_IRQHandler 100 SPI2_IRQHandler 101 USART1_IRQHandler 102 USART2_IRQHandler 103 USART3_IRQHandler 104 EXTI15_10_IRQHandler 105 RTCAlarm_IRQHandler 106 USBWakeUp_IRQHandler 107 TIM8_BRK_IRQHandler 108 TIM8_UP_IRQHandler 109 TIM8_TRG_COM_IRQHandler 110 TIM8_CC_IRQHandler 111 ADC3_IRQHandler 112 FSMC_IRQHandler 113 SDIO_IRQHandler 114 TIM5_IRQHandler 115 SPI3_IRQHandler 116 UART4_IRQHandler 117 UART5_IRQHandler 118 TIM6_IRQHandler 119 TIM7_IRQHandler 120 DMA2_Channel1_IRQHandler 121 DMA2_Channel2_IRQHandler 122 DMA2_Channel3_IRQHandler 123 DMA2_Channel4_5_IRQHandler 124 B . 125 126 ENDP
先定義,當外部中斷觸發時,B . 表示跳進去執行。
匯編基礎5:
由於前面只是定義了堆棧段並沒有初始化,這里對堆棧段進行初始化。 就像定義了: int a; 初始化 a = 1;也可以像代碼段一樣定義的同時就初始化: int b = 2;
在_main中,會調用一下的程序,進行堆棧的初始化,進而為進入到C語言中的main函數做好准備。
下面代碼中有個__MICROLIB,對應后面 MDK 截圖的 Use MicroLIB,如果選了勾選了 Use MicroLIB, IF 就為真,否則為假
初始化堆棧段
1 IF :DEF:__MICROLIB 2 3 EXPORT __initial_sp 4 EXPORT __heap_base 5 EXPORT __heap_limit 6 7 ELSE 8 9 IMPORT __use_two_region_memory 10 EXPORT __user_initial_stackheap 11 12 __user_initial_stackheap 13 14 LDR R0, = Heap_Mem 15 LDR R1, =(Stack_Mem + Stack_Size) 16 LDR R2, = (Heap_Mem + Heap_Size) 17 LDR R3, = Stack_Mem 18 BX LR 19 20 ALIGN 21 22 ENDIF 23 24 END
microlib是缺省C庫的備選庫。它旨在與需要裝入到極少量內存中的深層嵌入式應用程序配合使用。這些應用程序不在操作系統中運行。microlib進行了高度優化以使代碼變得很小。它的功能比缺省C庫少,並且根本不具備某些ISOC特性。某些庫函數的運行速度也比較慢,例如,memcpy()。不管使用與否,都可以,但是啟動時稍微有點區別。
- 啟動流程1(使用標准庫,不使用Microlib)如下圖:
- 啟動流程2(使用Microlib)如下圖:
假設STM32被設置為從內部FLASH啟動(這也是最常見的一種情況),中斷向量表起始地位為0x8000000,則棧頂地址存放於0x8000000處(大部分是這個地址),而復位中斷服務入口地址存放於0x8000004處(復位地址在棧頂地址4字節后)。當STM32遇到復位信號后,則從0x80000004處取出復位中斷服務入口地址,繼而執行復位中斷服務程序,然后跳轉__main函數,最后進入mian函數,來到C的世界。
解釋一下一個小細節,絕大部分ARM-M協議的芯片,復位之后先進入廠商boot,此時所有的用戶均無法接入處理器;廠商boot主要負責芯片最初級的初始化,加密及
對MCU進行一些差異性設置等,BOOT完成后,會把主動權交給用戶,也就是啟動代碼;啟動代碼(執行匯編語言不需要此啟動代碼),在啟動文件中,會設置MSP(主堆棧指針)和PC(程序計數器)的值,MSP的地址默認是0x00000000,PC的地址默認是0x00000004,這兩個地址可以通過CORTEX-M中的VTOR寄存器來進行重映射,修改。
后面,手把手寫一下啟動文件,並進行驗證一下;寫完啟動文件之后寫分散加載文件。