在Arduino中,可以使用AnalogWrite來使用硬體產生490Hz/980Hz的pwm波,並可根據參數來設定占空比。不瞭解這個的同學可以去 "AnalogWrite" 學習下, "SecretsOfArduinoPWM" 也是講了Arduino在avr的定時/計數器上做的封裝,我們這裡並不講 ...
在Arduino中,可以使用AnalogWrite來使用硬體產生490Hz/980Hz的pwm波,並可根據參數來設定占空比。不瞭解這個的同學可以去AnalogWrite學習下,SecretsOfArduinoPWM也是講了Arduino在avr的定時/計數器上做的封裝,我們這裡並不講Arduino相關,而是講AVR的定時/計數器,如何產生更多PWM波和定時/計數器的中斷使用。
AVR Timer/Counter(以下統稱Timer)
以ATmega358p為例,其內部擁有一個16位計時器,兩個8位計時器,下圖則為16位計時器的大致圖解:
對於沒有接觸過avr內部的Arduino同學來說,這張圖看不出來任何意思,別急,這些都是AVR-GCC里定義的縮寫,我們先來解釋下圖中的縮寫對照:
縮寫 | 全稱 |
---|---|
TCNT | Timer/Counter Register |
TCCR | Timer/Counter Control Register |
OCR | Output Compare Register |
OC | Output Compare Match Output |
ICR | Input Capture Register |
Int.Req | Interrupt Request |
TOV | Timer Overflow |
ICF | Input Capture Flag |
而圖中TCNT為主要工作部件,其工作模式的是依據TCCR的設定值。以該16位計時器為例,該計時器在ATmega358p中的序號為1,則其所有縮寫都會與1有關,即TCNT1的工作模式由TCCR1A和TCCR1B來決定。不急著看別的,我們先來瞅瞅TCCR1A與TCCR1B是怎麼來配置TCNT1的工作模式的:
TCCR1A:
圖中7、6、5、4位分別應該是COM1A1、COM1A0、COM1B1、COM1B0,話說這datasheet有時候也真是會省事兒
TCCR1B:
這兩個寄存器都是八位寄存器,再來一張表來對照一下圖中的縮寫:
縮寫 | 全稱 |
---|---|
COMnA | Compare Output Mode for Channel A |
COMnB | Compare Output Mode for Channel B |
WGM | Wave Generation Mode |
ICNC | Input Capture Noise Canceler |
ICES | Input Capture Edge Select |
CS | Clock Select |
我們通過配置這兩個寄存器來控制TCNT1的工作模式,通過配置WGM來選擇波型生成模式,主要有Fast PWM/PWM Phase Correct/CTC
模式,並且也擁有不同的計數TOP值,有0xFF/ICR1/OCR1A
等,通過COM結合WGM對針腳的輸出做配置,用CS來選擇生成波型的prescaler,分別有1/8/64/256/1024
,或者外部時鐘。TOP值為ICR1或OCR1A時,要對使用的寄存器進行賦值。工作模式中,Fast PWM與PWM Phase Correct這兩種計數模式不同的是,PWM Phase Correct可以到達top值後又遞減至0,Fast PWM到達top值後則會觸發上升或下降沿。
如下配置:
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
TCCR1B = _BV(CS12) | _BV(CS10);
//WGM配置了PWM Phase Correct,使用的TOP值為0x00FF
//COM配置了在往上計數時清零,往下計數時置位
//使用了1024的prescaler
OCR1A = 100;
OCR1B = 50;
則我們可以得到的時鐘頻率為Fclk/(prescaler*TOP)=16000000/(1024*255)/2=30.63Hz
,且OC1A占空比為OCR1A/TOP=100/255=39%
,OC1B占空比為OCR1B/TOP=50/255=19.6%
。
更多配置組合請看SecretsOfArduinoPWM/PWM的秘密或直接datasheet,其中要註意的是,使用OCR1A為TOP值時,可以在一定範圍內配置任意頻率,但OC1A的占空比為始終為50%,OC1B的占空比為(OCR1B+1)/(OCR1A+1)。
Interrupt
中斷是其它與CPU非同步進行的硬體與CPU交互的一種方法。這樣我們就不用在CPU中去等待其它某些任務的完成和觸髮狀態,由其它硬體去來觸發進入CPU主進程的時機。回頭看第一張大圖,在圖中可以找到四處(Int.Req),分別是TOVn(Timer/Counter Overflow), OCnA(Output Compare A Match), OCnB(Output Compare B Match), ICF(Input Capture Flag),根據字義我們便可瞭解到該中斷的作用,在使用中斷時,我們要先於TIMSK1(Timer/Counter 1 Interrupt Mask Register)中開啟相應的中斷位。
下圖為TIMSK1:
圖中的字母應為ICIE1, OCIE1B, OCIE1A, TOIE1,這數據手冊,也是太懶了。下表為對照:
縮寫 | 全稱 |
---|---|
ICIE | Input Capture Interrupt Enable |
OCIE | Output Compare Interrupt Enable |
TOIE | Timer/Counter Overflow Interrupt Enable |
開啟該遮罩位後,便可打開中斷,前提也是在全局中斷打開的情況下(sei()為打開全局中斷,cli()為關閉全局中斷
)。在當下的AVR-GCC環境中,我們使用巨集命令ISR來定義中斷程式,使用方法如下:
ISR(xxxx_vect)
{
// user code here
}
而這裡的xxxx_vect則是要監聽的中斷向量地址,可以於Atmel官網<avr/interrupt.h>: Interrupts上找得到所有中斷對應的向量名稱。
如上面的四種向量,於mega328p中,則分別對應:
ICIE1 | TIMER1_CAPT_vect |
OCIE1A | TIMER1_COMPA_vect |
OCIE1B | TIMER1_COMPB_vect |
TOIE1 | TIMER1_OVF_vect |
而中斷又分為兩種,一種為事件觸髮型
,這種中斷會在上一個中斷沒有運行結束前隊列等待,直至前面優先的任務完成後才能執行;另一種為中斷條件觸發
,如果上一個中斷還沒有返回回來,那麼則不會觸發這個中斷(如果定時器為高頻時則會出現該情況)。所以在使用中斷的時候,進入中斷、跳轉、返回都需要耗費時鐘,不要對高頻使用中斷,甚至會使主線程一直處於堆棧狀態,且中斷中不要運行太多程式。
There are basically two types of interrupts: The first type is triggered by an event that sets the Interrupt Flag. The second type of interrupts will trigger as long as the interrupt condition is present.
The traps when using interrupts中也是羅列了一些使用中斷的坑和技巧,可以看看。附上Atmega328p的datasheet,真是有問題就看datasheet,上面的圖與摘錄都是從datasheet里出來的。
Have fun.