STM32 시작 파일 구조와 어셈블리 명령어 이해
ARM 아키텍처 기반의 STM32 마이크로컨트롤러는 시스템 부팅 시 가장 먼저 실행되는 시작 코드(Startup Code)를 필요로 한다. 이 코드는 주로 어셈블리 언어로 작성되며, 초기 스택 설정, 벡터 테이블 구성, 시스템 초기화 호출 등의 역할을 수행한다. 아래에서는 주요 어셈블리 지시어와 STM32F4 계열의 시작 파일 구조를 중심으로 설명한다.
기본 어셈블리 지시어 요약
| 지시어 | 설명 |
|---|---|
EQU |
심볼 상수 정의 (C의 #define과 유사) |
AREA |
새로운 메모리 영역(섹션) 생성 |
SPACE |
0으로 초기화된 메모리 블록 할당 |
ALIGN |
다음 데이터나 코드의 주소 정렬 (기본 4바이트) |
EXPORT / GLOBAL |
외부 파일에서 참조 가능한 전역 심볼 선언 |
IMPORT |
외부에 정의된 심볼 사용 선언 (C의 extern) |
DCD |
32비트 상수 목록을 메모리에 배치 (4바이트 정렬 필요) |
PROC ~ ENDP |
서브루틴(함수)의 시작과 끝 표시 |
WEAK |
약한 참조: 외부에 동일 이름이 있으면 그쪽 우선 사용 |
B |
무조건 점프 (Branch) |
BL |
링크 레지스터(LR)에 리턴 주소 저장 후 점프 (Call) |
BX, BLX |
쓰레드 상태 전환 가능 점프 (Thumb/ARM 전환 지원) |
END |
소스 파일 종료 |
스택 영역 설정
Stack_Size EQU 0x00000400 ; 1KB 스택 크기 정의
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp ; 스택 최상위 주소 (초기 MSP 값)
AREA STACK, NOINIT, READWRITE, ALIGN=3: 초기화되지 않는 읽기/쓰기 가능한 스택 섹션 생성. 8바이트 정렬 (ALIGN=3→ 2³ = 8).SPACE로 스택 공간 확보. 실제 값은 링커가 결정.__initial_sp는 스택의 최상단 주소를 가리키며, 리셋 직후 이 값으로 Main Stack Pointer(MSP)가 자동 로드된다.
💡 주의: 스택 오버플로우는 하드 폴트(HardFault)를 유발할 수 있다. 큰 지역 변수나 깊은 함수 호출 시 스택 크기를 조정해야 한다.
힙 영역 설정
Heap_Size EQU 0x00000200 ; 512바이트 힙
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base ; 힙 시작 주소
Heap_Mem SPACE Heap_Size
__heap_limit ; 힙 끝 주소
malloc,free등 동적 메모리 할당 함수에서 사용.__heap_base와__heap_limit는 C 라이브러리가 힙 관리를 위해 참조하는 심볼.
명령어 세트 및 정렬 지정
PRESERVE8
THUMB
PRESERVE8: 현재 모듈이 스택을 8바이트 경계로 유지함을 알림.THUMB: 이후 코드가 Thumb 명령어 세트를 사용함을 지시 (ARM Cortex-M은 기본적으로 Thumb-2 사용).
인터럽트 벡터 테이블
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; 0번 벡터: MSP 초기값
DCD Reset_Handler ; 1번 벡터: 리셋 핸들러
DCD NMI_Handler ; 2번 벡터: NMI
DCD HardFault_Handler ; 3번 벡터: 하드 폴트
; ... 기타 예외 및 인터럽트
DCD SysTick_Handler ; 시스틱 인터럽트
; ... 외부 인터럽트 목록
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
- 벡터 테이블은 Flash 메모리의 0x0000_0000 주소부터 시작해야 함.
- 각 항목은 32비트 주소이며, 첫 번째 항목은 MSP 초기값, 두 번째는 리셋 핸들러 주소.
DCD는 실제로 함수 포인터처럼 동작하며, 예외 발생 시 해당 주소로 점프.
리셋 핸들러 (Reset Handler)
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0 ; 시스템 클럭 등 초기화
LDR R0, =__main
BX R0 ; C 런타임 초기화 진입
ENDP
SystemInit(): 클럭 설정, AHB/APB 버스 프리스케일러 구성 등.__main: ARM 컴파일러 제공 루틴으로,.data복사,.bss초기화 후main()호출.
✅
BLX와BX는 Thumb 상태 전환을 보장하여 안전하게 C 함수로 전환.
기본 예외 및 인터럽트 핸들러
대부분의 인터럽트는 기본적으로 약한 심볼(weak symbol)로 정의되어 사용자가 재정의할 수 있다.
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
B .는 현재 위치로 점프 → 무한 루프.- 실제 인터럽트 핸들러를 C 코드에서 정의하면, 링커가 약한 심볼 대신 사용자 정의 함수를 연결.
공통 인터럽트 처리 루틴
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
; ... 모든 외부 인터럽트를 WEAK로 선언
EXPORT FPU_IRQHandler [WEAK]
EXPORT SPI4_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
; ... 각 심볼에 대한 라벨만 존재
B .
ENDP
- 하나의 공통 핸들러로 여러 인터럽트를 묶어 관리.
- 사용자가 특정 인터럽트를 구현하면, 해당 심볼이 링크되고
B .대신 사용자 함수가 실행됨.
힙과 스택 초기화 루틴
마이크로라이브러리(MicroLib) 사용 여부에 따라 분기:
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, =Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, =Heap_Mem + Heap_Size
LDR R3, =Stack_Mem
BX LR ; LR로 리턴 (R0~R3에 값을 반환)
ALIGN
ENDIF
__user_initial_stackheap은 C 라이브러리에서 호출하는 컨벤션 함수.- R0: 힙 시작, R1: 스택 최상단, R2: 힙 끝, R3: 스택 시작.
시작 코드 실행 흐름 요약
- 스택 영역 할당:
Stack\_Size만큼 메모리 확보. - 벡터 테이블 구성: 초기 스택 포인터와 ISR 주소 목록 배치.
- 리셋 핸들러 실행: -
SystemInit()→ 시스템 클럭 설정 -\\\_\\\_main()→.data/.bss초기화 후main()진입 - 기본 인터럽트 처리기: 미구현 인터럽트는 무한 루프.
- 힙/스택 정보 공개: 동적 메모리 및 런타임 초기화 준비.
이와 같은 시작 코드는 임베디드 시스템의 안정적인 부팅을 위한 기반이 되며, 어셈블리 수준의 이해는 디버깅 및 성능 최적화에 필수적이다.