Computer Science
탄탄한 기반 실력을 위한
전공과 이론 지식 모음
Today I Learned!
배웠으면 기록을 해야지
TIL 사진
Flutter 사진
Flutter로 모바일까지
거꾸로캠퍼스 코딩랩 Flutter 앱개발 강사
스파르타코딩클럽 즉문즉답 튜터
카카오테크캠퍼스 3기 학습코치
프로필 사진
박성민
임베디드 세계에
발을 들인 박치기 공룡
임베디드 사진
EMBEDDED SYSTEM
임베디드 SW와 HW, 이론부터 실전까지
ALGORITHM
알고리즘 해결 전략 기록
🎓
CAU 소프트웨어학부
텔레칩스 차량용 임베디드 스쿨 3기
애플 개발자 아카데미 1기
깃허브 사진
GitHub
프로젝트 모아보기
Instagram
인스타그램 사진

Embedded System/ARM 프로세서 아키텍처

[ARM] ARM Processor Architecture - 2 : ARM 레지스터 구조와 Exception

sm_amoled 2025. 10. 23. 00:57

ARM의 레지스터와 프로세서 모드

이전 글에서 잠깐 소개했지만, 조금 더 디테일하게 들어가보자.

레지스터 구조

ARM Cortex-A 프로세서에는 43개의 물리적인 레지스터가 있다.

범용 레지스터:
R0~R7:  8개 (모든 모드 공유)
R8~R12: 7개 (User/System) + 5개 (FIQ) = 12개
R13:    6개 (모드별로 다름)
R14:    6개 (모드별로 다름)
R15:    1개 (PC, 모든 모드 공유)

상태 레지스터:
CPSR:   1개
SPSR:   5개 (각 예외 모드마다)

눈여겨 볼 점은 8개의 레지스터 R0~

R7 은 모든 모드에서 범용으로 사용하고, R8~

R12 는 나머지 모드에서는 모두 공용으로 사용하지만 FIQ(Fast Interrupt reQuest) 에서는 별도의 레지스터를 사용한다는 점이다. 레지스터 자체가 비싸서 (빠른 입출력이 가능해야함 + CPU에 가장 가까이 붙어야 하기에 HW 설계 복잡도) 많은 양의 레지스터를 사용하지 못하는데 이렇게 따로 전용 레지스터를 넣어줬다는 것은 뭔가 이유가 있기 때문.

일반적인 속도의 인터럽트가 들어왔을 때 프로세서가 IRQ 모드로 전환되면 아래의 과정을 거친다. IRQ_Handler에서 R0~ R12 레지스터를 사용하고자 하기 때문에 핸들러 처리 이후 원래 들어있던 값들을 복원하기 위해서 레지스터에 원래 들어있던 값들을 메모리에 저장해둬야 한다. 그리고 이 과정이 느리다!! 메모리 RW인데 얼마나 답답하겠어!! (물론 L3 캐시에 쓰느라 빠르겠지만)

 IRQ_Handler:
    STMFD SP!, {R0-R12, LR}  ; 레지스터 저장 (Push)
    ; ... 인터럽트 처리 ...
    LDMFD SP!, {R0-R12, PC}^ ; 레지스터 복원 (Pop)

그런데 만약에 10ms 안에 에어백이 터져야 하는 매우 급한 인터럽트라면 어떨까? 만약에 기존의 레지스터 값을 보존하기 위해서 메모리에 레지스터 값들을 쓰거나 읽어야 하는데 캐시 미스가 났다? 그러면 10 cycle로 예상했던 처리 속도가 갑자기 100 cycle로 늘어나버릴 수 있다. 그러면 급한 익셉션을 필요한 순간에 처리하지 못해서 큰 문제가 발생할 수 있다. (사상자 폭발 이후에 온 지진 재난문자st) 이러한 문제를 막기 위해서 FIQ 모드에서는 R8~ R12 에 대해서 전용 레지스터를 사용해 메모리에 값을 읽고 쓰는 불안감을 제거했다. FIQ의 처리를 위해서 최대한 5개의 레지스터를 이용한다면 메모리 복원 작업 없이 인터럽트를 처리할 수 있어 신속한 작업이 가능하다. (RTOS의 기본 소양?)

 FIQ_Handler:
    ; ... 인터럽트 처리 ...

Stack Pointer와 Link Register 또한 각 모드별로 독립된 레지스터를 사용한다.

