STM32 #11

홍태준·2026년 2월 19일

STM32

목록 보기
11/15
post-thumbnail

Week 3 Day 1: NVIC 구조, 인터럽트 우선순위 설정

학습 목표

  • ARM Cortex-M의 NVIC(Nested Vectored Interrupt Controller) 구조를 이해한다
  • 인터럽트 벡터 테이블의 역할과 구성을 파악한다
  • Preempt Priority와 Sub Priority의 차이를 이해하고 설정한다
  • Priority Group 개념을 이해하고 시스템 요구사항에 맞게 구성한다
  • HAL 라이브러리를 통한 NVIC 설정 방법을 익힌다

시작하기 앞서 ARM cortex-M의 NVIC(Nested Vector Interrupt Controller) 구조를 설명하자면 NVIC는 인터럽트를 효율적으로 관리하기 위해 CPU와 밀접하게 결합된 하드웨어 모듈입니다. 중첩 인터럽트(Nested Inturrupts) 구조로 더 높은 우선순위의 인터럽트가 발생하면 현재 실행 중인 인터럽트 서비스 루틴(ISR)을 중단하고 즉시 실행시키는 방식에 더해 백터화된 처리(Vectored) 방법을 사용해 인터럽트 발생 시 하드웨어가 직접 해당 ISR의 주소(Vector)를 찾아가기 때문에 소프트웨어적으로 분기 처리할 필요가 없어 지연 시간(Latency)이 짧은 특징이 있습니다.

1. 인터럽트 기본 개념

1.1 인터럽트란

폴링(Polling) vs 인터럽트(Interrupt)

폴링 방식:
CPU가 주기적으로 상태를 확인
while (1)
{
    if (button_pressed())  // 계속 확인
        do_something();
}
단점: CPU 자원 낭비, 다른 작업 불가

인터럽트 방식:
이벤트 발생 시 CPU에 신호 → CPU가 현재 작업 중단 → ISR 실행 → 복귀
장점: CPU 효율적 사용, 빠른 응답, 다중 이벤트 처리

인터럽트 처리 흐름

정상 실행 중
    ↓
인터럽트 발생 (버튼, 타이머, UART 등)
    ↓
현재 상태 저장 (Context Save) → 스택에 PC, LR, R0-R3, R12, xPSR 자동 저장
    ↓
ISR(Interrupt Service Routine) 실행
    ↓
현재 상태 복원 (Context Restore)
    ↓
인터럽트 발생 지점으로 복귀

1.2 STM32 인터럽트 종류

Exception vs Interrupt

Exception은 CPU가 처리해야 하는 모든 예외적인 상황을 아우르는 가장 큰 개념이며, Interrupt는 그 중 일부인 외부 신호를 의미합니다.

Exception (예외): ARM Cortex-M 코어 내부에서 발생
- Reset
- NMI (Non-Maskable Interrupt)
- HardFault
- MemManage
- BusFault
- UsageFault
- SVCall
- PendSV
- SysTick

Interrupt (인터럽트): 외부 주변장치(Peripheral)에서 발생
- EXTI (External Interrupt) - GPIO 핀
- TIM (Timer)
- USART
- SPI, I2C
- ADC
- DMA
- 기타 주변장치

IRQ(Interrupt Request Queue) 번호

Exception 번호: 음수 (ARM 정의)
  Reset    = -15
  NMI      = -14
  HardFault = -13
  SysTick  = -1

Interrupt 번호: 양수 (제조사 정의, STM32F4 기준)
  WWDG     = 0
  EXTI0    = 6
  TIM1_UP  = 25
  USART1   = 37
  ...

2. NVIC 구조

2.1 NVIC 개요

NVIC(Nested Vectored Interrupt Controller)

ARM Cortex-M의 인터럽트 컨트롤러

핵심 기능:
1. 인터럽트 활성화/비활성화
2. 우선순위 설정
3. 인터럽트 중첩(Nesting) 처리
4. 인터럽트 보류(Pending) 상태 관리
5. 벡터 테이블을 통한 ISR 주소 관리

특징:
- 최대 240개의 외부 인터럽트 지원 (STM32F4는 약 90개 사용)
- 하드웨어 기반 우선순위 처리 → 소프트웨어 오버헤드 최소화
- Tail-chaining: 연속된 인터럽트를 효율적으로 처리
- Late-arriving: 더 높은 우선순위 인터럽트 자동 처리

NVIC 레지스터 구성

