본문 바로가기

아두이노(arduino)

아두이노 인터럽트를 알면 100% 활용성 증가 / 예제코드

아두이노 인터럽트 소개

인터럽트는 마이크로컨트롤러가 실행 중인 코드를 일시 중단하고, 특정 조건이 충족되었을 때 미리 정의된 다른 코드(인터럽트 서비스 루틴, ISR)를 실행하는 기능입니다.

보통 아두이노 코드를 작성했을때 delay를 이용해 각종 모듈들을 제어합니다.

 

 

delay()함수가 사용됐을때 다른 어떠한 행동도 할 수가 없죠?

 

 

 

하지만 인터럽트를 사용할 경우 다른 행동도 가능하다는 것입니다. 아래 원리와 비유를 통해 좀더 간단하게 설명해볼까요?

 

 

 

 

 

인터럽트 원리와 비유

 

인터럽트를 쉽게 이해하기 위해 "전화벨"에 비유할 수 있습니다. 일을 하다가 전화벨이 울리면 일을 멈추고 전화를 받아야 합니다. 전화를 끊은 후 원래 하던 일로 돌아갑니다. 

 

 

 

또다른 예를 들어 보면 이렇습니다.

인터럽트를 '긴급 사항'에 비유해볼 수 있습니다.
상상해보십시오. 당신은 회사에서 일을 처리하고 있는데, 긴급한 상황이 발생했다고 전달받습니다. 이때 당신은 일을 잠시 멈추고, 긴급한 상황을 해결하기 위해 빠르게 대응해야 합니다. 문제가 해결되면 다시 원래의 일을 계속하게 됩니다.

 

 

인터럽트도 마찬가지입니다. 마이크로컨트롤러는 주 코드를 실행하다가 인터럽트 조건이 충족되면(즉, '긴급한 상황'이 발생하면) 현재 코드를 일시 중단하고 인터럽트 서비스 루틴(ISR)을 실행합니다. ISR은 여기서 긴급한 상황을 해결하는 코드입니다. ISR이 완료되면 마이크로컨트롤러는 원래 실행하던 코드로 돌아가 계속 실행하게 됩니다.

 

 

 

 

 

 

아두이노에서 인터럽트 사용하기

아두이노에서는 'attachInterrupt()' 함수를 사용하여 인터럽트를 설정할 수 있습니다. 이 함수에는 인터럽트 핀 번호, ISR 함수 이름, 트리거 모드를 인수로 전달합니다.

1. 인터럽트 핀 

아두이노에서 인터럽트를 사용하려면 먼저 인터럽트를 사용할 수 있는 핀을 선택해야 합니다. 아두이노의 각 보드마다 인터럽트를 지원하는 핀의 개수와 번호가 다릅니다. 예를 들어, 아두이노 우노에서는 2번과 3번 핀이 외부 인터럽트 핀으로 사용 가능합니다.

2. 동작할 함수 정의

인터럽트가 발생했을 때 실행될 함수인 인터럽트 서비스 루틴(ISR)을 정의합니다. 이 함수는 반환 값이 없고 매개변수도 없는 형태로 작성되어야 합니다.
3. 인터럽트 설정

아두이노에서 인터럽트를 설정하려면 attachInterrupt() 함수를 사용합니다. 이 함수에는 인터럽트 핀 번호, ISR 함수 이름, 트리거 모드를 인수로 전달해야 합니다. 트리거 모드에는 LOW, CHANGE, RISING, FALLING 등이 있습니다. 아두이노 우노의 경우, 핀 번호를 인터럽트 번호로 변환하기 위해 digitalPinToInterrupt() 함수를 사용해야 합니다.
4. 인터럽트 정지

인터럽트를 해제하려면 detachInterrupt() 함수를 사용하여 설정된 인터럽트를 제거합니다. 인터럽트 번호를 인수로 전달하면 해당 인터럽트가 비활성화됩니다.

 

인터럽트 종류 및 트리거

아두이노에서는 주로 외부 인터럽트를 사용하며, 트리거 모드로는 LOW, CHANGE, RISING, FALLING 등이 있습니다. 이들은 각각 핀이 낮은 상태, 상태 변화, 상승 에지, 하강 에지에 대응합니다.

 

 

외부 인터럽트(External Interrupts)

외부 인터럽트는 아두이노 보드의 특정 핀에만 사용할 수 있습니다. 이러한 핀은 각 보드마다 다르며, 아두이노 우노에서는 2번과 3번 핀이 외부 인터럽트 핀으로 사용할 수 있습니다. 외부 인터럽트는 아래와 같은 트리거 모드를 사용할 수 있습니다.

LOW: 핀이 LOW 상태일 때 인터럽트 발생
CHANGE: 핀의 상태가 변할 때 인터럽트 발생 (LOW에서 HIGH 또는 HIGH에서 LOW)
RISING: 핀의 상태가 LOW에서 HIGH로 변할 때 인터럽트 발생
FALLING: 핀의 상태가 HIGH에서 LOW로 변할 때 인터럽트 발생

 

 

 

핀 변경 인터럽트(Pin Change Interrupts)

핀 변경 인터럽트는 아두이노의 모든 디지털 입출력 핀에서 사용할 수 있습니다. 하지만 핀 변경 인터럽트는 외부 인터럽트와 달리 상태 변화(CHANGE)에만 반응하며, RISING 또는 FALLING에는 반응하지 않습니다. 그러므로 사용자가 상태 변화에 따른 처리를 직접 구현해야 합니다.

 

 

 

인터럽트를 활용한 예제 프로젝트

버튼을 누르면 LED가 켜지는 프로젝트를 만들어 봅시다. 버튼 입력에 인터럽트를 사용하면 프로그램의 메인 루프에 버튼 검사 코드를 넣지 않아도 됩니다.

 

경보기를 한번 만들어볼까요? 경보는 어느시간이던 위험을 감지하면 매번 울려야 합니다. 그렇기 때문에 항상 그 상태를 체크해야하죠.

 

필요한 구성 요소:

  1. 아두이노 보드 (예: 아두이노 우노)
  2. 전류 센서 (예: ACS712)
  3. 부저
  4. 점퍼 케이블

회로 구성:

 

 

 

 

  1. 전류 센서의 VCC 핀을 아두이노의 5V 핀에 연결합니다.
  2. 전류 센서의 GND 핀을 아두이노의 GND 핀에 연결합니다.
  3. 전류 센서의 OUT 핀을 아두이노의 A0 핀에 연결합니다.
  4. 부저의 양극을 아두이노의 디지털 핀 8에 연결하고, 음극을 GND에 연결합니다.

 

위 두가지 회로도를 한꺼번에 합치면 됩니다.

 

 

#include <Arduino.h>

const int currentSensorPin = A0; // 전류 센서의 출력 핀을 아날로그 A0에 연결
const int buzzerPin = 8;         // 부저의 양극을 디지털 핀 8에 연결
const float thresholdCurrent = 1.5; // 경보음이 울리는 전류 임계값 설정 (예: 1.5A)

volatile boolean alarmTriggered = false; // 경보음 발생 여부를 저장하는 전역 변수

// 인터럽트가 트리거될 때 실행되는 함수
void triggerAlarm() {
  alarmTriggered = true; // 전류 값이 임계값 이상이면 alarmTriggered를 true로 설정
}

void setup() {
  pinMode(buzzerPin, OUTPUT); // 부저 핀을 출력으로 설정
  // 아날로그 핀 A0에 연결된 전류 센서를 인터럽트 핀으로 설정하고, triggerAlarm 함수와 연결
  attachInterrupt(digitalPinToInterrupt(currentSensorPin), triggerAlarm, RISING);
}

void loop() {
  if (alarmTriggered) { // alarmTriggered가 true인 경우 (임계값 이상의 전류가 감지된 경우)
    tone(buzzerPin, 1000); // 부저에 1000Hz 주파수로 소리를 내도록 설정
    delay(1000); // 1초 동안 경보음이 울립니다.
    noTone(buzzerPin); // 부저의 소리를 끕니다.
    alarmTriggered = false; // 경보 발생 여부를 초기화합니다 (다음 경보를 대비하여 false로 설정)
  }
}

 

인터럽트 사용 시 주의사항