이럴거면 모든 레지스터를 그냥 전용으로 만들면 Matrix 형태로 HW를 구성하면서 훨씬 구조가 간단하지 않을까? 생각했는데, 각 모드가 전환될 때 이전 모드의 데이터를 사용하고 싶다면 메모리에 쓰고 모드 전환 후 다시 가져와야하는 번거로움이 있을 것 같다. 그래서 어느정도의 범용 레지스터 (R0~ R7)을 둔게 아닐까?

하드웨어 설계시 레지스터 하나의 크기가 생각보다 큼 + 전력 많이 먹음 + Access 시간 느려짐 + 복잡하고 열도 많이 난다. ISA 설계 관점에서도 레지스터 지정을 위해 더 많은 bit를 할애하려면 명령어 길이 길어짐 + 명령어 디코딩 시간 늘어나는 문제도 함께 발생한다.

 

프로세서 모드

Cortex-A

Cortex-A 에는 7가지 프로세서 모드가 있다.

  • User Mode
    • 일반적인 프로그램 실행 모드
    • CPSR 수정, 모드 변경 권한이 없음 (모드 변경을 요청할 뿐) + HW 직접 접근 안됨
  • System Mode
    • 일부 특수 권한을 가지고 있는 User Mode
    • CPSR 수정, 모드 변경, HW 직접 제어가 가능하다.
    • 디바이스 드라이버나 시스템 데몬 등을 실행 + 코프로세서(MMU, 캐시, TLB 등)를 제어할 때 권한이 필요하기에 있는 모드.
  • Supervisor Mode
    • OS 커널이 사용하는 모드
    • User Mode의 요청에 따라 System Call을 처리한다
  • IRQ Mode
    • GPIO, Timer, UART 등 일반적인 Interrupt를 처리
  • FIQ Mode
    • 최고 우선순위를 가지는 빠른 처리가 필요한 Interrupt를 위함
  • Undefined Mode
    • 정의되지 않은 명령어 실행 시도하면 Undefined 모드로 변경
    • SW적으로 처리할 수도 있다. (FPU 모듈이 없는데 FPU 기능을 사용하려 하면 FPU를 활성화 / 소프트웨어로 FPU 로직 수행 후 결과 return / 처리 불가로 핸들링)
  • Abort Mode
    • 잘못된 메모리 주소 or 접근 권한이 없는 메모리 주소에 접근하려 할 때 전환되는 모드

여기에서 프로세서의 Extension에 따라서 Monitor Mode (보안 모드), Hypervisor Mode (가상화 모드)를 제공하기도 한다. Hypervisor는 Supervisor를 여러 개 관리할 수 있는 모드로, 하나의 CPU에서 여러 OS를 다룬다고 생각하면 될 듯? 😮😮😮 바이오스 세팅에서 Hyper-V 옵션을 켜는게 이 모드를 활성화하는 것 비슷한 작업을 하는거였구나. 이제야 알았네!!! 개신기함 ㅎㄷㄷ

Cortex-M4 에서는?

Cortex-M4 MCU에 대한 메뉴얼 PDF에서 확인할 수 있다.

이번에 사용하는 Cortex-M4에는 단 2가지 모드와 훨씬 더 적은 레지스터만 있다.

단순한 이유? Cortex-M 시리즈의 목표는 저전력 + 빠른 인터럽트 응답 + Deterministic Timing (예측 가능한 처리시간) 이기 때문에 오히려 복잡하면 손해. 심지어 Cortex-A 처럼 OS가 시스템을 관리하는 형태가 아니라 하나의 프로그램으로 돌리는 단순한 형태이기 때문에 복잡한 권한 Mode 역시 불필요하다. 이새끼… 깔끔한게 마음에 들었어.

CPSR과 SPSR

CPSR—Current Program Status Register— 와 SPSR—Saved Program Status Register—에 대해 알아보자.

Condition Flag [28:31]

  • N : Negative - 연산 결과가 음수이면 Set
  • Z : Zero - 연산 결과가 0이면 Set
  • C : Carry - Unsigned 연산에서 올림 / 빌림이 발생하면 Set.
    덧셈과 뺄셈의 빌림 발생 시 C bit이 다름을 유의해야한다.
  • V : oVerflow - Signed 연산에서 오버플로우가 발생하면 Set
// Negative
MOV R0, #5
MOV R1, #10
SUBS R2, R0, R1    ; R2 = 5 - 10 = -5
// R2에 저장되는 결과가 음수 -> N이 1로 SET 된다.

// Zero
MOV R0, #10
SUBS R1, R0, #10   ; R1 = 10 - 10 = 0
// R2에 저장되는 결과가 0 -> Z가 1로 SET 된다.

// Carry
MOV R0, #0xFFFFFFFF
ADDS R1, R0, #1 
// Overflow 발생 -> C가 1로 SET 된다.
MOV R0, #5
SUBS R1, R0, #10       ; 5 - 10 (빌림 필요)
// 결과가 0xFFFFFFFB ... 빌림 발생 -> C가 0으로 SET 된다.

// oVerflow
MOV R0, #0x7FFFFFFF    ; 최대 양수
ADDS R1, R0, #1 
// 양수 + 양수 = 음수? → 오버플로우 발생 -> V가 1로 Set 된다.
MOV R0, #0x80000000    ; 최소 음수 (-2147483648)
SUBS R1, R0, #1        ; -1
// 음수 - 양수 = 양수? → 오버플로우 발생 -> V가 1로 Set 된다.

Condition Flag의 값들을 조합해서 연산의 결과가 어떤 상태인지 판단할 수 있다. 이 값은 이전 연산의 결과를 조건으로 사용하는 연산에서 활용한다.

// R0와 R1이 signed 라고 할 때
// 아래 명령어 수행의 결과로 업데이트된 NZCV를 이용해 두 값의 대소를 비교할 수 있다.
CMP R0, R1    ; R0 - R1 의 결과로 NZCV를 업데이트

R0 > R1  라면 Z=0 && N=V
R0 >= R1 라면 N=V
R0 < R1  라면 N!=V
R0 <= R1 라면 Z=1 || N!=V

Mode Bit [0:4]

현재 프로세서가 어떤 모드에 있는지 표현하기 위해 사용한다. 5bit를 사용하기 때문에 최대 32개의 모드를 지정할 수 있지만, 굳이 불필요하므로 Cortex-A 에서는 7개의 모드(+익스텐션에서는 추가 모드)를 사용한다.

비트 [4:0]   모드        약어
───────────────────────────────
0b10000    User        USR
0b10001    FIQ         FIQ
0b10010    IRQ         IRQ
0b10011    Supervisor  SVC
0b10111    Abort       ABT
0b11011    Undefined   UND
0b11111    System      SYS
0b11010    Hyp         HYP (ARMv7-A+)
0b11110    Monitor     MON (TrustZone)

프로세서 모드에 따른 CPSR 접근 권한 샘플코드에서 봤던게 요거구나. User Mode에서는 CPSR의 [0:4] 비트를 변경할 수 없는데, System Mode에서는 이 비트를 변경할 수 있었고, 이게 모드를 변경할 수 있다는 의미인 것 같다. 이 비트를 원하는 값으로 수정하면 HW적으로 프로세서 모드가 변경되나보다.

Thumb Bit [5]

T bit이 1이라면 현재 Thumb 모드로 실행 중이고, T bit이 0 이라면 현재 ARM 모드로 실행되고 있음을 말한다.

STM32F429ZIT6 에서는 항상 Thumb 모드이기 때문에, 항상 T bit이 1인 상태이다.

Interrupt Mask Bit [6:8]

6, 7, 8 번 bit는 각각 IRQ, FIQ, Imprecise Abort 에 대한 비활성화가 가능하다.

IRQ 모드에서 Interrupt를 처리하던 도중에 다른 인터럽트가 발생해서 해당 인터럽트를 처리하느라 모드를 변경해야하고 + 현재 IRQ 의 레지스터를 저장해야 하는 문제가 발생할 수 있다. 한 번에 하나의 IRQ 처리만 하기 위해서 인터럽트를 처리하는 동안에 마스킹 Bit을 Set 하고 그 동안에는 Interrupt를 무시하도록 구조를 잡았다. FIQ, Imprecise Abort에 대해서도 마찬가지.

만약 FIQ 가 Set 된 경우 → FIQ 처리가 가장 중요하기 때문에 IRQ, Abort 도 함께 Mask 된다.

Imprecise Abort - 비동기 Abort란?

동기 Abort (Synchronous Abort)는 메모리 명령어를 읽자마자 바로 Abort의 발생을 아는 것.

