Arduino PWM


 因之前使用過飛思卡爾及瑞薩16位單片機,在PWM模塊上使用很順手,也沒有產生任何疑惑,反倒是Arduino輸出的PWM固定頻率讓人很是惱火。試想你的電機在490hz的PWM控制下捏着鼻子嗡嗡叫,這是一件多么叫人憂傷的事呀。
     Arduino很好,但PWM很雞肋。當我想要查datasheet來對底層操作時,無意發現了PWM frequency 庫,感覺很好用,很省時間。那么,就翻出來造福大家嘍。這是我第一個認真寫並寫完整的技術文檔翻譯,歡迎交流指正。
By Ray







       在目前與Arduino微控制器相關的項目中,我發現沒有一種方法能不直接操作底層寄存器,而改變PWM頻率的。就目前我所google到的,沒有一種通用的庫能改變arduino微控制器的PWM頻率。網上有各種關於改變PWM頻率的代碼段,但是最終我還是決定參考400多頁的ARV-Mega系列單片機的datasheet來實現這些功能。
       據我推測,Arduino的編程人員沒有發行任何關於改變PWM頻率的方法是因為很難編寫一個簡單的,直觀的硬件定時器封裝程序,而不至使初學者困惑。硬件本身就有一些特殊的局限性,它們以一些奇怪的方式表現出來。
請允許我與大家分享一些:
  • PWM的行為是由叫作定時器的集成元件決定的。每個定時器有2個或4個通道。每個通道連到一個控制器引腳上。改變一個引腳的頻率需要改變它所連接的定時器的頻率,這樣反過來也會改變其它連到同一個定時器的引腳的頻率。
  • Timer0通常被用作實現Arduino的計時功能(例如,millis()函數)。改變timer0的頻率將會破壞你工程里使用計時函數的其它程序段部分。
  • Arduino中有兩種定時器,8位定時器和16位定時器。簡單來說,就是它們所存在的細微差別使得不限制一方或另一方的代碼實現變得困難。
  • 使用一個8位的定時器來設定定制的頻率(使用預分頻產生不了的頻率)需要犧牲一個通道。換句話說,每個設定定制頻率的8位定時器會失去在一個引腳產生pwm的能力(連接A通道的引腳會更准確些)。除了Leonardo的所有Arduino都有兩個8位定時器,這意味着如果你把所有定時器設定特殊的頻率,上述所說的Arduino控制器總共將會犧牲兩個引腳。
         先不管這些,我依然覺得對硬件定時器做一個庫或封裝是非常值得的,這樣我或者任何其他選擇使用這個庫的人不必花那么多時間去深挖那些容易產生bug的逐位操作的模塊和初始化部分。
這個庫有五個全局函數:
InitTimers()                                                                        初始化所有定時器。需要在改變定時器頻率或設定一個引腳的占空比之前用。
InitTimersSafe()                                                                除了為了保留計時功能函數而不初始化timer0外,其它都與InitTimers()一樣。
pwmWrite(uint8_tpin,uint8_t val)                                與‘analogWrite()’一樣,但是只有在相應定時器初始化后才工作
SetPinFrequency(int8_tpin,int32_t frequency)          設定引腳的頻率(hz),返回一個設定成功與否的布爾值
SetPinFrequencySafe(int8_tpin,int32_tfrequency)    除了不影響timer0外,其它都與SetPinFrequency函數一樣
         這個庫針對每個定時器還有五個函數。我沒有把代碼大小減小到一個我認為合理的程度,而是編寫了C++類,並做了大量細致的封裝工作。這里的每一個函數在都用明顯專門的宏定義在編譯前調出那些庫的頭文件里隱晦的函數。
