외부 인터럽트 및 이벤트 제어기(EXTI) 개요
STM32F103 마이크로컨트롤러는 총 60개의 마스킹 가능한 인터럽트 채널을 제공하며, 이 중 외부 신호에 반응하는 핵심 기능이 바로 EXTI(External Interrupt/Event Controller)입니다. EXTI는 외부 핀의 전압 변화를 감지하여 CPU에 인터럽트를 발생시키거나 특정 이벤트를 생성합니다.
주요 EXTI 라인 구성
STM32F103의 EXTI는 총 19개의 감지 라인을 가지고 있습니다.
- EXTI0 ~ EXTI4: 개별 인터럽트 라인 (라인당 1개의 인터럽트 벡터 할당)
- EXTI5 ~ EXTI9: EXTI9_5 인터럽트 벡터로 묶여 관리
- EXTI10 ~ EXTI15: EXTI15_10 인터럽트 벡터로 묶여 관리
- EXTI16: PVD(전원 전압 감지) 출력 연결
- EXTI17: RTC 알람 이벤트 연결
- EXTI18: USB 웨이크업 이벤트 연결
- EXTI19: 이더넷 웨이크업 이벤트 (특정 모델 전용)
GPIO와 EXTI 라인의 매핑
STM32F103의 수많은 GPIO 핀은 총 16개의 외부 인터럽트 라인(EXTI0~15)을 공유합니다. 예를 들어, EXTI0 라인은 PA0, PB0, PC0 등 모든 포트의 0번 핀 중 하나와 연결될 수 있습니다. 이러한 연결 설정은 AFIO_EXTICR1 ~ AFIO_EXTICR4 레지스터를 통해 수행됩니다.
EXTI 내부 구조 및 작동 원리
EXTI의 신호 흐름은 크게 두 가지 경로로 나뉩니다. 첫 번째는 인터럽트 서비스 루틴(ISR)을 실행하기 위한 NVIC 경로이고, 두 번째는 하드웨어 트리거용 펄스를 생성하는 이벤트 경로입니다.
- 엣지 검출 회로: 상승 엣지(Rising), 하강 엣지(Falling) 또는 양쪽 모두에서 트리거되도록 설정할 수 있습니다.
- 소프트웨어 인터럽트: 레지스터 설정을 통해 외부 신호 없이도 소프트웨어적으로 인터럽트를 강제 발생시킬 수 있습니다.
- 인터럽트 마스크: 특정 라인의 인터럽트 요청이 NVIC로 전달될지 여부를 결정합니다.
- 펜딩 레지스터(Pending Register): 인터럽트가 발생했음을 기록하며, ISR 내에서 소프트웨어적으로 1을 써서 클리어해야 합니다.
외부 인터럽트 설정 단계
- GPIO 설정: 인터럽트로 사용할 핀을 입력 모드(플로팅, 풀업 또는 풀다운)로 초기화합니다.
- AFIO 클럭 활성화: 핀 매핑을 위해 AFIO(Alternate Function I/O) 클럭을 활성화합니다.
- EXTI 라인 매핑: 사용할 GPIO 핀을 대응하는 EXTI 라인에 연결합니다.
- 트리거 및 마스크 설정: 엣지 트리거 방식(상승/하강)을 선택하고 인터럽트 마스크를 해제합니다.
- NVIC 우선순위 설정: 해당 EXTI 라인의 인터럽트 우선순위를 결정하고 활성화합니다.
코드 구현 예시
EXTI 초기화 함수
다음은 특정 GPIO 핀을 외부 인터럽트 라인으로 구성하는 로직입니다.
typedef enum {
EDGE_FALLING = 0x01,
EDGE_RISING = 0x02,
EDGE_BOTH = 0x03
} EXTI_Trigger_TypeDef;
/**
* @brief 외부 인터럽트 라인 구성 함수
* @param port_id 핀의 포트 번호 (0:A, 1:B, ...)
* @param pin_num 핀 번호 (0-15)
* @param trigger 트리거 모드
*/
void Config_EXTI_Line(uint8_t port_id, uint8_t pin_num, EXTI_Trigger_TypeDef trigger) {
uint8_t cr_idx = pin_num / 4;
uint8_t cr_offset = (pin_num % 4) * 4;
// 1. AFIO 클럭 활성화
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2. 외부 인터럽트 라인 매핑 (AFIO_EXTICR 설정)
AFIO->EXTICR[cr_idx] &= ~(0xF << cr_offset);
AFIO->EXTICR[cr_idx] |= (port_id << cr_offset);
// 3. 인터럽트 및 이벤트 마스크 해제
EXTI->IMR |= (1 << pin_num);
EXTI->EMR |= (1 << pin_num);
// 4. 트리거 엣지 설정
if (trigger & EDGE_FALLING) {
EXTI->FTSR |= (1 << pin_num);
} else {
EXTI->FTSR &= ~(1 << pin_num);
}
if (trigger & EDGE_RISING) {
EXTI->RTSR |= (1 << pin_num);
} else {
EXTI->RTSR &= ~(1 << pin_num);
}
}
인터럽트 서비스 루틴(ISR) 구현
PC5 핀(EXTI 라인 5)에 연결된 버튼을 통해 LED를 제어하는 예시입니다.
void EXTI9_5_IRQHandler(void) {
// EXTI 라인 5에서 인터럽트가 발생했는지 확인
if (EXTI->PR & (1 << 5)) {
// 사용자 정의 작업: LED 상태 반전
GPIOA->ODR ^= (1 << 8);
// 펜딩 비트 클리어 (1을 써서 클리어)
EXTI->PR = (1 << 5);
}
// 다른 라인(6~9)에 대한 체크 로직 추가 가능
}
메인 루프 설정
int main(void) {
// 시스템 클럭 및 하드웨어 초기화
System_Clock_Init();
// PA8을 LED 출력으로 설정
// PC5를 버튼 입력(풀업)으로 설정
GPIO_Init_Output(PORT_A, 8);
GPIO_Init_Input_PullUp(PORT_C, 5);
// PC5 핀에 대해 하강 엣지 외부 인터럽트 설정
Config_EXTI_Line(2, 5, EDGE_FALLING); // 2: Port C
// NVIC 설정: EXTI9_5 그룹 인터럽트 활성화
NVIC_SetPriority(EXTI9_5_IRQn, 2);
NVIC_EnableIRQ(EXTI9_5_IRQn);
while (1) {
// 인터럽트 방식으로 동작하므로 메인 루프는 비워두거나 다른 작업 수행
__WFI(); // 대기 모드 진입 가능
}
}
주의 사항
- 펜딩 비트 클리어: ISR 내부에서
EXTI->PR레지스터의 해당 비트를 반드시 클리어해야 합니다. 클리어하지 않으면 인터럽트가 무한 반복 호출될 수 있습니다. - AFIO 클럭:
AFIO_EXTICR레지스터를 건드리기 전에 반드시RCC_APB2ENR에서 AFIO 클럭을 먼저 켜야 설정이 반영됩니다. - 공유 인터럽트 라인: EXTI5~9와 EXTI10~15는 하나의 인터럽트 벡터를 공유하므로, 핸들러 내부에서 어떤 핀에서 신호가 왔는지
EXTI->PR비트를 통해 개별적으로 판별해야 합니다.