TL; DR:
Bir Kesme Servisi Rutini (ISR) yazarken:
- Kısa tut
- Kullanma
delay ()
- Seri baskı yapma
- Değişkenleri ana kodla paylaşın
- Ana kodla paylaşılan değişkenlerin "kritik bölümler" tarafından korunması gerekebilir (aşağıya bakın)
- Kesmeleri kapatmaya veya açmaya çalışmayın
Kesmeler nedir?
İşlemcilerin çoğunda kesinti var. Kesmeler, başka bir şey yaparken "harici" olaylara yanıt vermenizi sağlar. Örneğin, akşam yemeği pişiriyorsanız, patatesleri 20 dakika pişirmeye bırakabilirsiniz. 20 dakika boyunca saate bakmak yerine bir zamanlayıcı ayarlayabilir ve sonra TV izlemeye gidebilirsiniz. Zamanlayıcı çaldığında patates ile bir şeyler yapmak için TV izlemenizi "kesersiniz".
Kesmeler örneği
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
}
Bu örnek, ana döngü hiçbir şey yapmasa bile, D2 pimine basıldığında pim 13 üzerindeki LED'i nasıl açıp kapatabileceğinizi gösterir.
Bunu test etmek için D2 ile Toprak arasına bir kablo (veya anahtar) bağlayın. Dahili çekme (kurulumda etkinleştirilir) pimi normalde YÜKSEK zorlar. Topraklandığında, DÜŞÜK olur. Pinteki değişiklik, Kesme Servisi Rutini'nin (ISR) çağrılmasına neden olan bir DEĞİŞTİRME kesmesi ile algılanır.
Daha karmaşık bir örnekte, ana döngü sıcaklık okumaları almak gibi yararlı bir şey yapıyor olabilir ve kesme işleyicisinin itilen bir düğmeyi algılamasına izin verebilir.
Pin numaralarını kesme numaralarına dönüştürme
Kesme vektör numaralarını pin numaralarına dönüştürmeyi basitleştirmek için digitalPinToInterrupt()
, bir pin numarası ileterek işlevi çağırabilirsiniz . Uygun kesme numarasını veya NOT_AN_INTERRUPT
(-1) değerini döndürür .
Örneğin, Uno'da, karttaki D2 pimi kesme 0'dır (aşağıdaki tablodan INT0_vect).
Böylece bu iki çizgi aynı etkiye sahiptir:
attachInterrupt (0, switchPressed, CHANGE); // that is, for pin D2
attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);
Ancak ikincisinin okunması daha kolay ve farklı Arduino tiplerine daha taşınabilir.
Kullanılabilir kesintiler
Atmega328 için öncelik sırasına göre kesintilerin bir listesi aşağıdadır:
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)
Dahili adlar (ISR geri aramalarını ayarlamak için kullanabileceğiniz) köşeli parantezlerdedir.
Uyarı: Kesinti vektör adını yanlış yazarsanız, büyük / küçük harfleri yanlış yapsanız bile (yapılması kolay bir şey) kesme rutini çağrılmaz ve derleyici hatası almazsınız.
Kesmeleri kullanma nedenleri
Kesmeleri kullanmanızın ana nedenleri:
- Pim değişikliklerini tespit etmek için (örn. Döner kodlayıcılar, düğmeye basma)
- Bekçi zamanlayıcısı (örn. 8 saniye sonra hiçbir şey olmazsa, beni durdurun)
- Zamanlayıcı kesintileri - zamanlayıcıları karşılaştırmak / taşmak için kullanılır
- SPI veri aktarımları
- I2C veri aktarımları
- USART veri aktarımları
- ADC dönüşümleri (analogdan dijitale)
- EEPROM kullanıma hazır
- Flash bellek hazır
"Veri aktarımları", seri bağlantı noktası, SPI bağlantı noktası veya I2C bağlantı noktasında veri gönderilirken veya alınırken bir programın başka bir şey yapmasına izin vermek için kullanılabilir.
İşlemciyi uyandır
Harici kesintiler, pim değiştirme kesintileri ve bekçi zamanlayıcısı kesintisi de işlemciyi uyandırmak için kullanılabilir. Bu çok kullanışlı olabilir, çünkü uyku modunda işlemci çok daha az güç kullanacak şekilde yapılandırılabilir (örneğin yaklaşık 10 mikroamper). Bir gadget'ı uyandırmak için yükselen, düşen veya düşük seviyeli bir kesinti kullanılabilir (örneğin, üzerinde bir düğmeye basarsanız) veya bir "bekçi köpeği zamanlayıcısı" kesintisi periyodik olarak uyandırabilir (örn. sıcaklık).
Pin değiştirme kesintileri, bir tuş takımında veya benzeri bir tuşa basıldığında işlemciyi uyandırmak için kullanılabilir.
İşlemci ayrıca bir zamanlayıcı kesmesi (örn. Belirli bir değere ulaşan veya taşan bir zamanlayıcı) ve gelen I2C mesajı gibi diğer bazı olaylarla da uyandırılabilir.
Kesmeleri etkinleştirme / devre dışı bırakma
"Sıfırla" kesmesi devre dışı bırakılamaz. Ancak, diğer kesmeler, genel kesme bayrağını temizleyerek geçici olarak devre dışı bırakılabilir.
Kesmeleri etkinleştir
"İnterrupts" veya "sei" işlev çağrısı ile kesmeleri şu şekilde etkinleştirebilirsiniz:
interrupts (); // or ...
sei (); // set interrupts flag
Kesmeleri devre dışı bırak
Kesmeleri devre dışı bırakmanız gerekirse, genel kesme bayrağını şu şekilde "temizleyebilirsiniz":
noInterrupts (); // or ...
cli (); // clear interrupts flag
Her iki yöntemin de aynı etkisi vardır, interrupts
/ yöntemini kullanmak noInterrupts
onların nasıl bir yol olduğunu hatırlamak biraz daha kolaydır.
Arduino'daki varsayılan, kesintilerin etkinleştirilmesidir. Bunları uzun süre devre dışı bırakmayın, aksi takdirde zamanlayıcılar düzgün çalışmaz.
Kesintileri neden devre dışı bırakmalıyım?
Kesintiye uğramasını istemediğiniz zaman açısından önemli kod parçaları olabilir, örneğin bir zamanlayıcı kesintisi.
Ayrıca çok baytlık alanlar bir ISR tarafından güncelleniyorsa, verileri "atomik olarak" almak için kesintileri devre dışı bırakmanız gerekebilir. Aksi takdirde, bir bayt diğerini okurken ISR tarafından güncellenebilir.
Örneğin:
noInterrupts ();
long myCounter = isrCounter; // get value set by ISR
interrupts ();
Kesintileri geçici olarak kapatmak, değerini alırken isrCounter'ın (ISR içinde ayarlanmış bir sayaç) değişmemesini sağlar.
Uyarı: kesintilerin zaten açık olup olmadığından emin değilseniz, geçerli durumu kaydetmeniz ve daha sonra geri yüklemeniz gerekir. Örneğin, millis () işlevindeki kod bunu yapar:
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;
}
Belirtilen satırların, kesme bayrağını içeren geçerli SREG'yi (durum yazmacı) kaydettiğini unutmayın. Zamanlayıcı değerini (4 bayt uzunluğunda) elde ettikten sonra, durum kaydını tekrar eski haline getirdik.
İpuçları
İşlev adları
cli
/ Fonksiyonları sei
ve SREG kaydı AVR işlemcilere özgüdür. ARM olanlar gibi başka işlemciler kullanıyorsanız, işlevler biraz farklı olabilir.
Global olarak devre dışı bırakmak veya bir kesmeyi devre dışı bırakmak
cli()
Kullanırsanız tüm kesintileri (zamanlayıcı kesintileri, seri kesintiler vb. Dahil) devre dışı bırakırsınız .
Ancak, yalnızca belirli bir kesmeyi devre dışı bırakmak istiyorsanız, söz konusu kesinti kaynağı için kesinti etkinleştirme bayrağını temizlemelisiniz. Örneğin, harici kesintiler için arayın detachInterrupt()
.
Kesme önceliği nedir?
25 kesme (sıfırlama dışında) olduğundan, bir kerede birden fazla kesme olayı meydana gelebilir veya en azından bir öncekinin işlenmesinden önce gerçekleşebilir. Kesmeler devre dışı bırakıldığında da bir kesme olayı meydana gelebilir.
Öncelik sırası, işlemcinin kesme olaylarını kontrol ettiği dizidir. Liste ne kadar yüksek olursa, öncelik de o kadar yüksek olur. Dolayısıyla, örneğin, Harici Kesme İsteği 1'den (iğne D3) önce bir Harici Kesme Talebi 0'a (pin D2) servis yapılır.
Kesmeler devre dışı bırakılırken kesmeler olabilir mi?
Kesilen olaylar (yani olayı fark eden) herhangi bir zamanda gerçekleşebilir ve çoğu işlemci içinde bir "kesme olayı" bayrağı ayarlanarak hatırlanır. Kesmeler devre dışı bırakılırsa, söz konusu kesme, yeniden etkinleştirildiklerinde öncelik sırasına göre ele alınacaktır.
Kesmeleri nasıl kullanıyorsunuz?
- Bir ISR (kesme hizmeti rutini) yazarsınız. Kesinti meydana geldiğinde buna çağrılır.
- Kesmenin ne zaman tetiklenmesini istediğinizi işlemciye söylersiniz.
ISR Yazma
Kesme Hizmeti Rutinleri bağımsız değişkeni olmayan işlevlerdir. Bazı Arduino kütüphaneleri kendi işlevlerinizi çağıracak şekilde tasarlanmıştır, bu nedenle sıradan bir işlev sağlarsınız (yukarıdaki örneklerde olduğu gibi), ör.
// Interrupt Service Routine (ISR)
void switchPressed ()
{
flag = true;
} // end of switchPressed
Ancak bir kütüphane zaten bir ISR'ye "kanca" sağlamamışsa, kendinizinkini oluşturabilirsiniz:
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
Bu durumda, "ISR" makrosunu kullanır ve ilgili kesme vektörünün adını sağlarsınız (daha önceki tablodan). Bu durumda ISR bir SPI aktarımını tamamlar. (Bazı eski kodlar ISR yerine SIGNAL kullanır, ancak SIGNAL kullanımdan kaldırılır).
Bir kesintiye bir ISR bağlama
Kütüphaneler tarafından halihazırda ele alınan kesintiler için sadece belgelenmiş arayüzü kullanırsınız. Örneğin:
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);
}
Bu durumda I2C kütüphanesi, gelen I2C baytlarını dahili olarak işleyecek şekilde tasarlanmıştır ve daha sonra gelen veri akışının sonunda verilen işlevi çağırır. Bu durumda, takeEvent kesinlikle bir ISR değildir (bir argümanı vardır), ancak dahili bir ISR tarafından çağrılır.
Başka bir örnek "harici pim" kesilmesidir.
// 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
Bu durumda attachInterrupt işlevi, switchPressed işlevini dahili bir tabloya ekler ve ek olarak işlemcideki uygun kesme bayraklarını yapılandırır.
İşlemciyi bir kesmeyi işleyecek şekilde yapılandırma
Bir sonraki adım, bir ISR'niz olduğunda, işlemciye bu özel durumun bir kesinti yaratmasını istediğinizi söylemektir.
Örnek olarak, Harici Kesme 0 (D2 kesme) için aşağıdaki gibi bir şey yapabilirsiniz:
EICRA &= ~3; // clear existing flags
EICRA |= 2; // set wanted flags (falling level interrupt)
EIMSK |= 1; // enable it
Daha okunabilir tanımlı adları kullanmaktır, örneğin:
EICRA &= ~(bit(ISC00) | bit (ISC01)); // clear existing flags
EICRA |= bit (ISC01); // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0); // enable it
EICRA (Harici Kesme Kontrol Kaydı A) bu tabloya göre Atmega328 veri sayfasından ayarlanır. Bu, tam olarak istediğiniz kesme türünü tanımlar:
- 0: Düşük INT0 seviyesi bir kesme isteği oluşturur (DÜŞÜK kesme).
- 1: INT0 üzerinde yapılan herhangi bir mantıksal değişiklik bir kesme isteği (CHANGE interrupt) üretir.
- 2: INT0'ın düşen kenarı bir kesme isteği oluşturur (FALLING kesme).
- 3: INT0'ın yükselen kenarı bir kesme isteği oluşturur (RISING kesme).
EIMSK (Harici Kesme Maskesi Kaydı) gerçekten kesmeyi etkinleştirir.
Neyse ki bu sayıları hatırlamanız gerekmez çünkü attachInterrupt bunu sizin için yapar. Ancak gerçekte olan budur ve diğer kesintiler için kesme bayraklarını "manuel olarak" ayarlamanız gerekebilir.
Düşük düzeyli ISR'ler ve kütüphane ISR'leri
Hayatınızı kolaylaştırmak için bazı genel kesme işleyicileri aslında kütüphane kodunun içindedir (örneğin INT0_vect ve INT1_vect) ve daha kullanıcı dostu bir arayüz sağlanır (örn. AttachInterrupt). AttachInterrupt'ın aslında yaptığı şey, istediğiniz kesme işleyicisinin adresini bir değişkene kaydetmek ve daha sonra gerektiğinde INT0_vect / INT1_vect'den çağırmaktır. Ayrıca gerektiğinde işleyiciyi çağırmak için uygun kayıt bayraklarını ayarlar.
ISR'ler kesintiye uğrayabilir mi?
Kısacası, hayır, onların olmasını istemedikçe değil.
Bir ISR girildiğinde, kesmeler devre dışı bırakılır . Doğal olarak, ilk etapta etkinleştirilmeleri gerekirdi, aksi takdirde ISR girilmezdi. Bununla birlikte, bir ISR'nin kendisinin kesilmesini önlemek için, işlemci kesintileri kapatır.
Bir ISR çıktığında, kesmeler tekrar etkinleştirilir . Derleyici ayrıca, kayıtlar ve durum bayraklarını kaydetmek için bir ISR içinde kod üretir, böylece kesme gerçekleştiğinde yaptığınız her şey etkilenmez.
Ancak yapabilirsiniz , kesinlikle gerekiyorsa mesela bir ISR içeride kesmeleri açın.
// Interrupt Service Routine (ISR)
void switchPressed ()
{
// handle pin change here
interrupts (); // allow more interrupts
} // end of switchPressed
Normalde bunu yapmak için oldukça iyi bir nedene ihtiyacınız olacaktır, çünkü başka bir kesinti artık pinChange için yinelenen bir çağrıya neden olabilir ve oldukça istenmeyen sonuçlarla sonuçlanabilir.
Bir ISR'nin yürütülmesi ne kadar sürer?
Veri sayfasına göre, bir kesintiye hizmet etmek için en az süre 4 saat döngüdür (geçerli program sayacını yığına itmek için) ve ardından kesinti vektör konumunda yürütülen koddur. Bu normalde kesme rutininin gerçekte olduğu yere bir sıçrama içerir, bu da 3 döngüdür. Derleyici tarafından üretilen kodun incelenmesi, "ISR" bildirimi ile yapılan bir ISR'nin yürütülmesi yaklaşık 2,625 µs alabileceğini ve ayrıca kodun ne yaptığını gösterir. Kesin tutar, kaç kaydın kaydedilmesi ve geri yüklenmesi gerektiğine bağlıdır. Minimum miktar 1.1875 µs olacaktır.
Harici kesmeler (attachInterrupt kullandığınız yerde) biraz daha fazlasını yapar ve toplamda yaklaşık 5.125 µs alır (16 MHz saatle çalışır).
İşlemci bir ISR'ye girmeye ne kadar süre var?
Bu biraz değişir. Yukarıda verilen rakamlar, kesmenin hemen işlendiği ideal rakamlardır. Birkaç faktör bunu geciktirebilir:
İşlemci uykudaysa, saat birkaç milisaniye olabilen "uyandırma" süreleri vardır ve saat hıza geri biriktirilir. Bu süre sigorta ayarlarına ve uykunun derinliğine bağlıdır.
Bir kesme servisi rutini zaten yürütülüyorsa, tamamlanana veya kendi kendini kesene kadar başka kesintiler de girilemez. Bu nedenle, her bir kesme hizmeti rutini kısa tutmalısınız, çünkü birinde harcadığınız her mikrosaniye, bir başkasının yürütülmesini geciktiriyorsunuzdur.
Bazı kod kesintileri keser. Örneğin, millis () öğesini kısaca kesmek kesintileri kapatır. Bu nedenle, bir kesintinin servis edilmesi için gereken süre, kesintilerin kapatıldığı süre kadar uzatılacaktır.
Kesmeler yalnızca bir talimatın sonunda servis edilebilir, bu nedenle belirli bir talimat üç saat döngüsü alırsa ve yeni başlamışsa, kesme en az birkaç saat döngüsü ertelenir.
Kesintileri tekrar açan bir olayın (örn. Bir kesme servis yordamından geri dönüş) en az bir komut daha yürütmesi garanti edilir. Bu nedenle, bir ISR sona erse ve kesintiniz beklemede olsa bile, servise gönderilmeden önce bir talimat daha beklemesi gerekir.
Kesmelerin bir önceliği olduğundan, ilgilendiğiniz kesmeden önce daha yüksek öncelikli bir kesime hizmet verilebilir.
Performans hususları
Kesmeler birçok durumda performansı artırabilir çünkü anahtarlara basılıp basılmadığını görmek için sürekli olarak test yapmak zorunda kalmadan programınızın "ana işine" devam edebilirsiniz. Bununla birlikte, yukarıda tartışıldığı gibi, bir kesintiye hizmet etme yükünün aslında tek bir giriş bağlantı noktasını "sıkı bir döngü" yoklaması yapmaktan daha fazlası olacaktır. Mikrosaniye içindeki bir olaya zar zor yanıt verebilirsiniz. Bu durumda kesintileri devre dışı bırakabilirsiniz (örn. Zamanlayıcılar) ve sadece pimin değişmesini isteyen döngüyü yapabilirsiniz.
Kesmeler nasıl sıraya alınır?
İki tür kesinti vardır:
Bazıları bir bayrak ayarladı ve bunlara neden olan olay durmuş olsa bile öncelik sırasına göre işlendi. Örneğin, pin D2'de yükselen, düşen veya değişen bir seviye kesintisi.
Diğerleri sadece "şu anda" oluyorlarsa test edilirler. Örneğin, pin D2'de düşük seviyeli bir kesme.
Bir bayrağını ayarlayanlar kuyruğa alınmış olarak kabul edilebilir, çünkü kesme bayrağının, kesme rutini girilene kadar işlemcinin bayrağını sildiği zamana kadar ayarlanmış olması beklenir. Tabii ki, sadece bir bayrak olduğundan, aynı kesme koşulu birincisi işlenmeden önce tekrar ortaya çıkarsa, iki kez servis yapılmaz.
Dikkat edilmesi gereken bir şey, kesme işaretleyiciyi takmadan önce bu bayrakların ayarlanabileceğidir. Örneğin, pin D2'de yükselen veya düşen bir seviye kesmesinin "işaretlenmesi" ve daha sonra bir attachInterrupt işlemi gerçekleşir kesilmez, olay bir saat önce gerçekleşmiş olsa bile derhal tetiklenir. Bundan kaçınmak için bayrağı manuel olarak silebilirsiniz. Örneğin:
EIFR = bit (INTF0); // clear flag for interrupt 0
EIFR = bit (INTF1); // clear flag for interrupt 1
Ancak "düşük seviye" kesintileri sürekli kontrol edilir, bu yüzden dikkatli olmazsanız kesme işlemi çağrıldıktan sonra bile ateş etmeye devam eder. Yani, ISR çıkacak ve sonra kesinti derhal tekrar açılacaktır. Bunu önlemek için kesmenin tetiklendiğini bildikten hemen sonra bir detachInterrupt yapmalısınız.
ISR yazma ipuçları
Kısacası, onları kısa tutun! Bir ISR yürütülürken diğer kesintiler işlenemez. Böylece, çok fazla şey yapmaya çalışırsanız, düğmeye basmayı veya gelen seri iletişimleri kolayca özleyebilirsiniz. Özellikle, bir ISR içinde "baskı" hata ayıklama yapmaya çalışmamalısınız. Bunları yapmak için geçen zaman, çözdüklerinden daha fazla soruna neden olabilir.
Yapılması gereken makul bir şey, tek baytlık bir bayrak ayarlamak ve daha sonra bu işareti ana döngü işlevinde sınamaktır. Veya, gelen bir baytı seri bağlantı noktasından bir arabellekte saklayın. Dahili zamanlayıcı kesintileri, dahili zamanlayıcının her taşmasıyla geçen süreyi takip eder ve böylece zamanlayıcının kaç kez taştığını bilerek geçen süreyi çalıştırabilirsiniz.
Unutmayın, bir ISR içinde kesintiler devre dışıdır. Böylece millis () işlev çağrıları tarafından döndürülen sürenin değişeceğini ummak hayal kırıklığına yol açacaktır. Zamanı bu şekilde elde etmek geçerlidir , sadece zamanlayıcının artmadığını unutmayın. ISR'de çok uzun süre harcarsanız, zamanlayıcı bir taşma olayını kaçırabilir ve milis () tarafından döndürülen sürenin yanlış olmasına neden olabilir.
Bir test, 16 MHz'lik bir Atmega328 işlemcide, micros () çağrısının 3.5625 µs aldığını gösterir. Millis () çağrısı 1.9375 µs alır. Geçerli zamanlayıcı değerinin kaydedilmesi (kaydedilmesi) bir ISR'de yapılması makul bir şeydir. Geçen milisaniyeyi bulmak geçen mikrosaniyeden daha hızlıdır (milisaniye sayımı sadece bir değişkenten alınır). Ancak mikrosaniye sayımı, Zamanlayıcı 0 zamanlayıcısının (artmaya devam edecek) geçerli değeri kaydedilmiş bir "Zamanlayıcı 0 taşma sayısı" na eklenerek elde edilir.
Uyarı: Kesintiler bir ISR içinde devre dışı bırakıldığından ve Arduino IDE'nin en son sürümü Seri okuma ve yazma için kesmeler kullandığından ve ayrıca "milis" ve "gecikme" tarafından kullanılan sayacı artırmak için bu işlevleri kullanmaya çalışmamalısınız bir ISR içinde. Başka bir deyişle:
- Gecikmeyi denemeyin, örneğin:
delay (100);
- Bir çağrıdan milis'e kadar olan zamanı alabilirsiniz, ancak artmayacaktır, bu yüzden artmasını bekleyerek gecikmeye çalışmayın.
- Seri baskı yapmayın (örn.
Serial.println ("ISR entered");
)
- Seri okuma yapmaya çalışmayın.
Pin değiştirme kesintileri
Pinlerdeki harici olayları tespit etmenin iki yolu vardır. Birincisi özel "harici kesme" pimleri, D2 ve D3. Bu genel kesik kesilme olayları, her pin için bir tane. Bunlara her pin için attachInterrupt komutunu kullanarak ulaşabilirsiniz. Kesme için yükselen, düşen, değişen veya düşük seviyeli bir koşul belirtebilirsiniz.
Bununla birlikte, tüm pinler için "pin değiştirme" kesintileri de vardır (Atmega328'de, diğer işlemcilerdeki tüm pinler olmayabilir). Bunlar pin gruplarına etki eder (D0 ila D7, D8 ila D13 ve A0 ila A5). Ayrıca harici olay kesintilerinden daha düşük önceliğe sahiptir. Bununla birlikte, harici kesmelerden daha kullanışlıdırlar çünkü gruplar halinde gruplandırılırlar. Yani kesme yangınları tam olarak hangi pin kesmeye neden kendi kodunda çalışmak zorunda.
Örnek kod:
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
}
Pin değiştirme kesintisini işlemek için yapmanız gerekenler:
- Gruptaki hangi raptiyeyi belirtin. Bu PCMSKn değişkenidir (burada n, aşağıdaki tablodan 0, 1 veya 2'dir). Birden fazla raptiyede kesinti olabilir.
- Uygun kesme grubunu etkinleştir (0, 1 veya 2)
- Yukarıda gösterildiği gibi bir kesme işleyicisi sağlayın
İğneler tablosu -> iğne değiştirme adları / maskeleri
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)
İşleyici işlemini kesme
Kesme işleyici, maske birden fazla belirtiyorsa hangi pimin kesintiye neden olduğunu bulması gerekir (örn. D8 / D9 / D10'da kesintiler istiyorsanız). Bunu yapmak için o iğnenin önceki durumunu saklamanız ve bu özel iğne değiştiyse (bir digitalRead veya benzeri yaparak) çalışmanız gerekir.
Muhtemelen kesintileri kullanıyorsunuz ...
"Normal" bir Arduino ortamı, kişisel olarak denemeseniz bile kesintileri kullanıyor. Millis () ve micros () işlev çağrıları "zamanlayıcı taşması" özelliğini kullanır. Dahili zamanlayıcılardan biri (zamanlayıcı 0) saniyede yaklaşık 1000 kez kesilecek ve etkin olarak milis () sayacı haline gelen bir dahili sayacı artıracak şekilde ayarlanmıştır. Tam saat hızı için ayar yapıldığı için bundan biraz daha fazlası var.
Ayrıca donanım seri kitaplığı, gelen ve giden seri verileri işlemek için kesmeler kullanır. Kesintiler tetiklenirken ve dahili bir arabelleği doldururken programınız başka şeyler yapıyor olabileceğinden bu çok kullanışlıdır. Daha sonra Serial.available () öğesini işaretlediğinizde, bu arabellekte herhangi bir şey varsa neyin yerleştirildiğini öğrenebilirsiniz.
Kesmeleri etkinleştirdikten sonra bir sonraki komutu yürütme
Arduino forumunda biraz tartışma ve araştırma yaptıktan sonra, kesintileri etkinleştirdikten sonra tam olarak ne olduğunu açıkladık. Daha önce etkin olmayan kesintileri etkinleştirebilmenin üç ana yolu vardır:
sei (); // set interrupt enable flag
SREG |= 0x80; // set the high-order bit in the status register
reti ; // assembler instruction "return from interrupt"
Her durumda, işlemci , bir kesinti olayı beklemiş olsa bile, kesintilerden sonraki bir sonraki talimatın (daha önce devre dışı bırakılmışlarsa) her zaman yürütüleceğini garanti eder . ("Bir sonraki" ile kastedilen, program dizisindeki bir sonrakini, fiziksel olarak takip edileni değil). Örneğin, bir RETI komutu kesmenin gerçekleştiği yere geri atlar ve bir komut daha yürütür).
Bu, şöyle bir kod yazmanıza olanak tanır:
sei ();
sleep_cpu ();
Bu garanti için değilse , işlemci uyumadan önce kesinti meydana gelebilir ve daha sonra hiç uyandırılmayabilir.
Boş kesmeler
İşlemciyi uyandırmak için yalnızca bir kesme yapmak istiyorsanız, ancak özellikle bir şey yapmıyorsanız, EMPTY_INTERRUPT tanımını kullanabilirsiniz, örn.
EMPTY_INTERRUPT (PCINT1_vect);
Bu sadece bir "reti" (kesme noktasından dönüş) talimatı oluşturur. Kayıtları kaydetmeye veya geri yüklemeye çalışmadığından, bu, uyandırmak için bir kesinti almanın en hızlı yolu olacaktır.
Kritik bölümler (atomik değişken erişim)
Kesme hizmeti yordamları (ISR'ler) ve ana kod (yani ISR'de olmayan kod) arasında paylaşılan değişkenlerle ilgili bazı ince sorunlar vardır.
Bir ISR, kesintiler etkinleştirildiğinde herhangi bir zamanda tetiklenebileceğinden, bu tür paylaşılan değişkenlere erişme konusunda dikkatli olmanız gerekir, çünkü bunlara eriştiğiniz anda güncellenebilirler.
İlk olarak ... "değişken" değişkenleri ne zaman kullanıyorsunuz?
Bir değişken yalnızca hem ISR'nin içinde hem de değişkenin dışında kullanılıyorsa değişken olarak işaretlenmelidir.
- Değişkenler sadece bir ISR dışında kullanılamaz gerektiğini değil uçucu olması.
- Değişkenler sadece bir ISR içinde kullanılan gerektiğini değil uçucu olması.
- ISR'nin içinde ve dışında kullanılan değişkenler değişken olmalıdır .
Örneğin.
volatile int counter;
Bir değişkeni geçici olarak işaretlemek, derleyiciye değişken içeriğini bir işlemci kaydına "önbelleğe almamasını", ancak gerektiğinde her zaman bellekten okumasını söyler. Bu işlemeyi yavaşlatabilir, bu yüzden gerekmediğinde her değişkeni geçici hale getirmezsiniz.
Değişken bir değişkene erişirken kesintileri kapatma
Örneğin, bir count
sayı ile karşılaştırmak için, bir baytın count
diğer bayt tarafından değil, ISR tarafından güncellenmesi durumunda karşılaştırma sırasında kesintileri kapatın .
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
Veri sayfasını okuyun!
Kesmeler, zamanlayıcılar, vb. Hakkında daha fazla bilgi işlemcinin veri sayfasından edinilebilir.
http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf
Diğer örnekler
Alanla ilgili dikkat edilmesi gereken noktalar (yazı boyutu sınırı) listelememin daha fazla örnek kod olmasını engelliyor. Daha fazla örnek kod için kesmelerle ilgili sayfamı inceleyin .