ISER (Interrupt Set-Enable Register)    - 인터럽트 활성화
ICER (Interrupt Clear-Enable Register)  - 인터럽트 비활성화
ISPR (Interrupt Set-Pending Register)   - 소프트웨어로 인터럽트 발생
ICPR (Interrupt Clear-Pending Register) - Pending 상태 제거
IABR (Interrupt Active Bit Register)    - 현재 처리 중인 인터럽트 확인
IPR  (Interrupt Priority Register)      - 우선순위 설정

접근 방법 (직접):
NVIC->ISER[0] |= (1 << IRQ_NUMBER);  // 활성화
NVIC->IPR[n]   = priority << 4;       // 우선순위 (상위 4비트 사용)

접근 방법 (HAL):
HAL_NVIC_EnableIRQ(IRQn);
HAL_NVIC_SetPriority(IRQn, preempt, sub);

2.2 벡터 테이블

벡터 테이블 구조

플래시 시작 주소(0x08000000)에 위치

오프셋   내용
0x0000   스택 초기 주소 (MSP)
0x0004   Reset_Handler 주소
0x0008   NMI_Handler 주소
0x000C   HardFault_Handler 주소
0x0010   MemManage_Handler 주소
0x0014   BusFault_Handler 주소
0x0018   UsageFault_Handler 주소
...
0x0040   SysTick_Handler 주소
0x0044   WWDG_IRQHandler 주소        (IRQ 0)
0x0048   PVD_IRQHandler 주소         (IRQ 1)
...
0x009C   EXTI0_IRQHandler 주소       (IRQ 6)
...

인터럽트 발생 시:
1. NVIC가 해당 IRQ 번호 확인
2. 벡터 테이블에서 ISR 주소 읽기
3. 해당 주소로 점프하여 ISR 실행

startup_stm32f4xx.s 에서의 벡터 테이블

; startup 파일에서 실제 벡터 테이블 정의
g_pfnVectors:
  .word  _estack              ; 스택 최상위 주소
  .word  Reset_Handler
  .word  NMI_Handler
  .word  HardFault_Handler
  ; ...
  .word  EXTI0_IRQHandler
  ; ...
  .word  TIM1_UP_TIM10_IRQHandler
  ; ...
  .word  USART1_IRQHandler

벡터 테이블 재배치 (VTOR)

// 부트로더 사용 시 벡터 테이블 주소 변경
// SCB->VTOR 레지스터에 새 주소 기록
SCB->VTOR = FLASH_BASE | 0x20000;  // 예: 0x08020000

// CubeMX 생성 코드에서는 SystemInit() 내에서 처리됨

3. 인터럽트 우선순위

3.1 Priority 비트 구성

Priority 레지스터 구조

ARM Cortex-M의 IPR 레지스터: 8비트
STM32F4는 상위 4비트만 사용 (하위 4비트 무시)

8비트 중 사용: [7:4] → 0~15 단계
값이 낮을수록 우선순위가 높음

비교:
IPR = 0x00 → 최고 우선순위 (0)
IPR = 0xF0 → 최저 우선순위 (15)

Priority 비트 분할: Priority Group

4비트를 두 그룹으로 분할:
1. Preempt Priority (선점 우선순위): 상위 비트
2. Sub Priority (부 우선순위): 하위 비트

Priority Group 설정 (SCB->AIRCR 레지스터):
NVIC_PRIORITYGROUP_0: [0비트 선점 / 4비트 서브]  → 선점 0단계, 서브 0~15
NVIC_PRIORITYGROUP_1: [1비트 선점 / 3비트 서브]  → 선점 0~1,  서브 0~7
NVIC_PRIORITYGROUP_2: [2비트 선점 / 2비트 서브]  → 선점 0~3,  서브 0~3
NVIC_PRIORITYGROUP_3: [3비트 선점 / 1비트 서브]  → 선점 0~7,  서브 0~1
NVIC_PRIORITYGROUP_4: [4비트 선점 / 0비트 서브]  → 선점 0~15, 서브 없음 (기본값)

3.2 Preempt Priority vs Sub Priority

Preempt Priority (선점 우선순위)

핵심 개념: 인터럽트 중첩(Nesting) 결정

낮은 Preempt Priority 값 = 더 높은 우선순위

동작:
ISR_A 실행 중 → ISR_B 발생
  - ISR_B의 Preempt Priority < ISR_A  → ISR_A 중단, ISR_B 먼저 실행 (선점)
  - ISR_B의 Preempt Priority >= ISR_A → ISR_B는 ISR_A 완료 후 실행 (선점 불가)

