본문 바로가기
임베디드 개발/STM32 (ARM Cortex-M)

STM32 , 외부 인터럽트 ( EXTI ) 사용하기

by eteo 2022. 5. 22.

 

EXTI는 External Interrupt의 약자로 인터럽트 Interrupt란 CPU(프로세스)가 프로그램을 실행하고 있을 때 입출력 하드웨어 등의 장치 등에서 예외상항이 발생하여 처리가 필요한 경우 프로세서에게 알려 처리할 수 있도록 하는 기능이다.

 

 

 

결과물

첫번째 스위치를 누르면 오른쪽으로 점멸하며 이동

두번째 스위치를 누르면 왼쪽으로 점멸하며 이동

세번째 스위치를 누르면 전체 점등

네번째 스위치를 누르면 전체 꺼짐

 

 

 

 

 

 

STM32F42시리즈는 다음과 같은 EXTI 구조를 가지고 있다. 각각의 PA0~PH0 핀이 EXTI0 에 연결되고 PA1~PH1이 EXTI1에 연결되는 식이다.

 

따라서 PD0번과 PC0번을 동시에 EXTI 용도로 설정할 수 없다.

 

https://embedded-lab.com/blog/stm32-external-interrupt/

 

 

 

 

위 사진에 보이듯이 EXTI0 - EXIT4 는 개별 NVIC 인터페이스를 가지고 EXTI5 - EXTI9번은 같은 그룹으로 묶이고 그리고 EXTI10 - EXTI15는 같은 그룹으로 묶여 NVIC에 연결된다.

 

 

 

 

 

먼저 호환보드의 LED인 PD0-PD7을 output으로 설정하고 스위치인 PG0-PG3 을 

 

External Interrupt Mode with Falling edge trigger detection 으로 설정한다.

스위치가 풀업방식이라 평소에는 High이고 스위치가 눌려졌을 때 low가 되므로 하강 엣지일때 triggered 되도록 했다.

 

다른 GPIO 설정은 건들지 않았다.

 

 

 

 

 

 

그 밑의 NVIC (Nested vector interrupt control) 에서 EXTI line 0부터 line 3을 Enabled에 체크해준다.

 

중첩 벡터 인터럽트 제어는 인터럽트 우선 순위를 설계하는 데 사용된다.

 

 

 

 

 

 

 

Generate Code를 하고나서 stm32f4xx_it.c 파일에 가면

 

 

 

 

 

 

 

아래처럼 EXTI번호 별로 IRQHandler가 생긴 것을 볼 수 있다. IRQ는 Interrupt request의 약자이다.

 

Ctrl을 누른채로 클릭하여 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0)를 따라가보면,

 

 

 

 

 

 

다음과 같이 나오는데, IRQHandler는 건들필요 없고

 

Callback 함수만 main.c에 가져와서 사용한다.

 

 

 

__weak 라고 되어있는 것은 사용자가 재정의 하여 사용하라는 의미이다.

 

 

 

소스코드

 

( 2022.05.26 수정 : 플래그 변수를 굳이 4개 만들어줄 필요 없이 모두 같은 LED제어에 사용하는 거니까 플래그 변수를 하나를 두고 인터럽트에선 각각 값을 대입해서 while문 내에서는 플래그 변수의 값이 뭐냐에 따라 if else 문으로 행동을 나눠주는게 더 효율적인 방식인거 같다.)

 

 

전역변수 선언구간에 인터럽트가 발생하면 on/off해 줄 플래그 선언

/* USER CODE BEGIN PV */
GPIO_PinState flag_sw1 =0;
GPIO_PinState flag_sw2 =0;
GPIO_PinState flag_sw3 =0;
GPIO_PinState flag_sw4 =0;
/* USER CODE END PV */

 

 

 

main문 바깥에(나는 USER CODE BEGIN 0에 작성) 위에서 본 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 함수를 그대로 가져와 인터럽트 발생시 실행될 구문을 작성한다.

GPIO_Pin 이 매개변수로 들어오면 핀 1,2,3,4가 같은 LED를 제어하기 때문에 충돌이 일어나지 않도록 다른플래그는 끄고 해당하는 플래그만 켜는 식으로 구성된다.

 

한편 플래그를 사용하지 않고 직접 EXTI 함수 내에 LED를 토글시키는 동작 등을 포함시켜도 된다. 다만 주의할 것은 Callback 함수 내부에서는 HAL_Delay를 사용할 수 없다. Callback 함수도 Systick 타이머를 사용하고 HAL_Delay도 Systick 타이머를 사용해서 충돌이 일어나기 때문이다.

 

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if(GPIO_Pin == GPIO_PIN_0){
	  flag_sw2 = 0;
	  flag_sw3 = 0;
	  flag_sw4 = 0;
	  flag_sw1 = 1;
  }else if(GPIO_Pin == GPIO_PIN_1){
	  flag_sw1 = 0;
	  flag_sw3 = 0;
	  flag_sw4 = 0;
	  flag_sw2 = 1;
  }else if(GPIO_Pin == GPIO_PIN_2){
	  flag_sw1 = 0;
	  flag_sw2 = 0;
	  flag_sw4 = 0;
	  flag_sw3 = 1;
  }else if(GPIO_Pin == GPIO_PIN_3){
	  flag_sw1 = 0;
	  flag_sw2 = 0;
	  flag_sw3 = 0;
	  flag_sw4 = 1;
  }
}
/* USER CODE END 0 */

 

 

Led 점멸이동을 위한 변수 선언

  /* USER CODE BEGIN 2 */
  uint16_t ledLocation = 0x01;
  /* USER CODE END 2 */

 

while문 내

  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  if(flag_sw1==1){
		  ledLocation <<= 1;
		  if(ledLocation > 0x80) ledLocation = 0x1;
		  HAL_GPIO_WritePin(GPIOD, ledLocation, 1);
		  HAL_Delay(100);
		  HAL_GPIO_WritePin(GPIOD, ledLocation, 0);
	  }else if(flag_sw2==1){
		  ledLocation >>= 1;
		  if(ledLocation < 1) ledLocation = 0x80;
		  HAL_GPIO_WritePin(GPIOD, ledLocation, 1);
		  HAL_Delay(100);
		  HAL_GPIO_WritePin(GPIOD, ledLocation, 0);
	  }else if(flag_sw3==1){
		  GPIOD->ODR = 0xff;
	  }else if(flag_sw4==1){
		  GPIOD->ODR = 0x00;
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }