Arduino Uno 및 유사한 보드에서 인터럽트는 어떻게 작동합니까?


11

ATmega328P 프로세서를 사용하여 Arduino Uno 및 관련 보드에서 인터럽트가 어떻게 작동하는지 설명하십시오. 다음과 같은 보드 :

  • 우노
  • 미니
  • 나노
  • 프로 미니
  • 릴리 패드

특히 다음을 논의하십시오.

  • 인터럽트를 사용하는 것
  • 인터럽트 서비스 루틴 (ISR)을 작성하는 방법
  • 타이밍 문제
  • 중요 섹션
  • 데이터에 대한 원자 적 접근

참고 : 이것은 참조 질문 입니다.

답변:


25

TL; DR :

ISR (Interrupt Service Routine)을 작성할 때 :

  • 짧게 유지
  • 사용하지 마십시오 delay ()
  • 연속 인쇄하지 마십시오
  • 메인 코드와 공유되는 변수를 휘발성으로 만들기
  • 메인 코드와 공유되는 변수는 "중요한 부분"으로 보호해야 할 수도 있습니다 (아래 참조).
  • 인터럽트를 끄거나 켜려고하지 마십시오

인터럽트 란 무엇입니까?

대부분의 프로세서에는 인터럽트가 있습니다. 인터럽트를 사용하면 다른 작업을 수행하면서 "외부"이벤트에 응답 할 수 있습니다. 예를 들어, 저녁 식사를 요리하는 경우 감자를 넣어 20 분 동안 요리 할 수 ​​있습니다. 20 분 동안 시계를 응시하지 않고 타이머를 설정 한 다음 TV를 시청할 수 있습니다. 타이머가 울리면 TV 시청을 "중단"하여 감자로 무언가를하십시오.


인터럽트의 예

const byte LED = 13;
const byte SWITCH = 2;

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  if (digitalRead (SWITCH) == HIGH)
    digitalWrite (LED, HIGH);
  else
    digitalWrite (LED, LOW);
}  // end of switchPressed

void setup ()
{
  pinMode (LED, OUTPUT);  // so we can update the LED
  pinMode (SWITCH, INPUT_PULLUP);
  attachInterrupt (digitalPinToInterrupt (SWITCH), switchPressed, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  // loop doing nothing
}

이 예는 메인 루프가 아무 작업도하지 않더라도 핀 D2의 스위치를 누르면 핀 13의 LED를 켜거나 끄는 방법을 보여줍니다.

이를 테스트하려면 D2와 접지 사이에 와이어 (또는 스위치)를 연결하십시오. 내부 풀업 (설정에서 활성화)은 핀을 정상적으로 HIGH로 설정합니다. 접지되면 LOW가됩니다. 핀 변경은 CHANGE 인터럽트에 의해 감지되어 ISR (Interrupt Service Routine)이 호출됩니다.

더 복잡한 예에서, 메인 루프는 온도 판독과 같은 유용한 작업을 수행하고 인터럽트 핸들러가 버튼을 누른 것을 감지 할 수 있도록합니다.


핀 번호를 인터럽트 번호로 변환

인터럽트 벡터 번호를 핀 번호로 변환하는 것을 단순화하기 위해 핀 번호를 digitalPinToInterrupt()전달 하여 함수를 호출 할 수 있습니다 . 적절한 인터럽트 번호 또는 NOT_AN_INTERRUPT(-1)을 반환합니다 .

예를 들어, Uno에서 보드의 핀 D2는 인터럽트 0입니다 (아래 표의 INT0_vect).

따라서이 두 줄은 같은 효과를 갖습니다.

  attachInterrupt (0, switchPressed, CHANGE);    // that is, for pin D2
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);

그러나 두 번째는 읽기 쉽고 다른 Arduino 유형에 더 이식성이 있습니다.


사용 가능한 인터럽트

다음은 Atmega328의 인터럽트 목록입니다 (우선 순위 순).

 1  Reset
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

내부 이름 (ISR 콜백을 설정하는 데 사용할 수 있음)은 괄호 안에 있습니다.

경고 : 인터럽트 벡터 이름의 철자를 잘못 입력하면 대문자를 잘못 입력하더라도 (쉽게 수행 할 수 있음) 인터럽트 루틴 이 호출 되지 않으며 컴파일러 오류가 발생하지 않습니다.


