레이블이 attachInterrupt인 게시물을 표시합니다. 모든 게시물 표시
레이블이 attachInterrupt인 게시물을 표시합니다. 모든 게시물 표시

2018년 11월 22일 목요일

ESP32에서 인터럽트 사용하기

ESP32는 26개의 GPIO핀을 인터럽트 소스로 사용할 수 있다.

* 아두이노 우노/메가에서는 인터럽트 소스로 사용할 수 있는 핀이 몇개 안된다.




위의 그림에서 빨간색 사각형 안에 들어있는 GPIO 핀들이 인터럽트 소스로 사용 가능한 핀이다.

아두이노에서 인터럽트를 사용하려면 attachInterrupt() 함수로 인터럽트를 설정해 줘야 한다.

attachInterrupt (digitalPinToInterrupt(GPIO), function, mode);

첫번째 파라미터는 인터럽트 소스로 사용할 GPIO 핀 번호인데 번호를 그냥 사용하지 말고 digitalPinToInterrupt(GPIO) 로 넣어줘야 한다. 예를 들어 GPIO 13을 인터럽트 소스로 사용하려면 digitalPinToInterrupt(13) 을 넣어주면 된다.

두번째 파라미터는 인터럽트가 발생했을 때 호출될 함수(Interrupt Service Routine)의 이름이다.

세번째 파라미터는 인터럽트 발생 조건이다. 총 5가지 모드중에 하나를 선택하면 된다.

* LOW - 인터럽트 핀 상태가 LOW면 인터럽트가 발생
* HIGH - 인터럽트 핀 상태가 HIGH면 인터럽트가 발생
* CHANGE - 인터럽트 핀 상태가 바뀌면 인터럽트가 발생. 즉 HIGH에서 LOW로 바뀌거나, LOW에서 HIGH로 바뀌면 인터럽트가 발생
* FALLING - 인터럽트 핀 상태가 HIGH에서 LOW로 바뀔 때 인터럽트가 발생
* RISING - 인터럽트 핀 상태가 LOW에서 HIGH로 바뀔 때 인터럽트가 발생

그러므로 인터럽트를 사용하는 경우 코드의 구조는 다음과 같이 된다.

// 인터럽트가 발생하면 이 함수가 호출됨
void IRAM_ATTR intSvc() 
{
  // 스위치가 눌렸을 때 원하는 동작 코드를 이 부분에 넣어줌
  Serial.println("# Button pressed");
}
void setup()
{
  ...
  attachInterrupt(digitalPinToInterrupt(13), intSvc, LOW);  // GPIO13번 핀에 연결된 스위치가 눌리면 인터럽트가 발생해 intSvc 함수를 호출하도록 설정
  ...
}

void loop()
{
}

위의 코드를 잘 보면 인터럽트가 발생했을 때 호출될 함수 선언에 IRAM_ATTR 이라는 것이 추가되어 있는것을 알 수 있을 것이다. IRAM_ATTR을 넣어줘야 인터럽트 처리 함수가 RAM에 들어가게 된다. IRAM_ATTR이 없으면 인터럽트 처리 함수가 플래쉬에 들어가게 되어 실행 속도가 느려진다.


위와 같이 연결 해 놓고 스위치를 누를 때 마다 LED가 토글되는 코드는 다음과 같다.

#define LED 25
#define SW 13

volatile boolean gLedState = LOW;
 
void IRAM_ATTR toggle() 
{
  gLedState = !gLedState;
  digitalWrite(LED, gLedState);
}


void setup()
{
  pinMode(SW, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SW), toggle, LOW);  // GPIO13번 핀에 연결된 스위치가 눌리면 toggle 함수를 호출하도록 설정
  pinMode(LED, OTUPUT);
}

void loop()
{
}






2009년 6월 2일 화요일

아뒤노에서 타이머 인터럽트 (Timer interrupt in Arduino)



아뒤노가 임베디드 환경을 처음 접하는 사람들에게 매우 쉬운 환경임에 틀림없지만 조금만 복잡한 작업을 하려고만 하면 제약사항이 꽤 많아진다.
특히 가장 아쉬운게 attachInterrupt()를 통해서 외부 인터럽트 2개(Arduino Mega에서는 6개로 늘어났다)만 사용할 수 있다는 점이다.

