# STM32 Bare-Metal Programming with GCC and Makefile
github:https://github.com/lisyuanhao/STM32-Bare-Metal-Programming-with-GCC-and-Makefile
## 前言
此學習筆記為實作成大Jserv教授的教材:[嵌入式系統建構:開發運作於stm32的韌體程式](http://wiki.csie.ncku.edu.tw/embedded/Lab19/stm32-prog.pdf),**就是Bare-Metal Programming,不使用[ST](https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html)提供的IDE(STM32CubeIDE)與HAL,從編譯的makefile、連結的linker、到燒錄位置都要手動設定**。目標為點亮LED,因此需要查規格書,知道有哪些暫存器會控制LED、會從哪裡開機(Boot area)、要燒錄到Flash的哪個位址。
## 作業環境
1. 作業系統:Windows11
2. 開發版:STM32H745ZIQ
3. 編譯器:GNU Arm Embedded Toolchain (arm-none-eabi-gcc)
4. 燒錄工具:OpenOCD + ST-Link

## Flash開機位址
### Option bytes
要先提到option bytes,因為這會決定我們要將程式碼燒錄到Flash的哪個位址,閱讀[Reference manual](https://www.st.com/resource/en/reference_manual/rm0399-stm32h745755-and-stm32h747757-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)中的4.4.1及4.4.2章節,Option bytes是Flash中的一段非揮發性記憶體,會在上電後reset被載入到Flash configuration registers當中,並且可以根據應用需求修改。因此可以透過設定Boot address option bytes來設定要在哪個位址開機。
> The embedded flash memory includes a set of non-volatile option bytes. They are loaded at power-on reset and can be read and modified only through onfiguration registers. These option bytes are configured by the end-user depending on the application requirements.
> When the user application successfully modifies the option byte content through the
embedded flash memory registers, the non-volatile option bytes are programmed and the embedded flash memory automatically reloads all option bytes to update the option registers.
以下為兩個核心各自可以設定的option bytes,因為我們只會用CM7核心,因此都看CM7相關的部分。首先,要啟用CM7 core,必須要設定BOOT_CM7 bit為1(預設為1)。
> 4.4.7 Description of boot address option bytes
> Below the list of option bytes that can be used to configure the appropriate boot address for your application:
> • Arm® Cortex®-M7 boot options
> – BOOT_CM7: option bit to enable Arm® Cortex®-M7 boot, when set to 1.
> – BOOT_CM7_ADD0/1: MSB of the Arm® Cortex®-M7 boot address when the BOOT pin is low (respectively high)

至於位址的部分則取決於BOOT_CM7_ADD0/1,依照手冊說明,若boot pin為0,則看BOOT_CM7_ADD0。內文提到:
> BOOT_CM7_ADD0/1: MSB of the Arm® Cortex®-M7 boot address when the BOOT pin is low (respectively high)。
這裡的MSB,並不是指most significant bit,而是代表boot address的高16位,且其預設值為0x0800(BOOT_ADD0BOOT_CM7_ADD0[15:0]),查詢4.9.17 FLASH register boot address有寫到:
> Bits 15:0 BOOT_CM7_ADD0: Arm® Cortex®-M7 boot address 0
> These bits reflect the MSB of the Arm® Cortex®-M7 boot address when the BOOT pin is low.
>


也可以從以下的Table 9看出Boot=0,Boot area是在Flash的0x0800 0000開始的,這就我們後續在linker中要寫的位址。

接著就要查BOOT pin,沒有在Reference manual,而是在[User manual](https://www.st.com/resource/en/user_manual/um2408-stm32h7-nucleo144-boards-mb1363-stmicroelectronics.pdf) ,預設為Low,若要拉高要接到3V3_VDD(pin 5)。
|  |  |
| --------------------------------------------------- | --------------------------------------------------- |
> The default state of BOOT0 is LOW. It can be set to HIGH when a jumper is ON between pins 5 and 7 of CN11. Refer to the warning at the end of Section 7.4.8: Internal SMPS/LDO configuration.
### RAM(Random-access memory)起始位址
使用AXI SRAM(Advanced eXtensible interface static random access memory),起始位址為0x2400 0000,

## 專案結構
```
├── blink.c
├── startup.c
├── reg.h
├── simple.ld
└── Makefile
```
## LED GPIO
### GPIO與AHB4匯流排
要點亮LED1,閱讀User manual的7.6 LEDs章節說明:
> User LD1: a green user LED is connected to the STM32H7 I/O PB0 (SB65 OFF and SB54 ON) or PA5 (SB65 ON and SB54 OFF) corresponding to the ST Zio D13.
我們要找到GPIO B的第0號Pin,從[datasheet](https://www.st.com/resource/en/datasheet/stm32h745zg.pdf)可以找到 Figure 1. STM32H745xI/G block diagram,其中GPIO B部分是由AHB4匯流排控制,因此從Reference manual的Table 7. Register boundary addresses,找到**RCC(Reset and Clock Control)** 與**GPIO B**的基底位址。


### RCC AHB4ENR register
要啟用GPIO B的clock要定位到RCC_AHB4ENR暫存器,方法為從剛剛找到的RCC基底位址+offset=0x58024400+0x0E0,下方表格可定位到RCC_AHB4ENR或RCC_C1_AHB4ENR,沒有C1後綴屬於Common register bank,兩個CPU都能存取。此暫存器的bit 1位置為GPIOBEN,因此這部分的程式碼為:
```c!
#define AHB4_RCC 0x58024400
#define RCC_AHB4ENR_OFFSET 0x0E0
#define RCC_AHB4ENR (*((volatile unsigned long *)(AHB4_RCC + RCC_AHB4ENR_OFFSET)))
RCC_AHB4ENR |= (1 << 1);
```

> The RCC block manages the clock and reset generation for the whole microcontroller, which embeds two CPUs: an Arm® Cortex®-M7 and an Arm® Cortex®-M4, called CPU1 and CPU2, respectively. CPU1 can allocate a peripheral for itself by setting the dedicated PERxEN bit in:
> • RCC_DnxxxxENR registers or
> • RCC_C1_DnxxxxENR registers.
### GPIOB register
接著就是GPIO B相關的暫存器,我們需要從Table 7. Register boundary addresses找到GPIO B的基底位址:0x58020400,並根據各暫存器的offset來定位到該位址。總共有3個暫存器要設定:
1. GPIOx_MODER:設成輸出模式(General purpose output mode)
2. GPIOx_OTYPER:設成推挽模式(Output push-pull)
3. GPIOx_BSRR:設定Port x 為1(Port x reset I/O pin y )
### GPIOx_MODER
GPIOx_MODER的offset為0x00,需要將MODER0兩個bit設成01,因此程式碼為:
```c!
#define AHB4_GPIOB 0x58020400
#define GPIO_MODER_OFFSET 0x00
#define GPIO_MODER (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_MODER_OFFSET)))
GPIO_MODER &= ~(0x3 << 0);//先清除此2 bit
GPIO_MODER |= (0x1 << 0);//將bit 0設為1
```

### GPIOx_OTYPER
GPIOx_OTYPER的offset為0x04,需要將OT0 bit設成0,因此程式碼為:
```c!
#define GPIO_OTYPER_OFFSET 0x04
#define GPIO_OTYPER (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_OTYPER_OFFSET)))
GPIO_OTYPER &= ~(0x1 << 0);
```

### GPIOx_BSRR
GPIOx_BSRR的offset為0x18,
點亮LED:BS0設成1,
熄滅LED:BR0設成1,因此程式碼為:
```c!
#define GPIO_BSRR_OFFSET 0x18
#define GPIO_BSRR (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_BSRR_OFFSET)))
#define LED_ON() (GPIO_BSRR = (0x1))
#define LED_OFF() (GPIO_BSRR = (0x1 << 16))
```

### reg.h
```c
#define AHB4_GPIOB 0x58020400
#define AHB4_RCC 0x58024400
#define RCC_AHB4ENR_OFFSET 0x0E0
#define GPIO_MODER_OFFSET 0x00
#define GPIO_OTYPER_OFFSET 0x04
#define GPIO_BSRR_OFFSET 0x18
#define RCC_AHB4ENR (*((volatile unsigned long *)(AHB4_RCC + RCC_AHB4ENR_OFFSET)))
#define GPIO_MODER (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_MODER_OFFSET)))
#define GPIO_OTYPER (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_OTYPER_OFFSET)))
#define GPIO_BSRR (*((volatile unsigned long *)(AHB4_GPIOB + GPIO_BSRR_OFFSET)))
#define LED_ON() (GPIO_BSRR = (0x1))
#define LED_OFF() (GPIO_BSRR = (0x1 << 16))
```
### blink.c
```c
#include "reg.h"
int global = 123;
int main(void){
unsigned int c = 0;
c = global;
RCC_AHB4ENR |= (1 << 1);
GPIO_MODER &= ~(0x3 << 0);
GPIO_MODER |= (0x1 << 0);
GPIO_OTYPER &= ~(0x1 << 0);
while (1){
LED_ON();
for(c = 0; c < 1000000; c++);
LED_OFF();
for(c = 0; c < 1000000; c++);
}
}
```
## Linker
會先使用關鍵字:MEMORY來定義兩個記憶體區間,亦即`FLASH`與`RAM`區域,定義如下,起始位址為0x0800 0000,大小為1MB,RAM的起始位址為0x2400 0000,大小為512KB。
### simple.ld
```
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024k
RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512k
}
SECTIONS
{
.flash :{
KEEP(*(.isr_vector));
KEEP(*(.reset_handler));
_load_text = .;
} > FLASH
.text_ram : AT(_load_text)
{
_text_ram = .;
*(.text)
*(.text*)
_load_data = .; /* Flash 中 data section 的載入位址 */
} > RAM
.data :AT (_load_data)
{
_data = .;
*(.data)
*(.data*)
_edata = . ;
} > RAM
.bss : {
_bss = . ;
*(.bss)
*(.bss*)
_ebss = . ;
} > RAM
_end = . ;
_estack = ORIGIN(RAM) + LENGTH(RAM);
/DISCARD/ : {
*(.glue_7)
*(.glue_7t)
*(.vfp11_veneer)
*(.v4_bx)
*(.iplt)
*(.rel.dyn)
*(.rel.iplt)
*(.igot.plt)
}
}
```
我們需要定義text section、data section需要放的地方。需注意的是,若有定義已初始化的全域變數,會先將其放置於Flash,需要將其複製到RAM這段可讀可寫的區域中。MCU上電reset後,會依照先前設定的option byte跳到指定位址找vector table,並且讀取前兩個entry:
1. Main stack pointer
2. Reset handler
### 中斷向量表(vector table)
以下為[programming manual](https://www.st.com/resource/en/programming_manual/pm0253-stm32f7-series-and-stm32h7-series-cortexm7-processor-programming-manual-stmicroelectronics.pdf#page=31.10)中的vector table,前兩個entry為Initial SP value、Reset,也因此我們自己宣告的vector table前兩個entry也要為此。

> On system reset, the vector table is at address 0x00000000. Privileged software can write to the VTOR to relocate the vector table start address to a different memory location, in the range 0x00000000 to 0xFFFFFF80.
```c!
extern uint32_t _estack;
extern uint32_t _load_text;
extern uint32_t _text_ram;
extern uint32_t _edata;
extern uint32_t _bss;
extern uint32_t _ebss;
__attribute__((section(".isr_vector")))
uint32_t *vector_table[] = {
(uint32_t *) &_estack,
(uint32_t *) reset_handler,
(uint32_t *) nmi_handler,
};
__attribute__((section(".reset_handler")))
void reset_handler(void){
uint32_t *src, *dst;
src = &_load_text;
dst = &_text_ram;
while (dst < &_edata) *dst++ = *src++;
for(dst = &_bss; dst < &_ebss; dst++)
*dst = 0;
main();
}
void nmi_handler(void){
while(1);
}
```
* 前面加`__attribute__((section(".isr_vector")))`可讓開發者自行定義一個section,讓linker知道這一section放的是中斷向量表。
> You can place code and data by separating them into their own objects without having to use toolchain-specific pragmas or attributes. However, you can also use __attribute__((section("name"))) to place an item in a separate ELF section. You can then use a scatter file to place the named sections at specific locations.
> *Ref*:https://developer.arm.com/documentation/dui0377/c/using-scatter-files/using---attribute----section--name----
* _estack是在linker中定義的變數,`_estack = ORIGIN(RAM) + LENGTH(RAM);`,代表top of stack,對應到前面提到的Initial SP value。
* reset_handler負責將data section從Flash搬到RAM
* Jserv教授的講義中有提到:
>大家如果開發過ARM9上的 bootloader 的話,應該都有過類似的經驗。這類 bootloader 的實作中,開始運行的第一步就是把text section,data section全部移到 RAM 中,尤其是對有些從 NAND FLASH 啟動的開發板,這一步是無法避免的。這個例子比前一個例子只是多了一個text section的複製,看起來並不算複雜,關鍵是對中斷向量表及中斷服務例程的特殊處理,讀者可以做為練習自己解說一下。給出的參考例程中,中斷向量表和中斷服務例程還是運行在 FLASH 上的,感興趣的話可以,把除了ResetISR()的中斷服務例程也放在 SRAM中。自己試試吧!
因為中斷向量表和中斷服務例程的reset handler還是運行在 Flash 上的,所以會將這兩個段獨立出來,nmi_handler則不需要,因為這個ISR是要被移到RAM的。**因此linker部分會依照講義說的:**
**1. 把除了ResetISR()的中斷服務例程也放在 SRAM中**
**2. 把text section,data section全部移到 RAM 中,如下圖:**

以下為將中斷向量表isr_vector及reset_handler的中斷服務例程放在Flash中,最後面的"`> FLASH`"代表是要放在Flash中。
```
.flash :{
KEEP(*(.isr_vector));
KEEP(*(.reset_handler));
_load_text = .;
} > FLASH
```
> 在.isr_vector 上加KEEP是為了防止連結器的垃圾回收(garbage collection)功能啟用時,忽略.isr_vector section
> GNU linker ld參考手冊定義了以下兩種位址:
> ● LMA (Load Memory Address): 表示section被保存在記憶體上的位址
> ● VMA (Virtual Memory Address): 表示程式碼執行時期的位址
> 大多數情況來說(也就是不需要section搬移的情況),這兩類位址可視為相同,一如目的檔由GNU/Linux或其他作業系統提供的載入器去載入的情況,或者像上面那個例子,沒有作業系統載入器,但不需要作data section搬移的動作,指令直接從Flash被讀取並執行。不過,在本節探討的案例中,由於data section需要從Flash中搬移到SRAM中,因此data section的LMA和VMA不會相同。text section儲存在Flash上的位址是LMA,搬移到SRAM裡頭的位址則是VMA。
以下為reset_handler以後的text section,因為是要移到RAM,因此最後面的符號為"`> RAM`"。而 `AT(...)` 後面接的是載入位址(LMA),也就是這段內容在 Flash 中的實際儲存位置。代表`.text_ram` 這段雖然執行時會放在 RAM,但它的初始內容存放在 Flash 的 `_load_text` 位置。而 `_load_text` 是在前一段 `.flash` 段中定義的符號(`_load_text` = .;),它記錄了 Reset Handler 結束時 Flash 內的位址,因此從這個位址開始的程式碼(也就是 Reset Handler 之後的所有`.text` 區段)都會被視為需要搬到 RAM 執行的程式碼。
```
.text_ram : AT(_load_text){
_text_ram = .;
*(.text)
*(.text*)
_load_data = .; /* Flash 中 data section 的載入位址 */
} > RAM
```
以下為data section,段的起始位址為_load_data,也就是上一個段:.text_ram的結束位址
```
.data :AT (_load_data){
_data = .;
*(.data)
*(.data*)
_edata = . ;
} > RAM
```
以下為bss section,因為直接在RAM處理(清0),因此只須要記錄下起始位址`_bss` 與結束位址`_ebss`。
```
.bss : {
_bss = . ;
*(.bss)
*(.bss*)
_ebss = . ;
} > RAM
```
### Reset handler
以下僅列出.reset_handler段與linker中定義的變數,其中的`_load_text`、`_text_ram`、`_edata`、`_bss`、`_ebss`皆為linker中定義的變數,用來代表各個段的起始或結束位址。
`_load_text`(src)代表的是.reset_handler段的結束位址,同時也代表其他text段在Flash的起始位址,我們要從這個位址開始複製。要複製到的地方為`.text_ram`(dst)在RAM中的位址,因此在while迴圈內寫`*dst++ = *src++;`,直到在RAM中的位址`_text_ram`(dst)到達data section在RAM中的結束位址`_edata`。並且將bss section的值都清0後,跳到`main()`,執行我們的blink.c。
```c!
extern uint32_t _estack;
extern uint32_t _load_text;
extern uint32_t _text_ram;
extern uint32_t _edata;
extern uint32_t _bss;
extern uint32_t _ebss;
__attribute__((section(".reset_handler")))
void reset_handler(void){
uint32_t *src, *dst;
src = &_load_text;
dst = &_text_ram;
while (dst < &_edata) *dst++ = *src++;
for(dst = &_bss; dst < &_ebss; dst++)
*dst = 0;
main();
}
```
### startup.c
```c
#include <stdint.h>
extern uint32_t _estack;
extern uint32_t _load_text;
extern uint32_t _text_ram;
extern uint32_t _edata;
extern uint32_t _bss;
extern uint32_t _ebss;
void reset_handler(void);
void nmi_handler(void);
int main(void);
__attribute__((section(".isr_vector")))
uint32_t *vector_table[] = {
(uint32_t *) &_estack,
(uint32_t *) reset_handler,
(uint32_t *) nmi_handler,
};
__attribute__((section(".reset_handler")))
void reset_handler(void){
uint32_t *src, *dst;
src = &_load_text;
dst = &_text_ram;
while (dst < &_edata) *dst++ = *src++;
for(dst = &_bss; dst < &_ebss; dst++)
*dst = 0;
main();
}
void nmi_handler(void){
while(1);
}
```
## Makefile
* arm-none-eabi-gcc
> ARM architecture,no vendor,not target an operating system,complies with the ARM EABI 用於編譯 ARM 架構的裸機系統(包括 ARM Linux 的 boot、kernel,不適用編譯 Linux 應用 Application),一般適合 ARM7、Cortex-M 和 Cortex-R 內核的芯片使用,所以不支持那些跟操作系統關係密切的函數,比如fork(2),他使用的是 newlib 這個專用於嵌入式系統的C庫。
> *Ref:* https://jasonblog.github.io/note/toolchain/173.html
* -mcpu:使用ARM Cortex-M7產生的指令
* -mthumb:Cortex-M7只支援thumb指令集
>The ARMv7-M Thumb instruction set, defined in the ARM®v7-M Architecture Reference Manual.
>*Ref:*[ARM® Cortex®-M7 Processor](https://developer.arm.com/documentation/ddi0489/f/introduction/about-the-cortex-m7-processor/features?lang=en)
* -nostartfiles:
> 要求連結階段不要使用標準系統起始檔案(starup file),這在沒有作業系統支援的環境是必要的,因為我們自己處理C語言程式main()函式之前的種種準備動作。
* -Map:blink.map,輸出一份記憶體配置報告。下一小節會根據此檔案分析startup.c與linker分配的記憶體位址是否正確。
* 使用openocd進行燒錄:
1. -f interface/stlink.cfg:使用 ST-Link 作為連線介面
1. -f target/stm32h7x.cfg:設定目標為 STM32H7 系列晶片
1. init : 初始化介面與 target,建立連線
1. reset init : 重置並暫停 MCU,確保可安全燒錄
1. flash write_image erase blink.out : 把程式寫進 Flash
1. reset run : 寫完後讓 MCU 執行程式
1. shutdown : 關閉 OpenOCD
```
CROSS_COMPILE ?= arm-none-eabi-
blink.o: blink.c
$(CROSS_COMPILE)gcc -mcpu=cortex-m7 -mthumb -nostartfiles -c blink.c -o blink.o
startup.o: startup.c
$(CROSS_COMPILE)gcc -mcpu=cortex-m7 -mthumb -nostartfiles -c startup.c -o startup.o
blink.out: blink.o startup.o simple.ld
$(CROSS_COMPILE)ld -T simple.ld -Map=blink.map --gc-sections --strip-all -o blink.out blink.o startup.o
blink.bin: blink.out
$(CROSS_COMPILE)objcopy -j .text -O binary blink.out blink.bin
$(CROSS_COMPILE)size blink.out
flash: blink.bin
openocd \
-f interface/stlink.cfg \
-f target/stm32h7x.cfg \
-c "init" \
-c "reset init" \
-c "flash write_image erase blink.out" \
-c "reset run" \
-c "shutdown"
clean:
-del /Q *.o *.bin *.out *.elf *.lst *.map blink 2>nul || echo.
```
## Memory layout

```
Discarded input sections
.bss 0x00000000 0x0 blink.o
.data 0x00000000 0x0 startup.o
.bss 0x00000000 0x0 startup.o
Memory Configuration
Name Origin Length Attributes
FLASH 0x08000000 0x00100000 xr
RAM 0x24000000 0x00080000 xrw
*default* 0x00000000 0xffffffff
Linker script and memory map
.flash 0x08000000 0x78
*(.isr_vector)
.isr_vector 0x08000000 0xc startup.o
0x08000000 vector_table
*(.reset_handler)
.reset_handler
0x0800000c 0x64 startup.o
0x0800000c reset_handler
.reset_handler.__stub
0x08000070 0x8 linker stubs
0x08000078 _load_text = .
```
* 以上為定義的Flash與RAM的記憶體位址與其大小,以及中斷向量表(.isr_vector)與reset handler放在Flash的記憶體layout。
* 以下為將其餘的text section與其餘ISR(nmi_handler)放入RAM的記憶體layout
* 可以看到被放到RAM的text section在原先的Flash位址為0x08000078,跟上方的結尾位址相同。
* 以及data section有一個`0x4 load address 0x24000096`,此為我們在blink.c中宣告的全域變數`int global = 123;`其放置於Flash中的位址,並且其後置於RAM當中,`0x24000098 0x4 blink.o`也顯示此變數是來自於blink.o,使得RAM的記憶體位置由`0x24000098`變成`0x2400009c`
```
.text_ram 0x24000000 0x96 load address 0x08000078
0x24000000 _text_ram = .
*(.text)
.text 0x24000000 0x90 blink.o
0x24000000 main
.text 0x24000090 0x6 startup.o
0x24000090 nmi_handler
*(.text*)
0x24000096 _load_data = .
.data 0x24000098 0x4 load address 0x24000096
0x24000098 _data = .
*(.data)
.data 0x24000098 0x4 blink.o
0x24000098 global
*(.data*)
0x2400009c _edata = .
.bss 0x2400009c 0x0
0x2400009c _bss = .
*(.bss)
*(.bss*)
0x2400009c _ebss = .
0x2400009c _end = .
0x24080000 _estack = (ORIGIN (RAM) + LENGTH (RAM))
/DISCARD/
*(.glue_7)
*(.glue_7t)
*(.vfp11_veneer)
*(.v4_bx)
*(.iplt)
*(.rel.dyn)
*(.rel.iplt)
*(.igot.plt)
LOAD blink.o
LOAD startup.o
OUTPUT(blink.out elf32-littlearm)
LOAD linker stubs
.comment 0x00000000 0x45
.comment 0x00000000 0x45 blink.o
0x46 (size before relaxing)
.comment 0x00000045 0x46 startup.o
.ARM.attributes
0x00000000 0x2e
.ARM.attributes
0x00000000 0x2e blink.o
.ARM.attributes
0x0000002e 0x2e startup.o
```
## 結語
之前在用MCU都是用廠商提供的整合式IDE,其包含了HAL,且能夠一鍵編譯-連結-燒錄,此時會跳出很多程式碼與燒錄位置,但卻知其然而不知其所以然,不知道背後發生了什麼事。此專題讓我學會進行裸機開發,了解過程中的每個步驟該如何寫。也更深入的閱讀規格書,光是開機位址就找了很久,因為講義用的stm32版本有memory aliasing,linker可以寫到0x0,其他位址能夠映射過去,但是stm32H745系列則是會用vector table offset register(VTOR)+0x0跳到指定位址,而VTOR的值又會跟option byte一樣,因此linker沒辦法寫0x0,而是需要依照實際的boot設定進行調整。做完這次專題才明白整合式IDE幫我們做了多少事,像是編譯、連結、搬移 .`data`到 RAM、初始化 `.bss` 等動作,才真正理解從開機到執行的全貌。
## 參考資料
[[基礎] OpenOCD 與 STM32](https://loserembedded.blogspot.com/2016/07/openocd-stm32.html)
[mini-arm-os](https://github.com/jserv/mini-arm-os)
[Reference manual](https://www.st.com/resource/en/reference_manual/rm0399-stm32h745755-and-stm32h747757-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)
[User manual](https://www.st.com/resource/en/user_manual/um2408-stm32h7-nucleo144-boards-mb1363-stmicroelectronics.pdf)
[programming manual](https://www.st.com/resource/en/programming_manual/pm0253-stm32f7-series-and-stm32h7-series-cortexm7-processor-programming-manual-stmicroelectronics.pdf#page=31.10)
[ARM® Cortex®-M7 Processor](https://developer.arm.com/documentation/ddi0489/f/introduction/about-the-cortex-m7-processor/features?lang=en)
[ARM Compiler toolchain v4.1 for µVision Using the Linker](https://developer.arm.com/documentation/dui0377/c/using-scatter-files/using---attribute----section--name----)
[【1】Jserv mini-arm-os 學習筆記_開機流程x脫離IDEx你好](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/rkJtpGdAT)