인터럽트를 사용해야하는 이유

인터럽트를 사용할 수있는 주요 이유는 다음과 같습니다.

  • 핀 변경을 감지하려면 (예 : 로터리 엔코더, 버튼 누름)
  • 워치 독 타이머
  • 타이머 인터럽트-타이머 비교 / 오버플로에 사용
  • SPI 데이터 전송
  • I2C 데이터 전송
  • USART 데이터 전송
  • ADC 변환 (아날로그에서 디지털로)
  • EEPROM 사용 준비
  • 플래시 메모리 준비

"데이터 전송"을 사용하여 직렬 포트, SPI 포트 또는 I2C 포트에서 데이터를 보내거나받는 동안 프로그램이 다른 작업을 수행 할 수 있습니다.

프로세서 깨우기

프로세서를 깨우기 위해 외부 인터럽트, 핀 변환 인터럽트 및 워치 독 타이머 인터럽트를 사용할 수도 있습니다. 슬립 모드에서 프로세서가 훨씬 적은 전력 (예 : 약 10 마이크로 암페어)을 사용하도록 구성 될 수 있기 때문에 이것은 매우 유용 할 수 있습니다. 상승, 하강 또는 저수준 인터럽트를 사용하여 가젯을 깨우거나 (예 : 버튼을 누르면) "워치 독 타이머"인터럽트가 주기적으로 깨어날 수 있습니다 (예 : 시간 확인 또는 온도).

핀 변환 인터럽트는 키패드 등에서 키를 누른 경우 프로세서를 깨우기 위해 사용될 수 있습니다.

프로세서는 또한 타이머 인터럽트 (예를 들어, 타이머가 특정 값에 도달하거나 오버플로하는) 및 들어오는 I2C 메시지와 같은 특정 다른 이벤트에 의해 깨어날 수있다.


인터럽트 활성화 / 비활성화

"재설정"인터럽트는 비활성화 할 수 없습니다. 그러나 전역 인터럽트 플래그를 지우면 다른 인터럽트를 일시적으로 비활성화 할 수 있습니다.

인터럽트 활성화

다음과 같이 "interrupts"또는 "sei"함수 호출로 인터럽트를 활성화 할 수 있습니다.

interrupts ();  // or ...
sei ();         // set interrupts flag

인터럽트 비활성화

인터럽트를 비활성화해야하는 경우 다음과 같이 전역 인터럽트 플래그를 "지울"수 있습니다.

noInterrupts ();  // or ...
cli ();           // clear interrupts flag

어느 방법을 사용하여 같은 효과를가 interrupts/ noInterrupts그들이 주위에 어떤 방법을 기억하는 것이 조금 더 쉽다.

Arduino의 기본값은 인터럽트 활성화입니다. 오랫동안 비활성화하지 마십시오. 타이머와 같은 기능이 제대로 작동하지 않습니다.

왜 인터럽트를 비활성화합니까?

타이머 인터럽트와 같이 중단하고 싶지 않은 시간 결정적인 코드가있을 수 있습니다.

또한 ISR에서 멀티 바이트 필드를 업데이트하는 경우 데이터를 "원자 적으로"얻기 위해 인터럽트를 비활성화해야 할 수도 있습니다. 그렇지 않으면 다른 바이트를 읽는 동안 ISR이 한 바이트를 업데이트 할 수 있습니다.

예를 들면 다음과 같습니다.

noInterrupts ();
long myCounter = isrCounter;  // get value set by ISR
interrupts ();

인터럽트를 일시적으로 끄면 값을 얻는 동안 isrCounter (ISR 내부에 설정된 카운터)가 변경되지 않습니다.

경고 : 인터럽트가 이미 설정되어 있는지 확실하지 않은 경우 현재 상태를 저장하고 나중에 복원해야합니다. 예를 들어 millis () 함수의 코드는 다음을 수행합니다.

unsigned long millis()
{
  unsigned long m;
  uint8_t oldSREG = SREG;    // <--------- save status register

  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  m = timer0_millis;
  SREG = oldSREG;            // <---------- restore status register including interrupt flag

  return m;
}

표시된 행은 인터럽트 플래그를 포함하는 현재 SREG (상태 레지스터)를 저장합니다. 타이머 값 (4 바이트 길이)을 얻은 후 상태 레지스터를 원래 상태로 되돌립니다.


