很多時候我們都會使用到微控制器,基于其具備的特點
正因為微控制器的特點,能處理多個輸入,能做很多事情,因此微控制器将很忙碌。而忙碌的微控制器需要一種方式來管理外部事件,比如按鈕按下,同時兼顧其他輸入和輸出定時處理。
中斷是Arduino和其他微控制器的一個非常重要的基本特性,是能夠保持對外部輸入或内部計時事件的控制的一種方法。
中斷是如何工作的
中斷顧名思義,就是中斷當前程序執行以便處理其他事情的方法。中斷不是微控制器所獨有的,它們已經在計算機和控制器中使用了幾十年。當在鍵盤上輸入、移動鼠标或在觸摸屏上滑動時,都會觸發中斷和中斷服務程序,從而對操作産生适當的響應。
中斷的工作流程
中斷對于監測間歇性發生的開關按下或警報觸發等事件非常有用。當需要精确測量輸入脈沖時,它們也是合适的選擇。
微控制器和微處理器使用的中斷有很多種,不同模型的中斷特性各不相同。它們可以大緻分為兩類:
Arduino Uno 中斷
Arduino程序執行流程
Arduino程序執行流程
Arduino Uno支持三種中斷類型:
當中斷産生時,需要一個中斷服務程序(isr,Interrupt Service Routine)對其進行處理,并且ISR隻在中斷發生時運行。
中斷服務程序
中斷服務程序(ISR)本質上是一個函數。但與常規Arduino函數不同:
為何需要使用中斷
下面将通過2個按鈕控制LED亮滅的示例來展現為何需要使用中斷。
示例1:通過按鈕控制led亮滅。
示例1:通過按鈕控制led亮滅
代碼
// 定義LED和按鈕引腳 const byte ledPin = 13; const byte buttonPin = 2; // 定義布爾變量用于記錄開關狀态 // volatile為類型修飾符,常用于中斷ISR全局變量 volatile bool toggleState = false; // 檢測按鈕狀态,并控制LED亮滅 void checkSwitch() { if (digitalRead(buttonPin) == LOW) { delay(200); // 如果按鈕按下,則翻轉開關狀态變量的值 toggleState = !toggleState; // 控制led digitalWrite(ledPin,toggleState); } } void setup() { // 設置LED引腳為輸出模式 pinMode(ledPin, OUTPUT); // 設置按鈕引腳為上拉輸入模式 pinMode(buttonPin, INPUT_PULLUP); } void loop() { // 調用按鈕檢測函數 checkSwitch(); } |
上傳程序,按鈕能完美控制led的亮滅。
示例2:在示例1的基礎上增加了一個延時,再來看看按鈕是否能有效的控制led的亮滅。
代碼
const byte ledPin = 13; const byte buttonPin = 2; volatile bool toggleState = false; void checkSwitch() { if (digitalRead(buttonPin) == LOW) { delay(200); toggleState = !toggleState; digitalWrite(ledPin, toggleState); } } void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT_PULLUP); Serial.begin(9600); } void loop() { checkSwitch(); // 增加一個5秒的延時 Serial.println("延時開始"); delay(5000); Serial.println("延時結束"); Serial.println(".............."); } |
上傳程序,可以發現一個問題就是:按鈕好像失效了,隻能偶然在延時的間隙能幸運觸發按鈕翻轉led的狀态。為了解決這個問題,最好的方式是使用中斷。
硬件中斷
硬件中斷是外部中斷,在大多數Arduino型号上都限于特定的引腳。這些引腳被配置為輸入,可以通過操縱它們的邏輯狀态觸發硬件中斷。
Arduino硬件中斷引腳
不同型号的arduino主闆中斷引腳
使用硬件中斷主要包括兩個步驟
attachInterrupt()函數
attachInterrupt()函數
digitalPinToInterrupt()函數
示例3:使用硬件中斷重寫示例2項目,驗證是否能有效觸發按鈕事件,控制led亮滅
const byte ledPin = 13; const byte buttonPin = 2; volatile bool toggleState = false; // ISR中相比前面示例移除了200ms延時,因為在ISR中不能使用delay()函數 void checkSwitch() { if (digitalRead(buttonPin) == LOW) { toggleState = !toggleState; digitalWrite(ledPin, toggleState); } } void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT_PULLUP); // 附件中斷處理函數,并指定中斷觸發模式為FALLING,即按下按鈕時觸發中斷 attachInterrupt(digitalPinToInterrupt(buttonPin),checkSwitch, FALLING); } void loop() { Serial.println("延時開始"); delay(5000); Serial.println("延時結束"); Serial.println(".............."); } |
注意,volatile修飾的布爾變量,它的值是在中斷服務例程中被操作的。如果沒有volatile,Arduino IDE編譯器可能會嘗試過度優化代碼并删除變量。
結論:通過上述3個示例的對比,可以了解為什麼需要使用中斷。
PCI中斷(Pin Change Interrupts)
引腳變更中斷(PCI)是硬件中斷的另一種形式。它不局限于特定的引腳,所有的引腳都可以用于引腳變更中斷。
PCI中斷是以端口形式分組的,同一端口的所有引腳産生相同的引腳變更中斷,因此如果同端口下多個引腳都會産生中斷,則需要自行在中斷服務程序中進行引腳識别,以便響應正确的中斷事件和正确的處理方式。
PCI中斷的模式為CHANGE,因此對于按鈕來說,按下和釋放按鈕會産生2次PCI中斷。
Arduino UNO支持PCI的端口組
Arduino UNO支持PCI的端口組
如何使用PCI中斷
PCI控制寄存器
Pin Change Mask
Pin Change Mask設置
PCI中斷的ISR名稱
PCI中斷示例
PCI中斷示例
示例1:使用單個PCI引腳中斷控制led亮滅。
代碼
const byte ledPin = 13; const byte buttonPin = 7; volatile bool togglestate = false; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT_PULLUP); // 啟用端口組Port D(bit2--PORTD,bit1--PORTC,bit0--PORTB) PCICR |= B00000100; // 啟用D7引腳,PCMSK0,PCMSK1,PCMSK2分别對應PORTB,PORTC,PORTD PCMSK2 |= B10000000; } void loop() { // No code in Loop } // PCI中斷的ISR程序名稱已經預先定義,需要選擇正确的名稱。 ISR (PCINT2_vect) { togglestate = !togglestate; digitalWrite(ledPin, togglestate); } |
示例2:使用同一端口組的多個PCI引腳中斷
代碼
// 定義引腳 const byte ledPin1 = 11; const byte ledPin2 = 13; const byte buttonPin1 = 2; const byte buttonPin2 = 7; // 定義狀态變量,使用volatile修飾 volatile bool D2_state = LOW; volatile bool D7_state = LOW; void setup() { pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); pinMode(buttonPin1, INPUT_PULLUP); pinMode(buttonPin2, INPUT_PULLUP); // 啟用Port D端口組 PCICR |= B00000100; // 啟用D2 和 D7引腳 PCMSK2 |= B10000100; } void loop() { // Loop code } // 選擇正确的ISR名稱 ISR (PCINT2_vect) { // D2 按鈕PCI中斷處理 if (digitalRead(buttonPin1) == LOW) { //D2 引腳在按鈕按下(下降沿)時觸發 ISR D2_state = !D2_state; digitalWrite(ledPin1, D2_state); } // D7 按鈕PCI中斷處理 if (digitalRead(buttonPin2) == LOW) { //D7引腳在按鈕按下(下降沿)時觸發 ISR D7_state = !D7_state; digitalWrite(ledPin2, D7_state); } } |
注意:如前面所述,因為按鈕按下(H-->L)和釋放(L-->H)會觸發兩次PCI中斷,因此需要在ISR程序中進行判斷。
定時器中斷(Timer Interrupts)
定時器中斷不使用外部信号,而時由軟件中生成的中斷,計時是基于Arduino Uno的16 MHz時鐘振蕩器。如Servo和Tone庫,在内部使用了Timer Interrupts,因此在編寫代碼時應注意避免發生沖突。
Arduino UNO有3個内部定時器,其位數有所不同,位數決定了定時器的最大計數數,8位定時器為256位,16位定時器為65,536位。
計時器中的值以時鐘頻率或時鐘頻率的分數為單位遞增。可以使用軟件來設置中斷觸發的次數,也可以在計時器溢出時觸發中斷。
Arduino UNO有3個内部定時器
劃分時鐘頻率
定時器由ATmega328内部的16mhz振蕩器計時。
劃分時鐘頻率
時鐘一個周期為計時器的一個“tick”,其時間為62.5ns,這是一個非常短的時間,對于許多計時應用程序來說,它太短沒有太多的實際用途。
為了降低時鐘信号,ATmega328有一個“預分頻器”,本質上是一個時鐘頻率的分配器。預分頻器可以将時鐘劃分為更易于管理的較低頻率,從而選擇許多常見的時間頻率,最長可達64us。
預分頻器
每個計時器有三個時鐘選擇位,通過對這三個時鐘選擇位的設置确定Prescaler的值,以及計時源。也可以将所有時鐘選擇位設置為零停用時鐘源。
Timer0, 8位定時器
Timer1, 16位定時器
Timer2, 8位定時器
使用定時器中斷
定時器中斷可以在兩種不同的模式下操作,
計算公式
定時器相關寄存器
定時器中斷服務程序ISR
定時器中斷模式
示例:配置一個2hz輸出的定時器,用其控制LED閃爍(每秒2次)
代碼
#define ledPin 13 // 定義變量存儲比較寄存器值 int timer1_compare_match; ISR(TIMER1_COMPA_vect) // 使用timer1的比較匹配模式ISR { // 預加載比較匹配寄存器值,該值根據所需的頻率,通過上述公式可以計算得出 TCNT1 = timer1_compare_match; // 翻轉LED狀态,^按位異或,相同為0,相異為1 digitalWrite(ledPin, digitalRead(ledPin) ^ 1); } void setup() { pinMode(ledPin, OUTPUT); // 禁用所有中斷,避免在配置定時器中斷時産生中斷 noInterrupts(); // 初始化Timer1 TCCR1A = 0; TCCR1B = 0; // 期望2hz,如果256的預分頻器,則計算得出比較匹配寄存器的值為。 // [16000000/(256*2)]-1=31249 timer1_compare_match = 31249; // 預加載比較匹配寄存器值 TCNT1 = timer1_compare_match; // 設置預分頻器為256, TCCR1B |= (1 << CS12); // 啟用定時器中斷timer1比較匹配模式 TIMSK1 |= (1 << OCIE1A); // 啟用所有中斷 interrupts(); } void loop() { } |
總結
對于需要精确定時或響應式用戶界面的項目,中斷是一種很好的方式。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!