Arduino成长日记6 - 中断机制
中斷是在程序運行中經常用到的功能,用于處理一下實時性比較高的事件,首先來了解一下中斷的概念。
中斷
當出現需要及時處理的事件(中斷請求)時,CPU暫停當前工作的執行轉而處理應急事件(中斷)的過程。即在程序運行過程中,系統出現了一個必須由CPU立即處理的情況,此時,CPU暫停當前程序去處理這個新事件的過程就就是中斷。
舉個栗子:你正在看著一本Arduino程序設計的書,看到95頁時肚子餓了,你記下來頁碼跑去吃東西,吃完東西后繼續從書的95頁往下看;這里面看書就類似CPU在正常工作,肚子餓了吃東西就像處理中斷,中斷事件處理(吃東西)結束后(CPU)又從停下來的地方繼續運行(看書)。
Arduino外部中斷
外部中斷是單片機實時地處理外部事件的一種內部機制。當某種外部事件發生時,單片機的中斷系統將迫使CPU暫停正在執行的程序,轉而去進行中斷事件的處理;中斷處理完畢后.又返回被中斷的程序處,繼續執行下去。
大多數的Arduino板至少擁有兩個外部中斷引腳:0號中斷(引腳2)和1號中斷(引腳3)。不同的Arduino控制板的中斷引腳還有所區別,如下表所示。
| Uno,Ethernet | 2 | 3 | X | X | X | X |
| Mega | 2 | 3 | 21 | 20 | 19 | 18 |
| Leonardo | 3 | 2 | 0 | 1 | X | X |
| Due | 所 | 有 | IO | 口 | 均 | 可 |
表中int表示Arduino板的中斷號對應的引腳,X 則表示該板沒有對應的中斷引腳, 在所有的Arduino控制板中,Arduino Due比較強大的中斷功能,允許在所有IO腳觸發外部中斷。
Arduino編程中的中斷均已函數調用的形式來配置及使用,相對來說比較簡單,下面是外部中斷的常用函數。
1、中斷函數
attachInterrupt(interrupt,function,mode)
描述:當發生外部中斷時,調用一個指定的函數。在程序中再次調用時可以指定新的中斷調用函數;如在setup函數中初始化函數A為中斷0的中斷調用函數,再次調用 attachInterrupt 函數時,可以指定函數B為中斷0的中斷調用函數,在程序中有需要可多次調用更新中斷函數。
語法:attachInterrupt(interrupt,function,mode)
attachInterrupt(pin,function,mode)(Due專用)
參數:
interrupt,中斷編號;pin,引腳編號(Due專用);
function:中斷發生時調用的函數,此函數必須不帶任何參數,不返回任何值。
mode:定義什么情況下觸發中斷,以下四個常數為mode的有效值:
- LOW: 當引腳為低電平時,觸發中斷;
- CHANGE:當引腳電平發生變化時,觸發中斷;
- RISING:當引腳電平由低變高時,觸發中斷(上升沿中斷);
- FALLING:當引腳電平由高變低時,觸發中斷(下降沿中斷);
對于Due而言,多了一個專用參數 – HIGH,即當引腳為高電平時,觸發中斷;
注意:不要妄想在中斷函數中加延時,在Arduino中斷函數中,delay()不會生效,millis()不會持續累加;當中斷發生時,串口數據可能會出現丟包;在中斷函數里面使用到的全局變量應該聲明為volatile變量。
示例:
const byte ledPin = 13; const byte interruptpin = 2; volatile byte state = LOW;void setup(void) {pinMode(ledPin,OUTPUT);pinMode(interruptPin,INPUT_PULLUP);attachInterrupt(digitalPinToInterrupt(interruptPin),blink,CHANGE); }void loop() {digitalWrite(ledPin,state); }void blink() {state = !state; }detachInterrupt(interrupt)
描述:關閉對應的中斷。
參數:interrupt,禁用中斷的編號(0或1)。
2、中斷使能函數
interrupts()
描述:啟用中斷/重新啟用中斷(在被禁用中斷過后)。
noInterrupts()
描述:禁用中斷。在程序中如果有一些函數的運行不希望被中斷打斷,可以調用noInterrupts函數來禁用中斷的發生,再配合interrupts函數恢復中斷使能。
示例:
Arduino定時器中斷
外部中斷是通過檢測輸入電平的變化,而產生中斷信號。除了外部中斷方式外,Arduino控制板還可以按時間變化產生中斷,這里使用到定時器(Timer),而對應產生的中斷被稱為定時器中斷。
定時器是嵌入式系統中的一個特殊的計數器。它可以對分頻后時鐘信號的進行計數,當計數值達到設定值,即會產生定時器中斷。且通過時鐘頻率和計數值可以計算出時間,所以可以達到以時間觸發中斷的效果。
即當需要按一定的時間間隔執行某個操作時,就需要用到定時器中斷了。
以UNO為例,一共有3個定時器可以使用:Timer0,Timer1,Timer2。
每個定時器都有自己的函數庫,通過使用硬件內部計時器中斷來實現中斷效果,下面以Timer1庫的程序為例說明~
首先需要安裝TimeOne庫,也就是Timer1的對應庫。下面提供兩種安裝方式,可以點擊鏈接跳到對應的安裝教程:
第一種:Arduino IDE安裝TimerOne庫
第二種:Sublime Text3環境安裝TimerOne庫
安裝完成后,大概了解一下Timer1的庫函數,打開.h文件TimerOne.h,除了包含 Arduino.h 外,還有一個宏定義聲明了Timer1是一個16位定時器。
隨后在TimerOne類中定義了定時器相關函數,在配置函數中包含了初始化函數initialize()函數,參數默認為1000000毫秒,也就是1秒;setPeriod()函數較長就不發出來了,主要作用是設置定時器的觸發周期,在initialize()函數中調用。
//****************************// Configuration//****************************void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) {TCCR1B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timerTCCR1A = 0; // clear control register A setPeriod(microseconds);}void setPeriod(unsigned long microseconds) __attribute__((always_inline)) {...}定時器狀態控制函數
//****************************// Run Control//****************************void start() __attribute__((always_inline)) {TCCR1B = 0;TCNT1 = 0; // TODO: does this cause an undesired interrupt?resume();}void stop() __attribute__((always_inline)) {TCCR1B = _BV(WGM13);}void restart() __attribute__((always_inline)) {start();}void resume() __attribute__((always_inline)) {TCCR1B = _BV(WGM13) | clockSelectBits;}PWM控制相關函數(函數體省略)
//****************************// PWM outputs//****************************void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) {...}void pwm(char pin, unsigned int duty) __attribute__((always_inline)) {...}void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) {...}void disablePwm(char pin) __attribute__((always_inline)) {...}最重要的來了,定時器中斷相關函數
//****************************// Interrupt Function//****************************void attachInterrupt(void (*isr)()) __attribute__((always_inline)) {isrCallback = isr;TIMSK1 = _BV(TOIE1);}void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) {if(microseconds > 0) setPeriod(microseconds);attachInterrupt(isr);}void detachInterrupt() __attribute__((always_inline)) {TIMSK1 = 0;}大概了解過定時器相關函數后,可以開始編寫定時器中斷程序了。
1、添加TimerOne頭文件(安裝庫之后);
2、初始化定時器,開啟定時器中斷;
3、編寫定時器中斷操作;
簡單程序如下
使用定時器中斷可以很好的解決Arduino的單任務機制,但是UNO的3個定時器資源同樣跟引腳PWM會有沖突,比如Timer0定時器負責 millis()函數 和 delay()函數 的計時工作,同時控制著數字引腳 pin5,pin6 的PWM功能;Timer1定時器負責數字引腳 pin9、pin10 的PWM功能,也影響 Servo.h庫 的使用;Timer2定時器負責數字引腳 pin11,pin3 的PWM功能,也和蜂鳴器的 tone()函數有關。
綜上所述,定時器的使用會影響對應引腳的PWM功能,和某些時序函數的運行,比如Timer0負責delay()函數延時操作的,使用pin5、pin6的PWM輸出可能會有些微誤差,像analogWrite(5,0)和analogWrite(5,6)可能是一樣的效果
除了對應的硬件定時器,還可以用Timer庫和SimpleTimer庫來實現定時效果,這兩個庫不用到硬件定時器,也是用到 millis()函數 來實現延時,所以對 delay函數 也是會有點影響。
串口中斷(沒這回事兒)
關于Arduino串口中斷的也是找了一段時間沒結果,后來發現了Arduino沒有串口中斷這回事,但是卻機緣巧合的在main函數中找到了串口的端倪。
以下是Arduino中main函數的內容~
即是Arduino沒有串口中斷,但是卻用for循環來檢測串口事件,與loop函數在同個循環中,相當于loop函數運行一次順帶檢測一次串口事件。
總結
以上是生活随笔為你收集整理的Arduino成长日记6 - 中断机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 高斯白噪声-python高
- 下一篇: 函数指针实现回调函数