비동기 Abort (Asynchronous Abort)는 메모리 명령어를 읽고 몇 Cycle이 지난 다음 Abort의 발생을 아는 것. (CPU는 그냥 메모리 쓰기 명령을 내려두고 그 다음 명령을 수행하러 떠남 … 메모리에서 Abort 라고 알려줄 때는 이미 2-3CLK이 지나있을 때) 그래서 어떤 명령어에서 Abort가 발생했는지 정확히 알 수 없는 것이 특징 (= Imprecise Abort).

STR R0, [R1]         ; 메모리 쓰기 시작 (Write Buffer에 저장)
ADD R2, R3, R4       ; 다음 명령어 실행
MOV R5, R6           ; 또 다음 명령어 실행
                     ; 이때! 이전 STR에서 오류 발견!
                     ; Abort 발생
                     ; 하지만 PC는 이미 멀리 와있음
                     ; 어떤 명령어가 문제였는지 불분명!

만약 중요한 Critical Section 중간에 갑자기 Imprecise Abort 가 발생해버리는 경우에 어디에서 어떤 문제가 발생했는지 찾는 것이 쉽지 않다. 그래서 어떤 명령어에서 Abort가 발생했는지 찾을 수 있는 Synchronous Abort 가 발생했다면 핸들링을 하지만, Imprecise Abort는 처리하지 않도록 비활성화 한다.

만약에 Interrupt를 처리하느라 IRQ가 비활성화 되어있는 상태에서 다른 Interrupt가 발생한다면, 이 Interrupt는 앞전의 Interrupt가 처리가 모두 끝나고 다시 IRQ가 활성화될 때 까지 대기(Pending)한 뒤 CPU가 다음 명령어를 실행하려고 하는 순간 Interrupt를 발생시켜 다시 IRQ 모드로 진입한다. 사라지는게 아니라 대기하는 것이다!! 만약 Interrupt가 여럿이서 대기중이라면 우선순위에 따라 처리한다. ⇒ 이건 Interrupt Controller의 역할.

나머지 CPSR의 Bit

  • [27] Q : Sticky Overflow
    • 한 번 Q bit에 Overflow가 SET 되면 직접 RESET 할 때 까지 값을 유지
    • SIMD, DSP(디지털신호처리) 연산에 사용한다.
  • [24:26] Reserved
  • [19:16] GE : Greater than or Equal
    • SIMD 명령어를 위한 플래그
    • 4개 Byte의 동시 덧셈에 대해서 각 Byte의 연산 결과를 4개의 Bit에 Set
  • [26:25], [15:10] IT : If-Then
    • Thumb2 의 조건부 실행 블럭 상태를 나타냄
  • [9] E : Endianness
    • 데이터의 엔디안 형식을 설정
    • 주로 E = 0 (Little Endian)

SPSR

현재 프로그램의 실행 상태가 CPSR에 담겨있다. 프로그램을 실행하던 도중에 인터럽트가 발생해 IRQ Handler를 실행해야 한다면 인터럽트 처리 이후에 정상적으로 프로그램의 실행을 재개하기 위해서는 CPSR의 상태가 복원되어야 한다. 이걸 위해서 CPSR을 메모리에 저장하고 → 다시 불러오는 과정이 너무 번거롭고 느리기 때문에 잠시 CPSR을 저장해둘 수 있는 공간을 마련하였다.

User 모드 실행 중
CPSR = 0x60000010 (User, IRQ 활성화)
    ↓
IRQ 발생!
    ↓
자동으로:
**1. SPSR_irq = CPSR (0x60000010 저장)
2. CPSR = 0x600000D2 (IRQ 모드, IRQ 비활성화)**
3. LR_irq = return address
4. PC = 0x18 (IRQ vector)
    ↓
IRQ Handler 실행
    ↓
복귀:
SUBS PC, LR, #4        ; 또는
LDMFD SP!, {R0-R12, PC}^
    ↓
자동으로:
**CPSR = SPSR_irq (원래 상태 복원)**

(즉, SPSR은 내가 돌아갈 때 복원할 CPSR을 잠시 저장해두는 레지스터)

User 모드와 System 모드를 제외한 나머지 모드에서는 SPSR을 가지고 있다.

