임베디드/STM32

13. [STM32] Interrupt-Register

fuhehe 2024. 9. 26. 16:38

이번장에서는 인터럽트에 관련된 레지스터에 관해 살펴보자.

순서는 인터럽트가 초기화 되는 순서대로 볼 것이다.

이 순서는 MX_GPIO_Init()함수에서 각 핀별로 호출되는 HAL_GPIO_Init()함수와 HAL_NVIC_SetPriority(), HAL_NVIC_EnableIRQ()에 구현된 순서이다.

 

HAL_GPIO_Init()에서 사용되는 레지스터의 경우 '1. 레지스터 초기화(GPIO설정)'에서 나머지는 '2. Control 레지스터 초기화'에서 설명한다.

 

1. 레지스터 초기화(GPIO 설정)

인터럽트 초기화 설정은 CubeMX의 설정 따라 MX_GPIO_Init()에서 설정되고 각 핀별로 HAL_GPIO_Init()을 통해 초기화 된다.

HAL_GPIO_Init()함수의 GPIO설정 부분은 GPIO Peripheral Register 2 에서 이미 살펴보았으므로 이번장에서는 인터럽트에 대한 레지스터 설정만 살펴보자.

 

HAL_GPIO_Init() 함수의 마지막 부분에 인터럽트 설정이 나오는데 EXTI_RTSR, EXTI_FTSR, EXTI_EMR, EXTI_IMR레지스터를 설정하고 있다.

그리고 System Configuration Cotroller인 SYSCFG의 EXTICR레지스터도 동시에 엑세스한다.

 

 

SYSCFG_EXTICRx

앞장에서   EXTI는 0~22까지 총 23개가 있다고 하였다. 이것들 중에서 용도가 정해져 있는 16번 이상의 번호를 제외한 0~15까지의 EXTI가 PA~PI중 어느 핀으로 제어될 지를 매핑하는 레지스터이다.

[SYSCFG_EXTICRn]

 

이 레지스터는 EXTICR1~EXTICR4 까지 4개의레지스터로 분리되어 있고 하나에 4개의 EXTI설정이 들어있다.

만약 PE3핀을 인터럽트로 사용한다면 SYSCFG_EXTICR1의 [15:12]비트에 0100을 세팅해야 하고,  PF7핀을 인터럽트로 사용한다면 SYSCFG_EXTICR2의 [15:12]비트에 0101을 세팅해야 한다.

 


EXTI_RTSR, EXTI_FTSR

이 레지스터는 각각 인터럽트 신호의 Rising Edge/Falling Edge 검출시 인터럽트를 발생시킬 지 여부를 결정하는 레지스터이다. (FTSR은 Falling Edge라는 점만 다를뿐 RTSR과 동일하다)

[EXTI_RTSR]

각 비트는 EXTI 0~22번호와 대응하며 1로 설정할 경우 해당 라인에서 Rising Edge가 검출될 때마다 인터럽트를 발생시킨다.

마찬가지로 EXTI_FTSR의 경우 1로 설정할 경우 해당 라인에서 Falling Edge가 검출될 때 마다 인터럽트를 발생시킨다.

물론 둘다 1로 설정해서 사용할 수 도 있다. 

CubeMX설정을 보면 다음과 같이 Rising, Falling, Rising/Falling 세가지중 하나를 선택할 수 있다.

[Interrupt Mode 설정]

 

 

EXTI_IMR

이 레지스터는 EXTI 0~22 Line별로 마스크를 설정하는 레지스터이다. 즉 이 레지스터에서 해당 Line의 비트가 1로 설정되어야(Not masked) 인터럽트가 발생한다.

[EXTI_IMR]

CubeMX의 NVIC설정에서 해당 EXTI Line에 체크를 하면 HAL_GPIO_Init()에서 1로 설정한다.

 

EXTI_EMR

이 레지스터는 EXTI 0~22 Line별로 이벤트 마스크를 설정하는 레지스터이다.

이벤트라는 사항이외에는 EXTI_IMR과 내용은 동일하므로 레지스터 내용은 생략한다.

 

이 레지스터는 CubMX에서 아래와 같이 해당 핀을 Event로 지정했을 때 1로 설정된다.

[Event설정]

 

EXTI_PR

이 레지스터는 Pending 레지스터로 해당 EXTI Line에서 인터럽트가 발생하면 해당 비트가 0에서 1로 설정된다.

[EXTI_PR]

주의할 건 인터럽트가 발생했을때 S/W에서 ISR수행시 다시 이 레지스터를 리셋해줘야 하는데 위 레지스터 문서와 같이 리셋할 때는 해당 비트를 0으로 쓰는게 아니라 1로 써야 한다.

단, CubeMX에서는 이미 해당 EXTI Handler()함수에 클리어하는 매크로를 수행하도록 되어 있으므로 CubeMX 소스를 그대로 사용한다면 별도 처리하지 않아도 된다.

 

예를 들어 PC13 (User Button)핀에 인터럽트가 발생한 경우.

  • void EXTI15_10_IRQHandler() 호출. (stm32f4xx_it.c)
  • EXTI15_10_IRQHandler()에서는 다시 HAL_GPIO_EXTI_IRQHandler()함수 호출.(stm32f4xx_hal_gpio.c)
  • HAL_GPIO_EXTI_IRQHandler()에서는 __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 함수 호출.
  • 우리는 weak로 선언된 HAL_GPIO_EXTI_Callback()를 재정의해서 자체 인터럽트 로직 구현.

위와 같은 순서로 실행된다.

이때 HAL_GPIO_EXTI_IRQHandler()함수 구현을 보면

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

이렇게 구현되어 있는데

