인터럽트와 예외: 하던 일을 멈추고 다녀오기
이벤트가 생기면 CPU가 상태를 저장하고 처리 루틴으로 뛰었다가 돌아옵니다. Cortex-M에서 그 과정이 어떻게 일어나는지 정리했습니다.
버튼이 눌렸는지 계속 확인하는 폴링은 CPU를 낭비하고 반응도 늦습니다. 인터럽트는 하드웨어가 "일이 생겼다"고 CPU를 불러 세우는 방식입니다.
Cortex-M에서 일어나는 일
- 주변장치가 인터럽트 신호를 올린다
- NVIC가 우선순위를 확인하고 CPU에 알린다
- CPU가 r0~r3, r12, lr, pc, xPSR을 자동으로 스택에 저장한다
- 벡터 테이블에서 처리 함수 주소를 찾아 점프한다
- 처리 함수가 끝나면 저장했던 레지스터를 복원하고 원래 자리로 돌아간다
레지스터 저장이 하드웨어로 자동 처리되기 때문에 Cortex-M의 인터럽트 함수는 보통 C 함수와 똑같이 씁니다.
예외
인터럽트는 예외의 한 종류입니다. 리셋, NMI, 하드폴트, SysTick, SVCall도 모두 예외이고 벡터 테이블 앞쪽에 자리가 있습니다. 잘못된 주소 접근이나 0으로 나누기 같은 문제가 생기면 하드폴트 예외로 들어갑니다.
처리 함수 작성 원칙
- 짧게. 플래그를 세우거나 버퍼에 넣고 끝냅니다.
- 메인 코드와 공유하는 변수는
volatile로, 여러 바이트 값은 원자적으로 읽히게 보호합니다. - 인터럽트 원인 플래그를 지우지 않으면 함수가 끝나자마자 다시 들어옵니다.
인터럽트 한 번에 일어나는 일
Cortex-M에서는 다음 순서가 하드웨어로 자동 처리됩니다.
- 실행 중이던 명령을 끝냅니다.
- R0~R3, R12, LR, PC, xPSR 여덟 개를 스택에 저장합니다.
- 벡터 테이블에서 핸들러 주소를 읽어 그곳으로 뜁니다.
- 핸들러가 끝나면 저장한 값을 꺼내 하던 일로 돌아갑니다.
레지스터 저장을 하드웨어가 해 주기 때문에 핸들러를 평범한 C 함수로 쓸 수 있습니다. 요청부터 핸들러 첫 명령까지는 Cortex-M3/M4 기준 12사이클입니다.
3D 실험실여기까지 읽은 내용을 직접 돌려 볼 수 있습니다→핸들러는 짧게, 일은 메인에서
volatile uint8_t btn_flag;
volatile uint32_t btn_tick;
void EXTI0_IRQHandler(void) {
EXTI->PR = EXTI_PR_PR0; // 1) 플래그부터 지우기
btn_tick = sys_tick; // 2) 시각만 기록
btn_flag = 1; // 3) 깃발만 세우기
}
int main(void) {
while (1) {
if (btn_flag) { btn_flag = 0; handle_button(btn_tick); }
}
}
핸들러 안에서 printf를 부르거나 지연 함수를 돌리면 그동안 우선순위가 같거나 낮은 다른 인터럽트가 전부 밀립니다.
현업에서 자주 터지는 문제
| 증상 | 원인 |
|---|---|
| 핸들러가 끝없이 다시 불림 | 인터럽트 플래그를 지우지 않음 |
| 메인 루프가 멈춘 것처럼 보임 | 핸들러가 너무 길거나 너무 자주 발생 |
| 값이 가끔 깨짐 | 메인과 핸들러가 같은 변수를 보호 없이 수정 |
| 릴리스 빌드에서만 안 됨 | 공유 변수에 volatile 누락 |
| 전혀 안 불림 | 핸들러 이름이 벡터 테이블의 이름과 다름(약한 심볼이 대신 불림) |
마지막 경우가 초보 때 가장 오래 헤매는 문제입니다. 스타트업 파일에 적힌 이름과 한 글자라도 다르면 링커는 오류 없이 기본 핸들러(무한 루프)를 연결합니다.
스스로 확인
- 인터럽트 지연 시간이 일정해야 하는 제어 루프에서, 메인 코드가 인터럽트를 오래 꺼 두면 어떤 문제가 생길까요?
- 우선순위가 같은 인터럽트 두 개가 동시에 들어오면 어느 쪽이 먼저 처리될까요?
연결된 개념
영상으로 이어 보기회로와 하드웨어 시리즈 8편전자제품 속 회로 소자는 어떻게 동작할까? · 저항 · 커패시터 · 인덕터 완벽 정리FREE PDF무료 전자책 3권 받기이 주제를 한 권으로 이어서 읽고 싶다면. BLDC 모터 제어, 직렬통신, 센서를 책 분량으로 묶었습니다. 이메일만 남기면 바로 받습니다.무료로 받기 →