tft每日頭條

 > 圖文

 > arduino波特率用哪個定時器

arduino波特率用哪個定時器

圖文 更新时间:2024-12-23 01:43:10

很多時候我們都會使用到微控制器,基于其具備的特點

  • 微控制器可以處理多個輸入和輸出。
  • 微控制器可以提供精确的定時脈沖。
  • 微控制器速度快。

正因為微控制器的特點,能處理多個輸入,能做很多事情,因此微控制器将很忙碌。而忙碌的微控制器需要一種方式來管理外部事件,比如按鈕按下,同時兼顧其他輸入和輸出定時處理。

中斷是Arduino和其他微控制器的一個非常重要的基本特性,是能夠保持對外部輸入或内部計時事件的控制的一種方法。

中斷是如何工作的

中斷顧名思義,就是中斷當前程序執行以便處理其他事情的方法。中斷不是微控制器所獨有的,它們已經在計算機和控制器中使用了幾十年。當在鍵盤上輸入、移動鼠标或在觸摸屏上滑動時,都會觸發中斷和中斷服務程序,從而對操作産生适當的響應。

中斷的工作流程

  • 一個程序正在運行
  • 産生一個中斷
  • 程序被暫停同時其相應數據被存放在一邊,以便稍後恢複。
  • 運行與中斷相關的代碼。
  • 當中斷代碼完成時,程序從它暫停的地方恢複。

中斷對于監測間歇性發生的開關按下或警報觸發等事件非常有用。當需要精确測量輸入脈沖時,它們也是合适的選擇。

微控制器和微處理器使用的中斷有很多種,不同模型的中斷特性各不相同。它們可以大緻分為兩類:

  • 硬件中斷——這些中斷通常來自外部信号。
  • 軟件中斷——這些是内部信号,通常由計時器或軟件相關事件控制。

Arduino Uno 中斷

Arduino程序執行流程

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)1

Arduino程序執行流程

Arduino Uno支持三種中斷類型:

  • 硬件中斷——特定引腳上的外部中斷信号。
  • 引腳改變中斷(PCI,Pin Change Interrupts)——在任何引腳上的外部中斷,以端口分組。
  • 定時器中斷——内部定時器産生的中斷,在軟件中操作。

當中斷産生時,需要一個中斷服務程序(isr,Interrupt Service Routine)對其進行處理,并且ISR隻在中斷發生時運行。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)2

中斷服務程序

中斷服務程序(ISR)本質上是一個函數。但與常規Arduino函數不同:

  • 不能向ISR傳遞參數
  • 不能從ISR獲得任何返回值
  • 應當盡可能的快
    • 不能使用delay()函數
    • 不能使用millis()函數
    • 不能使用Serial庫,因此不能打印到串行監視器
    • 隻使用全局變量,全局變量應該聲明為volatile類型

為何需要使用中斷

下面将通過2個按鈕控制LED亮滅的示例來展現為何需要使用中斷。

示例1:通過按鈕控制led亮滅。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)3

示例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 UNO隻有2個硬件中斷引腳
    • Pin2:INT0
    • Pin3:INT1
  • 不同型号的arduino主闆中斷引腳和數量不一樣

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)4

不同型号的arduino主闆中斷引腳

使用硬件中斷主要包括兩個步驟

  • 編寫一個中斷服務程序ISR(Interrupt Service Routine)
  • 将ISR函數附加到特定中斷,并指定觸發方式。

attachInterrupt()函數

  • 通常在setup()中調用attachInterrupt()函數

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)5

attachInterrupt()函數

  • 因為中斷号不同于引腳号,最簡單的方式是使用digitalPinToInterrupt()函數

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)6

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波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)7

Arduino UNO支持PCI的端口組

如何使用PCI中斷

  • 确定需要使用PCI中斷的引腳(同時即确定了所在端口組port)
  • 啟用對應端口組port中斷。
    • 需要使用PCI控制寄存器(PCICR,Pin Change Interrupt Control Register),PCICR寄存器低三位分别對應三個端口組,對應位設置為1即啟用響應端口組,可同時啟用多個端口組。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)8

PCI控制寄存器

  • 啟用對應引腳pin中斷
    • 需要使用Pin Change Mask (PMSKx)來選擇對應的引腳,有3個PMSK分别對應三個端口組,将對應位置為1即啟用對應引腳中斷。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)9

Pin Change Mask

  • 可以在setup()函數中進行設置

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)10

Pin Change Mask設置

  • 編寫中斷服務程序ISR,如果使用同一端口組的多個引腳,則需要在ISR中進行對應識别。
    • 和硬件中斷不同,PCI中斷的ISR已經定義好名稱,需要根據實際情況選擇正确的ISR名稱。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)11

PCI中斷的ISR名稱

  • ISR中斷服務程序規則和硬件中斷一樣,對于全局變量需要使用volatile修飾。

PCI中斷示例

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)12

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波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)13

Arduino UNO有3個内部定時器

劃分時鐘頻率

定時器由ATmega328内部的16mhz振蕩器計時。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)14

劃分時鐘頻率

時鐘一個周期為計時器的一個“tick”,其時間為62.5ns,這是一個非常短的時間,對于許多計時應用程序來說,它太短沒有太多的實際用途。

為了降低時鐘信号,ATmega328有一個“預分頻器”,本質上是一個時鐘頻率的分配器。預分頻器可以将時鐘劃分為更易于管理的較低頻率,從而選擇許多常見的時間頻率,最長可達64us。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)15

預分頻器

每個計時器有三個時鐘選擇位,通過對這三個時鐘選擇位的設置确定Prescaler的值,以及計時源。也可以将所有時鐘選擇位設置為零停用時鐘源。

  • Timer0, 8位定時器, 使用 CS01, CS02和CS03.

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)16

Timer0, 8位定時器

  • Timer1, 16位定時器, 使用CS10, CS11和CS12.

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)17

Timer1, 16位定時器

  • Timer2, 8位定時器, 使用CS20, CS21和CS22.

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)18

Timer2, 8位定時器

使用定時器中斷

定時器中斷可以在兩種不同的模式下操作,

  • 比較匹配模式,Compare Match Mode:将一個計數器值放入比較匹配寄存器。當定時器計數器與寄存器中的值相匹配時,産生定時器中斷。
  • 溢出模式,Overflow Mode:當定時器技術器達到它的最大計數值時,産生一個中斷,計數器重置為零并重新開始計數。
  • 通過将比較匹配寄存器與預分頻器相結合,可以獲得在計時器的範圍内想要的任何計時周期(8位計時器最多隻能除以255)。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)19

計算公式

定時器相關寄存器

  • Timer/Counter control registers (TCCR1A/B) 是8位寄存器。
  • 所有中斷都用定時器中斷掩碼寄存器(TIMSK1)單獨屏蔽。

定時器中斷服務程序ISR

  • 每個定時器都有兩個isr相關聯,一個用于比較匹配模式,另一個用于溢出模式。

arduino波特率用哪個定時器(Note-12.Arduino硬件中斷PCI中斷)20

定時器中斷模式

示例:配置一個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每日頭條,我们将持续为您更新最新资讯!

查看全部

相关圖文资讯推荐

热门圖文资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved