[STM32] Interrupt

CS·2025년 3월 2일

STM32

목록 보기
3/8

Interrupt

MCU와 연결된 외부의 변화로 발생한 것.

Polling방식 대신, Interrupt를 통해 Busy-wait을 막을 수 있다.
즉, Core의 명령어 실행cycle보다 우선순위가 높은 작업이다.

Interrupt간에도 우선순위가 존재한다. 숫자가 낮을수록 높은 우선순위를 갖는다.
Interrupt가 발생하면 하던 일을 멈추고, Interrupt를 처리하도록 요청하는데(ISR),
이를 수행하는 논리 회로를 "Interrupt Controller"라 한다.

MCU 제조사마다, 본인들의 MCU에 특화된 컨트롤러를 개발하여 사용하는데
Cortex-M 시리즈를 사용하는 경우 NVIC(Nested Vectored Interrupt Controller)라는 공통 컨트롤러를 사용한다.
즉, 제조사 상관없이, Cortex-M Core를 사용하는 MCU는 모두 NVIC 컨트롤러를 사용하여,
code에 대한 이식성을 높일 수 있다.

기본적으로 Interrupt가 발생하면, ISR이라는 "함수"를 호출하는데, 이를 미리 정의해 두어야한다.

  • Interrupt vector table = ISR의 시작 주소들을 순서대로 저장해 둔 테이블

ISR을 마무리하면, PC가 다음에 실행할 명령어를 가르키게된다.

흐름은 컴퓨터구조에서 학습한 내용과 동일하다.

즉, 임의의 Interrupt 발생
-> NVIC가 우선순위를 따져 최종적으로 처리할 Interrupt에 대한 ISR을 호출
참고로, 1개의 Interrupt 처리 중, 다른 Interrupt를 발생시키지 않는다는 것에 주의해야 한다.

추가로, 사용자가 작성한 code에서 ISR을 호출하는 것이 아니라 framework를 통해 생성된 ISR이 따로 있다.
__weak keyword가 붙은 callback함수를 사용자 code에서 재정의하긴 한다.

main()함수의 작업보다 우선순위가 높기 때문에, ISR 처리에서 너무 오래 머물면 전체적인 시스템 동작에서 실패가 발생할 수 있다는 점도 유의해야 한다. 왜냐면 main함수 내 while문은 수십us안에 한 loop가 도는게 이상적이기 때문이다. ms단위를 넘는 경우 이상적이지 못하다.

Event

MCU 내부에서 설정한 조건에 의해 발생되는 것

물론, ISR에서 처리할 내용이 너무 많은 경우 SW적으로 event를 발생시켜, callback function을 호출해 수행할 수 있다. ISR이 호출하는 함수기에, 동일한 우선순위와 동작 mode를 가진다.

External GPIO Interrupt

외부 Interrupt와 event를 처리하는 controller는
Interrupt와 event 요청을 생성하기 위하여 각각 19개의 edge detector lines로 구성된다.

동일한 핀 번호를 갖는 [PAx, PGx]는 AFIO_EXTICR 레지스터의 동일한 EXTIx(0~15)에 연결된다.
예를 들면, AFIO_EXTICR1(1~4) 레지스터의 EXTI0[3:0]에는 PA0, PB0, PC0, ... , PG0중 하나를 Interrupt source로 선택한다(MUX).
즉, PA0랑 PB0는 동시에 Interrupt source로 사용이 불가능하다.

EXTI0부터 EXTI4까지는 각각 자신만의 Interrupt line을 가지지만,
EXTI5부터 EXTI9까지는 EXTI9_5 line 하나로,
EXTI10부터 EXTI15까지는 EXTI15_10 line 하나에 묶여있다.
framework를 통해 code를 생성해보면 확인이 가능하다.

즉 하나로 묶여있기 때문에, 공용 ISR을 사용한다
이 경우, 구분은 Interrupt callback 함수인 HAL_GPIO_EXTI_Callback()에서 구분해준다.

이런 식으로, Pin을 선택하여 입력으로 받아들이고, 내부에 Edge 검출 회로 + Falling/Rising trigger 선택 레지스터 + SW Interrupt event register등을 통해 주변장치 Interface와 연결된다. 여기서 작업들을 통해 NVIC Interrupt Controller(회로)로 전달되는 것이다.

EXTI의 경우 EXTICR(EXTernal Interrupt Configuration Register)에 존재한다.
이 레지스터의 Address offset은 0x08이며, reset value는 0x0000이다.
여기서 설정된 값에 따라 선택되어 들어간다.

즉, GPIO를 통해 외부 Interrupt가 들어오면, STM32내부에서 NVIC로 보내기 위해 event화를 진행한다.
이 event들은 우선순위를 가지고 있다.(Interrupt Vector Table)

EXTI가 낮을수록 더 높은 우선순위를 갖는 형태로 되어있다.
EXTI0~4는 따로 EXTI Linex Interrupt를 가지며, 일대일로 NVIC에 연결되어있다.

EXTI5~9는 1개의 Line을 사용해 NVIC로 공급되며,
EXTI10~15 또한 마찬가지이다.

이 16개의 GPIO EXTernal Interrut이외에도 추가적으로 외부 Interrupt와 line을 제공한다.
추가 EXTI line들은 모두 MCU가 sleep mode에 진입시 깨우기 위한 event 발생용이다.
이와 같은, event 관련 Interrupt는 추가적으로 ISR을 갖지않고,
main loop가 다시 시작하도록 MCU를 wakeup 해주도록 쓰는 것이 일반적인 사용법이다.