기능 이름

기능 cli/ sei및 레지스터 SREG는 AVR 프로세서에 따라 다릅니다. ARM 프로세서와 같은 다른 프로세서를 사용하는 경우 기능이 약간 다를 수 있습니다.

전역 비활성화 및 하나의 인터럽트 비활성화

사용하는 경우 모든 인터럽트 (타이머 인터럽트, 직렬 인터럽트 등) cli()를 비활성화 합니다 .

그러나 특정 인터럽트 를 비활성화 하려면 해당 인터럽트 소스에 대한 인터럽트 활성화 플래그를 지워야합니다. 예를 들어 외부 인터럽트의 경우을 호출하십시오 detachInterrupt().


인터럽트 우선 순위는 무엇입니까?

25 개의 인터럽트 (재설정 제외)가 있으므로 한 번에 둘 이상의 인터럽트 이벤트가 발생하거나 적어도 이전 인터럽트가 처리되기 전에 발생할 수 있습니다. 또한 인터럽트가 비활성화 된 동안 인터럽트 이벤트가 발생할 수 있습니다.

우선 순위는 프로세서가 인터럽트 이벤트를 확인하는 순서입니다. 목록이 높을수록 우선 순위가 높습니다. 예를 들어 외부 인터럽트 요청 0 (핀 D2)은 외부 인터럽트 요청 1 (핀 D3)보다 먼저 서비스됩니다.


인터럽트가 비활성화 된 동안 인터럽트가 발생할 수 있습니까?

인터럽트 이벤트 (이벤트 알림)는 언제라도 발생할 수 있으며 대부분 프로세서 내부에 "인터럽트 이벤트"플래그를 설정하면 기억됩니다. 인터럽트가 비활성화되면 해당 인터럽트는 우선적으로 다시 활성화 될 때 처리됩니다.


인터럽트는 어떻게 사용합니까?

  • ISR (인터럽트 서비스 루틴)을 작성합니다. 인터럽트가 발생할 때 호출됩니다.
  • 인터럽트 발생 시점을 프로세서에 알려줍니다.

ISR 작성

인터럽트 서비스 루틴은 인수가없는 함수입니다. 일부 Arduino 라이브러리는 자체 함수를 호출하도록 설계되었으므로 예를 들어 위의 예와 같이 일반 함수 만 제공하면됩니다.

// Interrupt Service Routine (ISR)
void switchPressed ()
{
 flag = true;
}  // end of switchPressed

그러나 라이브러리가 ISR에 "후크"를 제공하지 않은 경우 다음과 같이 직접 만들 수 있습니다.

volatile char buf [100];
volatile byte pos;

// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

이 경우 "ISR"매크로를 사용하고 관련 인터럽트 벡터의 이름을 제공하십시오 (앞의 표에서). 이 경우 ISR이 SPI 전송 완료를 처리하고 있습니다. (이전 코드 중 일부는 ISR 대신 SIGNAL을 사용하지만 SIGNAL은 더 이상 사용되지 않습니다).

ISR을 인터럽트에 연결

라이브러리가 이미 처리 한 인터럽트의 경우 문서화 된 인터페이스 만 사용하십시오. 예를 들면 다음과 같습니다.

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
    {
    char c = Wire.receive ();
    // do something with the incoming byte
    }
}  // end of receiveEvent

void setup ()
  {
  Wire.onReceive(receiveEvent);
  }

이 경우 I2C 라이브러리는 들어오는 I2C 바이트를 내부적으로 처리 한 다음 들어오는 데이터 스트림의 끝에서 제공된 함수를 호출하도록 설계되었습니다. 이 경우 receiveEvent는 ISR (인수 포함)이 아니지만 내장 ISR에 의해 호출됩니다.

또 다른 예는 "외부 핀"인터럽트입니다.

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
}  // end of switchPressed

void setup ()
{
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);  // attach interrupt handler for D2
}  // end of setup

이 경우 attachInterrupt 함수는 switchPressed 함수를 내부 테이블에 추가하고 프로세서에서 적절한 인터럽트 플래그를 구성합니다.

인터럽트를 처리하도록 프로세서 구성

다음 단계는 일단 ISR이 있으면 프로세서에게이 특정 조건이 인터럽트를 발생 시키길 원한다고 알려주는 것입니다.

예를 들어, 외부 인터럽트 0 (D2 인터럽트)의 경우 다음과 같이 할 수 있습니다.

EICRA &= ~3;  // clear existing flags
EICRA |= 2;   // set wanted flags (falling level interrupt)
EIMSK |= 1;   // enable it

다음과 같이 정의 된 이름을 사용하는 것이 더 읽기 쉽습니다.

EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
EICRA |= bit (ISC01);    // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0);     // enable it

EICRA (외부 인터럽트 제어 레지스터 A)는 Atmega328 데이터 시트에서이 표에 따라 설정됩니다. 원하는 정확한 인터럽트 유형을 정의합니다.

  • 0 : INT0의 하위 레벨이 인터럽트 요청 (LOW 인터럽트)을 생성합니다.
  • 1 : INT0의 논리적 변경은 인터럽트 요청 (CHANGE 인터럽트)을 생성합니다.
  • 2 : INT0의 하강 에지가 인터럽트 요청 (FALLING 인터럽트)을 생성합니다.
  • 3 : INT0의 상승 에지가 인터럽트 요청 (RISING 인터럽트)을 생성합니다.

EIMSK (외부 인터럽트 마스크 레지스터)는 실제로 인터럽트를 활성화합니다.

다행히도, 당신은 attachInterrupt가 당신을 위해 그것을하기 때문에 그 숫자를 기억할 필요가 없습니다. 그러나 이것이 실제로 일어나는 일이며 다른 인터럽트의 경우 인터럽트 플래그를 "수동으로"설정해야 할 수도 있습니다.


저수준 ISR 및 라이브러리 ISR

인생을 단순화하기 위해 일부 일반적인 인터럽트 처리기는 실제로 라이브러리 코드 안에 있습니다 (예 : INT0_vect 및 INT1_vect).보다 사용자 친화적 인 인터페이스가 제공됩니다 (예 : attachInterrupt). attachInterrupt가 실제로하는 일은 원하는 인터럽트 핸들러의 주소를 변수에 저장 한 다음 필요할 때 INT0_vect / INT1_vect에서 호출하는 것입니다. 또한 필요한 경우 핸들러를 호출하도록 적절한 레지스터 플래그를 설정합니다.


ISR을 중단 할 수 있습니까?

간단히 말해서, 당신이 원하지 않는 한 아닙니다.

ISR이 입력되면 인터럽트가 비활성화 됩니다. 당연히 그들은 처음부터 활성화되어야합니다. 그렇지 않으면 ISR이 입력되지 않습니다. 그러나 ISR 자체가 중단되지 않도록 프로세서가 인터럽트를 끕니다.

ISR이 종료되면 인터럽트가 다시 활성화됩니다 . 또한 컴파일러는 ISR 내부에 코드를 생성하여 레지스터와 상태 플래그를 저장하므로 인터럽트 발생시 수행 한 작업에 영향을 미치지 않습니다.

그러나 반드시 필요한 경우 ISR 내부에서 인터럽트를 켤 수 있습니다 .

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
  interrupts ();  // allow more interrupts

}  // end of switchPressed

일반적으로 다른 인터럽트로 인해 pinChange에 대한 재귀 호출이 발생할 수 있으므로 바람직하지 않은 결과가 발생할 수 있으므로 상당한 이유가 필요합니다.


ISR을 실행하는 데 얼마나 걸립니까?

데이터 시트에 따르면, 인터럽트를 처리하는 최소 시간은 4 클록 사이클 (현재 프로그램 카운터를 스택에 푸시하기 위해)과 그 다음에 인터럽트 벡터 위치에서 실행되는 코드입니다. 이것은 일반적으로 인터럽트 루틴이 실제로있는 곳으로의 점프를 포함합니다. 이것은 또 다른 3 사이클입니다. 컴파일러가 생성 한 코드를 조사한 결과 "ISR"선언으로 작성된 ISR은 실행하는 데 약 2.625 µs가 소요될 수 있으며 코드 자체가 수행하는 모든 작업을 수행 할 수 있습니다. 정확한 양은 저장 및 복원해야하는 레지스터 수에 따라 다릅니다. 최소량은 1.1875 µs입니다.

attachInterrupt를 사용하는 외부 인터럽트는 조금 더 많은 작업을 수행하고 총 5.125µs (16MHz 클럭으로 실행)를 사용합니다.


프로세서가 ISR에 들어가기까지 얼마나 걸립니까?

이것은 다소 다릅니다. 위에서 인용 한 수치는 인터럽트가 즉시 처리되는 이상적인 수치입니다. 몇 가지 요인으로 인해 지연 될 수 있습니다.

  • 프로세서가 절전 모드 인 경우 "깨우기"시간이 지정되는데,이 시간은 꽤 밀리 초일 수 있으며 시계는 속도로 스풀링됩니다. 이 시간은 퓨즈 설정과 수면 깊이에 따라 다릅니다.

  • 인터럽트 서비스 루틴이 이미 실행 중이면 완료되거나 인터럽트 자체를 활성화 할 때까지 추가 인터럽트를 입력 할 수 없습니다. 그렇기 때문에 각 인터럽트 서비스 루틴을 짧게 유지해야합니다. 마이크로 초마다 소비 할 때마다 다른 인터럽트 실행을 지연시킬 수 있습니다.

  • 일부 코드는 인터럽트를 끕니다. 예를 들어 millis ()를 호출하면 인터럽트가 잠깐 꺼집니다. 따라서 인터럽트가 서비스되는 시간은 인터럽트가 꺼진 시간만큼 연장됩니다.

  • 인터럽트는 명령이 끝날 때만 서비스 할 수 있으므로 특정 명령이 세 개의 클럭 사이클을 취하고 방금 시작된 경우 인터럽트는 적어도 두 개의 클럭 사이클에서 지연됩니다.

  • 인터럽트를 다시 켜는 이벤트 (예 : 인터럽트 서비스 루틴에서 복귀)는 하나 이상의 명령어를 실행하도록 보장됩니다. 따라서 ISR이 종료되고 인터럽트가 보류중인 경우에도 서비스를 제공하기 전에 하나 이상의 명령을 기다려야합니다.

  • 인터럽트는 우선 순위를 가지므로 관심있는 인터럽트보다 우선 순위가 높은 인터럽트가 서비스 될 수 있습니다.


성능 고려 사항

스위치가 눌 렸는지 지속적으로 테스트하지 않고도 프로그램의 "주요 작업"을 수행 할 수 있기 때문에 많은 상황에서 인터럽트가 성능을 향상시킬 수 있습니다. 위에서 언급했듯이 인터럽트 서비스 오버 헤드는 실제로 단일 입력 포트를 폴링하는 "긴밀한 루프"를 수행하는 것 이상이라고 할 수 있습니다. 예를 들어 마이크로 초 이내에 이벤트에 거의 응답 할 수 없습니다. 이 경우 인터럽트 (예 : 타이머)를 비활성화하고 변경하려는 핀을 찾기 만하면됩니다.


인터럽트는 어떻게 대기합니까?

두 가지 종류의 인터럽트가 있습니다 :

  • 일부는 플래그를 설정했으며이를 유발 한 이벤트가 중지 된 경우에도 우선 순위에 따라 처리됩니다. 예를 들어, 핀 D2의 상승, 하강 또는 변경 레벨 인터럽트.

  • 다른 사람들은 "지금"발생하는 경우에만 테스트됩니다. 예를 들어, 핀 D2의 저수준 인터럽트입니다.

인터럽트 루틴이 입력 될 때까지 프로세서가 플래그를 지우는 시간까지 인터럽트 플래그가 설정된 상태로 유지되므로 플래그를 설정 한 플래그는 대기중인 것으로 간주 될 수 있습니다. 물론 하나의 플래그 만 있기 때문에 첫 번째 플래그가 처리되기 전에 동일한 인터럽트 조건이 다시 발생하면 두 번 서비스되지 않습니다.

알아야 할 것은 인터럽트 핸들러를 연결하기 전에 이러한 플래그를 설정할 수 있다는 것입니다. 예를 들어, 핀 D2의 상승 또는 하강 레벨 인터럽트가 "플래그"될 수 있으며, attachInterrupt를 수행하자마자 이벤트가 한 시간 전에 발생한 경우에도 인터럽트가 즉시 발생합니다. 이를 피하기 위해 수동으로 플래그를 지울 수 있습니다. 예를 들면 다음과 같습니다.

