---
# System prepended metadata

title: STM32 Bare-Metal Programming with GCC and Makefile

---

# 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
![image](https://hackmd.io/_uploads/HJCCDg2nge.png)

## 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)

![image](https://hackmd.io/_uploads/HJl5a8C2ex.png)
至於位址的部分則取決於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. 
> 
![image](https://hackmd.io/_uploads/H1p-CIRnee.png)
![image](https://hackmd.io/_uploads/r1xzqT0nxg.png)
也可以從以下的Table 9看出Boot=0，Boot area是在Flash的0x0800 0000開始的，這就我們後續在linker中要寫的位址。
![image](https://hackmd.io/_uploads/r1exZDkaee.png)





接著就要查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)。
| ![image](https://hackmd.io/_uploads/SJ-3G12ixx.png) | ![image](https://hackmd.io/_uploads/Hy9GQGahel.png) |
| --------------------------------------------------- | --------------------------------------------------- |

> 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，
![image](https://hackmd.io/_uploads/r1ePNIZpex.png)

## 專案結構
```
├── 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**的基底位址。

![image](https://hackmd.io/_uploads/rkq7iyTjel.png)
![image](https://hackmd.io/_uploads/ry-_5cJTgg.png)

### 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);
```

![image](https://hackmd.io/_uploads/BkxsPsJael.png)

>  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
```
![image](https://hackmd.io/_uploads/B1ARzWb6el.png)

### 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);
```

![image](https://hackmd.io/_uploads/SkULr-Zale.png)

### 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))
```
![image](https://hackmd.io/_uploads/BJCpF-ball.png)

### 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也要為此。
![image](https://hackmd.io/_uploads/HyW82PWaxg.png)
> 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 中，如下圖：**
![image](https://hackmd.io/_uploads/rJQdP0mAxx.png =80%x)


以下為將中斷向量表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
![image](https://hackmd.io/_uploads/SkvZdCm0gg.png =80%x)

```

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)
