답변:
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 포트에서 데이터를 보내거나받는 동안 프로그램이 다른 작업을 수행 할 수 있습니다.
프로세서를 깨우기 위해 외부 인터럽트, 핀 변환 인터럽트 및 워치 독 타이머 인터럽트를 사용할 수도 있습니다. 슬립 모드에서 프로세서가 훨씬 적은 전력 (예 : 약 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)보다 먼저 서비스됩니다.
인터럽트 이벤트 (이벤트 알림)는 언제라도 발생할 수 있으며 대부분 프로세서 내부에 "인터럽트 이벤트"플래그를 설정하면 기억됩니다. 인터럽트가 비활성화되면 해당 인터럽트는 우선적으로 다시 활성화 될 때 처리됩니다.
인터럽트 서비스 루틴은 인수가없는 함수입니다. 일부 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은 더 이상 사용되지 않습니다).
라이브러리가 이미 처리 한 인터럽트의 경우 문서화 된 인터페이스 만 사용하십시오. 예를 들면 다음과 같습니다.
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 데이터 시트에서이 표에 따라 설정됩니다. 원하는 정확한 인터럽트 유형을 정의합니다.
EIMSK (외부 인터럽트 마스크 레지스터)는 실제로 인터럽트를 활성화합니다.
다행히도, 당신은 attachInterrupt가 당신을 위해 그것을하기 때문에 그 숫자를 기억할 필요가 없습니다. 그러나 이것이 실제로 일어나는 일이며 다른 인터럽트의 경우 인터럽트 플래그를 "수동으로"설정해야 할 수도 있습니다.
인생을 단순화하기 위해 일부 일반적인 인터럽트 처리기는 실제로 라이브러리 코드 안에 있습니다 (예 : INT0_vect 및 INT1_vect).보다 사용자 친화적 인 인터페이스가 제공됩니다 (예 : attachInterrupt). attachInterrupt가 실제로하는 일은 원하는 인터럽트 핸들러의 주소를 변수에 저장 한 다음 필요할 때 INT0_vect / INT1_vect에서 호출하는 것입니다. 또한 필요한 경우 핸들러를 호출하도록 적절한 레지스터 플래그를 설정합니다.
간단히 말해서, 당신이 원하지 않는 한 아닙니다.
ISR이 입력되면 인터럽트가 비활성화 됩니다. 당연히 그들은 처음부터 활성화되어야합니다. 그렇지 않으면 ISR이 입력되지 않습니다. 그러나 ISR 자체가 중단되지 않도록 프로세서가 인터럽트를 끕니다.
ISR이 종료되면 인터럽트가 다시 활성화됩니다 . 또한 컴파일러는 ISR 내부에 코드를 생성하여 레지스터와 상태 플래그를 저장하므로 인터럽트 발생시 수행 한 작업에 영향을 미치지 않습니다.
그러나 반드시 필요한 경우 ISR 내부에서 인터럽트를 켤 수 있습니다 .
// Interrupt Service Routine (ISR)
void switchPressed ()
{
// handle pin change here
interrupts (); // allow more interrupts
} // end of switchPressed
일반적으로 다른 인터럽트로 인해 pinChange에 대한 재귀 호출이 발생할 수 있으므로 바람직하지 않은 결과가 발생할 수 있으므로 상당한 이유가 필요합니다.
데이터 시트에 따르면, 인터럽트를 처리하는 최소 시간은 4 클록 사이클 (현재 프로그램 카운터를 스택에 푸시하기 위해)과 그 다음에 인터럽트 벡터 위치에서 실행되는 코드입니다. 이것은 일반적으로 인터럽트 루틴이 실제로있는 곳으로의 점프를 포함합니다. 이것은 또 다른 3 사이클입니다. 컴파일러가 생성 한 코드를 조사한 결과 "ISR"선언으로 작성된 ISR은 실행하는 데 약 2.625 µs가 소요될 수 있으며 코드 자체가 수행하는 모든 작업을 수행 할 수 있습니다. 정확한 양은 저장 및 복원해야하는 레지스터 수에 따라 다릅니다. 최소량은 1.1875 µs입니다.
attachInterrupt를 사용하는 외부 인터럽트는 조금 더 많은 작업을 수행하고 총 5.125µs (16MHz 클럭으로 실행)를 사용합니다.
이것은 다소 다릅니다. 위에서 인용 한 수치는 인터럽트가 즉시 처리되는 이상적인 수치입니다. 몇 가지 요인으로 인해 지연 될 수 있습니다.
프로세서가 절전 모드 인 경우 "깨우기"시간이 지정되는데,이 시간은 꽤 밀리 초일 수 있으며 시계는 속도로 스풀링됩니다. 이 시간은 퓨즈 설정과 수면 깊이에 따라 다릅니다.
인터럽트 서비스 루틴이 이미 실행 중이면 완료되거나 인터럽트 자체를 활성화 할 때까지 추가 인터럽트를 입력 할 수 없습니다. 그렇기 때문에 각 인터럽트 서비스 루틴을 짧게 유지해야합니다. 마이크로 초마다 소비 할 때마다 다른 인터럽트 실행을 지연시킬 수 있습니다.
일부 코드는 인터럽트를 끕니다. 예를 들어 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 내부에서 "인쇄"디버깅을 시도해서는 안됩니다. 그렇게하는 데 걸리는 시간은 해결하는 것보다 더 많은 문제를 일으킬 수 있습니다.
합리적인 방법은 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);
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
}
핀 변경 인터럽트를 처리하려면 다음이 필요합니다.
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 내부와 외부에서 모두 사용되는 경우에만 휘발성으로 표시해야합니다.
예.
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
인터럽트, 타이머 등에 대한 자세한 정보는 프로세서의 데이터 시트에서 얻을 수 있습니다.
공간 고려 사항 (포스트 크기 제한)으로 인해 목록에 더 많은 예제 코드가 표시되지 않습니다. 더 많은 예제 코드 는 인터럽트에 대한 내 페이지를 참조하십시오 .