EIFR = bit (INTF0);  // clear flag for interrupt 0
EIFR = bit (INTF1);  // clear flag for interrupt 1

그러나 "낮은 레벨"인터럽트는 계속 확인되므로주의하지 않으면 인터럽트가 호출 된 후에도 계속 실행됩니다. 즉, ISR이 종료 된 다음 인터럽트가 즉시 다시 시작됩니다. 이를 피하려면 인터럽트가 발생했다는 것을 알게 된 직후 detachInterrupt를 수행해야합니다.


ISR 작성을위한 힌트

간단히 말해서 짧게 유지하십시오! ISR이 실행되는 동안 다른 인터럽트를 처리 할 수 ​​없습니다. 너무 많은 노력을 기울이면 버튼 누름이나 직렬 통신 수신을 쉽게 놓칠 수 있습니다. 특히, ISR 내부에서 "인쇄"디버깅을 시도해서는 안됩니다. 그렇게하는 데 걸리는 시간은 해결하는 것보다 더 많은 문제를 일으킬 수 있습니다.

합리적인 방법은 1 바이트 플래그를 설정 한 다음 기본 루프 기능에서 해당 플래그를 테스트하는 것입니다. 또는 직렬 포트에서 들어오는 바이트를 버퍼에 저장하십시오. 내장 타이머 인터럽트는 내부 타이머 오버플로가 발생할 때마다 발생하여 경과 시간을 추적하므로 타이머 오버플로 횟수를 알면 경과 시간을 계산할 수 있습니다.

내부 ISR 인터럽트는 비활성화되어 있습니다. 따라서 millis () 함수 호출에 의해 반환되는 시간이 변경되기를 기대하면 실망 할 것입니다. 하는 유효 얻는 방법은, 그냥 타이머가 증가되지 않는다는 것을 인식하는 시간을. 그리고 ISR에서 너무 오래 소비하면 타이머에 오버플로 이벤트가 누락되어 millis ()가 반환 한 시간이 잘못 될 수 있습니다.

테스트 결과, 16MHz Atmega328 프로세서에서 micros () 호출에 3.5625 µs가 걸립니다. millis () 호출에는 1.9375 µs가 걸립니다. 현재 타이머 값을 기록 (저장)하는 것은 ISR에서 합리적인 일입니다. 경과 된 밀리 초를 찾는 것이 경과 된 마이크로 초보다 빠릅니다 (밀리 초 카운트는 변수에서 검색됩니다). 그러나 마이크로 초 카운트는 타이머 0 타이머의 현재 값 (증가를 유지)을 저장된 "타이머 0 오버 플로우 카운트"에 추가하여 얻습니다.

경고 : ISR 내에서 인터럽트가 비활성화되어 있고 최신 버전의 Arduino IDE는 직렬 읽기 및 쓰기에 인터럽트를 사용하고 "millis"및 "delay"에 의해 사용되는 카운터를 증가시키기 위해 이러한 기능을 사용하지 않아야합니다. ISR 내부. 다시 말하면 :

  • 지연을 시도하지 마십시오. 예 : delay (100);
  • 당신은 millis에 전화에서 시간을 얻을 수 있지만, 증가하지 않습니다, 그래서 시간이 증가 할 때까지 대기하지 마십시오.
  • 직렬 인쇄하지 마십시오 (예. Serial.println ("ISR entered");)
  • 연속 판독을 시도하지 마십시오.

핀 변경 인터럽트

핀에서 외부 이벤트를 감지 할 수있는 두 가지 방법이 있습니다. 첫 번째는 특수한 "외부 인터럽트"핀인 D2 및 D3입니다. 이러한 일반적인 이산 인터럽트 이벤트는 핀당 하나씩입니다. 각 핀마다 attachInterrupt를 사용하여 얻을 수 있습니다. 인터럽트에 대한 상승, 하강, 변경 또는 하위 레벨 조건을 지정할 수 있습니다.

