• logo

      SeolMyeongTang

  • 보드로 알아보는 인터럽트

    2022년 1월 02일

    저번 시간에 인터럽트에 관한 글을 살펴보았습니다. 인터럽트의 힘을 보기 위해 운영체제 구현도 좋지만 보드를 이용한다면 보다 쉽게 확인할 수 있습니다. 보드를 통해 인터럽트를 사용하면 어떤 효과를 볼 수 있을지 알아보는 시간을 가져보겠습니다.

    먼저, 인터럽트 구현을 위한 보드를 골라봅시다. 인터럽트 라이브러리가 잘 되어 있는 아두이노, 라즈베리파이로 시작해도 좋습니다. 하지만 제가 사용할 보드는 STM32F10X 계열의 보드입니다. 아두이노 보드 보다 사양이 더 좋기도 하고 최근에 이 보드를 가지고 프로젝트를 진행하여 STM32F10X 보드를 가지고 설명 하도록 하겠습니다. 저희는 이 보드에 달려있는 기본 모듈들과 TFT LCD, 부저를 가지고 간단한 네모네모로직 게임 프로젝트를 만들어 보도록 하겠습니다. 그 중 인터럽트 부분을 집중적으로 살펴보도록 하겠습니다.

    네모네모로직 예시
    네모네모로직 예시

    저희가 사용할 모듈을 보면, 게임을 보여주는 스크린(TFT LCD), 게임 조작을 위한 조이스틱과 버튼, 백그라운드에 깔린 BGM을 틀기위한 부저로 4가지 입니다. 사용자가 이 모듈들을 써서 보드에 신호를 보내고 그에 맞는 동작을 해야합니다. 즉, 인터럽트 동작을 감지하고 해당하는 인터럽트 핸들러 함수를 실행해야합니다. 최종적으로 인터럽트 핸들러를 작성하면 되는데 이 목표에 도달하기 위한 과정을 빠르게 살펴보면 다음과 같습니다.

    1. 모듈과 보드의 디지털 핀 연결(HW)
    2. GPIO, EXTI, NVIC 활성화(SW)
    3. 인터럽트 벡터 테이블을 참조하여 인터럽트 핸들러 작성(SW)

    인터럽트 처리를 위해 가장 먼저 해줘야할 일은 모듈과 보드의 디지털 핀 연결입니다. 그리고 아두이노의 pinMode()처럼 해당 핀을 활성화 해주는 GPIO 작업이 필요합니다. GPIO는 핀마다 EXTI(EXTernal Interrupt/event controller) line과 연결되어 있습니다. 모듈의 입력을 interrupt로 처리하기 위해서 EXTI 설정을 해주어야 합니다. 그런데 만약 동시에 모듈 입력이 된다면 어느 모듈부터 처리해야 할까요? 이 문제를 해결하기 위해 NVIC(Nested Vectored Interrupt Controller)를 설정하여 우선순위를 매겨 어떤 인터럽트가 먼저 실행되어야 할지 보드에 알려줍니다. 마지막으로 인터럽트 벡터 테이블을 참조[1]하여 해당 EXTI line의 인터럽트 핸들러 함수를 작성하면 됩니다.

    위의 설명을 block diagram으로 도식화 해봅시다. 다소 복잡해 보이지만 EXTI line의 입력, 즉 모듈 입력(Input Line)이 들어오면 clock edge를 감지하고 NVIC으로 넘겨주는 모습을 볼 수 있습니다. 더 자세한 STM32F10x의 EXTI, NVIC에 대해서 알고 싶다면 공식 Reference Manual(p. 197~214)을 참고해주세요.

    EXTI block diagram
    EXTI block diagram[2]

    코드로 본다면 다음과 같습니다. 조이스틱 Up에 대한 코드 예제입니다.

    • 코드

      C
      void RCC_Configure(void)
      {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
      }
      
      void GPIO_Configure(void)
      {
        GPIO_InitTypeDef GPIO_InitStructure;
      
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
      }
      
      void EXTI_Configure(void)
      {
        EXTI_InitTypeDef EXTI_InitStructure;
      
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
        EXTI_InitStructure.EXTI_Line = EXTI_Line5;
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);
      }
      
      void NVIC_Configure(void)
      {
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
      
        NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
      }
      
      void EXTI2_IRQHandler(void)
      {
        /* Joystick Up Handler */
      }
      
      int main(void)
      {
        SystemInit();
        RCC_Configure();
        GPIO_Configure();
        EXTI_Configure();
        NVIC_Configure();
      }
      

    모든 설정이 끝났다면 보드를 통해 직접 확인해 볼 차례입니다. 저희 예제에서의 핵심은 백그라운드에서 부저 소리가 계속 나오는 중간에 어떤 요청이 들어오면 부저 소리가 끊기지 않고 처리가 되는가?입니다. 평소라면 백그라운드 부저가 자원을 계속 선점하므로 중간에 끼어들지 못하지만 인터럽트 덕분에 다른 입력을 처리할 수 있게 됩니다.

    브금이 계속 나오면서 중간에 조이스틱/버튼 입력을 받아 동작하는 모습을 보실 수 있습니다. 이로써 STM32F10x 보드와 인터럽트를 활용하여 간단한 게임을 만들었습니다. 자세한 코드가 궁금하다면 이 repo를 참고하면 됩니다. 여러분도 인터럽트의 참맛을 느끼고 싶다면 이론을 공부하는 것도 재미있지만 직접 구현하여 느껴보는 것이 더욱 재밌게 다가올 수 있을 겁니다. 분명 잘 안되는 부분도 있겠지만 시행착오를 겪다 완성하면 인터럽트와 친해진 나 자신을 볼 수 있을 겁니다!


    [1] STM32F105/107 Reference Manual p. 198

    [2] STM32F105/107 Reference Manual p. 207