예시 (Priority Group 4 기준):
TIM1_IRQ : Preempt = 1  (높은 우선순위)
USART1_IRQ: Preempt = 3  (낮은 우선순위)

USART1 ISR 실행 중 → TIM1 인터럽트 발생
→ USART1 ISR 중단 → TIM1 ISR 실행 → 복귀 → USART1 ISR 재개

Sub Priority (부 우선순위)

핵심 개념: 동일 Preempt Priority에서의 처리 순서 결정
           인터럽트 중첩에는 영향을 주지 않음

동작:
동일 Preempt Priority를 가진 인터럽트 A, B가 동시 발생
  - Sub Priority가 낮은 쪽이 먼저 처리됨
  - 어느 쪽도 상대방을 선점하지 못함

예시:
EXTI0_IRQ: Preempt = 2, Sub = 0  (먼저 처리)
EXTI1_IRQ: Preempt = 2, Sub = 1  (나중 처리)

EXTI0, EXTI1 동시 발생 → EXTI0 먼저, EXTI1 대기 → EXTI0 완료 후 EXTI1 실행
단, EXTI1 ISR 실행 중 EXTI0 발생해도 선점 불가 (Preempt 동일)

우선순위 비교 요약

                Preempt Priority    Sub Priority
선점 가능 여부        O                  X
동시 발생 처리순서    O                  O
값이 낮을수록         높은 우선순위      높은 우선순위

3.3 Priority Group 선택 기준

실무 가이드라인

NVIC_PRIORITYGROUP_4 (기본값, 권장):
- 선점 0~15단계, 서브 없음
- 단순하고 직관적
- 중첩 처리만 필요한 대부분의 경우 적합
- CubeMX 기본 설정

NVIC_PRIORITYGROUP_2:
- 선점 0~3단계, 서브 0~3단계
- 중첩과 동시 발생 모두 세분화 필요한 경우

NVIC_PRIORITYGROUP_0:
- 선점 없음, 서브만 15단계
- 인터럽트 중첩을 허용하지 않을 때
- 단순한 시스템

주의: Priority Group은 시스템 전체에서 하나만 설정
     실행 중 변경 금지 (예측 불가 동작)

4. HAL 라이브러리를 통한 NVIC 설정

4.1 CubeMX 설정

NVIC 설정 화면

1. System Core → NVIC 선택
2. Priority Group 설정
   - NVIC → Priority Group 드롭다운에서 선택
   - 기본값: 4 bits for pre-emption priority, 0 bits for subpriority

3. 각 인터럽트 설정
   - Enable 체크박스 체크
   - Preemption Priority 값 입력 (0이 최고)
   - Sub Priority 값 입력

4. Code Generation → 자동으로 MX_NVIC_Init() 생성

주요 인터럽트 권장 우선순위 (Priority Group 4 기준)

SysTick     : Preempt = 15  (가장 낮게, HAL_Delay 기반)
USART1      : Preempt = 5   (빠른 응답 필요)
TIM 일반    : Preempt = 6
EXTI (버튼) : Preempt = 10  (낮은 우선순위 무방)
DMA         : Preempt = 4   (데이터 손실 방지)

4.2 HAL NVIC 함수

기본 함수

// 우선순위 그룹 설정 (시스템 초기화 시 1회만)
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

// 우선순위 설정
// IRQn: 인터럽트 번호 (IRQn_Type 열거형)
// PreemptPriority: 선점 우선순위
// SubPriority: 부 우선순위
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);

// 인터럽트 활성화
HAL_NVIC_EnableIRQ(USART1_IRQn);

// 인터럽트 비활성화
HAL_NVIC_DisableIRQ(USART1_IRQn);

// 소프트웨어로 인터럽트 발생 (테스트용)
HAL_NVIC_SetPendingIRQ(USART1_IRQn);

// Pending 상태 확인
uint32_t pending = HAL_NVIC_GetPendingIRQ(USART1_IRQn);

// Pending 상태 제거
HAL_NVIC_ClearPendingIRQ(USART1_IRQn);

// 현재 활성(Active) 인터럽트 확인
uint32_t active = HAL_NVIC_GetActive(USART1_IRQn);

// 우선순위 읽기
uint32_t preempt, sub;
HAL_NVIC_GetPriority(USART1_IRQn, NVIC_PRIORITYGROUP_4, &preempt, &sub);

전역 인터럽트 제어