그러나 모든 핀에 대한 "핀 변경"인터럽트도 있습니다 (Atmega328에는 반드시 다른 프로세서의 모든 핀이 아님). 핀 그룹 (D0 ~ D7, D8 ~ D13 및 A0 ~ A5)에 작용합니다. 또한 외부 이벤트 인터럽트보다 우선 순위가 낮습니다. 그러나 배치로 그룹화되어 있기 때문에 외부 인터럽트보다 사용하기가 약간 더 어려워집니다. 따라서 인터럽트가 발생하면 정확히 어떤 핀이 인터럽트를 일으켰는지 직접 코드에서 해결해야합니다.

예제 코드 :

ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 }  // end of PCINT2_vect


void setup ()
  {
  // pin change interrupt (example for D9)
  PCMSK0 |= bit (PCINT1);  // want pin 9
  PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13
  }

핀 변경 인터럽트를 처리하려면 다음이 필요합니다.

  • 그룹의 핀을 지정하십시오. 이것은 PCMSKn 변수입니다 (여기서 n은 아래 표에서 0, 1 또는 2 임). 하나 이상의 핀에서 인터럽트를 가질 수 있습니다.
  • 적절한 인터럽트 그룹 활성화 (0, 1 또는 2)
  • 위와 같이 인터럽트 핸들러를 제공하십시오

핀 테이블-> 핀 변경 이름 / 마스크

D0    PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1    PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2    PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3    PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4    PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5    PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6    PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7    PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8    PCINT0  (PCMSK0 / PCIF0 / PCIE0)
D9    PCINT1  (PCMSK0 / PCIF0 / PCIE0)
D10   PCINT2  (PCMSK0 / PCIF0 / PCIE0)
D11   PCINT3  (PCMSK0 / PCIF0 / PCIE0)
D12   PCINT4  (PCMSK0 / PCIF0 / PCIE0)
D13   PCINT5  (PCMSK0 / PCIF0 / PCIE0)
A0    PCINT8  (PCMSK1 / PCIF1 / PCIE1)
A1    PCINT9  (PCMSK1 / PCIF1 / PCIE1)
A2    PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3    PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4    PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5    PCINT13 (PCMSK1 / PCIF1 / PCIE1)

인터럽트 핸들러 처리

마스크가 하나 이상을 지정한 경우 (예 : D8 / D9 / D10에서 인터럽트를 원하는 경우) 인터럽트 처리기는 인터럽트를 일으킨 핀을 해결해야합니다. 이렇게하려면 해당 핀의 이전 상태를 저장하고이 특정 핀이 변경된 경우 (digitalRead 등을 수행하여) 해결해야합니다.


어쨌든 인터럽트를 사용하고있을 것입니다 ...

"일반적인"Arduino 환경은 개인적으로 시도하지 않더라도 이미 인터럽트를 사용하고 있습니다. millis () 및 micros () 함수 호출은 "타이머 오버플로"기능을 사용합니다. 내부 타이머 중 하나 (타이머 0)는 초당 약 1000 번 인터럽트하도록 설정되어 내부 카운터를 증가시켜 효과적으로 millis () 카운터가됩니다. 정확한 클럭 속도에 대한 조정이 이루어지기 때문에 그보다 조금 더 있습니다.

또한 하드웨어 직렬 라이브러리는 인터럽트를 사용하여 들어오고 나가는 직렬 데이터를 처리합니다. 인터럽트가 발생하고 내부 버퍼를 채우는 동안 프로그램이 다른 작업을 수행 할 수 있으므로 매우 유용합니다. 그런 다음 Serial.available ()을 확인하면 해당 버퍼에 무엇이 있는지 알 수 있습니다.


인터럽트를 활성화 한 후 다음 명령어 실행

Arduino 포럼에서 약간의 토론과 연구를 한 후, 인터럽트를 활성화 한 후 발생하는 상황을 정확하게 설명했습니다. 이전에 활성화되지 않은 인터럽트를 활성화 할 수 있다고 생각할 수있는 세 가지 주요 방법이 있습니다.

  sei ();  // set interrupt enable flag
  SREG |= 0x80;  // set the high-order bit in the status register
  reti  ;   // assembler instruction "return from interrupt"

모든 경우에 프로세서 는 인터럽트 이벤트가 보류중인 경우에도 인터럽트가 활성화 된 후 (이전에 비활성화 된 경우) 다음 명령어가 항상 실행되도록 보장합니다 . ( "다음"은 프로그램 순서에서 다음을 의미하며 반드시 물리적으로 뒤 따르는 것은 아닙니다.) 예를 들어, RETI 명령은 인터럽트가 발생한 위치로 되돌아 가서 하나 이상의 명령을 실행합니다.

