今天看了看以前的博客,赫然發現有個PWM的祕密(上) ,第二部分竟然被我忘掉了,主要是這個部分實在不太好理解,能夠認爲是Arduino PWM的提升篇。說實話大部分兄弟平時應該是用不上的,不過能夠先收起來,之後真遇到了能知道是怎麼回事。 git
Atmega 168/328的時鐘們
ATmega328P有三個時鐘,Timer0,Timer1和Timer2。每一個時鐘都有兩個比較寄存器,能夠同時支持兩路輸出。其中比較寄存器用於控制PWM的佔空比,具體的原理等會兒會介紹。大多數狀況下,每一個時鐘的兩路輸出會有相同的頻率,可是能夠有不一樣的佔空比(取決於那兩個比較寄存器的設置) 程序員
每一個時鐘都有一個「預約標器」,它的做用是設置timer的時鐘週期,這個週期通常是有Arduino的系統時鐘除以一個預設的因子來實現的。這個因子通常是1,8,64,256或1024這樣的數值。Arduino的系統時鐘週期是16MHz,因此這些Timer的頻率就是系統時鐘除以這個預設值的標定值。須要注意的是,Timer2的時鐘標定值是獨立的,而Timer0和Timer1使用的是相同的。 函數
這些時鐘均可以有多種不一樣的運行模式。常見的模式包括「快速PWM」和「相位修正PWM」,這兩種PWM的定義也會在後面解釋。這些時鐘能夠從0計數到255,也能夠計數到某個指定的值。例如16位的Timer1就能夠支持計數到16位(2個字節)。 ui
除了比較寄存器外,還有一些其餘的寄存器用來控制時鐘。例如TCCRnA和TCCRnB就是用來設置時鐘的計數位數。這些寄存器包含了不少位(bit),它們分別的做用以下:
脈衝生成模式控制位(WGM):用來設置時鐘的模式
時鐘選擇位(CS):設置時鐘的預約標器
輸出模式控制位(COMnA和COMnB):使能/禁用/反相 輸出A和輸出B
輸出比較器(OCRnA和OCRnB):當計數器等於這兩個值時,輸出值根據不一樣的模式進行變化 spa
不一樣時鐘的這些設置位稍有不一樣,因此使用的時候須要查一下資料。其中Timer1是一個16位的時鐘,Timer2可使用不一樣的預約標器。 翻譯
快速PWM
對於快速PWM來講,時鐘都是從0計數到255。當計數器=0時,輸出高電平1,當計數器等於比較寄存器時,輸出低電平0。因此輸出比較器越大,佔空比越高。這就是傳說中的快速PWM模式。後面的例子會解釋如何用OCRnA和OCRnB設置兩路輸出的佔空比。很明顯這種狀況下,這兩路輸出的週期是相同的,只是佔空比不一樣。 code
快速PWM的例子
下面這個例子以Timer2爲例,把Pin3和Pin11做爲快速PWM的兩個輸出管腳。其中:
WGM的設置爲011,表示選擇了快速PWM模式;
COM2A和COM2B設置爲10,表示A和B輸出都是非反轉的PWM;
CS的設置爲100,表示時鐘週期是系統時鐘的1/64;
OCR2A和OCR2B分別是180和50,表示兩路輸出的佔空比; 開發
pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(CS22); OCR2A = 180; OCR2B = 50;
這段代碼看上去有點暈,其實很簡單。_BV(n)的意思就是1< COM2A1,表示COM2A的第1位(靠,實際上是第2位,不過程序員們是從0開始數數的)。因此_BV(COM2A1)表示COM2A = 10;
相似的,_BV(WGM21) | _BV(WGM20) 表示 WGM2 = 011。 get
在Arduino Duemilanove開發板,上面這幾行代碼的結果爲:
輸出 A 頻率: 16 MHz / 64 / 256 = 976.5625Hz
輸出 A 佔空比: (180+1) / 256 = 70.7%
輸出 B 頻率: 16 MHz / 64 / 256 = 976.5625Hz
輸出 B 佔空比: (50+1) / 256 = 19.9% 博客
頻率的計算裏都除以了256,這是由於除以64是獲得了時鐘的計數週期,而256個計數週期是一個循環,因此PWM的週期指的是這個循環。
另外,佔空比的計算都加了1,這個仍是由於無聊的程序員們都從0開始計數。
相位修正PWM
另一種PWM模式是相位修正模式,也有人把它叫作「雙斜率PWM」。這種模式下,計數器從0數到255,而後從255再倒數到0。當計數器在上升過程當中遇到比較器的時候,輸出0;在降低過程當中遇到比較器的時候,輸出1。說實話,我以爲這種模式除了頻率下降了一倍以外,沒看出和快速PWM有什麼區別。多是在集成電路的底層級別上有區別吧。原文說「它具備更加對稱的輸出」,好吧,也許老外都比較傻吧。
相位修正PWM的例子
下面的例子仍是以Timer2爲例,設置Pin3和Pin11爲輸出管腳。其中WGM設置爲001,表示相位修正模式,其餘位設置和前面的例子相同:
pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20); TCCR2B = _BV(CS22); OCR2A = 180; OCR2B = 50;
在Arduino Duemilanove開發板,上面這幾行代碼的結果爲:
輸出 A 頻率: 16 MHz / 64 / 255 / 2 = 490.196Hz
輸出 A 佔空比: 180 / 255 = 70.6%
輸出 B 頻率: 16 MHz / 64 / 255 / 2 = 490.196Hz
輸出 B 佔空比: 50 / 255 = 19.6%
這裏的頻率計數又多除了一個2,緣由上面解釋過了。佔空比的計算不用加1了,緣由本身掰手指頭算算就知道了
快速PWM下,修改時鐘的計數上限
快速PWM和相位修正PWM均可以從新設置輸出的頻率,先看看快速PWM是如何設置的。在修改頻率的模式下,時鐘從0開始計數到OCRA而不是255,注意這個OCRA咱們以前是用來作比較用的。這樣一來,頻率的設置就很是靈活了。對Timer1來講,OCRA能夠設置到16位(應該是0~65535)
等等,OCRA用來設置總數了,那麼誰用來作比較捏?好吧,靈活的代價就是這種模式下,只能輸出一路PWM。即OCRA用來設置總數,OCRB用來設置比較器。
儘管如此,無孔不入的程序員們依然仍是設置了一種特殊的模式,每次計數器數到頭的時候,輸出A作一次反相,這樣能湊合輸出一個佔空比爲50%的方波。
下面的例子中,咱們依然使用Timer2,Pin3和Pin11。其中OCR2A用來設置週期和頻率,OCR2B用來設置B的佔空比,同時A輸出50%的方波。具體的設置是:
WGM設置爲111表示「OCRA控制計數上限的快速PWM」;
OCR2A設置爲180,表示從0數到180;
OCR2B設置比較器爲50;
COM2A設置爲01,表示OCR2A「當數到頭是反相」,用來輸出50%的方波(其中WGM被設置到了兩個變量裏);
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 Duemilanove開發板,上面這幾行代碼的結果爲:
輸出 A 頻率: 16 MHz / 64 / (180+1) / 2 = 690.6Hz
輸出 A 佔空比: 50%
輸出 B 頻率: 16 MHz / 64 / (180+1) = 1381.2Hz
輸出 B 佔空比: (50+1) / (180+1) = 28.2%
其中頻率的計算用了180+1,依然是數數的問題;A輸出的頻率是B輸出的一半,由於輸出A每兩個大週期才能循環一次。
相位修正PWM下,修改時鐘的計數上限
相似的,相位修正模式下,也能夠修改輸出PWM的頻率。代碼幾乎徹底和上個例子同樣,區別是WGM的值設置爲101:
pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20); TCCR2B = _BV(WGM22) | _BV(CS22); OCR2A = 180; OCR2B = 50;
在Arduino Duemilanove開發板,上面這幾行代碼的結果爲:
輸出 A 頻率: 16 MHz / 64 / 180 / 2 / 2 = 347.2Hz
輸出 A 佔空比: 50%
輸出 B 頻率: 16 MHz / 64 / 180 / 2 = 694.4Hz
輸出 B 佔空比: 50 / 180 = 27.8%
跟以前的對比相似,相位修正模式下,一個大週期從0數到180,而後倒數到0,總共是360個時鐘週期;而在快速PWM模式下,一個週期是從0數到180,其實是181個時鐘週期。這可能就是鬼子們說的「更加對稱」的好處,好吧,可能老外們其實並不傻。
數不清楚這二者區別的同窗,能夠用OCRA=3爲例:
快速PWM:0123-0123-0123….. 每一個週期時鐘數是4=3+1
相位修正:012321-012321-012321….每一個週期時鐘數是6=3*2
相應的佔空比計算也有微小的區別,快速PWM模式下,高位的輸出會多一個時鐘週期。上面的這個例子,以比較器=1爲例:
快速PWM:當計數器=1時反相,這時候已經經歷了2個時鐘週期,因此佔空比是2/4
相位修正:計數器0到1時輸出0,計數器1到0時輸出1,佔空比是1/3
一些其餘的說明
前面的程序有一個很是疑惑的問題:Pin3和Pin11是怎麼和Timer2對應上的呢?這個只能查表了,並非任意對應的:
時鐘輸出 | Arduino輸出Pin編號 | 芯片Pin | Pin name
OC0A 6 12 PD6
OC0B 5 11 PD5
OC1A 9 15 PB1
OC1B 10 16 PB2
OC2A 11 17 PB3
OC2B 3 5 PD3
通常來講,普通用戶是不須要設置這些時鐘參數。Arduino默認有一些設置,全部的時鐘週期都是系統週期的1/64。Timer0默認是快速PWM,而Timer1和Timer2默認是相位修正PWM。具體的設置能夠查看Arduino源代碼中writing.c的設置。
須要特別特別注意的是,Arduino的開發系統中,millis()和delay()這兩個函數是基於Timer0時鐘的,因此若是你修改了Timer0的時鐘週期,這兩個函數也會受到影響。直接的效果就是delay(1000)再也不是標準的1秒,也許會變成1/64秒,這個須要特別注意。
在程序中使用analogWrite(pin, duty_cycle)函數的時候,就啓動了PWM模式;當調用digitalWrite()函數時則取消了PWM模式。請參考wiring_analog.c和 wiring_digital.c文件。
還有一件頗有意思的現象,對於快速PWM模式,若是咱們設置analogWrite(5, 0),實際上應該有1/256的佔空比,事實上你會發現輸出的是永遠低電平的0。這個其實是在Arduino系統中強制設定的,若是發現輸入的是0,那麼就關閉PWM。隨之而來的問題是,若是咱們設置analogWrite(5, 1),那麼佔空比是多少呢?答案是2/256,也就是說0和1之間是有一個跳躍
翻譯了半天已經暈頭轉向了,最後再提醒一點,不是全部的參數配置均可以隨意組合的。例如COM2A=01只有在WGM是111或者101時纔有效,具體怎麼用,仍是去官網查表吧
原文連接:http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM