STM32 시작 코드 분석 및 ARM 어셈블리 언어 기초

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() 호출.

BLXBX는 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: 스택 시작.

시작 코드 실행 흐름 요약

  1. 스택 영역 할당: Stack\_Size만큼 메모리 확보.
  2. 벡터 테이블 구성: 초기 스택 포인터와 ISR 주소 목록 배치.
  3. 리셋 핸들러 실행: - SystemInit() → 시스템 클럭 설정 - \\\_\\\_main().data/.bss 초기화 후 main() 진입
  4. 기본 인터럽트 처리기: 미구현 인터럽트는 무한 루프.
  5. 힙/스택 정보 공개: 동적 메모리 및 런타임 초기화 준비.

이와 같은 시작 코드는 임베디드 시스템의 안정적인 부팅을 위한 기반이 되며, 어셈블리 수준의 이해는 디버깅 및 성능 최적화에 필수적이다.

태그: STM32 ARM 어셈블리 시작 코드 벡터 테이블 스택 힙

8월 1일 00:57에 게시됨