이거슨 User와 System 모드는 특별하게 뭔가 처리하기 위해서 오는 모드가 아니라 그냥 평범하게 프로그램이 실행될 때 있는 모드라 그렇다고 생각된다. IRQ, FIQ 같이 잠깐 사용하는 모드라면 Context Switching을 해서 뭔가 처리하고 다시 User Mode로 돌아가는 그림이 자연스럽다고 느껴진다. 그런데 User Mode → User Mode로 점프 → User Mode로 한 번 더 점프 … 이런 경우에는 아직 유효한 SPSR을 다시 덮어써야하는 문제가 발생할 수 있다.
사실 예외가 발생하는 곳이 User, System 모드라서 그럴 수도 있겠다.

Cortex-M4에서의 CPSR

아무리 검색해도 안나오더라니, Cortex-M4 에서는 CPSR 대신에 PSR 이라는 이름으로 레지스터가 있었다.

Cortex-M4의 PSR은 3개로 구성된다.

  • APSR : 응용프로그램 용 Program Status Register
    • NZCVQ 5개 bit 만 사용한다.
  • IPSR : 인터럽트 정보 용 Program Status Register
    • 현재 실행중인 인터럽트에 대한 정보만 담는다. 만약 Number가 0 이라면 Interrupt 없음.
    • Cortex-A 에서는 인터럽트에 따라서 Mode가 달라졌지만, Cortex-M 에서는 프로세서 모드가 2개 뿐이기 때문에 IPSR에서 ‘어떤 인터럽트’인지 구분.
  • EPSR : 실행 상태를 나타내는 Program Status Register
    • [24] T : Thumb bit (항상 1이겠지?)
    • [26:25][15:10] IT : If-Then 블럭
    • [26:25] ICI : Interruptible-Continuable Instruction : 인터럽트로 실행이 중단되더라도 진행 상태를 저장

그런데 각각의 비트가 비껴나가있는 상태이기 때문에, 3개가 겹처진다! 사실은 이게 물리적으로는 하나의 레지스터에 있는데 논리적으로 구분해둔 상태이다. 각 역할을 담당하는 레지스터 Bit에 접근할 때 이름도 다르다! 이렇게 구조를 논리적으로 3개의 분리된 PSR로 생각한다면 접근 권한에 대한 제어가 용이해지고, HW최적화가 가능해진다는 장점이 있다.

Cortex-M 이 A 시리즈보다 좀 더 최신에 나온 구조라 그런지 요런 실험적인 것들도 많이 시도한 것 같네

CPSR, SPSR 값에 접근하기

MRS (Move to Register from Status) / MSR (Move to Status from Register) 명령어를 이용하면 CPSR에 있는 값을 레지스터로 가져올 수 있다. 이때 내가 원하는 필드만 뽑아 가져오는 방법도 있다. 주로 CPSR의 값을 업데이트하기 위해서 사용한다!

; 플래그만 변경 (모드는 그대로)
MRS R0, CPSR
BIC R0, R0, #0xF0000000    ; N,Z,C,V 클리어
ORR R0, R0, #0x80000000    ; N=1 설정
MSR CPSR_f, R0             ; 플래그만 업데이트

; 모드만 변경 (플래그는 그대로)
MRS R0, CPSR
BIC R0, R0, #0x1F          ; 모드 비트 클리어
ORR R0, R0, #0x13          ; SVC 모드
MSR CPSR_c, R0             ; Control 필드만 업데이트

Exception Vector Table과 예외 처리

예외(Exception) : 정상적인 프로그램의 실행 흐름을 중단하고 특별하게 처리해줘야 하는 이벤트. 인터럽트, Abort, Undefined 도 모두 Exception의 일종이다.

Cortex-A의 Exception Vector Table (예외 벡터 테이블)

예외가 발생했을 때 어떤 명령어를 실행하면 되는지 나타낸 ****테이블.

; 벡터 테이블 (메모리 0x00000000부터)
.section .vectors

reset_vector:
    B Reset_Handler         ; 0x00: Reset
    B Undef_Handler         ; 0x04: Undefined Instruction
    B SVC_Handler           ; 0x08: Supervisor Call
    B Prefetch_Handler      ; 0x0C: Prefetch Abort
    B Data_Handler          ; 0x10: Data Abort
    NOP                     ; 0x14: Reserved
    B IRQ_Handler           ; 0x18: IRQ
    B FIQ_Handler           ; 0x1C: FIQ

; 각각의 핸들러에 대해서 구현해둬야한다.
IRQ_Handler:
    ; 레지스터 저장
    ; 인터럽트 처리
    ; 레지스터 복원