// 모든 인터럽트 비활성화 (임계 구역 진입)
__disable_irq();  // 또는 PRIMASK 레지스터 직접 조작

// 모든 인터럽트 활성화 (임계 구역 종료)
__enable_irq();

// 임계 구역 예시 (공유 변수 보호)
void update_shared_data(void)
{
    __disable_irq();
    shared_variable = new_value;  // 원자적 처리 보장
    __enable_irq();
}

// NMI, HardFault를 제외한 인터럽트 마스킹 (BASEPRI 레지스터)
// 특정 우선순위 이하만 마스킹 (FreeRTOS에서 사용)
__set_BASEPRI(priority_value);
__set_BASEPRI(0);  // 마스킹 해제

4.3 ISR 구현

ISR 함수명 규칙

// 함수명은 startup 파일의 벡터 테이블과 반드시 일치
// stm32f4xx_it.c 파일에 작성

void EXTI0_IRQHandler(void)         // EXTI 0번 핀
void EXTI1_IRQHandler(void)         // EXTI 1번 핀
void EXTI15_10_IRQHandler(void)     // EXTI 10~15번 핀 (공유)
void TIM1_UP_TIM10_IRQHandler(void) // TIM1 Update, TIM10
void USART1_IRQHandler(void)        // USART1
void DMA1_Stream0_IRQHandler(void)  // DMA1 Stream0

HAL 방식 ISR 구현

// stm32f4xx_it.c
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);  // HAL 핸들러 호출
    // HAL이 내부적으로 Pending 클리어 후 콜백 호출
}

// main.c 또는 별도 파일
// 콜백 함수 오버라이드 (__weak 키워드 + 함수)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        // 실제 처리 코드 작성
        button_flag = 1;
    }
}

직접 ISR 구현 (HAL 미사용)

void EXTI0_IRQHandler(void)
{
    // Pending 비트 확인
    if (EXTI->PR & EXTI_PR_PR0)
    {
        // Pending 비트 클리어 (1을 써서 클리어)
        EXTI->PR |= EXTI_PR_PR0;
        
        // 처리 코드
        button_flag = 1;
    }
}

5. 실전 예제

5.1 다중 우선순위 인터럽트 설정

시나리오: 모터 제어 + 통신 + 사용자 입력

// 우선순위 계획 (Priority Group 4)
// 
// Preempt 0: 예약 (사용 금지, NMI 대비)
// Preempt 1: 모터 PWM 타이머 (최고 시간 정밀도 필요)
// Preempt 2: DMA 완료 인터럽트 (데이터 손실 방지)
// Preempt 3: USART RX (통신 데이터 손실 방지)
// Preempt 5: ADC 변환 완료
// Preempt 10: 버튼 입력 (느린 응답 무방)
// Preempt 15: SysTick (HAL 타임베이스)

void system_nvic_init(void)
{
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
    
    // 모터 제어 타이머 (TIM1 Update)
    HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
    
    // DMA1 Stream5 (USART1 RX)
    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
    
    // USART1
    HAL_NVIC_SetPriority(USART1_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
    
    // ADC
    HAL_NVIC_SetPriority(ADC_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(ADC_IRQn);
    
    // 버튼 (EXTI0)
    HAL_NVIC_SetPriority(EXTI0_IRQn, 10, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

5.2 인터럽트 중첩 확인 예제

중첩 동작 확인 코드

// 두 타이머 인터럽트로 중첩 동작 확인
// TIM2: Preempt 1 (높은 우선순위)
// TIM3: Preempt 3 (낮은 우선순위)

volatile uint32_t tim2_enter_count = 0;
volatile uint32_t tim3_enter_count = 0;
volatile uint8_t  nesting_detected = 0;
volatile uint8_t  in_tim3_isr = 0;

void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&htim2);
}

void TIM3_IRQHandler(void)
{
    in_tim3_isr = 1;
    tim3_enter_count++;
    HAL_TIM_IRQHandler(&htim3);
    in_tim3_isr = 0;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        tim2_enter_count++;
        
        // TIM3 ISR 실행 중에 TIM2가 선점했는지 확인
        if (in_tim3_isr)
        {
            nesting_detected = 1;
            printf("Nesting! TIM2 preempted TIM3\r\n");
        }
        
        HAL_Delay(1);  // 의도적 지연 (중첩 유발)
    }
    
    if (htim->Instance == TIM3)
    {
        printf("TIM3 count: %lu\r\n", tim3_enter_count);
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_TIM2_Init();
    MX_TIM3_Init();
    
    // TIM2: 높은 우선순위, 빠른 주기
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    
    // TIM3: 낮은 우선순위, 느린 주기
    HAL_NVIC_SetPriority(TIM3_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
    
    HAL_TIM_Base_Start_IT(&htim2);
    HAL_TIM_Base_Start_IT(&htim3);
    
    while (1)
    {
        if (nesting_detected)
        {
            printf("Nesting confirmed. TIM2: %lu, TIM3: %lu\r\n",
                   tim2_enter_count, tim3_enter_count);
            nesting_detected = 0;
        }
        
        HAL_Delay(1000);
    }
}

5.3 우선순위 충돌 방지

HAL 함수 사용 시 주의사항

// HAL_Delay()는 SysTick 인터럽트를 사용
// ISR 내에서 HAL_Delay() 호출 시 SysTick 우선순위가 ISR보다 낮으면 무한 대기

// 잘못된 예시:
// SysTick Priority = 15, USART1 Priority = 3
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    HAL_Delay(100);  // SysTick(15)이 USART1(3)보다 낮아 동작 안 함 (교착)
}

// 올바른 해결책 1: HAL_Delay 대신 루프 사용
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    // ISR 내부는 최대한 빠르게 처리, 지연 금지
    rx_flag = 1;  // 플래그만 설정
}

// 올바른 해결책 2: SysTick 우선순위를 가장 높게 설정
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);  // SysTick 최고 우선순위

