MCU는 main부터 시작할까? Reset부터 main까지 흐름 정리

카드뉴스

C 코드의 시작점과 MCU의 실제 시작점은 다릅니다

임베디드 C 프로젝트를 보면 사용자가 작성하는 코드는 대부분 main 함수에서 시작하는 것처럼 보입니다. 하지만 MCU에 전원이 들어오거나 reset이 발생했을 때 CPU가 곧바로 main을 호출하는 것은 아닙니다. 일반적인 ARM Cortex-M 계열에서는 reset 직후 정해진 주소의 벡터 테이블을 읽고, 초기 스택 포인터와 Reset_Handler 주소를 가져오는 흐름이 먼저 있습니다.

이 차이를 알면 startup file, vector table, SystemInit 같은 파일과 함수가 왜 프로젝트에 들어 있는지 이해하기 쉬워집니다. IDE가 자동으로 만들어 주기 때문에 처음에는 보이지 않는 영역처럼 느껴지지만, 인터럽트가 연결되지 않거나 HardFault가 발생하거나 부트로더를 다룰 때는 reset부터 main까지의 흐름을 알아야 디버깅이 됩니다.

Reset_Handler는 실행 환경을 준비합니다

Reset_Handler는 main 이전에 실행되는 시작 코드입니다. 보통 플래시에 저장된 초기값을 RAM의 data 영역으로 복사하고, 초기값이 없는 bss 영역을 0으로 지웁니다. 전역 변수와 static 변수가 main에 들어갔을 때 이미 기대한 값으로 보이는 이유가 이 초기화 과정 때문입니다.

그다음 시스템 클럭이나 FPU 설정, C 라이브러리 초기화, C++ 생성자 호출 같은 준비가 이어질 수 있습니다. 프로젝트나 툴체인에 따라 세부 순서는 조금 다르지만, 공통적인 목적은 C 코드가 실행될 수 있는 최소 환경을 만드는 것입니다. main에서 HAL_Init이나 SystemClock_Config를 호출하더라도 그보다 앞서 CPU가 실행할 수 있는 기반은 startup 코드가 마련합니다.

벡터 테이블은 예외와 인터럽트의 진입점을 담습니다

벡터 테이블에는 reset뿐 아니라 NMI, HardFault, SysTick, 주변장치 인터럽트 핸들러 주소가 들어갑니다. 인터럽트가 발생하면 CPU는 이 테이블을 보고 해당 handler로 이동합니다. 그래서 함수 이름 하나가 맞지 않거나 weak handler가 그대로 남아 있으면, 인터럽트가 발생해도 사용자가 기대한 코드가 실행되지 않을 수 있습니다.

부트로더를 쓰거나 애플리케이션 시작 주소를 옮기는 경우에는 벡터 테이블 위치 설정도 중요해집니다. Cortex-M에서는 VTOR 같은 레지스터로 벡터 테이블 기준 주소를 바꾸는 구성이 사용될 수 있습니다. 이 영역은 평소에는 자동 생성 파일처럼 보이지만, 메모리 맵이 바뀌는 순간 펌웨어 실행의 핵심 조건이 됩니다.

main 이전 흐름을 알면 디버깅 포인트가 늘어납니다

main에 breakpoint를 걸었는데 도달하지 못한다면 전원, reset pin, boot mode, vector table, stack pointer, Reset_Handler, clock 설정을 순서대로 확인해야 합니다. 단순히 “main이 실행되지 않는다”가 아니라 어디까지 실행됐는지를 나눠 보면 문제를 좁히기 쉽습니다. MCU는 main부터 시작하지 않습니다. main은 startup 코드가 환경을 준비한 뒤 도착하는 지점에 가깝습니다.

댓글 남기기