Exception의 종류

  • Reset (0x00)
    • Supervisor 모드에서 실행됨
    • 시스템 시작 or 재시작
    • 파워 공급, 리셋 버튼, SW 리셋, 워치독 타이머 등에 의해 호출
  • Undefined Instruction (0x04)
    • Undefined 모드에서 실행됨
    • 정의되지 않은 명령어를 실행한 경우 호출
    • 특정 상황에 대해서 원하는 대로 명령어 작성 (FPU 명령어인데 FPU 장치 없음 → 코드로 구현해서 로직 수행 / 프로그램 종료하기)
  • Supervisor Call (0x08)
    • Supervisor 모드에서 실행됨
    • System Call (OS 요청)
  • Prefetch Abort (0x0C)
    • Abort 모드에서 실행됨
    • 잘못된 메모리 주소에서 명령어를 가져오려 시도하거나, 메모리 보호 규칙을 위반한 경우 발생
    • Handler에서 위반 내용을 살펴보고 복구 시도. 복구 불가능하면 프로그램 종료
  • Data Abort (0x10)
    • Abort 모드에서 실행됨
    • 잘못된 메모리 주소에서 데이터를 읽고 쓰려고 시도하거나, 메모리 보호 규칙을 위반한 경우 발생
  • IRQ (0x18)
    • IRQ 모드에서 실행됨
  • FIQ (0x1C)
    • FIQ 모드에서 실행됨
    • FIQ는 가장 마지막에 위치해있음 → FIQ로 처리해야하는 핸들러 코드를 바로 작성해서 B 명령어 (Jump) 조차 없앨 수 있음. 빠른 처리가 핵심이니깐!

FIQ 뒤에 바로 Handler 작성해서 명령어 수 줄이고 최대한 빠르게 실행할 수 있게 한 건 좀 친다. 설계하면서 이거 생각해낸 사람은 내가 볼 때 일주일 동안 기분좋았을 듯 ㅋㅋ

Exception 우선순위

  1. Reset (최고)
  2. Data Abort (← 의외로 이게 FIQ 보다 높네?)
  3. FIQ
  4. IRQ
  5. Prefetch Abort
  6. SVC, Undefined (최저)

Cortex-M4의 Exception Vector Table

Cortex-M4의 Exception 벡터 테이블은 완전히 다르게 설계되었다. (왜…? 👀👀)

// 벡터 테이블 (startup 파일)
__attribute__((section(".isr_vector")))
const uint32_t vector_table[] = {
    (uint32_t)&_estack,          // 0x00: Initial SP
    (uint32_t)Reset_Handler,     // 0x04: Reset
    (uint32_t)NMI_Handler,        // 0x08: NMI
    (uint32_t)HardFault_Handler,  // 0x0C: Hard Fault
    (uint32_t)MemManage_Handler,  // 0x10: MPU Fault
    (uint32_t)BusFault_Handler,   // 0x14: Bus Fault
    (uint32_t)UsageFault_Handler, // 0x18: Usage Fault
    0,                            // 0x1C: Reserved
    0,                            // 0x20: Reserved
    0,                            // 0x24: Reserved
    0,                            // 0x28: Reserved
    (uint32_t)SVC_Handler,        // 0x2C: SVCall
    (uint32_t)DebugMon_Handler,   // 0x30: Debug Monitor
    0,                            // 0x34: Reserved
    (uint32_t)PendSV_Handler,     // 0x38: PendSV
    (uint32_t)SysTick_Handler,    // 0x3C: SysTick

    // 외부 인터럽트 (IRQ)
    (uint32_t)WWDG_IRQHandler,    // 0x40: Window Watchdog
    (uint32_t)PVD_IRQHandler,     // 0x44: PVD
    // ... (수십 개 더)
    (uint32_t)TIM2_IRQHandler,    // Timer 2
    (uint32_t)USART2_IRQHandler,  // USART 2
    // ...
};

Cortex-M4 에서는 VTOR 레지스터로 설정한 가변 주소를 사용하고, 훨씬 많은 (수 백개의) 엔트리를 사용한다. 그리고 각 엔트리는 헨들러의 주소를 담고 있다.

오 이거 오늘 배운 Startup 파일에 이렇게 정리가 되어있다고 한다. 정리 하고나서 찾아봐야겠다.

320x100