由於Arduino預設的PWM控制方法僅有500Hz(好像還有另一個),想要知道怎樣才可以調整成其他頻率,以此做記錄。
先說明Timer設定方式、PWM Mode,之後會在Arduino上實做。
如果對_BV()沒有概念,可以參考【Arduino I/O Ports】Control under avr-libc這篇文章。
如果對_BV()沒有概念,可以參考【Arduino I/O Ports】Control under avr-libc這篇文章。
Timer & Registers
ATmega328內與Timer相關的Register大略如下(n代表Timern):
- Timer Counter Control Register A (TCCRnA):決定timer的運作模式(operating mode)
- Timer Counter Control Register B (TCCRnB):決定timer的prescale value
- Timer Counter Register (TCNTn):儲存timer目前的累積計數(timer count)
- Output Compare Register A (OCRnA):當TCNTn累積計數到OCRnA的計數時,觸發中斷(interrupt)
- Ouput Compare Register B (OCRnB):當TCNTn累積計數到OCRnB的計數時,觸發中斷(interrupt)
Timer0與Timer2皆為8-bit的Timer,表示TCNT0, 2範圍為0~255;Timer1的TCNT1則為16-bit,可以計數0~65535。
除了PWM,Timers也有負責ㄧ些其他功能:
- Timer0: millis()與delay()函數使用,以及pin 5, 6的analogWrite
- Timer1: 負責pin9, 10的analogWrite,以及使用Servo library驅動servo時使用
- Timer2: 負責pin 3, 11的analogWrite
Clock Select and Timer Frequency (Arduino Timers and Interrupts )
不同的單晶片有不同的CPU時脈,實際上的運用不一定需要這麼高的時脈,這時候便需要進行「除頻」的動作,將CPU的時脈除以除率(prescaler),藉此將Timer的時脈降低,ATmega328中Timer0與Timer1使用的是相同的一組prescaler(1, 8, 64, 256, 1024),而Timer2則是有較多的prescaler選項(1, 8, 32, 64, 128, 256, 1024)。
- clk I/O是原始時脈16MHz
- clk Tn是經過除頻後的時脈,prescaler為1,clk Tn = (clk I/O) / 1
- TCNTn是Timer的累積計數(timer counter)
每ㄧ次clk I/O振盪,都會讓clk Tn輸出HIGH,使得TCNTn的計數+1,可以看到TCNTn從MAX-1到MAX(最大值)、BOTTOM(再從0開始)、BOTTOM+1,再接著繼續計數。
此時dlk Tn的時脈與dlk I/O同為16MHz,表示每秒TCNTn會數16000000次。
當prescaler為8時,代表clk Tn = (clk I/O) / 8,此時clk I/O振盪了8次,clk Tn才會有一次振盪,TCNTn才跟著進行計數,所以TCNTn在每秒的計數次數變會從16M降至2M,用這種方式將Timer的頻率拉低成2MHz。
當prescaler為8時,代表clk Tn = (clk I/O) / 8,此時clk I/O振盪了8次,clk Tn才會有一次振盪,TCNTn才跟著進行計數,所以TCNTn在每秒的計數次數變會從16M降至2M,用這種方式將Timer的頻率拉低成2MHz。
上圖中TCNTn的BOTTON、MAX,另外還有TOP在datasheet中的定義如下:
- BOTTOM:當計數為0的時候
- MAX:當計數到達最大值時,TCNT0, 2為255,TCNT1為65535
- TOP:使用者也可以自訂計數的上限,可以指定數值作為上限,或是將該數值儲存在OCRnA暫存器中,要使用TOP的話,記得要調整timer的模式
說了這麼多,到底該怎麼根據期望的Timer frequency、挑選timer以及prescaler呢?
這邊以Arduino UNO的ATmega328為例,希望將Timer1降低至2Hz為目標,進行計算:
- ATmega328自帶的CPU frequency為16MHz
- 查看timer counter的最大值(check TCNTn MAX, 255 for 8-bit timer, 65535 for 16-bit)
- 將CPU除以選好的prescaler值(16000000/256 = 62500)
- 將3的結果除以目標頻率(62500/2 = 31250)
- 比較4的結果是否小於timer counter的最大值(31250 < 65525, 成功),若大於timer counter的最大值,則選擇更大的prescaler,然後再從步驟3開始運算一次。如果已經是prescaler的最大值還是不夠,建議換成16-bit的Timer1。
接下來的筆記大部分參考Cooper Maa的6.1) PWM modes part1,並非原創,另外手動增加register的對照圖表。
Fast PWM Mode
首先說明最簡單直覺的PWM Mode,Fast PWM,timer counter會從BOTTOM數到MAX,timer counter為0時,OCnA輸出HIGH,接著timer counter繼續計數,直到timer counter(TCNTn)跟output compare register(OCRn)的數值相同時才將輸出關閉(turn off)。因此OCRn的數值越大,duty cycle也跟著越大。
下圖為timer counter是不斷從0數到255,OCRnA和OCRnB設定在不同數值時,輸出的狀況,可以看出OCRnB的設定值較小,對應OCnB的duty cycle也較小。
Timer與Arduino PWM腳位的關係如下表,其中OCn的n便是表示為Timern所控制:
接著來實做把pin 3和11(Timer2)設定成Fast PWM Mode!
Timer相關暫存器所需配置如下:
- Waveform Generation Mode Bit (WGM)
- 首先在datasheet中找到Timer2的操作模式設定表。模式設定是由Waveform Generation Mode Bit所控制,只要在datasheet搜尋「WGMn」(n為Timern之意,此時n=2),便可以找到此表。
- 表中顯示Mode3與Mode7皆為Fast PWM Mode,不同的是TOP的數值
- Mode 3的TOP值為0xFF,表示TCNT2將會數到255便歸0,與上面的圖有相同的Timer輸出
- Mode 7的TOP值為OCRA,是另外設定TOP值的方法,在之後會說明
- WGM22:0的設定使用Mode 3的011
- Compare Match Output A Mode
- 首先在datasheet中找到Timer2的Compare Match Output A Mode表。只要在datasheet搜尋「COMnA」(n為Timern之意,此時n=2 ),便可以找到此表。COMnA內配置位元將會決定Output Compare pin (OCnA)的行為。
- 將COM2A1:0設定為10,選擇OC2A跟OC2B的PWM輸出不為反向
- Compare Match Output B Mode
- 首先在datasheet中找到Timer2的Compare Match Output B Mode表。只要在datasheet搜尋「COMnB」(n為Timern之意,此時n=2 ),便可以找到此表。COMnB內配置位元將會決定Output Compare pin (OCnB)的行為。
- 與COM2A一樣,COM2B1:0配置為10
- Clock Select
- 首先在datasheet中找到Timer2的Clock Select表。只要在datasheet搜尋「CSn」(n為Timern之意,此時n=2 ),便可以找到此表。CSn2:0內配置位元將會決定prescaler的值。
- 將CS22:0配置為100,設定prescaler為64,使得clk Tn降為250KHz(16M/64)
上面提到的暫存器,分別位於TCCR2A以及TCCR2B。
Arduino程式碼如下:
pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(CS22); OCR2A = 180; OCR2B = 50;
在Arduino Uno上,這些配置的效果是:
- OCR2A (pin 11)
- frequncy:16MHz / 64 / 256 = 976.5625Hz
- duty cycle:(180+1) / 256 = 70.7%
- OCR2B (pin 3)
- frequncy:16MHz / 64 / 256 = 976.5625Hz
- duty cycle:(50+1) / 256 = 19.9%
frequency在除以prescaler後,要再除以256,是因為該PWM的timer counter要從0累積計數到255才算一個週期。另外要注意Fast PWM會保持輸出為High比OCR多一個週期。
Fast PWM的頻率計算公式為:
PWM_frequency = clock_speed/ [Prescaller_Value * (1 + TOP_Value)]
Fast PWM Mode與自訂TOP值
透過上面介紹的調整方式,只能將frequency調成與prescaler相對應的5或7個數值,接下來將介紹如何透過更動timer counter的累計計數上限,來進行frequency的微調。
更動累計計數上限的意思便是timer counter不在是從0數到255,而是從0數到OCRnA,注意在這個模式下,只有OCnB可以做PWM輸出,OCRnA不能同時做為另一個PWM輸出的TOP值,又做為所屬pin腳的比較值。
另外在Compare Match Output A Mode中有一個叫做"Toggle OCnA on Compare Match"的模式,會在每一個周期結束時切換OCnA的輸出,產生一個duty cycle固定為50%且頻率為0.5倍的效果。
實際輸出的圖形如下,timer counter會在與OCRnA相等時歸零,與前面的圖比起來,此時OCnB的輸出頻率是比較快的,因為timer counter還未數到255時便已完成一個週期,透過此種方式,可以對timer counter重置的時間進行調整,間接的也可以對frequency進行微調。
若要將pin 3與11(Timer2)設定成Fast PWM,並使用OCR2A做為TOP值,首先Timer暫存器所需配置如下:
- WGM22:0配置成111,表示使用Mode 7,參考OCR2A做為TOP值
- COM2A1:0配置成01,選擇"Toggle OCnA on Compare Match"模式
- COM2B1:0配置與先前一樣為10
- CS22:0配置為100,設定prescaler為64
Arduino Uno程式如下:
pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | _BV(CS22); OCR2A = 180; OCR2B = 50;
在Arduino Uno上,這些設定的效果是:
- OCR2A (pin 11)
- frequncy:16 MHz / 64 / (180+1) / 2 = 690.6Hz
- duty cycle:50%
- OCR2B (pin 3)
- frequncy:16 MHz / 64 / (180+1) = 1381.2Hz
- duty cycle:(50+1) / (180+1) = 28.2%
在這個範例中,timer counter從 0 數到 180,總共花 181 個周期,所以OCnA與OCnB輸出頻率要除以 181而非180。最後結果OCnA的頻率只有OCnB的一半。
Summary
最後用8-bit Timer的Block Diagram來總結一下。
- 右上角是Clock Select的過程,使用Prescaler進行除頻,降低時脈
- 左邊可以看到Timer/Counter的累積計數(TCNTn)會不斷與OCRnA, OCRnB進行比較,若相等則進入Waveform Generation,進行輸出的調整
- Waveform Generation內可以設定不同模式的輸出,Non-PWM、CTC、Fast PWM、Phase Correct PWM等等模式,這些設定將影響OCnA的輸出行為
Nice article. Extremely detailed!
ReplyDelete