아두이노 인터럽트와 EXTI 사용 방법과 예제
인터럽트(Interrupt)란 말은 사전적으로 '방해하다', '중단시키다'를 의미합니다. 아두이노에서 인터럽트는 일반적인 동작을 하는 중에 외부 이벤트(또는 지정된 알람)이 발생하면 하던 동작을 멈추고 이벤트에 따라 지정된 기능을 수행 후 다시 멈추었던 동작부터 수행하게 하는 것을 의미합니다.
폴링 방식과 인터럽트 방식을 이해하고, Arduino 에서 EXTI(Externel Interrupt) 사용법과 예제
에 대해 알아보겠습니다.폴링(Polling), 인터럽트(Interrupt), 우선순위
폴링방식과 인터럽트방식
코로나로 인해 재택근무를 하지만, 집에서 몰래 게임을 한다고 가정해봅시다. 회사에서도 재택근무 재대로 수행되는지 단속을 하기 위해 메신저에 답장이 늦으면 패널티가 쌓인다고 가정합니다. 게임을 하면서 패널티를 먹지 않기 위해 2가지 방법이 있습니다. 주기적으로 메신저를 확인하거나, 메신저로 메세지가 오면 소리 또는 팝업창으로 알려주는 방식을 사용 할 수 있습니다. 이렇게 주기적으로 확인을 하는 수고를 하는 것이 폴링 방식, 외부의 이벤트가 있는 경우 알림을 주는 것이 인터럽트입니다.
- 게임을 하면서 주기적으로 회사 메신저를 확인 👉 폴링방식
- 메신저로 메세지가 오면, 소리로 알려 주도록 메신저를 설정 👉 인터럽트 방식
폴링 방식과 같이 주기적으로 또는 자주 특정한 이벤트가 발생하는지 확인을 하게 되면, 이벤트가 발생했는지 확인하고 기다리는 시간 소비가 켜저 기본적으로 하는 일에 지장이 있습니다. 인터럽트 방식은 특정한 이벤트가 생기면 알림으로 알려줘 이벤트에 맞는 행동을 즉시 할 수 있습니다.
게임을 계속하고 싶지만, 게임을 방해하는 요소는 많습니다. 회사에서 오는 메신저, 택배 아저씨의 알람, 휴대폰의 SNS 알람 이런 모든 것들이 인터럽트를 발생 시키는 이벤트 입니다. 기본적으로 게임을 하고 있지만, 이벤트가 발생할 때 이벤트에 반응하는 행동을 할지, 기억을 하고 있다가 나중에 처리를 할지 우선순위에 따라 행동을 결정하게 됩니다. 동시에 이벤트가 발생할 때에도 우선 순위의 지정이 필요합니다.
인터럽트 우선순위
인터럽트는 여러가지 종류가 있을 수 있고, 동시에 발생 할 수도 있습니다. 이럴 경우를 대비에 모든 경우에 우선순위가 정해져 있어야 합니다. 사실 나에게 제일 중요한 것은 게임을 하는 즐거운 시간을 보내는 것이지만, 재택 근무 중에 하는 것이므로, 회사 연락에 가장 빠르게 반응 하는 것이 최우선 순위이고, 다음이 택배로 음식이 온 경우, 다음이 게임, 제일 마지막이 일반 택배가 온 경우 입니다. 이렇게 인터럽트와 우선순위는 항상 같이 고려되어야 합니다.
아두이노의 인터럽트
아두이노에도 여러가지 인터럽트 기능을 제공합니다. 엄밀히 말하면 Arduino Board 에 내장되어있는 마이크로컨트롤러(Arduino Uno의 경우 Atmega328)에 있는 기능을 가공해서 아두이노 언어로 사용가능한 함수로 제공됩니다. 아래 그림은 Atmega328 의 인터럽트 벡터(Interrupt Vector)를 나타내는 표입니다. 인터럽트 벡터는 MCU에서 제공하는 모든 인터럽트의 종류와 우선순위 정보를 말합니다. 1
일반 PC에서 재부팅 버튼을 누르면 컴퓨터가 재부팅 되듯이, MCU 에서도 MCU 핀(External pin)으로 들어오는 RESET 신호의 우선순위가 가장 높습니다. 그외 EXTI, WDT(Watchdog Timer), Timer, 각종 통신 SPI, UART 등등.. 다양한 이벤트와 인터럽트가 존재합니다.
아래 표에서 Program Address 는 해당 Vector 번호의 인터럽트가 발생하면, 실행되는 함수의 주소를 가리킵니다.
Atmega328 Interrupt Vector Table
이런 인터럽트의 종류와 우선순위를 사용하여, 메인 기능을 반복해서 수행하면서, 버튼이 눌러지면 불을 켜고, 외부의 통신으로 데이터 요청이 오면 처리를 해주는 것처럼 여러가지 일을 동시에 하는 것처럼 프로그램을 작성할 수 있습니다.
EXTI(External Interrupt)
Arduino Uno EXTI pinout
아두이노 EXTI 동작 방식
attachInterrupt()
Syntax
- attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
Parameters
- pin : 아두이노 핀 번호 (Arduino Uno의 경우 2,3번 핀만 가능)
- ISR : pin 이 mode 를 만족하는 경우 호출 되는 함수명 (ISR : Interrupt Service Routine)
- mode
- RISING : 입력 전압이 LOW -> HIGH 로 변경 될 떄
- FALLING : 입력 전압이 HIGH -> LOW 로 변경 될 떄
- CHANGE : RISING, FALLING 모두
Returns : 반환값 없음
Remark : 인터럽트 사용할 핀은 입력 모드로 설정 되어 있어야 함
Example
attachInterrupt(digitalPinToInterrupt(3),toggle,RISING): 3번핀을 RISING 에지입력 인터럽트 설정
※ 주의사항 : 아래 내용중 일부는 다른 마이크로프로세서 사용에도 적용되는 내용입니다.
- 인터럽트 호출 함수 내의 코드를 수행하는 동안 delay(), millis() 시간 측정이 진행 되지 않음
- 인터럽트 호출 함수 내부는 짧게 작성 -> flag 설정으로만 끝나는 형태를 지향한다.
- 인터럽트 호출 함수는 반환 값이 없어야 한다.
- 인터럽트 호출 함수 내부에서 사용되는 전역 변수는 volatile 로 선언 되어야 한다.
- loop() 함수에서 인터럽트 함수 호출을 하지 않아, 컴파일러에서 코드 최적화시 사용하지 않는 변수로 간주 할 수 있습니다. volatile 로 선언되면 컴파일러가 최적화하는 것을 방지합니다.
예제1 : RISING 에지 인터럽트로 LED 토글
아래 회로와 같이 2번 핀에 연결된 스위치가 눌려지는 순간에 Arduino Board 내장 LED 가 반전되는 프로그램입니다. 아래 회로도와 같이 2번 핀에 스위치와 저항을 연결하는 경우 D2핀에 아래와 같이 전압이 인가 됩니다.
- Switch ON : D2번 포트에 LOW (0V)
- Switch OFF : D2번 포트에 HIGH (5V)
예제1 회로도
코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | volatile boolean state = LOW; void setup() { pinMode(13, OUTPUT); pinMode(2, INPUT); attachInterrupt(digitalPinToInterrupt(2),toggle,FALLING); } void loop() { digitalWrite(13, state); } void toggle() { state = !state; } | cs |
volatile boolean state 인터럽트 호출 함수에서 사용하는 전역변수는 volatile 로 선언되어야 합니다.
예제2 : LED blinking 하면서 버튼이 눌러지면 시리얼 메세지 송신
아래의 코드는 loop() 함수에서 LED 를 점멸 하면서, 스위치가 눌려지면 "Button Pushed.!" 시리얼 통신을 보내는 프로그램입니다. 여러가지 기능을 함께 하는 경우 사용 할 수 있는 프로그래밍 구조입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | const int ledPin = 13; const int swPin = 2; volatile int isrFlag = 0; unsigned long past = 0; // 과거 시간 저장 변수 int timerflag = 0; // 과거 기준 시간 보다 500ms 이상 지날 경우를 판단하는 flag void setup(){ pinMode(ledPin, OUTPUT); pinMode(swPin, INPUT); attachInterrupt(digitalPinToInterrupt(swPin),extiRequest,FALLING); Serial.begin(9600); } void loop(){ unsigned long now = millis(); // 현재 시간을 저장 // 현재 시간이 과거 시간보다 500ms 지났을 때 // 과거 시간에 현재 시간을 저장 // 500ms 시간이 지낫음을 알려 주는 timerflag 를 1로 활성화 if(now - past >= 500){ past = now; timerflag = 1; } // timerflag 가 1인 경우 // LED를 현재 상태 반전으로 출력 // flag 를 0으로 초기화 if(timerflag == 1){ digitalWrite(ledPin, !(digitalRead(13))); timerflag = 0; } // 시리얼 데이터가 도착한 경우 시리얼 통신 출력 if(isrFlag){ isrFlag = 0; Serial.println("Button Pushed.!"); } } void extiRequest(){ isrFlag = 1; } | cs |
LED 점멸하는 코드는 이전 포스트에서 delay() 없이 LED 점멸하는 코드에서 약간 수정하였습니다. 이와 관련된 내용이 필요하신 분은 아래 링크를 참조해주세요.
2020/07/02 - [Arduino/Basic] - 아두이노 강좌 #3 시간 관련 함수 설명, delay() 없이 LED Blink 코드 작성
마무리
본 포스트에서는 폴링 방식과 인터럽트 방식에 대해 알아보고, 아두이노에서 제공하는 EXTI 인터럽트 사용 방법에 대해 알아보았습니다. 이후에는 시리얼 수신 인터럽트 사용 방법과 활용에 대해 작성할 예정입니다.
끝까지 읽어주셔서 감사합니다.^^
'Embedded > Arduino' 카테고리의 다른 글
아두이노 강좌 #12 아날로그 입력 analogRead() 와 map() 함수 (1) | 2020.07.11 |
---|---|
아두이노 강좌 #11 시리얼 UART 수신 인터럽트 serialEvent() (5) | 2020.07.10 |
아두이노 강좌 #9 프로그래밍 기초 - 반복문 while, for 로 로또 번호 생성 (0) | 2020.07.08 |
아두이노 강좌 #8 프로그래밍 기초 - 조건문 if, switch ~ case (0) | 2020.07.07 |
아두이노 강좌 #7 프로그래밍 기초 - 데이터 타입, 변수, 상수, 선언자 (0) | 2020.07.06 |