對timer1而言,這些函數是:
Timer1_GetFrequency()                                讀取定時器的頻率(hz)
Timer1_SetFrequency(intfrequency)          設定定時器的頻率
Timer1_GetPrescaler()                                  讀取預分頻因子
Timer1_SetPrescaler(enumvalue)               設定預分頻因子
Timer1_GetTop()                                            讀取定時器寄存器最大值
Timer1_SetTop(inttop)                                 設定定時器寄存器最大值
Timer1_Initialize()                                          初始化定時器

         預分頻因子在不同的定時器中是不一致的。我認為使用枚舉數是最好的解決方法,因為大多數無效的類型輸入將會在編譯的時候被發現。例如一個普通的定時器,使用下面其中一個作為參數:ps_1,ps_8,ps_64,ps_256,ps_1024.如果這些都報類型錯誤,那么你所使用的定時器就是與其它定時器不一樣的一個例子,你應該用Psalt_1,psalt_8,psalt_32,psalt_64,psalt_128,psalt_256,或者psalt_1024代替。如果你想使用不同的定時器,只要改變序號即可(例如,Timer2_GetFrequency()讀取定時器2的頻率)。這取決於你是否要使用定時器的特殊功能。全局函數對大多數情況下已經夠用了。使用這個庫,16位的定時器的頻率可以從1hz變化到8Mhz,8位定時器的頻率可以從31hz變化到2Mhz。但是隨着頻率的越來越大,占空比可變化的范圍就越小。將頻率設定為8Mhz是可以實現的,但是這樣占空比的變化范圍將變得非常小。請確定定時器的頻率正確的設置了,並檢查這個函數的返回值。如果你想要犧牲任何8位的PWM引腳,就不要調用定時器對應的初始化函數,試着改變預分頻因子來改變它的頻率。有許多教程解釋預分頻因子怎么影響定時器的,這個庫包函這些方法,並能使其變得更簡單,在處理過程中更不容易產生bug。到目前為止,我已經在UNO和Mega上測試過了。這個庫能兼容除了Leonardo和Due的任何arduino控制器。如果你擁有的arduino控制器不是Mega或者Uno,請在上面測試並把運行狀況告訴我。如果你們手上有示波器,試着變換它生成的頻率,顯示正確,則說明這個庫是可以用的。

現在,這個庫正在測試中。這個庫的發展將在后面的帖子里描述。
下面是目前這個庫的一些特征:

1.使用函數封裝了定時器的特殊功能。(例如定時器寄存器的最大值和預分頻因子)
2.基於引腳(與定時器無關)的函數。
3.擁有在定時器級或引腳級讀取和設定頻率的函數。
4.擁有定時器級和引腳級的定時器分辨率測量工具。

最新庫是的05版本:
鏈接: http://code.google.com/p/arduino-pwm-frequency-library/downloads/list

下面是跟帖中比較有用的部分總結:
硬件上,Mega系統控制板,11引腳連着timer1,引腳9連接timer2, 引腳7連接timer4。這是軟件改變不了的。

關於分辨率的問題:
8位的定時器兼容8位的分辨率,16位的定時器兼容16位的分辨率。為了與analogWrite()保持一致性,pwmWrite()函數都使用8位分辨率。如何需要更高的分辨率時,使用pwmWriteHR()代替。

Void pwmWrite(uint8_t pin, uint8_t duty) 8-bit, 0-255
Void pwmWriteHR(uint8_t pin, uint16_t duty) 16-bit 0-65535

不幸的是,一旦使用了定制的頻率,分辨率控制將不會那么簡單。如果你修改定時器的頻率,分辨將跟着變化。經驗法則是頻率越高,分辨率越低。有一些變量在SetFrequency函數里封裝了。函數知道他們,並在給定的頻率下精確的使用可能的最高的分辨率。盡管pwmWriteHR()接受16位的整數,它將自動映射到定時器任一分辨率下(也就是說它的分辨率不一定是16位的)。為了知道在特定頻率下分辨率是否小於界限值,我添加了兩個函數。

Float  GetPinResolution(uint8_t pin)
Float  TimerX_GetResolution() (用定時器號來替換X)

這些函數能讀出分辨率,以二進制的位數來表示分辨率。請注意這兩個函數都返回浮點數,而不是整數,那是我故意這么做的。如果你更喜歡用最大可能值而非二進制數來表示的話,使用TimerX_GetTop()然后加1(其實質是一樣的,只不過是十進制的)。
我在工程中添加了一個叫做PWM_lib_resolution_example的例程,來演示這些函數和定時器頻率、分辨率之間的關系。
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM