-
STM32 Cortex-M0에서 Vector Table 옮기기개발 지식/임베디드 2026. 10. 9. 07:44
지난 글에서는 Cortex-M3에서 IAP와 Application을 분리하여 사용할 때 Application의 시작 주소와 Vector Table을 어떻게 설정해야 하는지 알아보았습니다.
Application을 Flash의 다른 위치에서 실행하기 위해 Linker Script의 시작 주소를 변경하였고, Cortex-M3에서는 VECT_TAB_OFFSET을 이용하여 Vector Table의 위치도 Application에 맞게 변경할 수 있었습니다.
이후 Cortex-M0를 사용하는 MCU에서도 동일하게 IAP와 Application을 분리하는 구조를 적용하게 되었습니다.
처음에는 Cortex-M3에서 사용했던 방법을 그대로 적용하면 될 것이라고 생각했습니다.
하지만 Cortex-M0에서는 같은 방법으로 Vector Table의 위치를 변경할 수 없었습니다.
왜 Cortex-M0에서는 다른 방법이 필요한 걸까요?
Cortex-M0에는 VTOR이 없다
Cortex-M3에서는 VTOR(Vector Table Offset Register)을 이용하여 Vector Table의 시작 위치를 변경할 수 있습니다.
그래서 Application을 Flash의 다른 위치에 배치하더라도 Vector Table의 위치를 Application에 맞게 변경할 수 있었습니다.
하지만 Cortex-M0에는 VTOR이 없습니다.
따라서 Cortex-M3에서 했던 것처럼 Application이 위치한 Flash 주소를 Vector Table의 새로운 기준 주소로 직접 지정하는 방법을 사용할 수 없었습니다.
Application 자체를 IAP 뒤의 Flash 영역에서 실행하는 것은 가능합니다.
문제는 Application이 실행된 이후 Interrupt가 발생했을 때 어떤 Vector Table을 사용하느냐였습니다.
그렇다면 Cortex-M0에서는 Application의 Vector Table을 어떻게 사용할 수 있을까요?
Vector Table을 SRAM으로 복사한다
참고한 방법에서는 Application의 Vector Table을 SRAM으로 복사하여 사용하고 있었습니다.
Application의 원래 Vector Table은 Application의 Flash 시작 위치에 그대로 존재합니다.
예를 들어 Application이 0x08003000부터 시작한다면 해당 위치에는 초기 Stack Pointer와 Reset Handler를 포함한 Application의 Vector Table이 존재합니다.
이 Vector Table을 Application이 시작될 때 SRAM의 별도 영역으로 복사합니다.
여기서 중요한 점은 Flash에 있는 기존 Vector Table 자체를 RAM으로 이동시키는 것이 아니라 RAM에 Vector Table의 복사본을 만든다는 것입니다.
이를 위해 먼저 Vector Table을 저장할 배열을 별도의 Section에 배치합니다.
참고한 코드에서는 다음과 같이 .ram_vector라는 Section을 사용하고 있었습니다.
volatile uint32_t __attribute__((section(".ram_vector,\"aw\",%nobits @"))) ram_vector[VECTOR_TABLE_SIZE]; extern volatile uint32_t g_pfnVectors[VECTOR_TABLE_SIZE];g_pfnVectors는 Startup Code에 존재하는 원래 Application의 Vector Table이고, ram_vector는 이를 복사하여 사용할 SRAM의 Vector Table입니다.
그렇다면 ram_vector가 다른 데이터에 의해 덮어쓰이지 않도록 하려면 어떻게 해야 할까요?
Linker Script에 Vector Table 영역 만들기
ram_vector라는 배열을 선언하기만 한다고 해서 자동으로 SRAM의 원하는 위치에 배치되는 것은 아닙니다.
Linker Script에서도 .ram_vector Section을 RAM의 앞부분에 배치하도록 설정해야 합니다.
참고한 예제에서는 RAM을 사용하는 다른 Section보다 먼저 다음 영역을 두고 있었습니다.
.ram_vector : { *(.ram_vector) } >RAM이렇게 하면 Application용 Vector Table의 복사본이 RAM의 시작 부분에 배치됩니다.
일반적인 .data나 다른 RAM 영역보다 먼저 배치하여 해당 영역이 다른 데이터와 겹치지 않도록 하는 것입니다.
결과적으로 RAM은 개념적으로 다음과 같은 형태가 됩니다.
+--------------------------+ | | | Application RAM | | | +--------------------------+ | Vector Table Copy | +--------------------------+ 0x20000000여기까지 하면 Application의 Vector Table 복사본을 SRAM에 둘 수 있습니다.
하지만 아직 CPU는 이 Vector Table을 사용하지 않습니다.
왜 SRAM을 Remap할까?
Cortex-M0에서는 VTOR을 이용하여 0x20000000을 새로운 Vector Table 주소로 지정할 수 없습니다.
대신 STM32F0에서는 Memory Remap 기능을 이용할 수 있습니다.
SYSCFG->CFGR1의 MEM_MODE를 변경하면 0x00000000에 어떤 Memory를 Mapping할 것인지 선택할 수 있습니다.
참고한 STM32F0의 설정에서는 Main Flash, System Flash, Embedded SRAM 중 하나를 0x00000000 영역에 Mapping할 수 있습니다.
Application의 Vector Table을 SRAM 시작 부분에 복사한 뒤 Embedded SRAM을 0x00000000으로 Remap하면 CPU가 Interrupt Vector를 참조할 때 SRAM에 있는 Application용 Vector Table을 사용하게 됩니다.
Application 시작 부분에서 처리하기
그렇다면 Vector Table 복사와 SRAM Remap은 IAP에서 해야 할까요?
처음에는 IAP에서 Application으로 Jump하기 전에 모든 처리를 해야 한다고 생각할 수도 있습니다.
하지만 참고한 구현에서는 IAP는 기존 구조를 그대로 유지하고 Application의 main() 가장 앞부분에서 Vector Table을 복사하고 SRAM을 Remap하고 있었습니다.
개념을 단순화하면 다음과 같습니다.
int main(void) { RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; for (uint32_t i = 0; i < VECTOR_TABLE_SIZE; i++) { ram_vector[i] = g_pfnVectors[i]; } /* SRAM을 0x00000000으로 Remap */ SYSCFG->CFGR1 = ...; /* 이후 Application 초기화 */ }참고한 예제에서는 이 작업을 HAL_Init()과 SystemClock_Config()보다 먼저 수행하고 있었습니다.
Application 초기화가 진행되면서 Interrupt가 사용되기 전에 Vector Table을 먼저 준비하기 위한 것입니다.
즉 IAP에서는 기존과 같이 Application의 초기 Stack Pointer와 Reset Handler를 이용하여 Application으로 이동하고, Application이 시작되면 자신의 Vector Table을 SRAM에 복사한 뒤 Remap하는 구조로 볼 수 있습니다.
Cortex-M3와 무엇이 다를까?
결국 Cortex-M3와 Cortex-M0에서 해결하고자 하는 문제는 같습니다.
IAP에서 Application으로 이동한 이후 Interrupt가 발생하면 현재 실행하고 있는 Application의 Vector Table을 사용해야 합니다.
Cortex-M3에서는 VTOR을 이용하여 Vector Table의 위치를 Application이 존재하는 Flash 영역으로 변경할 수 있었습니다.
하지만 Cortex-M0에는 VTOR이 없기 때문에 같은 방법을 사용할 수 없습니다.
그래서 이번에는 Flash에 존재하는 Application의 Vector Table을 SRAM으로 복사하고 SRAM을 0x00000000 영역으로 Remap하는 방식을 사용하였습니다.
정리하면 Cortex-M3에서는 Vector Table을 바라보는 주소를 변경했고, Cortex-M0에서는 Vector Table의 복사본을 SRAM에 만든 뒤 해당 SRAM을 Vector Table 영역으로 보이도록 Remap한 것입니다.
다시 정리해 보자
처음에는 Cortex-M3에서 사용했던 방식을 Cortex-M0에서도 그대로 적용할 수 있을 것이라고 생각했습니다.
하지만 Cortex-M0에는 VTOR이 없었기 때문에 다른 방법이 필요했습니다.
Application의 원본 Vector Table은 Flash의 Application 시작 부분에 그대로 두고, Application이 시작되면 이를 SRAM으로 복사합니다.
Linker Script에서는 .ram_vector Section을 이용하여 복사본이 SRAM의 앞부분에 위치하도록 영역을 확보합니다.
그런 다음 SYSCFG->CFGR1의 Memory Mapping 설정을 이용하여 SRAM을 0x00000000으로 Remap합니다.
이렇게 하면 Interrupt가 발생했을 때 SRAM에 복사해 둔 Application의 Vector Table을 사용할 수 있었습니다.
후기
처음에는 같은 Cortex-M 계열이기 때문에 Cortex-M3에서 사용했던 방법을 Cortex-M0에서도 그대로 사용할 수 있을 것이라고 생각했습니다.
하지만 Cortex-M0에는 VTOR이 없었고 Vector Table을 직접 다른 Flash 주소로 옮겨 바라보게 하는 방식은 사용할 수 없었습니다.
처음에는 VECT_TAB_OFFSET과 비슷한 설정을 찾으려고 했지만 실제로는 접근 방법 자체가 달랐습니다.
Application의 Vector Table을 SRAM에 복사하고 Linker Script를 이용하여 해당 영역을 확보한 뒤 SRAM을 0x00000000으로 Remap하여 사용하는 방식이었습니다.
이 과정을 확인하면서 Linker Script가 단순히 프로그램의 Flash 시작 주소만 설정하는 것이 아니라 RAM의 배치에도 직접 관여한다는 것을 다시 알 수 있었습니다.
또 같은 STM32라고 하더라도 사용하는 Cortex-M Core에 따라 제공되는 기능에 차이가 있기 때문에 다른 MCU에서 사용했던 방법을 그대로 적용하기보다 현재 사용하는 Core에서 어떤 기능을 지원하는지 확인하는 것이 중요하다는 것도 알게 되었습니다.
처음에는 기존 C 코드를 C++로 변경하고 구조를 정리하기 위해 시작한 작업이었습니다.
하지만 프로젝트를 다시 구성하면서 IAP에서 Application으로 어떻게 이동하는지부터 Vector Table, Linker Script, Memory Remap까지 기존에는 크게 신경 쓰지 않았던 부분들을 하나씩 확인하게 되었습니다.
단순히 코드를 C++로 변경한 것보다 기존 프로젝트가 왜 이런 구조로 만들어져 있었는지를 이해하게 된 것이 이번 작업에서 더 크게 얻은 부분인 것 같습니다.
참고
STMicroelectronics Community - How to boot to random address without VECT_TAB_OFFSET? STM32F072
'개발 지식 > 임베디드' 카테고리의 다른 글
STM32 IAP에서 Application 실행하기 (0) 2026.10.07 Keil에서 STM32CubeIDE로 프로젝트 옮기기 (0) 2026.10.06