결론적으로, 기억해야 할 것은
GPIO EXTI가 발생하면
-> 해당 Interrupt handler(ISR)이 호출되고
-> Callback 함수가 호출되어 Interrupt를 처리 후 main으로 복귀한다.


Example

예를 들어, 보드의 Blue button(PC13)으로 GPIO EXTI를 걸고 싶다면,
PC13의 설정을 EXTI Mode With Falling edge trigger detection으로 고르고(falling edge사용시),
그에 따라서 Pull-up을 선택해 주어야 한다.
모든 GPIO Input Port는 초기 Pull-up/Pull-down 설정을 해주는 것이 안정적이다.

PC13은 EXTI15_10 Line에 물려 있으므로, CubeMX의 NVIC 설정에서 EXTI line[15:10] Interrupts를 Enable 해주어야 한다.

이후, STM32F4xx_it.c file을 열어보면 PC13에 대한 GPIO EXTI ISR인
EXTI15_10_IRQHandler(void)가 추가 된 것을 확인 가능하다.
NVIC가 이 IRQ를 관리한다.
즉, 우선순위를 기반으로 적절한 핸들러(ISR)을 실행한다.

그리고 HAL_GPIO_IRQHandler함수에 F3을 눌러 확인해보면,

HAL_GPIO_EXTI_Callback 함수가 있다. 이를 또 F3을 눌러 확인해보자.

__weak이라는 keyword로 수식되어 있음을 볼 수 있다.
이렇게 수식된 함수는, 다른 Source file에서 재정의를 하면 재정의 된 함수를
그렇지 않다면, weak 함수를 수행하는 동작을 한다.

main.c에 weak keyword없이 동일 prototype을 가진 함수를 원하는 내용으로 재정의 해주면,

Interrupt 발생시 -> EXTI15_10_IRQHandler()가 호출(ISR)
-> HAL_GPIO_EXTI_Callback 함수가 호출되며 인터럽트 처리를 수행한다.

중요한 점은, CubeMX에서 GPIO EXTI Interrupt line들을 Enable해주어야 한다는 점이다.
또한 Input pin을 Floating상태에 두면, 잘못된 Interrupt가 들어올 수도 있다는 점에 주의하여
가능하면 모든 GPIO에 Pull-up / Pull-down 설정을 잘 해주도록 하자.

이와 같은 Logic으로 TIM, UART, SPI, I2C등 주변장치에 원하는 순간 Interrupt를 걸어주고
그에 따른 ISR을 작성하여 사용하면 된다.

Interrupt Vector Table

Cortex-M 시리즈의 IVT이다.
MCU제조사와 무관히 0번부터 15번은 동일한 System exception으로 예악되어 있고,
16번부터 MCU마다 따로 만들어진 Interrupt들을 사용한다.
STM32의 경우 MCU에 따라 더 좋은 성능을 가지긴 할 수 있어도 순서와 종류는 거의 동일하다.
이는 M3와 M4 시리즈도 마찬가지이다.
Core가 달라도, 두 개의 MCU는 거의 같은 IVT를 가져 code 이식성을 확보할 수 있다.

여하튼, 이들 System exception이 언제 발생하는지 알아둘 필요가 있다.
Hard Fault까지는 우선순위 조정이 불가능하다.


0번(NA): 인터럽트가 아니라 초기 Stack Pointer 값을 저장하는 위치.
1번(Reset): 모든 리셋 상황에서 실행되는 벡터.
2~6번 (NMI, Hard Fault, MMU Fault, Bus Fault, Usage Fault): CPU 내부 오류 관련 인터럽트.
10번(SVCall): 운영체제의 시스템 콜에서 주로 사용됨.
13번(PendSV), 14번(SysTick): RTOS에서 컨텍스트 스위칭과 주기적인 작업에 필수적.
Cortex-M4에서는 SysTick과 PendSV를 활용하면 효율적인 RTOS 커널 스케줄링이 가능하므로 중요한 인터럽트.

또한, Hard Fault, Bus Fault, Usage Fault 같은 예외들은 디버깅할 때 매우 중요하므로, 프로그램이 비정상 종료될 때 반드시 로그를 확인해야 한다.

Cortex-M4 Memory Map

Reset의 경우, PC는 0x0000_0004를 참조하여 Reset Vector(handler routine)을 포함하는 bootstrap으로 가게 된다.

이 bootstrap code는 필요한 stack, heap영역 구성 등 필요한 작업을 수행 후, main()함수를 호출하는 CStartUp code이다. 즉, main함수를 호출하고 함수의 시작 주소와 매개변수, 각종 변수들을 저장하기 위한 stack 영역을 설정한다.

MSP(Main Stack Pointer)값은 0x0000_0000에 저장되어 있는데, 내부를 보면 0x2000_0720의 MSP값을 가지고 있다. 이때 Cortex-M4 MSP는 기본적으로 Full Descending Addressing으로 동작한다는 것을 기억하자.


컴퓨터 구조에서 자주 나오는 그림을 mapping해보면 이렇게 되는듯하다.

이처럼, 메모리와 Interrupt들에 대해 알아보았다.

profile
학습

0개의 댓글