단순하게 센서값을 읽어서 그 값에 따라 LED나 모터를 구동하는건 별 문제가 없지만 동시에 여러개의 입력을 기다리면서 그와 별도로 작업을 처리하거나 해야 한다면 타이머 인터럽트는 거의 필수적이 되어 버린다.

물론 atmega의 타이머 관련 레지스터(TCCR2, ASSR 등등)를 직접 제어하면 인터럽트를 사용할 수 있지만 이 경우 아뒤노의 장점(?)이 사라져 버린다.
그래서 아뒤노 라이브러리 섹션의 contributed libraries (아뒤노 사용자들이 만든 라이브러리들)에 보면 MsTimer2가 있다. Atmega의 timer 2를 사용해서 ms 단위의 해상도로 타이머 인터럽트를 걸어줄 수 있다.
현재 Atmega 1280, 328, 48/88/168, 128/8을 사용한 arduino 및 arduino clone에서 사용할 수 있다.

이 라이브러리를 사용하려면 MsTimer2를 다운받아 압축을 해제한 다음 만들어 진 폴더를 아뒤노가 설치된 폴더 내의 hardware/libraries 에 복사해 주면 그것으로 설치가 끝난다. 그리고 스케치북에 #include <MsTimer2.h> 를 넣어주면 된다.

set, start, stop 이렇게 단 3개의 메쏘드만 있어 사용법은 매우 쉽다.
먼저 MsTimer2::set(unsigned long ms, void (*f)()) 을 사용해서 타이머를 설정해 준다. 이 함수에는 두개의 파라메터가 필요하다. 첫번째는 타이머 시간(ms 단위, 즉 여기에 1000을 넣어주면 1초마다 한번씩 타이머 인터럽트가 발생한다.), 두번째는 인터럽트 서비스 루틴(인터럽트가 발생했을 때 호출할 함수) 이름이다.
set()을 이용해서 타이머를 설정했으면 MsTimer2::start() 를 호출해주면 타이머가 동작하기 시작해 정해진 시간마다 인터럽트 서비스 루틴이 실행된다.
타이머 동작을 멈추려면 MsTimer2::stop()을 호출해주면 된다.

타이머 인터럽트가 왜 유용한가는 아래의 예제를 보면 잘 알수있다.
0.5초마다 LED를 깜빡이게 하는 프로그램이다.

boolean output = HIGH;
void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, output);
output = !output;
delay(500);
}
---------------------------------------
// Toggle LED on pin 13 each second
#include <MsTimer2.h>

void flash() {
static boolean output = HIGH;

digitalWrite(13, output);
output = !output;
}

void setup() {
pinMode(13, OUTPUT);

MsTimer2::set(500, flash); // 500ms period
MsTimer2::start();
}

void loop() {
}

둘 다 같은 동작을 한다. 그런데 위의 경우와 같이 타이머 인터럽트를 사용하지 않은 경우 0.5초동안 다른 일을 하지 못하고 단지 기다려야 한다. 그에 비해 아래쪽 프로그램의 경우는 LED를 토글하는걸 인터럽트 서비스 루틴(여기서는 flash())에서 처리해주기 때문에 따로 0.5초를 기다릴 필요가 없이 그 동안 다른 일을 할 수 있게 된다. (loop() 함수에서 아무일도 하지 않는걸 확인할 수 있다. 여기에 다른 작업을 넣어주면 된다.)

예를 들어 동작중에 시리얼 포트를 통해 시간을 입력받아 LED가 깜빡이는 간격을 조정하도록 만들려면 상당히 힘들어 지지만 아래쪽 프로그램의 경우 시리얼 포트를 통해 데이터를 입력받는 부분은 loop() 안에서 처리하게 하고 그 입력받은 값에 따라 타이머 설정만 바꿔주면 실제 깜빡이는건 타이머 인터럽트가 담당해 주기 때문에 매우 간단해진다.