// CubeMX에서 uwTickPrio 변수 확인 (기본값: 15)
// HAL_Init() 내부에서 설정됨

6. NVIC 디버깅

6.1 우선순위 확인

런타임 우선순위 확인

void print_nvic_info(IRQn_Type irq)
{
    uint32_t preempt, sub;
    
    HAL_NVIC_GetPriority(irq, NVIC_PRIORITYGROUP_4, &preempt, &sub);
    
    uint32_t enabled = NVIC_GetEnableIRQ(irq);
    uint32_t pending = NVIC_GetPendingIRQ(irq);
    uint32_t active  = NVIC_GetActive(irq);
    
    printf("IRQ %d: Preempt=%lu, Sub=%lu, En=%lu, Pending=%lu, Active=%lu\r\n",
           (int)irq, preempt, sub, enabled, pending, active);
}

void debug_all_nvic(void)
{
    printf("\r\n=== NVIC Status ===\r\n");
    print_nvic_info(TIM1_UP_TIM10_IRQn);
    print_nvic_info(USART1_IRQn);
    print_nvic_info(EXTI0_IRQn);
    print_nvic_info(DMA1_Stream5_IRQn);
    printf("Priority Group: %lu\r\n", HAL_NVIC_GetPriorityGrouping());
}

6.2 일반적인 실수와 해결

문제 1: ISR이 실행되지 않음

// 진단 순서:
// 1. NVIC EnableIRQ 호출 여부 확인
printf("TIM2 Enable: %lu\r\n", NVIC_GetEnableIRQ(TIM2_IRQn));

// 2. 주변장치 인터럽트 활성화 여부 확인 (예: 타이머)
// TIM->DIER 레지스터의 UIE 비트 확인
printf("TIM2 DIER: 0x%08lX\r\n", TIM2->DIER);

// 3. Pending 비트가 클리어되는지 확인
// ISR 진입 후 Pending 미클리어 시 즉시 재진입
// HAL_XXX_IRQHandler() 내부에서 자동 처리됨

// 4. 벡터 테이블의 함수명 확인
// startup 파일과 stm32f4xx_it.c 함수명 일치 여부

문제 2: 예상치 못한 HardFault 발생

// ISR 내에서 스택 오버플로우 발생 가능
// ISR은 별도 스택 프레임 사용 (자동 저장: 8 레지스터 × 4바이트 = 32바이트)
// ISR 내 지역 변수 최소화

// HardFault 핸들러에서 원인 파악
void HardFault_Handler(void)
{
    // MSP, PSP 레지스터로 스택 상태 확인
    volatile uint32_t* sp = (uint32_t*)__get_MSP();
    
    printf("HardFault!\r\n");
    printf("PC : 0x%08lX\r\n", sp[6]);  // 스택에 자동 저장된 PC
    printf("LR : 0x%08lX\r\n", sp[5]);
    printf("PSR: 0x%08lX\r\n", sp[7]);
    
    while (1);  // 무한 루프로 디버거 연결 대기
}

문제 3: 인터럽트 폭주 (Interrupt Storm)

