延續上篇【嵌入式學習心得#1】簡易雷達調光的MCU設定,從看懂Datasheet開始,本篇文章會著重在雷射調光的PWM功能實踐。
Timer 到底應該怎麼切?
先補充一下上回Timer 未分享完的部分。
記得上篇文章的切法,我是以 10 millisecs 為最小單位做切割,當時只是想說,客人的要求既然是 1 min,如果切 1 millisec,豈不是要設定變數值為 60000,因為是 8 bit 的 MCU,資料型態的決定上就需要更加小心,不然真的後患無窮(溢位很容易就出問題),但後來發現這樣真的很不方便,因為刻度不夠細,沒有辦法到非常精準,雖然肉眼看不太出來,但客人還是會拿示波器挑戰你。
從上面的觀點來看,好像要精準就沒辦法計時太久,而要計時久一點,就沒辦法太精準。但其實是有解法的,前輩有提到通常還是習慣用 1 millisec 做切割,如果需要比較長時間的計時,會設多個全域變數,如下片段程式碼:
https://gist.github.com/Ryanhu1015/1a60a19b6231d3f0fc76b85c537bb085#file-timer0-c
以上的寫法只是用了三個全域變數做累加,分別區分毫秒、秒、分。至於實際執行(ISR)的程式碼片段,有人會放 main 裡面,也有人喜歡直接放在 ISR function 內做判斷,但現在刻度變得這麼細,還是放在 main 裡面保險一點(雖然說 main 裡面盡可能乾淨沒錯),畢竟如果 ISR 執行時間超過 1 millisec,基本上程式就會開始出現問題(會 crash,之前做 NTP 時鐘的時候就有出現過)。另外,因為現在是 1 millisec,所以 TH0 和 TL0 兩個暫存器的值也要做調整,相關算法請見上篇。
PWM 該如何實作?
接下來是本章的重頭戲:PWM該怎麼實作?
由 SN8F5701 的 datasheet 知道 PWM 的實作是在 Timer 3,16-bit up counting timer。PWM 是 output pin,總共有 6 個 channel 的 PWM function,即是和 GPIO shared 腳位,這裡不特別細講 PWM 和 GPIO 之間的轉換。PWM 再回到 GPIO 時,會是上一次變成 PWM 前的 GPIO mode,以及 T3 interrupt,因為這次沒用到。
有趣的是在 Arduino 上直接用 analogWrite 的 function 可以做的事情,在這裡需要自己設定所有東西,就用以下程式碼片段做介紹:
https://gist.github.com/Ryanhu1015/6d9b7377aa5328fd3784608a6b01817a#file-pwm-c
如上程式碼,PWCH 這個暫存器是控制那隻 pin 腳要設為 PWM output,我的例子是 P01,我覺得最有趣的還是 T3YH 和 T3YL 兩個暫存器,他們決定了 PWM 要切成幾個等份,和 T3RATE(速度)配合去做出某種頻率的 pwm,客人的 spec 要求要 1K 的 pwm,我這次很懶,直接讓 T3RATE 設為 fosc/1,算算剛好 T3YH 和 T3YL 切成 120 等份會有 1K pwm 的效果,通常還是切 100 等份比較直覺。
而 PWM 的 duty cycle 即是用 PW1DH 和 PW1DL 兩個暫存器設定的,客人的 spec 要求是 20% duty cycle,而因為電路設計的關係,剛好會是倒過來的,即設定 80% duty cycle 表現出來的才是 20%。
其實 PWM 還有滿多功能可以玩的,比如說 inverse function、頻率倍數調整等。不過這些這次我都還沒有機會實作到,如果之後有遇到會再分享出來。
從datasheet了解fosc / fcpu
這兩個新名詞我第一次看到的時候真的是不知道代表什麼意思,直到全部看完 datasheet 才漸漸瞭解各別代表的意義。fosc 是震盪器的頻率 (system clock -> IHRC 為 32MHz),而 fcpu 是可以根據 CLKSEL 和 CLKCMD 兩個暫存器做調整,意思是說 fcpu <= fosc (固定值)。
小結
本文最後要來分享一下,截至目前為止,無關乎我對埋控(MCU)理解的多寡,純粹是要講在開發的過程中哪些該注意,哪些可以讓未來的維護更容易,或是往後再開發的速度能夠有效掌控的方式。
就從上一篇文章提到的程式碼架構開始,因為客人需求的多變,可能今天開給我們的 spec 跟他現場真的看到的樣子有落差,就會再和我們討論調整,如果程式碼寫死(意即照 spec freestyle 硬幹),客人要我們改點小東西,我們在沒有架構的情況下,可能又要花上一天的時間把程式碼做修正。
為了不讓這麼蠢的事情發生,在開始照著 spec 下手寫 code 前,都習慣會先自己假定幾種客人「可能會再改的部分」,這個很靠經驗,把這些會再改的部分一一變成參數的形式,程式碼會相對變比較肥,但好處絕對勝過沒架構的版本,這樣在完成後只需要做參數,比如說 const 做 #define的調整,程式碼就能依照新的參數執行。
比如說上面的 pwmValue 就是一個 const int,使用者可以直接更改數字去做到不同 duty cycle 的 PWM效果,只是個超小的範例,但就是這個感覺沒錯。
另外,前輩也有跟我提到,「寫久了就會建自己的 Library」,比如說 PWM function 其實每顆埋控的實作都大同小異,所以把 PWM模板寫好,等到下次遇到別顆埋控時,只要照 datasheet 改變參數就可以。但我發現其實沒這麼簡單,因為所謂「模板」,程度上很難抓,會不知道到底要寫的多細節,或是多粗糙才算是有用的模板(我試過,基本上寫到後來整個 PWM function 都移過去了),我發現這個也很靠經驗,所以等我真的知道怎麼拿捏時再分享出來。
建自己的 library 真的有很方便的感覺,同樣的東西就不用一直重寫,對於開發的人也比較好做後續的維護,因為不會每次都不一樣。
p.s:下篇文章是簡易雷達調光的最終章,要談 UART 和 ADC 的功能實作 ,請待下回揭曉。
只需不到短短一分鐘...
輸入您的信箱與ID註冊即可享有一切福利!
會員福利
免費電子報
會員搶先看
主題訂閱
好文收藏
2020/11/30
整個脈絡寫得很好,從isr 應用到 PWM跟我目前用到的東西都息息相關,不知道前輩你可以指點一下 你平常看得 FW書籍有哪些可以參考 。 小弟也很想跟你學習