"버튼만 누르면 시스템이 멈춰요!" 인터럽트 함수 내 금기 사항을 배우고, 메인 루프와 협업하는 'Flag 기반 설계' 가이드를 확인하세요.
신입 개발자들이 인터럽트(EXTI)를 처음 배우고 가장 먼저 하는 일이 뭘까요? 아마 버튼 콜백 함수 안에 `printf`를 넣거나 `HAL_Delay`를 넣는 일일 겁니다. 결과는? 시스템 먹통이죠. 오늘은 인터럽트의 핵심 철학인 "치고 빠지기"를 어떻게 코드로 구현하는지 상세히 알려드릴게요. 😊
1. ISR의 황금률: "가장 짧게, 가장 빠르게" 🏃♂️
인터럽트 서비스 루틴(ISR)은 CPU의 긴급 상황입니다. 이 상황에서 `HAL_Delay()` 같은 함수를 호출하는 것은 응급실 환자를 수술하다가 의사가 잠을 자러 가는 것과 같습니다.
⚠️ 주의하세요!
인터럽트 내부에서 시스템 틱(Systick)보다 우선순위가 높은 작업을 처리할 때
인터럽트 내부에서 시스템 틱(Systick)보다 우선순위가 높은 작업을 처리할 때
HAL_Delay를 쓰면 무한 루프에 빠져 시스템이 정지됩니다. printf 또한 내부적으로 처리가 길어 ISR에는 적합하지 않습니다.
2. 소프트웨어 채터링(Debouncing) 해결법 🛠️
버튼을 한 번 눌렀는데 전등이 두 번 켜진다면? 그건 채터링(Bouncing) 현상 때문입니다. 기계식 접점이 붙을 때 미세하게 떨리는 현상이죠.
우리는 하드웨어 커패시터 없이 오직 소프트웨어 타이머만으로 이 문제를 해결할 겁니다. 인터럽트 발생 시점의 시간을 기록하고, 일정 시간(예: 50ms) 이내의 입력은 무시하는 전략이죠.
3. 코드 구현: Before & After 👩💻
안 좋은 예시 (Before) ❌
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == B1_Pin) {
HAL_Delay(50); // 시스템 멈춤의 원인!
printf("Button Pressed!\n"); // ISR에서 너무 긴 작업
}
}
좋은 예시 (After) ✅
// ISR은 오직 깃발(Flag)만 세웁니다.
volatile uint8_t buttonPressedFlag = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
static uint32_t lastTick = 0;
uint32_t currentTick = HAL_GetTick();
if(currentTick - lastTick > 50) { // S/W Debouncing
buttonPressedFlag = 1;
lastTick = currentTick;
}
}
// 실제 처리는 main 루프에서!
while (1) {
if(buttonPressedFlag) {
ProcessButtonAction(); // 무거운 작업 수행 가능
buttonPressedFlag = 0;
}
}
글의 핵심 요약 📝
- ISR "치고 빠지기": 모든 인터럽트 함수는 최소한의 코드만 담아야 합니다.
- Flag 활용: 무거운 작업(통신, 연산)은 메인 루프에 위임하세요.
- S/W 디바운싱:
HAL_GetTick()을 활용해 불필요한 입력을 걸러내세요.
자주 묻는 질문 ❓
Q: volatile 키워드는 왜 쓰나요?
A: 컴파일러가 변수를 마음대로 최적화(생략)하지 못하게 하기 위해서입니다. ISR과 메인 루프가 공유하는 변수에는 반드시 붙여야 합니다.
Q: 인터럽트 우선순위는 어떻게 설정하나요?
A: STM32CubeIDE의 NVIC 설정에서 가능합니다. 중요한 건 Systick보다 낮게 설정해야 딜레이 함수가 꼬이지 않는다는 점입니다.
버튼 하나에도 이런 아키텍처가 숨어 있답니다. 😊