되도록 사용하지 않는 것이 좋습니다. ISR 내에서 긴 시간 동안 코드를 실행하면 다른 인터럽트 또는 주 코드의 실행이 지연될 수 있습니다. 또한, 전역 변수를 인터럽트와 주 코드에서 모두 사용할 때는 변수에 'volatile' 키워드를 사용해야 합니다. 이는 컴파일러가 변수 값을 캐싱하지 않고 항상 메모리에서 읽도록 함으로써 문제를 방지합니다.

 

이것 역시 간단한 비유를 통해 알아보도록 하겠습니다. 

 

인터럽트 서비스 루틴(ISR)의 처리 시간: 요리사가 음식을 만들 때, 동시에 여러 음식을 만들어야 할 때가 있습니다. 만약 한 음식에 너무 많은 시간을 쏟게 되면 다른 음식이 방치되거나 지연될 수 있습니다. 마찬가지로, ISR 내에서 너무 긴 시간 동안 코드를 실행하면 다른 인터럽트나 주 코드의 실행이 지연되는 문제가 발생할 수 있습니다. 따라서 ISR은 가능한 한 빠르게 실행되어야 합니다.

변수 공유: 요리사가 재료를 공유할 때, 각각의 재료 사용량을 정확하게 파악해야 합니다. 그렇지 않으면 어느 한 음식에 재료가 부족하거나 과다하게 사용될 수 있습니다. 마찬가지로, 인터럽트와 주 코드에서 전역 변수를 공유할 때는 'volatile' 키워드를 사용하여 변수 값을 정확하게 관리해야 합니다. 이렇게 함으로써 컴파일러가 변수 값을 캐싱하지 않고 항상 메모리에서 읽어 올 수 있게 되어 문제를 방지할 수 있습니다.

delay() 함수와 시간 소모적인 함수 사용: 요리사가 일을 하다가 휴식을 취하면, 음식의 품질이 떨어질 수 있고 고객들의 대기 시간이 길어질 것입니다. 인터럽트 서비스 루틴에서도 delay() 함수나 시간 소모가 큰 함수를 사용하면 인터럽트 처리가 지연되어 전체 시스템의 성능이 저하될 수 있습니다.

 

 

인터럽트 사용코드 예제

// : 설명글(주석) 코드 동작과는 무관합니다.

빨간색 : 코드 동작과 직접적인 연관이 있습니다.

 

// 아두이노에 필요한 라이브러리를 포함합니다.
#include <Arduino.h>

// 사용할 LED와 버튼의 핀 번호를 상수로 선언합니다.
const int ledPin = 13;
const int buttonPin = 2;

// 버튼 상태를 저장할 전역 변수를 선언하고 초기화합니다. 인터럽트와 주 코드에서 사용되므로 volatile로 선언합니다.
volatile boolean buttonPressed = false;

// 인터럽트 서비스 루틴(ISR)을 정의합니다. 버튼이 눌렸을 때 호출되는 함수입니다.
void handleButtonPress() {
  buttonPressed = true; // 버튼이 눌렸음을 나타내는 변수의 값을 참으로 변경합니다.
}

// 아두이노의 setup() 함수를 정의합니다. 처음 실행될 때 한 번 호출됩니다.
void setup() {
  pinMode(ledPin, OUTPUT); // LED 핀을 출력으로 설정합니다.
  pinMode(buttonPin, INPUT_PULLUP); // 버튼 핀을 내부 풀업 저항을 사용하는 입력으로 설정합니다.

  // 인터럽트를 설정합니다. digitalPinToInterrupt() 함수로 버튼 핀 번호를 인터럽트 번호로 변환하고, handleButtonPress 함수를 FALLING 모드로 연결합니다.
  attachInterrupt(digitalPinToInterrupt(buttonPin), handleButtonPress, FALLING);
}

// 아두이노의 loop() 함수를 정의합니다. 계속해서 실행됩니다.
void loop() {
  if (buttonPressed) { // 버튼이 눌렸다면
    digitalWrite(ledPin, HIGH); // LED를 켭니다.
    delay(1000); // 1초 동안 기다립니다.
    digitalWrite(ledPin, LOW); // LED를 끕니다.
    buttonPressed = false; // 버튼 상태를 다시 초기화합니다.
  }
}