| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- FIFO 설계
- ISR
- keynote 도형 복사
- Async FIFO
- cadence conformal eco
- asynchronous fifo
- keynote
- c
- lec check
- booth algorithm
- keynote 사용법
- Sync FIFO
- ACK Polling
- 연산 가속기 설계
- 자료구조
- AT24C16A
- LinkedList
- Keynote 표
- i2c
- malloc
- LEC
- booth multiplier
- C언어
- EEPROM 동작
- 곱셈기 설계
- keynote 도형 회전
- booth multiplier 설계
- Page Write
- 비동기 FIFO 구조
- ARM
- Today
- Total
JINTBEAT Design Life
ARM cortex-M3 ISR(Interrupt Service Routine) 구현 이론 정리(DTM) 본문
ARM cortex-M3 ISR(Interrupt Service Routine) 구현 이론 정리(DTM)
jintbeat_design 2025. 7. 22. 23:50Interrupt Vector Table 이란 ?
인터럽트 벡터 테이블(Interrupt Vector Table) 은 인터럽트 발생 시 실행될 함수의 시작 주소를 모아놓은 테이블이다.
Cortex-M3에서는 이 테이블이 메모리 주소 0x00000000 번지에 위치해 있고, 리셋 벡터, 예외 핸들러, 외부 인터럽트 핸들러 들이 순서대로 저장되어 있다.
- Vector Table 구조 (간단 예시)
// 벡터 테이블 예시 (startup_cm3.s 또는 startup.c)
__Vectors:
.word _estack // 초기 Stack Pointer 값
.word Reset_Handler // 리셋 핸들러
.word NMI_Handler // NMI 핸들러
.word HardFault_Handler // 하드폴트 핸들러
...
.word TIMER0_IRQHandler // Timer0 인터럽트 핸들러
...
- __Vectors는 실제 Vector Table 이름이며, 링커 스크립트를 통해 0x00000000에 위치한다.
- 각 .word 항목은 인터럽트나 예외 발생 시 호출할 함수의 주소이다.
- TIMER0_IRQHandler는 타이머 인터럽트가 발생하면 실행될 함수이다.
- 중요한 조건 : ISR 함수 이름이다.
Cortex-M3에서는 Interrupt 번호가 정해져 있기 때문에, CMSIS를 사용할 경우 다음과 같은 이름이 자동 Mapping된다.
- TIMER0_IRQn = Vector Table상 N번 째 항목
-> void TIMER0_IRQHandler(void) 함수가 있어야 해당 인터럽트 발생 시 호출된다.
typedef void(*ISR_Handler)(void);
// 예: 실제 메모리 맵 벡터 테이블 (예제용)
__attribute__((section(".isr_vector")))
ISR_Handler vector_table[] = {
(ISR_Handler)&_estack, // 초기 스택 포인터
Reset_Handler, // 리셋 핸들러
NMI_Handler,
HardFault_Handler,
...
TIMER0_IRQHandler, // 이 자리에 우리가 작성한 ISR 등록
};
ISR이 호출되기 위한 조건
1. 함수 이름을 정확히 맞춰줘야 함.
2. 링커와 시작 코드가 내 ISR을 Vector Table에 반영해야 한다.
- 대부분의 CMSIS 스타트업 코드에서는 기본적으로 __attribute__((weak))로 ISR이 선언되어 있고, 우리가 같은 이름으로 재정의하면 자동으로 연결된다.
CMSIS에서는 자동 연결되는 구조
CMSIS 기반 Startup code에서는 다음과 같이 연결되어 있다.
void __attribute__((weak)) TIMER0_IRQHandler(void) {
while (1); // 기본 핸들러 (사용자가 override 가능)
}
다음과 같은 함수를 작성하면 자동 연결된다.
void TIMER0_IRQHandler(void) {
// 이 함수가 인터럽트 발생 시 실행됨
}
__xxx__ 또는 __xxx 식별자는 왜 쓰는 걸까?
- 일반적인 목적은 C/C++ 컴파일러나 라이브러리, CMSIS 같은 저수준 코드에서 특수 목적으로 사용되는 식별자라는 의미이다.
- 일반 사용자 코드와 구분하기 위해 __를 붙인다.
- 2개를 붙이는 이유는 충돌 방지 + 내부 구현 명시이다.
예시 별 의미
| 식별자 | 의미 |
| __attribute__ | GNU C 컴파일러 확장 문법: 함수나 변수에 특별한 속성을 부여함 |
| __Vectors | 벡터 테이블의 심볼 이름 (보통 링크 스크립트에서 참조) |
| __weak 또는 __attribute__((weak)) | 약한 심볼(override 가능함) |
| __IO, __I, __O | CMSIS에서 정의한 IO 접근 매크로 (volatile 의미) |
| __isr_vector | 링커 스크립트에서 벡터 테이블이 위치할 섹션 이름 |
CMSIS 및 ARM 환경에서 자주 보이는 식별자들
__attribute__((section(".isr_vector"))) // 특정 섹션에 위치하도록 지정
__attribute__((weak)) // 기본 구현, 오버라이드 가능
__STATIC_INLINE // CMSIS에서 쓰는 정적 인라인 정의
__NVIC_EnableIRQ(...) // NVIC 인터럽트 Enable
ISR 함수 작성
- 목적
ISR은 인터럽트가 발생했을 때 CPU가 자동으로 호출하는 핸들러 함수이다.
이 함수 안에서 인터럽트 원인을 확인하고, 클리어하고, 원하는 동작을 수행한다.
- 기본 구조
void TIMER0_IRQHandler(void) {
// 1. 인터럽트 발생 여부 확인
if (TIMER0->MIS & 0x1) {
// 2. 인터럽트 클리어
TIMER0->ICR = 0x1;
// 3. 원하는 동작 수행
LED ^= 1; // 예: LED 토글
}
}
1. 인터럽트 확인 : 인터럽트 발생 상태를 레지스터에서 읽어서 확인(MIS 레지스터 등)
2. 클리어 : ICR, INTCLR 등, 인터럽트 상태를 클리어하지 않으면 계속 인터럽트 발생
3. 사용자 동작 : 타이머가 끝났을 때 실행할 코드: LED 토글, 카운터 증가 등.
- 주요 레지스터 (예: ARM Dual Timer 기준)
| 레지스터 | 설명 |
| LOAD | 타이머 초기값 (카운트 다운 시작값) |
| VALUE | 현재 카운트값 |
| CONTROL | 타이머 설정 (Enable, 모드, 인터럽트 등) |
| INTCLR or ICR | 인터럽트 클리어 |
| MIS | Masked Interrupt Status (1이면 인터럽트 발생) |
- 실제 예제(CMSIS + Dual Timer)
void TIMER0_IRQHandler(void) {
// ① 인터럽트 발생 여부 확인
if (DTIMER0->MIS & 0x1) {
// ② 인터럽트 클리어
DTIMER0->ICR = 0x1;
// ③ 사용자 동작
count++; // 전역 변수 증가
toggle_LED(); // 함수 호출 (예: GPIO 제어)
}
}
- 주의 할 점
| 항목 | 설명 |
| 인터럽트 클리어를 꼭 해야 함 | 클리어 안 하면 ISR이 계속 호출돼 무한 루프에 빠짐 |
| ISR 안에서는 시간 오래 걸리는 연산 피하기 | ISR은 짧고 빠르게! 딜레이 루프, printf 등은 피함 |
| 전역 변수는 volatile로 선언 | ISR과 main에서 공유하는 변수는 volatile 키워드 필수 |
volatile uint32_t count = 0; // ISR과 main에서 공유
- 반복 동작 타이머 예시 흐름
- 타이머가 설정한 시간만큼 흐르면
- MIS 비트가 1이 됨 → 인터럽트 발생
- NVIC가 TIMER0_IRQHandler() 호출
- ISR에서:
- MIS 비트 확인
- ICR로 클리어
- 사용자 정의 동작 수행
MIS(Masked Interrupt Status) : NVIC에서 마스크된 결과를 반영해 보여줌. 실제 인터럽트 동작 판단에 사용한다. 실제 인터럽트가 동작할 조건은 MIS가 1인 경우이기 때문에, ISR에서는 보통 MIS를 확인한다.
NVIC에서 Interrupt 활성화
- NVIC란 ?
Cortex-M3에는 NVIC라는 하드웨어 모듈이 있어서, 외부 인터럽트의 활성화, 우선 순위 설정, Pending 상태 관리 등을 담당한다.
- 인터럽트를 사용하려면 꼭 해야 하는 일
1. NVIC에서 해당 인터럽트 "Enable"
2. 인터럽트 우선 순위 설정(필요한 경우)
3. ISR(Handler) 함수 정의는 앞서 완료됨.
기본 코드 : NVIC 설정 예시(CMSIS 사용 기준)
#include "core_cm3.h" // NVIC 관련 함수 선언 포함
// 인터럽트 활성화
NVIC_EnableIRQ(TIMER0_IRQn);
// (선택) 인터럽트 우선순위 설정: 낮은 숫자가 높은 우선순위
NVIC_SetPriority(TIMER0_IRQn, 2);
TIMER0_IRQn은 CMSIS에서 제공하는 열거형(enum) 값으로, 해당 인터럽트의 번호를 의미한다.
NVIC 함수 설명(CMSIS 기준)
| 함수 이름 | 설명 |
| NVIC_EnableIRQ(IRQn) | 해당 인터럽트를 Enable 상태로 설정 |
| NVIC_DisableIRQ(IRQn) | Disable 상태로 설정 |
| NVIC_SetPriority(IRQn, priority) | 우선순위 설정 (priority 값 작을수록 우선순위 높음) |
| NVIC_ClearPendingIRQ(IRQn) | Pending 상태 제거 |
| NVIC_SetPendingIRQ(IRQn) | 강제로 인터럽트 Pending 상태로 만듦 (테스트용) |
예시 : 타이머 인터럽트를 NVIC에 등록
// 인터럽트 번호는 보통 헤더파일에 이렇게 정의되어 있어요
typedef enum {
...
TIMER0_IRQn = 18,
...
} IRQn_Type;
// 초기화 코드
void Timer0_NVIC_Init(void) {
NVIC_SetPriority(TIMER0_IRQn, 2); // 우선순위 2로 설정
NVIC_EnableIRQ(TIMER0_IRQn); // 인터럽트 활성화
}
위 코드는 보통 main() 함수에서 타이머 초기화 직후 호출한다.
직접 만든 SoC에서는 ?
CMSIS를 쓰지 않는 경우라면, NVIC 레지스터를 직접 제어해야 할 수도 있다. 예를 들면
#define NVIC_ISER0 (*(volatile uint32_t *)0xE000E100) // Interrupt Set Enable Register
#define IRQ_TIMER0 18 // 인터럽트 번호
NVIC_ISER0 = (1 << IRQ_TIMER0); // 인터럽트 Enable
하지만 이 방법은 CMSIS가 없는 bare-metal 환경에서 주로 사용된다.
인터럽트 우선순위란 ?
CM3에선느 인터럽트 우선순위가 0~255까지 표현 가능(8비트, 실제 구현에 따라 다를 수 있음)
값이 작을수록 우선 순위가 높다.
주의할 점
- ISR이 정의되지 않은 상태에서 NVIC를 Enable하면 HardFault 발생 가능
- 인터럽트 번호(TIMER0_IRQn)는 정확히 매칭되어야 함
- CMSIS가 없는 경우, NVIC 레지스터 직접 접근 시 비트 위치 주의
DTM 타이머 설정 - 기본 흐름
1. 카운트 시작 값 설정(LOAD)
2. 제어 레지스터 설정(CONTROL)
3. 인터럽트 Enable 설정 포함
4. 타이머 Enable 비트를 ON
5. 이후 타이머 만료 시 자동으로 인터럽트 발생
제어 레지스터(CONTROL) 설정 비트 예시
| 비트 위치 | 이름 | 설명 |
| [7] | Timer Enable | 1: 타이머 동작 시작 |
| [6] | Periodic Mode | 1: 주기 모드 (0: one-shot) |
| [5] | Interrupt Enable | 1: 인터럽트 발생 허용 |
| [2:1] | Prescale | 분주 비트 (00: /1, 01: /16, 10: /256) |
| [0] | 32-bit Mode | 1: 32비트, 0: 16비트 |
타이머 초기화 예제
void DTM_Timer0_Init(uint32_t load_value) {
DTIMER0->LOAD = load_value; // 타이머 시작값 설정 (예: 1초 주기)
DTIMER0->CONTROL =
(1 << 7) | // Timer Enable
(1 << 6) | // Periodic Mode (주기 반복)
(1 << 5) | // Interrupt Enable
(0 << 2) | // Prescale = /1
(1 << 1); // 32-bit 모드
}
예 : 50MHz Clock 기준 1초 주기를 원한다면 ?
load_value = 50,000,000; // 1초 = 50M 클럭
인터럽트 발생 흐름 요약
1. LOAD에 설정된 값부터 타이머가 감소(count down)
2. 0이 되면 MIS 비트가 1로 set 됨. 인터럽트가 NVIC로 전달됨.
3. NVIC는 TIMER0_IRQHandler() 호출
4. ISR에서 ICR에 1을 써서 인터럽트 클리어
전체 초기화 코드 흐름 정리
void Init_Timer0(void) {
// 1. 타이머 설정
DTM_Timer0_Init(50000000); // 1초 주기 (50MHz 기준)
// 2. NVIC 등록
NVIC_SetPriority(TIMER0_IRQn, 2);
NVIC_EnableIRQ(TIMER0_IRQn);
}
동작 테스트 예시(ISR까지 포함)
volatile uint32_t tick = 0;
void TIMER0_IRQHandler(void) {
if (DTIMER0->MIS & 0x1) {
DTIMER0->ICR = 0x1; // 인터럽트 클리어
tick++; // 1초마다 tick 증가
}
}
전체 흐름 요약!
1. 타이머 LOAD 설정 : 원하는 주기만큼 카운트 다운
2. CONTROL 설정 : Enable, Periodic, Interrupt 등 비트 설정
3. NVIC 설정 : 인터럽트 등록 및 우선 순위 설정
4. ISR 호출 : 인터럽트 발생 시 함수 실행
5. 인터럽트 클리어 : ICR에 1 써야 다음 인터럽트도 받을 수 있음.
'🖥️ - ARM' 카테고리의 다른 글
| ARM I2C 응용 - EEPROM(ATmel) 동작 분석 (1) | 2025.08.13 |
|---|---|
| ARM I2C(Inter-Integrated Circuit) 동작 원리 (7) | 2025.08.10 |
| ARM - UART 정리 (3) | 2025.07.31 |
| ARM porting - DTM register set 동작 정리 (1) | 2025.07.24 |
| ARM cortex-M3 ISR(Interrupt Service Routine) 구현 Code 예제 (2) | 2025.07.22 |