
EXTI(External Interrupt/Event Controller)는 GPIO 핀을 비롯한 외부 신호의 엣지(Edge) 변화를 감지하여 인터럽트 또는 이벤트를 발생시키는 STM32 전용 주변장치(Peripheral)입니다. ARM Cortex-M의 NVIC가 인터럽트를 관리하는 컨트롤러라면, EXTI는 GPIO와 NVIC 사이에서 외부 신호를 감지하고 전달하는 역할을 담당합니다. STM32F4 기준으로 최대 23개의 EXTI 라인을 제공합니다.
STM32F4의 EXTI 라인 종류
EXTI 라인 0 ~ 15 : GPIO 핀 연결 (PA0~PK15)
EXTI 라인 16 : PVD (Programmable Voltage Detector) 출력
EXTI 라인 17 : RTC Alarm 이벤트
EXTI 라인 18 : USB OTG FS Wakeup
EXTI 라인 19 : Ethernet Wakeup (STM32F4xx 일부 모델)
EXTI 라인 20 : USB OTG HS Wakeup
EXTI 라인 21 : RTC Tamper / Timestamp
EXTI 라인 22 : RTC Wakeup Timer
GPIO 핀은 라인 0~15만 사용하며,
나머지는 내부 주변장치의 Wakeup/이벤트 신호에 연결됨
GPIO 핀과 EXTI 라인 매핑
EXTI 라인 번호 = GPIO 핀 번호
EXTI Line 0 → PA0, PB0, PC0, PD0, PE0, PF0, PG0, PH0, PI0
EXTI Line 1 → PA1, PB1, PC1, ...
EXTI Line 2 → PA2, PB2, PC2, ...
...
EXTI Line 15 → PA15, PB15, PC15, ...
핵심 제약:
동일한 핀 번호의 GPIO는 하나의 EXTI 라인을 공유
→ PA0과 PB0을 동시에 EXTI로 사용 불가
→ SYSCFG(System Configuration Controller)->EXTICR 레지스터로 어느 포트를 연결할지 선택
마치 트롤리 딜레마와 같이 하나의 선로에서 n개의 분기가 생길 때 SYSCFG_EXTICR 레지스터가 특정 분기를 선택하는 레버의 역할을 합니다. 당연히 기차는 하나의 선로만 따라 갈 수밖에 없게 되죠.
SYSCFG EXTICR 레지스터
SYSCFG->EXTICR[0] : EXTI 0 ~ 3 포트 선택
SYSCFG->EXTICR[1] : EXTI 4 ~ 7 포트 선택
SYSCFG->EXTICR[2] : EXTI 8 ~ 11 포트 선택
SYSCFG->EXTICR[3] : EXTI 12 ~ 15 포트 선택
각 4비트로 포트 선택:
0x0 = GPIOA
0x1 = GPIOB
0x2 = GPIOC
0x3 = GPIOD
0x4 = GPIOE
...
예시: PB3을 EXTI3으로 설정
SYSCFG->EXTICR[0] 의 [15:12] 비트 = 0x1 (GPIOB)
EXTI 블록 다이어그램
GPIO 핀 입력 신호
│
▼
[엣지 감지 회로]
┌─────────────┐
│ Rising Edge │ ← EXTI->RTSR (Rising Trigger Selection Register)
│ Falling Edge│ ← EXTI->FTSR (Falling Trigger Selection Register)
└─────────────┘
│
▼
[소프트웨어 인터럽트] ← EXTI->SWIER (Software Interrupt Event Register)
│
▼
[인터럽트 마스크] ← EXTI->IMR (Interrupt Mask Register)
│
├──→ Pending Register (EXTI->PR) → NVIC → CPU (인터럽트)
│
[이벤트 마스크] ← EXTI->EMR (Event Mask Register)
│
└──→ 이벤트 라인 → Wakeup / DMA / 타이머 트리거 (이벤트)
인터럽트 모드 vs 이벤트 모드
인터럽트 모드 (Interrupt Mode):
- EXTI->IMR 마스크 활성화
- Pending 비트 설정 → NVIC 통해 CPU 인터럽트 발생
- ISR(Interrupt Service Routine) 실행
- 범용 처리에 사용
이벤트 모드 (Event Mode):
- EXTI->EMR 마스크 활성화
- CPU 인터럽트 발생 없음
- 직접 하드웨어 이벤트 라인으로 전달
→ 타이머 트리거, ADC 트리거, DMA 요청 등에 활용
- 저전력 Wakeup 신호로도 사용 (Stop/Standby 모드에서 복귀)
두 모드를 동시에 활성화하는 것도 가능
EXTI 레지스터 목록
IMR (Interrupt Mask Register) : 인터럽트 마스크 설정
EMR (Event Mask Register) : 이벤트 마스크 설정
RTSR (Rising Trigger Selection Register) : 상승 엣지 트리거 설정
FTSR (Falling Trigger Selection Register) : 하강 엣지 트리거 설정
SWIER (Software Interrupt Event Register) : 소프트웨어 인터럽트 발생
PR (Pending Register) : 인터럽트 Pending 상태 / 클리어
각 레지스터는 비트 단위로 EXTI 라인 번호에 대응
비트 0 → EXTI Line 0
비트 1 → EXTI Line 1
...
비트 22 → EXTI Line 22
레지스터 직접 조작 예시 (PB3, EXTI Line 3 설정)
// 1. SYSCFG 클럭 활성화
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// 2. EXTI3 소스를 GPIOB로 선택
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI3; // 비트 클리어
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI3_PB; // GPIOB 선택
// 3. 상승 엣지 트리거 활성화
EXTI->RTSR |= EXTI_RTSR_TR3;
// 4. 하강 엣지 트리거 활성화 (양쪽 엣지 감지 시 추가)
EXTI->FTSR |= EXTI_FTSR_TR3;
// 5. 인터럽트 마스크 언마스크 (활성화)
EXTI->IMR |= EXTI_IMR_MR3;
// 6. NVIC 설정
NVIC_SetPriority(EXTI3_IRQn, 10);
NVIC_EnableIRQ(EXTI3_IRQn);
앞서 배웠던 PWM(Pulse Width Modulation)은 출력(Output)이나 주기적인 신호 분석에 특화돼있고, EXTI는 마우스/키보드 버튼 클릭 같은 비정기적인 사건(Event) 감지에 특화돼있기 때문에 둘의 용도를 구분지어 사용해야 됩니다.
Pending 비트 동작
인터럽트 발생 조건 충족 시:
→ EXTI->PR 해당 비트 자동 Set (1)
→ NVIC에 인터럽트 요청 전달
ISR 진입 후:
→ 반드시 Pending 비트를 클리어해야 함
→ 클리어 방법: 해당 비트에 1을 기록 (1을 써야 클리어됨, 특이한 동작)
클리어 하지 않을 경우:
→ ISR 종료 후 즉시 재진입 (인터럽트 폭주)
Pending 비트 클리어
// ISR 내부에서 Pending 비트 클리어
void EXTI3_IRQHandler(void)
{
if (EXTI->PR & EXTI_PR_PR3) // Pending 확인
{
EXTI->PR |= EXTI_PR_PR3; // 1을 써서 클리어
// 처리 코드
}
}
// HAL 사용 시 자동 처리됨
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); // 내부에서 PR 클리어 후 콜백 호출
}
CPU는 한 번에 하나의 동작만 처리할 수 있기 때문에 ISR이 실행 중인 동안 다른 인터럽트가 발생했음을 잠시 기록해두는 목적으로 사용됩니다. Pending -> Active -> Inactive 순서로 진행되며 Active 상태일 때 CPU가 해당 ISR을 실행하고 Pending 비트를 지웁니다. 만약 Pending 비트를 지우지 않으면 무한루프가 발생할 수 있기 때문에 EXTI->PR |= EXTI_PR_PR0; 같은 코드로 "나 이제 이 일 다 했어"라고 표시해주는 과정이 필요합니다.
트리거 방식 비교
Rising Edge (상승 엣지):
신호: 0 → 1 전환 시 감지
┌────
────────────────┘
↑ 인터럽트 발생
Falling Edge (하강 엣지):
신호: 1 → 0 전환 시 감지
────────────────┐
└────
↑ 인터럽트 발생
Both Edge (양쪽 엣지):
0 → 1, 1 → 0 모두 감지
RTSR + FTSR 동시 설정
┌────┐
────────────────┘ └────
↑ ↑ 인터럽트 발생 (2회)
실제 사용 예시
버튼 누름 감지 (풀업 저항):
- 버튼 누름: VCC → GND → Falling Edge
- 버튼 해제: GND → VCC → Rising Edge
- 누름만 감지: Falling Edge 설정
- 누름/해제 모두 감지: Both Edge 설정
인코더 신호:
- 회전 방향 판별: 양쪽 엣지 설정 후 다른 채널과 위상 비교
UART 시작 비트:
- 1 → 0 전환 감지: Falling Edge 설정으로 Wakeup 트리거
글리치(Glitch) 문제
실제 GPIO 신호:
이상적: ─────┐
└──────
실제: ─────┐┌┐┌┐
└┘└┘└── ← 바운싱(Bouncing) 현상
기계식 버튼의 경우 접점 바운싱으로 인해
단 1회의 버튼 조작에 수십 회의 엣지 발생 가능
→ 소프트웨어 디바운싱 또는 하드웨어 필터 필요
STM32 내장 입력 필터
EXTI 자체에는 디지털 필터 없음
GPIO 내부의 슈미트 트리거(Schmitt Trigger)로 기본 노이즈 제거
추가 필터링:
1. 하드웨어: RC 필터 (저항 + 커패시터) 외부 회로
2. 소프트웨어: 디바운싱 알고리즘 (타이머 활용)
3. CubeMX: GPIO Input에서 Pull-up/Pull-down 설정으로 기본 안정화
GPIO EXTI 설정 순서
1. Pinout & Configuration → GPIO 핀 클릭
2. Mode 선택:
- GPIO_EXTI0 ~ GPIO_EXTI15 (핀 번호에 맞는 항목)
3. GPIO 설정:
- GPIO mode: External Interrupt Mode with Rising/Falling/Both Edge trigger
- GPIO Pull-up/Pull-down: 회로에 맞게 선택
4. NVIC 탭:
- 해당 EXTI IRQ Enable 체크
- Preemption Priority 설정
5. Code Generation → MX_GPIO_Init() 자동 생성
CubeMX 생성 코드 예시 (PC13, 하강 엣지)
// MX_GPIO_Init() 내부에 자동 생성
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 하강 엣지 인터럽트
GPIO_InitStruct.Pull = GPIO_NOPULL; // 외부 풀업 사용 가정
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 10, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
GPIO 초기화 시 Mode 선택
// GPIO Mode 옵션 (stm32f4xx_hal_gpio.h 정의)
GPIO_MODE_IT_RISING // 상승 엣지 인터럽트
GPIO_MODE_IT_FALLING // 하강 엣지 인터럽트
GPIO_MODE_IT_RISING_FALLING // 양쪽 엣지 인터럽트
GPIO_MODE_EVT_RISING // 상승 엣지 이벤트
GPIO_MODE_EVT_FALLING // 하강 엣지 이벤트
GPIO_MODE_EVT_RISING_FALLING // 양쪽 엣지 이벤트
EXTI 관련 HAL 함수
// ISR 내에서 호출 (Pending 클리어 + 콜백 호출)
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
// 콜백 함수 (오버라이드하여 실제 처리 구현)
// __weak 선언이므로 재정의 가능
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
// 소프트웨어로 EXTI 인터럽트 강제 발생
// EXTI->SWIER 레지스터 직접 조작 또는
HAL_NVIC_SetPendingIRQ(EXTI0_IRQn); // NVIC Pending 직접 설정
stm32f4xx_it.c 구성
// EXTI Line 10~15는 하나의 IRQ 핸들러 공유
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); // PC13 처리
}
// EXTI Line 5~9도 공유
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
// 사용 중인 핀 모두 호출
}
// EXTI Line 0~4는 개별 IRQ 핸들러
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
EXTI IRQ 핸들러 공유 범위
EXTI0_IRQHandler → EXTI Line 0 전용
EXTI1_IRQHandler → EXTI Line 1 전용
EXTI2_IRQHandler → EXTI Line 2 전용
EXTI3_IRQHandler → EXTI Line 3 전용
EXTI4_IRQHandler → EXTI Line 4 전용
EXTI9_5_IRQHandler → EXTI Line 5, 6, 7, 8, 9 공유
EXTI15_10_IRQHandler → EXTI Line 10, 11, 12, 13, 14, 15 공유
소프트웨어 인터럽트 발생 원리
EXTI->SWIER 레지스터의 특정 비트에 1을 기록하면
해당 EXTI 라인의 Pending 비트(PR)가 Set됨
→ 실제 GPIO 신호 변화 없이 인터럽트 강제 발생
Pending 비트 클리어 후 SWIER 비트도 자동으로 클리어됨
소프트웨어 인터럽트 사용 예시
// EXTI Line 0 소프트웨어 인터럽트 발생
void trigger_software_interrupt(void)
{
EXTI->SWIER |= EXTI_SWIER_SWIER0;
// EXTI0_IRQHandler가 즉시 호출됨
}
// 사용 사례:
// 1. 인터럽트 로직 테스트 및 검증
// 2. 다른 태스크에서 특정 ISR 강제 실행
// 3. RTOS 환경에서 태스크 간 신호 전달
이벤트 모드 설정 (저전력 Wakeup)
// Stop 모드에서 GPIO 신호로 Wakeup하는 예시
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_EVT_FALLING; // 이벤트 모드 (인터럽트 아님)
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// Wakeup 후 시스템 클럭 재설정 등 처리
// 이벤트 모드는 ISR 없이 하드웨어 직접 Wakeup
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFE); // WFE: Wait For Event
바운싱 현상
버튼 누름 시 실제 신호:
이상적: VCC ─────┐
└──── GND
실제: VCC ─────┐┌┐┌┐
└┘└┘└── GND
↑↑↑
수 ms ~ 수십 ms 동안 반복
EXTI 기준으로 수십 회 인터럽트 발생
타이머 디바운싱 원리
1. EXTI 인터럽트 발생 (첫 번째 엣지)
2. 타이머 시작 (예: 20ms)
3. 타이머 만료 전 추가 엣지 발생 시 → 타이머 재시작 (무시)
4. 타이머 만료 시 → 안정된 신호로 판단 → 실제 처리 수행
구현 예시
// 전역 변수
volatile uint8_t button_pressed = 0;
volatile uint32_t debounce_tick = 0;
#define DEBOUNCE_DELAY_MS 20U
// EXTI 콜백 (ISR 내에서 호출됨)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13)
{
debounce_tick = HAL_GetTick(); // 현재 시간 기록
button_pressed = 1; // 처리 요청 플래그
}
}
// 메인 루프에서 디바운싱 처리
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
if (button_pressed)
{
// DEBOUNCE_DELAY_MS 이상 경과했는지 확인
if ((HAL_GetTick() - debounce_tick) >= DEBOUNCE_DELAY_MS)
{
button_pressed = 0;
// 실제 GPIO 상태 재확인 (안정 상태 검증)
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET)
{
// 버튼 눌림 처리
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}
}
}
}
}
보다 견고한 디바운싱 구현
typedef enum
{
BTN_IDLE = 0,
BTN_DEBOUNCING,
BTN_PRESSED,
BTN_RELEASED
} ButtonState_t;
typedef struct
{
ButtonState_t state;
uint32_t timestamp;
uint8_t event_flag; // 메인 루프에서 소비할 이벤트
} Button_t;
Button_t button = {BTN_IDLE, 0, 0};
#define DEBOUNCE_DELAY_MS 20U
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13)
{
if (button.state == BTN_IDLE || button.state == BTN_RELEASED)
{
button.state = BTN_DEBOUNCING;
button.timestamp = HAL_GetTick();
}
else if (button.state == BTN_DEBOUNCING)
{
// 바운싱 기간 중 재발생 → 타이머 리셋
button.timestamp = HAL_GetTick();
}
}
}
void Button_Process(Button_t *btn)
{
if (btn->state == BTN_DEBOUNCING)
{
if ((HAL_GetTick() - btn->timestamp) >= DEBOUNCE_DELAY_MS)
{
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET)
{
btn->state = BTN_PRESSED;
btn->event_flag = 1; // 이벤트 발생 알림
}
else
{
btn->state = BTN_IDLE; // 노이즈로 판단, 무시
}
}
}
else if (btn->state == BTN_PRESSED)
{
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
{
btn->state = BTN_RELEASED;
}
}
else if (btn->state == BTN_RELEASED)
{
btn->state = BTN_IDLE;
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
Button_Process(&button);
if (button.event_flag)
{
button.event_flag = 0;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LED 토글
}
}
}
EXTI9_5, EXTI15_10 공유 핸들러
// 여러 핀이 동일 IRQ를 공유할 때
void EXTI15_10_IRQHandler(void)
{
// HAL_GPIO_EXTI_IRQHandler 내부에서 GPIO_Pin을 확인하고
// Pending 비트를 클리어한 뒤 콜백 호출
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
// 콜백에서 핀 구분
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch (GPIO_Pin)
{
case GPIO_PIN_10:
// EXTI10 처리
break;
case GPIO_PIN_11:
// EXTI11 처리
break;
case GPIO_PIN_13:
// EXTI13 처리 (예: 사용자 버튼)
break;
default:
break;
}
}
직접 Pending 비트 확인 방식
void EXTI15_10_IRQHandler(void)
{
// 어떤 라인이 발생했는지 PR 레지스터로 직접 확인
if (EXTI->PR & EXTI_PR_PR13)
{
EXTI->PR |= EXTI_PR_PR13; // 클리어
button_flag = 1;
}
if (EXTI->PR & EXTI_PR_PR10)
{
EXTI->PR |= EXTI_PR_PR10; // 클리어
sensor_flag = 1;
}
}
포트 충돌 발생 예시와 해결
요구사항: PA3 (센서 입력) + PB3 (버튼) 동시 EXTI 사용
문제: 둘 다 EXTI Line 3을 공유 → 동시 사용 불가
해결 방법 1: 핀 번호 변경
PB3 대신 PB7 사용 → PA3 (EXTI3), PB7 (EXTI7) 독립 사용
해결 방법 2: 폴링 방식 혼용
우선순위가 낮은 신호는 EXTI 대신 메인 루프에서 폴링 처리
해결 방법 3: 하드웨어 논리 게이트
두 신호를 AND/OR 게이트로 결합하여 단일 핀으로 수신 (설계 단계에서 결정)
시나리오: 3개 버튼, 3개 LED
// 핀 구성
// PA0: Button1 (EXTI0, Falling Edge, 풀업)
// PB4: Button2 (EXTI4, Falling Edge, 풀업)
// PC5: Button3 (EXTI9_5 공유, Falling Edge, 풀업)
// PA5, PA6, PA7: LED1, LED2, LED3 (Push-Pull 출력)
volatile uint8_t btn1_flag = 0;
volatile uint8_t btn2_flag = 0;
volatile uint8_t btn3_flag = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch (GPIO_Pin)
{
case GPIO_PIN_0: btn1_flag = 1; break;
case GPIO_PIN_4: btn2_flag = 1; break;
case GPIO_PIN_5: btn3_flag = 1; break;
default: break;
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
if (btn1_flag) { btn1_flag = 0; HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); }
if (btn2_flag) { btn2_flag = 0; HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6); }
if (btn3_flag) { btn3_flag = 0; HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_7); }
}
}
누름/해제 시간 측정
volatile uint32_t press_time = 0;
volatile uint32_t release_time = 0;
volatile uint8_t long_press = 0;
#define LONG_PRESS_MS 1000U
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13)
{
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET)
{
// 하강 엣지: 버튼 눌림
press_time = HAL_GetTick();
}
else
{
// 상승 엣지: 버튼 해제
release_time = HAL_GetTick();
if ((release_time - press_time) >= LONG_PRESS_MS)
{
long_press = 1; // 길게 누름
}
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // GPIO_MODE_IT_RISING_FALLING으로 설정
while (1)
{
if (long_press)
{
long_press = 0;
// 긴 누름 처리 (예: 설정 초기화)
}
}
}
런타임 EXTI 상태 출력
void print_exti_status(uint32_t line_mask)
{
printf("=== EXTI Status (mask: 0x%08lX) ===\r\n", line_mask);
printf("IMR : 0x%08lX\r\n", EXTI->IMR & line_mask);
printf("EMR : 0x%08lX\r\n", EXTI->EMR & line_mask);
printf("RTSR : 0x%08lX\r\n", EXTI->RTSR & line_mask);
printf("FTSR : 0x%08lX\r\n", EXTI->FTSR & line_mask);
printf("PR : 0x%08lX\r\n", EXTI->PR & line_mask);
}
// 사용 예
debug_all_exti:
print_exti_status(0x0000FFFF); // EXTI 0~15 확인
SYSCFG EXTICR 설정 확인
void print_syscfg_exticr(void)
{
printf("SYSCFG EXTICR:\r\n");
printf("EXTICR[0] (EXTI 0~3): 0x%08lX\r\n", SYSCFG->EXTICR[0]);
printf("EXTICR[1] (EXTI 4~7): 0x%08lX\r\n", SYSCFG->EXTICR[1]);
printf("EXTICR[2] (EXTI 8~11): 0x%08lX\r\n", SYSCFG->EXTICR[2]);
printf("EXTICR[3] (EXTI 12~15): 0x%08lX\r\n", SYSCFG->EXTICR[3]);
}
문제 1: ISR이 한 번만 실행되고 이후 동작 안 함
// 원인: Pending 비트 미클리어
// HAL 사용 시 HAL_GPIO_EXTI_IRQHandler() 누락
// 잘못된 예시:
void EXTI0_IRQHandler(void)
{
button_flag = 1; // Pending 미클리어 → 다음 엣지에서 재진입 불가
}
// 올바른 예시:
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 내부에서 Pending 클리어
}
문제 2: 동일 핀 번호 다른 포트 충돌
// 원인: PA5, PB5 동시 EXTI 설정 시 마지막 설정만 유효
// SYSCFG->EXTICR[1]의 EXTI5 필드는 하나뿐
// 진단:
printf("EXTICR[1]: 0x%08lX\r\n", SYSCFG->EXTICR[1]);
// EXTI5 필드(비트 7:4)가 예상 포트 값인지 확인
// 해결: 핀 번호 충돌 없도록 설계 단계에서 GPIO 핀 재배치
문제 3: 바운싱으로 인한 과도한 ISR 진입
// 진단: ISR 진입 횟수 카운터로 확인
volatile uint32_t exti_count = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13)
{
exti_count++;
}
}
// 버튼 1회 누름 후 exti_count 확인
// 1 이상이면 바운싱 발생 → 디바운싱 로직 추가
문제 4: EXTI9_5 또는 EXTI15_10 IRQ 핸들러에서 일부 핀 무시
// 원인: 공유 핸들러에서 사용 중인 핀을 모두 HAL 함수에 전달하지 않음
// 잘못된 예시:
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); // PIN_10, PIN_11 누락
// PIN_10, PIN_11의 Pending 비트가 클리어되지 않아 폭주
}
// 올바른 예시:
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
EXTI 구조
트리거와 모드
공유 IRQ 핸들러
디바운싱
1. EXTI 설정 순서
// CubeMX 사용 시 자동 생성, 수동 설정 시 순서:
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // 1. SYSCFG 클럭
SYSCFG->EXTICR[n] |= PORT_CODE; // 2. 포트 선택
EXTI->RTSR |= (1 << line) or EXTI->FTSR; // 3. 트리거 설정
EXTI->IMR |= (1 << line); // 4. 인터럽트 마스크 해제
NVIC_SetPriority(EXTIn_IRQn, priority); // 5. 우선순위
NVIC_EnableIRQ(EXTIn_IRQn); // 6. NVIC 활성화
2. ISR 필수 처리
void EXTIn_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_n); // Pending 클리어 + 콜백
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_n)
{
flag = 1; // 최소한의 처리만
}
}
3. EXTI 라인 공유 제약
PA0, PB0, PC0 → 모두 EXTI Line 0 공유 → 하나만 선택 가능
PA13, PB13 → 모두 EXTI Line 13 공유 → 하나만 선택 가능
설계 시 GPIO 핀 번호가 겹치지 않도록 핀 배치 결정 필요
| EXTI 라인 | IRQ 핸들러 | 비고 |
|---|---|---|
| 0 | EXTI0_IRQHandler | 개별 전용 |
| 1 | EXTI1_IRQHandler | 개별 전용 |
| 2 | EXTI2_IRQHandler | 개별 전용 |
| 3 | EXTI3_IRQHandler | 개별 전용 |
| 4 | EXTI4_IRQHandler | 개별 전용 |
| 5 ~ 9 | EXTI9_5_IRQHandler | 5개 공유 |
| 10 ~ 15 | EXTI15_10_IRQHandler | 6개 공유 |
| 16 | PVD_IRQHandler | PVD 전용 |
| 17 | RTC_Alarm_IRQHandler | RTC Alarm 전용 |
| 18 | OTG_FS_WKUP_IRQHandler | USB FS Wakeup |
| 22 | RTC_WKUP_IRQHandler | RTC Wakeup Timer |