__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);  이 매크로 부분이 EXTI_PR레지스터를 리셋하는 매크로이다.

따라서 우리가 로직을 구현할 HAL_GPIO_EXTI_Callback()함수 호출전 이미 EXTI_PR레지스터의 해당 EXTI bit는 리셋된다.

#define __HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__) (EXTI->PR = (__EXTI_LINE__))

이 매크로를 보면 위와 같은데 입력된 핀번호에 대한 bit를 1로 설정하고 있다.

PC13의 경우 아래와 같이 정의되어 있으므로 13번 비트만 1로 설정된다.

#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */

 

 

EXTI_SWIER

이 레지스터는 S/W적으로 인터럽트를 발생시키게 해주는 레지스터이다.

[EXTI_SWIER]

레지스터 문서 설명과 같이 당연하지만 해당 Line인터럽트를 발생시키기 위해서는 해당 라인의 인터럽트를 사용한다고 설정해야 한다.

소스에서 인터럽트를 강제 발생시키려면 직접 레지스터를 건드려도 되고 __HAL_GPIO_EXTI_GENERATE_SWIT 매크로를 사용해도 된다.

/**
  * @brief  Generates a Software interrupt on selected EXTI line.
  * @param  __EXTI_LINE__ specifies the EXTI line to check.
  *          This parameter can be GPIO_PIN_x where x can be(0..15)
  * @retval None
  */
#define __HAL_GPIO_EXTI_GENERATE_SWIT(__EXTI_LINE__) (EXTI->SWIER |= (__EXTI_LINE__))

 

 

 

2. Control 레지스터 초기화

MX_GPIO_Init()함수에서 각 GPIO의 레지스터 설정이 끝나고 나면 EXTI Line별로 우선순위와 사용여부를 NVIC관련 레지스터를 통해 설정을 해준다.

CubeMX의 NVIC설정에서 Preemption Priority와 Sub Priority를 설정할 수 있는데 이 값들은 MX_GPIO_Init()함수에서 HAL_NVIC_SetPriority()를 호출해서 설정한다.

그리고 여기서 사용되는 레지스터인 NVIC, SCB는 레퍼런스 매뉴얼이 아니라 프로그래밍 매뉴얼에 기술이 되어 있다.

 

NVIC_IP

[NVIC_IPRx]

이 레지스터는 인터럽트의 우선순위를 설정하는 레지스터이다.

STM32F는 총 240개의 인터럽트를 사용할 수 있고 하나의 인터럽트에 할당된 우선순위 비트는 8bit이므로 한 레지스터당 4개의 인터럽트를 설정할 수 있다. 

따라서 NVIC_IPR레지스터는 240 / 4 = 60개의 레지스터가 존재한다.

하지만 소스에서는 이를 32bit로 선언하지 않고 NVIC_Type::IP를 uint8_t IP[240U] 와 같이 240개의 8bit 배열로 선언해서 사용하므로(core_cm4.h 파일 411 Line) NVIC_SetPriority()함수에서는 별도 계산없이 정의된 IRQ번호를 배열첨자로 그대로 사용한다.

 

이때 우선순위 설정은 앞장에서 설명한 바와 같이 CubeMX의 NVIC설정에서 선택한 Priority Group에 따라(SCB_AIRCR 레지스터에 설정되어 있고 소스에서는 NVIC_GetPriorityGrouping()함수로 값을 읽음) Preemption/Sub Priority의 비트를 각각 설정해서 NVIC_IPR레지스터에 값을 기록한다.

 

NVIC_ISER

[NVIC_ISERx]

각 인터럽트의 사용여부를 설정하는 레지스터이다.

총 240개의 인터럽트를 사용할 수 있으므로 하나의 레지스터가 32bit이므로 하나당 32개씩 지정된 8개의 레지스터가 존재한다.(32 X 8 = 256. 마지막 NVIC_ISER7은 하위 16개의 bit만 사용한다.)

 

SCB_AIRCR

인터럽트 우선순위 그루핑이나 시스템 리셋 요청에 관련된 레지스터이다.

이중 인터럽트에 관련된 비트는 [10:8] PRIGROUP 필드인데 CubMX의 Priority Group에서 설정한 값이 이 필드에 세팅된다.

이 레지스터에 값을 쓰기 위해서는 [31:16]에 0x5FA값을 설정해야 한다.(이 필드를 읽으면 0xFA05값이 읽힌다.)

소스에서 이 값 설정하는 부분은 main()함수에서 호출하는 HAL_Init()함수를 따라가 보면 HAL_NVIC_SetPriorityGrouping() 호출 부분이 있는데 이 함수에서 값을 세팅하도록 되어 있다.

 

SCB_SHP

실제 레지스터는 SHPR1, SHPR2, SHPR3 3개의 레지스터로 존재. 소스에서는 SCB_Type으로 uint8_t SHP[12U]로 정의되어 총 96비트(32*3)를 8비트로 쪼개 12개로 관리한다.

이 레지스터는 일반 사용자 인터럽트에는 사용되지 않으며 IRQ번호가 0보다 작은 NMI, Memory Management, Bus Fault, Usage Fault, SVCall등의 시스템 인터럽트의 레벨을 설정하는 레지스터이다.

 

 

3. 마무리

이번 시간에는 Interrupt 설정에 관련된 레지스터에 대해 알아보았다.

Interrupt관련해서는 HAL_GPIO_EXTI_Callback()함수를 재정의 하는일 이외에는 CubeMX에서 만들어준 소스를 직접 수정해야 할 일은 없을듯 하다.

하지만 CubeMX의 소스를 사용하지 않고 필요한 기능만을 구현한 자신만의 소스로 개발을 해야 한다면 위의 레지스터 내용을 알고 있어야 할 것이다.