카드뉴스









`main()`은 전원을 켠 직후의 첫 명령이 아니다
Cortex-M 기반 MCU에 전원이 들어오거나 Reset이 발생하면 곧바로 `main()`부터 실행하지 않는다. CPU가 시작할 위치를 찾고 stack을 준비한 뒤, 전역·정적 변수가 C 코드가 기대하는 상태가 되도록 초기화해야 한다. 이 준비를 담당하는 것이 Startup 코드다.
일반적인 STM32 사용자 애플리케이션에서는 Reset, Vector Table 확인, 초기 MSP 설정, `Reset_Handler` 진입을 거친다. 이후 시스템과 메모리, C/C++ 런타임을 준비한 다음에야 `main()`이 호출된다.
Vector Table의 첫 두 항목이 출발점을 정한다
Vector Table의 첫 번째 워드에는 Main Stack Pointer의 초기값이 들어 있다. 함수 호출과 예외 처리를 시작하려면 stack이 먼저 필요하므로 CPU는 이 값을 MSP에 넣는다. stack은 SRAM 안에 놓이며 일반적으로 높은 주소에서 낮은 주소 방향으로 사용된다.
두 번째 워드는 Reset 이후 실행할 `Reset_Handler`의 주소다. CPU는 이 값을 Program Counter에 반영해 Startup 코드로 이동한다. 그 뒤의 Vector Table 항목에는 NMI, HardFault와 주변장치 인터럽트 핸들러의 주소가 배치된다.
Reset Handler가 SRAM의 시작 상태를 만든다
초기값이 있는 전역·정적 변수는 실행 중 SRAM의 `.data` 영역에서 사용된다. 초기값은 Flash의 load image에 보관해 두었다가 Startup 과정에서 SRAM으로 복사한다. 반대로 초기값을 명시하지 않은 전역·정적 변수는 `.bss` 영역에 놓고 0으로 채운다. `.bss`는 Flash에서 복사하는 영역이 아니라 zero fill 대상이다.
이 과정 전후로 시스템 초기화가 수행될 수 있다. 구체적인 호출 순서는 MCU와 Startup 파일, 툴체인에 따라 다르므로 현재 프로젝트의 `Reset_Handler`를 기준으로 확인해야 한다.
Startup 파일과 링커 스크립트를 함께 읽는다
Startup 파일에는 Vector Table, `Reset_Handler`, 기본 예외 핸들러와 `.data` 복사·`.bss` 초기화 코드가 들어 있다. 링커 스크립트는 Flash와 SRAM 범위, stack 위치, 각 섹션의 시작과 끝 주소를 정의한다. Startup 코드는 링커가 만든 경계 심볼을 사용해 복사하고 지울 범위를 찾는다.
메모리 준비가 끝나면 필요한 C/C++ 런타임 초기화와 전역 객체 생성자를 처리하고 `main()`으로 넘어간다. 부팅이 일찍 멈춘다면 `Reset_Handler`에 breakpoint를 두고 MSP, `.data` 복사, `.bss` 초기화, 런타임 진입을 차례로 보면 `main()` 이전의 문제를 코드 로직과 분리할 수 있다.