TL; DR:
เมื่อเขียน Interrupt Service Routine (ISR):
- ทำให้สั้น
- ห้ามใช้
delay ()
- อย่าทำการพิมพ์แบบอนุกรม
- ทำให้ตัวแปรที่ใช้ร่วมกันกับรหัสหลักระเหย
- ตัวแปรที่แชร์ด้วยรหัสหลักอาจต้องได้รับการปกป้องโดย "ส่วนที่สำคัญ" (ดูด้านล่าง)
- อย่าพยายามปิดหรือเปิดการขัดจังหวะ
การขัดจังหวะคืออะไร
โปรเซสเซอร์ส่วนใหญ่มีการขัดจังหวะ การขัดจังหวะช่วยให้คุณตอบสนองต่อเหตุการณ์ "ภายนอก" ในขณะที่ทำอย่างอื่น ตัวอย่างเช่นหากคุณกำลังทำอาหารเย็นคุณสามารถใส่มันฝรั่งเพื่อทำอาหารเป็นเวลา 20 นาที แทนที่จะจ้องที่นาฬิกาเป็นเวลา 20 นาทีคุณอาจตั้งเวลาแล้วไปดูทีวี เมื่อตัวจับเวลาดังขึ้นคุณจะ "ขัดจังหวะ" การดูทีวีของคุณเพื่อทำบางสิ่งกับมันฝรั่ง
ตัวอย่างของการขัดจังหวะ
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
}
ตัวอย่างนี้แสดงให้เห็นว่าถึงแม้ว่าห่วงหลักจะไม่ทำอะไรเลยคุณสามารถเปิดหรือปิดไฟ LED ที่ขา 13 หากปิดสวิตช์ D2 ที่ขา
ในการทดสอบเพียงแค่เชื่อมต่อสาย (หรือสวิทช์) ระหว่าง D2 และกราวด์ pullup ภายใน (เปิดใช้งานในการตั้งค่า) บังคับให้พิน HIGH ตามปกติ เมื่อต่อสายดินมันจะต่ำ ตรวจพบการเปลี่ยนแปลงพินโดยการขัดจังหวะโดย CHANGE ซึ่งทำให้การเรียกใช้งานบริการขัดจังหวะ (ISR) ถูกขัดจังหวะ
ในตัวอย่างที่มีความซับซ้อนมากขึ้นห่วงหลักอาจทำบางสิ่งที่มีประโยชน์เช่นการอ่านค่าอุณหภูมิและอนุญาตให้ตัวจัดการขัดจังหวะตรวจจับปุ่มที่ถูกผลัก
การแปลงหมายเลขพินเป็นตัวเลขขัดจังหวะ
เพื่อให้การแปลงหมายเลขเวคเตอร์อินเตอร์รัปต์เป็นพินง่ายขึ้นคุณสามารถเรียกใช้ฟังก์ชัน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) อยู่ในวงเล็บ
คำเตือน: หากคุณสะกดชื่อเวกเตอร์ขัดจังหวะแม้เพียงแค่ทำให้ตัวพิมพ์ใหญ่ผิด (เป็นเรื่องง่ายที่จะทำ) รูทีนการขัดจังหวะจะไม่ถูกเรียกใช้และคุณจะไม่ได้รับข้อผิดพลาดของคอมไพเลอร์
เหตุผลในการใช้อินเตอร์รัปต์
สาเหตุหลักที่คุณอาจใช้อินเตอร์รัปต์คือ:
- ในการตรวจจับการเปลี่ยนแปลงพิน (เช่นตัวเข้ารหัสแบบหมุนให้กดปุ่ม)
- ตัวจับเวลา Watchdog (เช่นถ้าไม่มีอะไรเกิดขึ้นหลังจาก 8 วินาทีขัดจังหวะฉัน)
- ขัดจังหวะตัวจับเวลา - ใช้สำหรับการเปรียบเทียบ / จับเวลาล้น
- การถ่ายโอนข้อมูล SPI
- การถ่ายโอนข้อมูล I2C
- การถ่ายโอนข้อมูล USART
- แปลง ADC (อนาล็อกเป็นดิจิตอล)
- EEPROM พร้อมใช้งาน
- หน่วยความจำแฟลชพร้อม
"การถ่ายโอนข้อมูล" สามารถใช้เพื่อให้โปรแกรมทำอย่างอื่นในขณะที่ข้อมูลกำลังถูกส่งหรือรับในพอร์ตอนุกรม, พอร์ต SPI หรือพอร์ต I2C
ปลุกโปรเซสเซอร์
การขัดจังหวะภายนอกการขัดจังหวะการเปลี่ยนพินและการจับเวลาขัดจังหวะการจ้องจับผิดก็สามารถใช้เพื่อปลุกโปรเซสเซอร์ให้ตื่นขึ้น สิ่งนี้มีประโยชน์มากเช่นในโหมดสลีปโปรเซสเซอร์สามารถกำหนดค่าให้ใช้พลังงานน้อยลงมาก (เช่นประมาณ 10 ไมโครแคม) การขัดจังหวะที่เพิ่มขึ้นลดลงหรือระดับต่ำสามารถใช้เพื่อปลุกแกดเจ็ต (เช่นหากคุณกดปุ่มบน) หรือการขัดจังหวะ "ตัวจับเวลาจ้องจับผิด" อาจปลุกเป็นระยะ ๆ (เช่นเพื่อตรวจสอบเวลาหรือ อุณหภูมิ).
การขัดจังหวะการเปลี่ยนขาสามารถใช้ปลุกหน่วยประมวลผลได้หากกดปุ่มบนแป้นพิมพ์หรือคล้ายกัน
โปรเซสเซอร์สามารถถูกปลุกได้ด้วยการจับเวลาขัดจังหวะ (เช่นจับเวลาถึงค่าที่แน่นอนหรือล้น) และเหตุการณ์อื่น ๆ เช่นข้อความ I2C ที่เข้ามา
เปิดใช้งาน / ปิดใช้งานการขัดจังหวะ
การขัดจังหวะ "รีเซ็ต" ไม่สามารถปิดใช้งานได้ อย่างไรก็ตามการขัดจังหวะอื่น ๆ สามารถปิดใช้งานได้ชั่วคราวด้วยการล้างการตั้งค่าสถานะอินเตอร์รัปต์โกลบอล
เปิดใช้งานการขัดจังหวะ
คุณสามารถเปิดใช้งานการขัดจังหวะด้วยการเรียกใช้ฟังก์ชัน "ขัดจังหวะ" หรือ "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
และ register SREG นั้นเฉพาะกับโปรเซสเซอร์ AVR หากคุณใช้โปรเซสเซอร์อื่น ๆ เช่น ARM ฟังก์ชั่นอาจแตกต่างกันเล็กน้อย
ปิดการใช้งานทั่วโลกและปิดการใช้งานหนึ่งขัดจังหวะ
หากคุณใช้cli()
คุณปิดการใช้งานการขัดจังหวะทั้งหมด (รวมถึงการขัดจังหวะตัวจับเวลาขัดจังหวะอนุกรม ฯลฯ )
อย่างไรก็ตามหากคุณต้องการปิดการใช้งานอินเตอร์รัปต์เฉพาะคุณควรล้างการตั้งค่าสถานะอินเตอร์รัปต์สำหรับแหล่งขัดจังหวะนั้นโดยเฉพาะ detachInterrupt()
ตัวอย่างเช่นสำหรับการขัดจังหวะภายนอกโทร
ลำดับความสำคัญของการขัดจังหวะคืออะไร
เนื่องจากมีอินเทอร์รัปต์ 25 รายการ (นอกเหนือจากการรีเซ็ต) จึงมีความเป็นไปได้ที่อาจเกิดเหตุการณ์อินเตอร์รัปต์มากกว่าหนึ่งเหตุการณ์ในครั้งเดียวหรืออย่างน้อยก็เกิดขึ้นก่อนที่จะประมวลผลก่อนหน้านี้ นอกจากนี้อาจเกิดเหตุการณ์ขัดจังหวะขณะที่อินเตอร์รัปต์ถูกปิดใช้งาน
ลำดับความสำคัญคือลำดับที่โปรเซสเซอร์ตรวจสอบเหตุการณ์ขัดจังหวะ รายการยิ่งสูงลำดับความสำคัญยิ่งสูง ตัวอย่างเช่นคำขอการขัดจังหวะภายนอก 0 (pin D2) จะได้รับการบริการก่อนคำขอการขัดจังหวะภายนอก 1 (ขา D3)
การขัดจังหวะสามารถเกิดขึ้นได้ในขณะที่การขัดจังหวะถูกปิดใช้งานหรือไม่?
เหตุการณ์ขัดจังหวะ(นั่นคือการสังเกตเห็นเหตุการณ์) สามารถเกิดขึ้นได้ตลอดเวลาและส่วนใหญ่จะถูกจดจำโดยการตั้งค่าสถานะ "ขัดจังหวะเหตุการณ์" ภายในโปรเซสเซอร์ หากการอินเตอร์รัปต์ถูกปิดใช้งานอินเทอร์รัปต์นั้นจะถูกจัดการเมื่อเปิดใช้งานอีกครั้งตามลำดับความสำคัญ
คุณใช้อินเตอร์รัปต์อย่างไร
- คุณเขียน ISR (รูทีนการบริการขัดจังหวะ) สิ่งนี้ถูกเรียกเมื่อเกิดการขัดจังหวะ
- คุณบอกโปรเซสเซอร์เมื่อคุณต้องการที่จะให้ขัดจังหวะ
เขียน ISR
รูทีนการบริการขัดจังหวะเป็นฟังก์ชันที่ไม่มีอาร์กิวเมนต์ ไลบรารี Arduino บางตัวได้รับการออกแบบมาเพื่อเรียกใช้ฟังก์ชันของคุณเองดังนั้นคุณเพียงแค่จัดหาฟังก์ชั่นทั่วไป (เช่นในตัวอย่างด้านบน) เช่น
// Interrupt Service Routine (ISR)
void switchPressed ()
{
flag = true;
} // end of switchPressed
อย่างไรก็ตามหากห้องสมุดยังไม่ได้ให้ "hook" กับ 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 ให้เสร็จสมบูรณ์ (หมายเหตุรหัสเก่าบางตัวใช้ SIGNAL แทน ISR แต่ 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 แบบ inbuilt
อีกตัวอย่างหนึ่งคืออินเตอร์รัปต์ "พินภายนอก"
// 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 จะเพิ่มฟังก์ชั่นสวิตช์กดลงในตารางภายในและนอกจากนี้ยังกำหนดค่าสถานะอินเตอร์รัปต์ที่เหมาะสมในโปรเซสเซอร์
การกำหนดค่าตัวประมวลผลเพื่อจัดการการขัดจังหวะ
ขั้นตอนต่อไปเมื่อคุณมี 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 (External Interrupt Control Register A) จะถูกตั้งค่าตามตารางนี้จากแผ่นข้อมูล Atmega328 ที่กำหนดประเภทของการขัดจังหวะที่คุณต้องการ:
- 0: INT0 ระดับต่ำสร้างคำขออินเตอร์รัปต์ (LOW Interrupt)
- 1: การเปลี่ยนแปลงเชิงตรรกะใด ๆ บน INT0 จะสร้างคำขออินเตอร์รัปต์ (CHANGE Interrupt)
- 2: ขอบที่ยาวเหยียดของ INT0 สร้างคำขออินเตอร์รัปต์ (FALLING อินเตอร์รัปต์)
- 3: ขอบที่เพิ่มขึ้นของ INT0 สร้างคำขออินเตอร์รัปต์ (RISING อินเตอร์รัปต์)
EIMSK (External Interrupt Mask Register) เปิดใช้งานการขัดจังหวะ
โชคดีที่คุณไม่จำเป็นต้องจำตัวเลขเหล่านั้นเพราะ AttachInterrupt ทำเพื่อคุณ อย่างไรก็ตามนั่นคือสิ่งที่เกิดขึ้นจริงและสำหรับการขัดจังหวะอื่น ๆ คุณอาจต้องตั้งค่าสถานะ "ขัดจังหวะ" ด้วยตนเอง
ISR ระดับต่ำกับไลบรารี ISR
เพื่อให้ชีวิตของคุณง่ายขึ้นตัวจัดการขัดจังหวะทั่วไปบางตัวจะอยู่ในรหัสห้องสมุด (ตัวอย่างเช่น INT0_vect และ INT1_vect) จากนั้นจะมีการให้ส่วนต่อประสานกับผู้ใช้ที่เป็นมิตรมากขึ้น (เช่น 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 tos ในการดำเนินการรวมถึงสิ่งที่ตัวเองทำ จำนวนเงินที่แน่นอนนั้นขึ้นอยู่กับจำนวนการลงทะเบียนและการเรียกคืนที่ต้องการ จำนวนเงินขั้นต่ำจะเป็น 1.1875 µs
อินเทอร์รัปต์ภายนอก (ที่คุณใช้ attachInterrupt) ทำได้อีกเล็กน้อยและใช้เวลาทั้งหมด 5.125 (s (ทำงานด้วยนาฬิกา 16 MHz)
นานแค่ไหนก่อนที่โปรเซสเซอร์จะเริ่มเข้าสู่ ISR
สิ่งนี้แตกต่างกันบ้าง ตัวเลขที่ยกมาข้างต้นเป็นตัวเลขในอุดมคติที่มีการประมวลผลอินเทอร์รัปต์ทันที ปัจจัยบางอย่างอาจล่าช้าที่:
หากตัวประมวลผลหลับมีการกำหนด "เวลาปลุก" ซึ่งอาจใช้เวลาไม่กี่มิลลิวินาทีในขณะที่นาฬิกาถูกสปูลกลับเป็นความเร็ว เวลานี้จะขึ้นอยู่กับการตั้งค่าฟิวส์และความลึกของการนอนหลับ
หากรูทีนการบริการขัดจังหวะการทำงานอยู่แล้วการขัดจังหวะเพิ่มเติมจะไม่สามารถป้อนได้จนกว่าจะเสร็จสิ้นหรือเปิดใช้งานการขัดจังหวะตัวเอง นี่คือเหตุผลที่คุณควรทำให้แต่ละบริการขัดจังหวะสั้น ๆ เพราะทุกวินาทีที่คุณใช้ในหนึ่งคุณอาจล่าช้าในการดำเนินการอีก
บางรหัสปิดการขัดจังหวะ ตัวอย่างเช่นการเรียกมิลลิวินาที () สั้น ๆ จะปิดการขัดจังหวะ ดังนั้นเวลาสำหรับการขัดจังหวะที่จะรับบริการจะขยายออกไปตามระยะเวลาที่ถูกขัดจังหวะ
การขัดจังหวะสามารถให้บริการได้เมื่อสิ้นสุดคำสั่งเท่านั้นดังนั้นหากคำสั่งนั้นใช้เวลาสามรอบนาฬิกาและเพิ่งเริ่มต้นการขัดจังหวะจะล่าช้าอย่างน้อยสองรอบนาฬิกา
เหตุการณ์ที่เปิดการขัดจังหวะกลับมา (เช่นการกลับมาจากรูทีนบริการขัดจังหวะ) รับประกันว่าจะดำเนินการคำสั่งอย่างน้อยหนึ่งครั้ง ดังนั้นแม้ว่า ISR จะสิ้นสุดลงและการขัดจังหวะของคุณยังคงค้างอยู่ก็ยังคงต้องรอคำสั่งเพิ่มเติมอีกครั้งก่อนที่จะให้บริการ
เนื่องจากการขัดจังหวะมีความสำคัญการขัดจังหวะที่มีลำดับความสำคัญสูงกว่าอาจได้รับการบริการก่อนการขัดจังหวะที่คุณสนใจ
ข้อควรพิจารณาด้านประสิทธิภาพ
การขัดจังหวะสามารถเพิ่มประสิทธิภาพได้ในหลาย ๆ สถานการณ์เพราะคุณสามารถทำงานกับ "งานหลัก" ของโปรแกรมได้โดยไม่ต้องทำการทดสอบอย่างต่อเนื่องเพื่อดูว่ามีการกดสวิตช์หรือไม่ ต้องบอกว่าค่าใช้จ่ายในการให้บริการขัดจังหวะดังที่ได้กล่าวไว้ข้างต้นจะเป็นมากกว่าการทำ "การวนรอบ" ที่แน่นหนาในการสำรวจพอร์ตอินพุตเดี่ยว คุณแทบจะไม่สามารถตอบกลับเหตุการณ์ภายในไมโครวินาทีได้ ในกรณีนี้คุณอาจปิดการใช้งานอินเตอร์รัปต์ (เช่นตัวจับเวลา) และเพียงแค่วนลูปเพื่อหาพินที่จะเปลี่ยน
อินเตอร์รัปต์ถูกจัดคิวอย่างไร
มีการขัดจังหวะสองประเภท:
บางคนตั้งค่าสถานะและพวกเขาได้รับการจัดการในลำดับความสำคัญแม้ว่าเหตุการณ์ที่ทำให้พวกเขาหยุด ตัวอย่างเช่นการเพิ่มขึ้นลดลงหรือการเปลี่ยนระดับการขัดจังหวะบนพิน D2
คนอื่นจะได้รับการทดสอบก็ต่อเมื่อพวกเขากำลัง "เกิดขึ้น" ตัวอย่างเช่นการขัดจังหวะระดับต่ำบนพิน D2
คนที่ตั้งค่าสถานะอาจถือได้ว่าถูกจัดคิวเนื่องจากสถานะการขัดจังหวะยังคงตั้งค่าจนกว่าจะถึงเวลาที่มีการป้อนประจำการขัดจังหวะซึ่งเวลาที่หน่วยประมวลผลล้างธง แน่นอนเนื่องจากมีเพียงหนึ่งแฟล็กหากเงื่อนไขการอินเตอร์รัปต์เดียวกันเกิดขึ้นอีกครั้งก่อนที่โพรเซสแรกจะถูกประมวลผลมันจะไม่ได้รับการบริการสองครั้ง
สิ่งที่ต้องระวังคือสามารถตั้งค่าสถานะเหล่านี้ก่อนที่คุณจะแนบตัวจัดการขัดจังหวะ ตัวอย่างเช่นเป็นไปได้ที่การขัดจังหวะระดับสูงขึ้นหรือลดลงบนพิน D2 จะเป็น "ตั้งค่าสถานะ" แล้วทันทีที่คุณทำการแนบอินเทอร์รัปต์อินเทอรัปต์จะขัดจังหวะการยิงทันทีแม้ว่าเหตุการณ์จะเกิดขึ้นหนึ่งชั่วโมงก่อนก็ตาม เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถล้างค่าสถานะด้วยตนเอง ตัวอย่างเช่น:
EIFR = bit (INTF0); // clear flag for interrupt 0
EIFR = bit (INTF1); // clear flag for interrupt 1
อย่างไรก็ตามอินเตอร์รัปต์ "ระดับต่ำ" จะถูกตรวจสอบอย่างต่อเนื่องดังนั้นหากคุณไม่ระวังพวกเขาก็จะทำการยิงต่อไปแม้ว่าจะมีการเรียกใช้อินเตอร์รัปต์ก็ตาม นั่นคือ ISR จะออกแล้วขัดจังหวะจะยิงอีกครั้งทันที เพื่อหลีกเลี่ยงปัญหานี้คุณควรทำการ detachInterrupt ทันทีหลังจากที่คุณทราบว่ามีการขัดจังหวะ
คำแนะนำสำหรับการเขียน ISR
โดยย่อให้พวกเขาสั้น ๆ ! ในขณะที่ ISR กำลังดำเนินการอินเตอร์รัปต์อื่นไม่สามารถประมวลผลได้ ดังนั้นคุณอาจพลาดการกดปุ่มหรือการสื่อสารแบบอนุกรมขาเข้าได้ง่ายหากคุณพยายามทำมากเกินไป โดยเฉพาะอย่างยิ่งคุณไม่ควรลองทำการดีบั๊ก "พิมพ์" ภายใน ISR เวลาในการทำสิ่งเหล่านั้นมีแนวโน้มที่จะทำให้เกิดปัญหามากกว่าที่พวกเขาแก้
สิ่งที่สมเหตุสมผลที่ต้องทำคือตั้งค่าสถานะไบต์เดียวแล้วทดสอบค่าสถานะนั้นในฟังก์ชันวนรอบหลัก หรือเก็บไบต์ขาเข้าจากพอร์ตอนุกรมลงในบัฟเฟอร์ การจับเวลาแบบ inbuilt ขัดจังหวะการติดตามเวลาที่ผ่านไปโดยการยิงทุกครั้งที่ตัวจับเวลาภายในล้นและทำให้คุณสามารถคำนวณเวลาที่ผ่านไปโดยรู้ว่าตัวจับเวลาล้นมากี่ครั้ง
โปรดจำไว้ว่าภายในการขัดจังหวะ ISR จะถูกปิดใช้งาน ดังนั้นหวังว่าเวลาที่ส่งคืนโดยการเรียกฟังก์ชันมิลลิวินาที () จะเปลี่ยนไปซึ่งจะนำไปสู่ความผิดหวัง ถูกต้องเพื่อให้ได้เวลาตามนั้นเพียงแค่ระวังว่าตัวจับเวลาไม่เพิ่มขึ้น และหากคุณใช้เวลานานเกินไปใน ISR ตัวจับเวลาอาจพลาดเหตุการณ์ล้นทำให้เวลาที่มิลลิวินาที () กลับมาไม่ถูกต้อง
การทดสอบแสดงให้เห็นว่าในโปรเซสเซอร์ 16 MHz Atmega328 การโทรไปยังไมโคร () ใช้เวลา 3.5625 µs การเรียกมิลลิวินาที () ใช้เวลา 1.9375 µs การบันทึก (การบันทึก) ค่าตัวจับเวลาปัจจุบันเป็นสิ่งที่สมเหตุสมผลที่ต้องทำใน ISR การค้นหามิลลิวินาทีที่ผ่านไปนั้นเร็วกว่าไมโครวินาทีที่ผ่านไป (จำนวนมิลลิวินาทีจะถูกดึงจากตัวแปร) อย่างไรก็ตามได้รับการนับไมโครวินาทีโดยการเพิ่มมูลค่าปัจจุบันของตัวจับเวลา Timer 0 (ซึ่งจะเพิ่มขึ้นเรื่อย ๆ ) ให้กับ "Timer 0 overflow count" ที่บันทึกไว้
คำเตือน:เนื่องจากอินเตอร์รัปต์ถูกปิดใช้งานภายใน ISR และเนื่องจาก Arduino IDE รุ่นล่าสุดใช้อินเทอร์รัปต์สำหรับการอ่านและการเขียนแบบอนุกรมและสำหรับการเพิ่มตัวนับที่ใช้โดย "มิลลิวินาที" และ "ล่าช้า" คุณไม่ควรใช้ฟังก์ชั่นเหล่านั้น ภายใน 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
}
หากต้องการจัดการการขัดจังหวะการเปลี่ยนพินคุณต้อง:
- ระบุหมุดที่อยู่ในกลุ่ม นี่คือตัวแปร 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) ถูกตั้งค่าให้ขัดจังหวะประมาณ 1,000 ครั้งต่อวินาทีและเพิ่มตัวนับภายในซึ่งจะกลายเป็นตัวนับมิลลิวินาทีได้อย่างมีประสิทธิภาพ มันมีอะไรที่มากกว่านั้นอีกเล็กน้อยเนื่องจากมีการปรับตามความเร็วสัญญาณนาฬิกาที่แน่นอน
นอกจากนี้ไลบรารีอนุกรมฮาร์ดแวร์ยังใช้การขัดจังหวะเพื่อจัดการข้อมูลอนุกรมขาเข้าและขาออก สิ่งนี้มีประโยชน์มากเพราะโปรแกรมของคุณสามารถทำสิ่งอื่น ๆ ในขณะที่สัญญาณขัดจังหวะการทำงานและการเติมบัฟเฟอร์ภายใน จากนั้นเมื่อคุณตรวจสอบ 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 หนึ่งไบต์ได้รับการอัปเดตโดย 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
ตัวอย่างเพิ่มเติม
ข้อควรพิจารณาเกี่ยวกับพื้นที่ (จำกัด ขนาดโพสต์) ป้องกันการแสดงตัวอย่างโค้ดของฉัน สำหรับโค้ดตัวอย่างเพิ่มเติมโปรดดูที่หน้าของฉันเกี่ยวกับการขัดจังหวะ