Repository navigation
Chapter 04
STM32F439ZI가 Reset된 뒤 main()에 도달하기까지의 과정을 이해하고, Linker Script와 Startup Code가 .data, .bss, Stack, Vector Table을 어떻게 준비하는지 확인한다.
Reset
↓
Vector Table
↓
Initial MSP / Reset_Handler
↓
.data Copy
↓
.bss Clear
↓
SystemInit()
↓
main()
Ch02~03에서는 임시 linker/startup 파일을 사용했다.
Ch04부터는 이후 모든 chapter에서 사용할:
common/linker/stm32f439zi.ld
common/startup/startup_stm32f439zi.s
를 기준으로 build한다.
이번 project에서 사용하는 주요 memory:
FLASH
Start : 0x08000000
Size : 2048 KB
SRAM
Start : 0x20000000
Size : 128 KB
CCM RAM
Start : 0x10000000
Size : 64 KB
CCM RAM은 이번 실습에서는 사용하지 않는다.
STM32F439ZI의 128KB SRAM 끝 주소:
0x20000000 + 0x00020000
=
0x20020000
따라서 initial stack pointer로 사용하는 _estack은:
0x20020000
이다.
Linker Script는 프로그램의 section을 어느 memory address에 배치할지 결정한다.
대표 section:
.isr_vector → FLASH
.text → FLASH
.rodata → FLASH
.data → RAM, initial value는 FLASH
.bss → RAM
개념적인 memory layout:
FLASH
0x08000000
┌──────────────────┐
│ .isr_vector │
├──────────────────┤
│ .text │
├──────────────────┤
│ .rodata │
├──────────────────┤
│ .data init value │
└──────────────────┘
RAM
0x20000000
┌──────────────────┐
│ .data │
├──────────────────┤
│ .bss │
├──────────────────┤
│ heap / stack │
└──────────────────┘
0x20020000
초기값이 있는 global/static variable.
Example:
unsigned int g_blink_count = 5;실행 중에는 RAM에 있어야 하지만, 전원이 꺼져도 초기값 5는 firmware에 남아 있어야 한다.
따라서:
FLASH
initial value = 5
↓
Reset_Handler copy
↓
RAM
g_blink_count = 5
가 된다.
초기값이 없는 global/static variable.
Example:
unsigned int g_toggle_flag;C에서는 program 시작 시:
g_toggle_flag = 0
이어야 한다.
따라서 Reset_Handler가 .bss 영역을 RAM에서 0으로 채운다.
.data를 이해할 때 중요한 개념이다.
Load Memory Address
Firmware image에서 초기값이 저장된 위치.
.data의 경우:
FLASH
Virtual / Run Memory Address
실행 중 CPU가 실제 variable을 접근하는 위치.
.data의 경우:
RAM
따라서:
.data
LMA → FLASH
VMA → RAM
이다.
Cortex-M4는 Reset 직후 main()으로 바로 가지 않는다.
Vector Table의 첫 두 word를 hardware가 읽는다.
Vector[0]
→ Initial MSP
Vector[1]
→ Reset_Handler
즉 conceptually:
MSP ← Vector[0]
PC ← Vector[1]
가 된다.
Startup code의 핵심 순서:
Reset_Handler
↓
Initial Stack
↓
Copy .data
FLASH → RAM
↓
Clear .bss
RAM → 0
↓
SystemInit()
↓
main()
이 작업이 끝나야 C program이 기대하는 runtime environment가 만들어진다.
STM32F439ZI는 Cortex exception뿐 아니라 많은 peripheral IRQ를 가진다.
이 project의 startup file에는 STM32F439ZI에 맞는 IRQ vector table을 구성했다.
사용하지 않는 handler는 보통:
weak symbol
↓
Default_Handler
로 연결한다.
나중에 application에서:
void EXTI15_10_IRQHandler(void)
{
...
}처럼 같은 이름의 handler를 정의하면 strong symbol이 weak symbol을 대신한다.
즉 startup file을 매번 수정할 필요가 없다.
이 chapter의 실제 실습 구성:
Ch04/
├── main.c
├── stm32f439zi.ld
└── startup_stm32f439zi.s
이후 chapter에서는 공용화해서:
common/
├── linker/
│ └── stm32f439zi.ld
└── startup/
└── startup_stm32f439zi.s
형태로 사용한다.
stm32f439zi.ld와startup_stm32f439zi.s의 전체 원본은 현재 Notion 본문에는 포함되어 있지 않다. 재실습 시 archived project의 실제 파일을 이 Wiki에 추가한다.
#define PERIPH_BASE (0x40000000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define RCC_AHB1ENR \
(*(volatile unsigned int *)(RCC_BASE + 0x30UL))
#define GPIOB_MODER \
(*(volatile unsigned int *)(GPIOB_BASE + 0x00UL))
#define GPIOB_ODR \
(*(volatile unsigned int *)(GPIOB_BASE + 0x14UL))
#define GPIOBEN (1UL << 1)
#define LD1_PIN (1UL << 0)
/* .data section verification */
unsigned int g_blink_count = 5;
/* .bss section verification */
unsigned int g_toggle_flag;
static void delay(volatile unsigned int count)
{
while (count--)
{
__asm__("nop");
}
}
int main(void)
{
RCC_AHB1ENR |= GPIOBEN;
GPIOB_MODER &= ~(0x3UL << (0 * 2));
GPIOB_MODER |= (0x1UL << (0 * 2));
/*
* .bss initialization check
*
* g_toggle_flag should be 0 after Reset_Handler.
* If not, use fast blinking as an error indication.
*/
if (g_toggle_flag != 0)
{
while (1)
{
GPIOB_ODR ^= LD1_PIN;
delay(50000);
}
}
while (1)
{
for (unsigned int i = 0;
i < g_blink_count;
i++)
{
GPIOB_ODR ^= LD1_PIN;
delay(800000);
}
delay(3200000);
g_toggle_flag++;
}
}unsigned int g_blink_count = 5;초기값이 있으므로:
.data
section에 위치한다.
Startup이 정상이라면:
FLASH의 5
↓
Reset_Handler
↓
RAM g_blink_count = 5
가 되어야 한다.
unsigned int g_toggle_flag;초기값이 없으므로:
.bss
section에 위치한다.
Startup이 정상이라면 main() 진입 시:
g_toggle_flag = 0
이어야 한다.
그래서:
if (g_toggle_flag != 0)이면 startup 문제가 있다고 판단하고 LED를 빠르게 깜빡이게 한다.
즉 이 chapter의 LED 동작은 단순 blink가 아니라:
.data / .bss Startup Verification
용이다.
Startup code와 linker script는 symbol을 통해 주소를 공유한다.
대표적으로:
_estack
_sidata
_sdata
_edata
_sbss
_ebss
의 의미는 다음과 같다.
_estack
→ initial stack address
_sidata
→ FLASH에 저장된 .data 초기값 시작
_sdata
→ RAM의 .data 시작
_edata
→ RAM의 .data 끝
_sbss
→ .bss 시작
_ebss
→ .bss 끝
Startup은 이를 이용해:
_sidata → _sdata ~ _edata
범위로 data를 복사하고:
_sbss ~ _ebss
범위를 0으로 초기화한다.
Power / Reset
↓
Vector Table
↓
MSP = _estack
↓
PC = Reset_Handler
↓
.data Copy
↓
.bss Clear
↓
SystemInit()
↓
main()
↓
g_blink_count = 5
g_toggle_flag = 0
Build:
makeSymbol 확인:
arm-none-eabi-nm \
ch04_linker_startup.elf \
| grep -E "_estack|_sdata|_edata|_sbss|_ebss"Section 확인:
arm-none-eabi-objdump \
-h ch04_linker_startup.elfProgram size:
arm-none-eabi-size \
ch04_linker_startup.elf실습에서 확인한 symbol:
20000008 B _ebss
20000004 D _edata
20020000 R _estack
20000004 B _sbss
20000000 D _sdata
RAM Start = 0x20000000
RAM Size = 128 KB = 0x20000
0x20000000
+
0x00020000
=
0x20020000
실제 결과:
_estack = 0x20020000
정확히 일치한다.
_sdata = 0x20000000
_edata = 0x20000004
크기:
4 bytes
이는:
unsigned int g_blink_count = 5;하나와 일치한다.
_sbss = 0x20000004
_ebss = 0x20000008
실제 global variable 영역:
4 bytes
로:
unsigned int g_toggle_flag;와 일치한다.
Build 결과에서는 heap/stack reservation까지 포함해 .bss 관련 RAM 사용량이 더 크게 표시될 수 있다.
재실습할 때는 build 성공만 보는 것보다 runtime variable까지 직접 확인하는 것이 좋다.
make clean
makearm-none-eabi-nm \
ch04_linker_startup.elf \
| grep -E "_estack|_sdata|_edata|_sbss|_ebss"확인:
_estack = 0x20020000
arm-none-eabi-objdump \
-h ch04_linker_startup.elf확인:
.isr_vector
.text
.data
.bss
main() 진입 직후:
g_blink_count = 5
g_toggle_flag = 0
인지 확인한다.
이 화면이:
.data copy PASS
.bss clear PASS
를 가장 직접적으로 보여준다.
정상 startup:
Normal blink pattern
.bss 초기화 이상:
Fast continuous blink
images/ch04_build.png
images/ch04_symbols.png
images/ch04_sections.png
images/ch04_debug_variables.png
images/ch04_led.gif
이 chapter에서는 특히:
nm symbol result
+
Debugger variable result
두 장이 가장 중요하다.
Linker Script
→ Where?
Startup Code
→ What happens after reset?
- Linker Script는 Flash/RAM과 section 배치를 결정한다.
- Vector Table의 첫 word는 initial MSP, 두 번째 word는
Reset_Handler다. -
.data는 초기값이 Flash에 있고 runtime에는 RAM에 존재한다. -
.bss는 RAM 공간만 필요하며 startup에서 0으로 초기화한다. -
.data는 LMA와 VMA가 다르다. -
Reset_Handler가 C runtime을 준비한 뒤main()을 호출한다. - weak interrupt handler를 사용하면 application에서 필요한 IRQ만 override할 수 있다.
-
nm,objdump, debugger를 통해 linker/startup 동작을 검증할 수 있다.