// 증상: 시스템이 응답 없이 ISR만 반복 실행
// 원인: ISR 내에서 Pending 비트 미클리어

// EXTI 예시:
void EXTI0_IRQHandler(void)
{
    // Pending 비트 클리어를 누락하면 즉시 재진입
    // EXTI->PR |= EXTI_PR_PR0;  // 이 줄 없으면 폭주
    
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);  // HAL이 자동 클리어
}

// 타이머 예시:
void TIM2_IRQHandler(void)
{
    // Update Flag 클리어 없으면 폭주
    // __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);  // HAL이 처리
    
    HAL_TIM_IRQHandler(&htim2);  // 내부에서 자동 클리어
}

7. ISR 작성 원칙

7.1 ISR 설계 지침

ISR 내부에서 해야 할 것과 하지 말아야 할 것

해야 할 것:
- 빠른 처리 (수 마이크로초 이내)
- 플래그 변수 설정
- 하드웨어 Pending 비트 클리어
- 링 버퍼에 데이터 저장
- 간단한 상태 업데이트

하지 말아야 할 것:
- HAL_Delay() 등 블로킹 함수 호출
- printf() 직접 호출 (UART 블로킹)
- 복잡한 연산
- 동적 메모리 할당 (malloc/free)
- 무한 루프

플래그 기반 처리 패턴

// ISR: 최소한의 처리만
volatile uint8_t uart_rx_flag = 0;
volatile uint8_t uart_rx_data = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        uart_rx_data = rx_buffer;  // 데이터 저장
        uart_rx_flag = 1;          // 플래그 설정
        // 다음 수신 준비
        HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);
    }
}

// 메인 루프: 실제 처리
int main(void)
{
    // ...
    
    while (1)
    {
        if (uart_rx_flag)
        {
            uart_rx_flag = 0;
            process_received_data(uart_rx_data);  // 메인 루프에서 처리
        }
    }
}

volatile 키워드 사용

// ISR과 메인 루프가 공유하는 변수는 반드시 volatile 선언
// volatile 없으면 컴파일러 최적화로 변수 읽기를 생략할 수 있음

volatile uint8_t flag = 0;         // 올바른 선언
volatile uint32_t counter = 0;

// 잘못된 예시 (volatile 누락):
uint8_t flag = 0;  // 컴파일러가 최적화로 while(!flag)를 무한 루프로 만들 수 있음

// 메인 루프에서:
while (!flag);  // volatile 없으면 항상 0으로 읽을 수 있음

8. 학습 정리

오늘 배운 내용

NVIC 구조

  • ARM Cortex-M의 하드웨어 인터럽트 컨트롤러
  • 벡터 테이블을 통해 ISR 주소 관리
  • 최대 240개 외부 인터럽트 지원

우선순위 시스템

  • Priority Group으로 Preempt / Sub 비트 분할
  • Preempt Priority: 인터럽트 중첩(선점) 결정
  • Sub Priority: 동시 발생 시 처리 순서 결정
  • 값이 낮을수록 높은 우선순위

HAL 설정

  • HAL_NVIC_SetPriorityGrouping() 으로 그룹 설정
  • HAL_NVIC_SetPriority() + HAL_NVIC_EnableIRQ() 로 활성화
  • ISR 내에서는 HAL_XXX_IRQHandler() 호출

핵심 개념 요약

1. Priority Group 4 (기본값) 우선순위 범위

Preempt Priority: 0 (최고) ~ 15 (최저)
Sub Priority: 없음 (0 고정)

2. 인터럽트 활성화 순서

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); // 1회만
HAL_NVIC_SetPriority(IRQn, preempt, sub);
HAL_NVIC_EnableIRQ(IRQn);

3. ISR 설계 원칙

빠르게 처리 → 플래그 설정 → 메인 루프에서 실제 처리
공유 변수에 volatile 선언
HAL_Delay() 등 블로킹 함수 ISR 내 사용 금지

인터럽트 우선순위 설계 참고표

시스템 역할권장 Preempt Priority이유
안전 긴급 처리0 ~ 1최고 응답성 필요
실시간 제어 타이머1 ~ 2주기 정확도 필수
DMA 완료2 ~ 3데이터 손실 방지
통신(UART, SPI)3 ~ 5오버런 방지
일반 타이머5 ~ 8중간 우선순위
사용자 입력8 ~ 12느린 응답 무방
SysTick (HAL)15HAL 기본값 유지 권장
profile
당신의 코딩 메이트

0개의 댓글