이를 통해 다음과 같은 코드를 작성할 수 있습니다.

sei ();
sleep_cpu ();

이 보증이 아닌 경우 , 프로세서가 잠들기 전에 인터럽트가 발생한 후 결코 깨어날 수 없습니다.


빈 인터럽트

프로세서를 깨우기 위해 인터럽트를 원하지만 특별히 수행하지 않는 경우 EMPTY_INTERRUPT 정의를 사용할 수 있습니다.

EMPTY_INTERRUPT (PCINT1_vect);

이것은 단순히 "reti"(인터럽트에서 복귀) 명령을 생성합니다. 레지스터를 저장하거나 복원하려고 시도하지 않기 때문에이를 깨우기 위해 인터럽트를 얻는 가장 빠른 방법입니다.


중요 섹션 (원자 변수 액세스)

ISR (인터럽트 서비스 루틴)과 주 코드 (즉, ISR에없는 코드)간에 공유되는 변수와 관련하여 미묘한 문제가 있습니다.

인터럽트가 활성화되면 ISR이 언제든 작동 할 수 있으므로 액세스하는 순간 업데이트 될 수 있으므로 이러한 공유 변수에 액세스하는 데주의해야합니다.

우선 ... 언제 "휘발성"변수를 사용합니까?

변수는 ISR 내부와 외부에서 모두 사용되는 경우에만 휘발성으로 표시해야합니다.

  • 변수 ISR이 외부에서 사용은해야 하지 휘발성합니다.
  • 변수는 단지 ISR이 내부에 사용해야 하지 휘발성합니다.
  • ISR 내부와 외부에서 사용되는 변수 휘발성 이어야 합니다.

예.

volatile int counter;

변수를 휘발성으로 표시하면 컴파일러가 변수 내용을 프로세서 레지스터에 "캐시"하지 않고 필요할 때 항상 메모리에서 읽습니다. 이로 인해 처리 속도가 느려질 수 있으므로 필요할 때마다 모든 변수를 일시적으로 변경하지는 않습니다.

휘발성 변수에 액세스하는 동안 인터럽트 끄기

예를 들어, count일부 숫자와 비교 하려면 한 바이트가 count다른 바이트가 아닌 ISR에 의해 업데이트 된 경우 비교 중에 인터럽트를 끄십시오 .

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  noInterrupts ();    // <------ critical section
  if (count > 20)
     digitalWrite (13, HIGH);
  interrupts ();      // <------ end critical section
  } // end of loop

데이터 시트를 읽으십시오!

인터럽트, 타이머 등에 대한 자세한 정보는 프로세서의 데이터 시트에서 얻을 수 있습니다.

http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf


추가 예

공간 고려 사항 (포스트 크기 제한)으로 인해 목록에 더 많은 예제 코드가 표시되지 않습니다. 더 많은 예제 코드 는 인터럽트에 대한 내 페이지를 참조하십시오 .


매우 유용한 참고 자료-매우 빠른 답변이었습니다.
Dat Han Bag 1

참고 질문이었습니다. 나는 대답을 준비했고 대답이 너무 길지 않았다면 훨씬 빨 랐을 것이므로 다시 정리해야했습니다. 자세한 내용은 링크 된 사이트를 참조하십시오.
Nick Gammon

"절전 모드"에 대해 Arduino를 500ms 동안 절전 모드로 만드는 것이 효율적입니까?
Dat Ha

@Nick Gammon CPU의 전원 켜기 또는 끄기 (자동화 여부에 관계없이)를 원한다면 기존의 인터럽트로 정의 할 수 있다고 생각합니다. "답변을 준비했습니다"– 당신은 내가 생각했던 순간의 모든 마술을 꺼 냈습니다.
Dat Han Bag 1

1
사실이 아닌 것 같습니다. 내가 가진 예를 들어 사용 핀 변화 인터럽트가 파워 다운 모드에서 깨울 수 있음을. 또한 내 페이지에서 인터럽트에 대해 언급 했듯이 Atmel은 외부 인터럽트가 프로세서를 깨울 것임을 확인했습니다 (예 : 상승 / 하강 / 변경 낮음).
Nick Gammon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.