最近正式到班,其實我心情是忐忑的,或許到了一個全新的環境這是必然的過程。
不過我忐忑的不是需要熟悉新的人事物,而是我會的東西,到底有沒有符合公司的需求? 我其實不知道我自己的能力現在到底在哪,畢竟當初在談的時候,老闆和技術長只有跟我說他們的東西比較底層,我完全是被這個吸引的,因為我就可能可以了解到單晶片到底是怎麼一回事。
我以前學的那些觀念都是用 Arduino 做實作,很多都是包好的東西,正所謂站在巨人的肩膀上,所以實作比較多像是疊積木,雖然聽起來好像很簡單,但事實不然,很多單晶片底下發生的事情我並不了解原因。雖然如此,我得說上班至今我所新學的、體會的,都能證明我過去學的東西沒有白學,觀念是正確的。
第一次真正拿給客人的產品
觀念很正確,但我的實務經驗的確少得可憐,而這裡的實務經驗是指面對市場、客人需求。同一份 code 應該要怎麼寫才有最好的架構?可以在面對客人各種需求時,做出最快速的調整,這可能是只有來公司上班才有辦法學習到的經驗。
我的第一個案子要做的東西其實很簡單,就是當初我剛開始接觸 Arduino 時,前輩給我的第一個功課——PWM 控制 LED 燈和馬達。「太簡單了吧!」腦袋瞬間閃過 Arduino UNO 版接跳線出來,寫個簡單的 PWM function 立馬搞定!
如果真有這麼簡單,我就不用花時間再整理一篇文章了。
這次要用的 MCU 是松翰的 SN8F5700 系列相容 8051,由於成本的考量而選擇了這顆 MCU。試想,如果每一片都用 Arduino 來做,第一,應該會嚴重虧損到公司直接倒閉;第二,只是個 PWM 用到 Arduino 未免貽笑大方;第三,Arduino 功能多是增加不必要麻煩。當初做一個創業專案的時候,估計成本時還真的用 Arduino 去計算,現在回想起來覺得當時的自己好天真。
難就難在,我並沒有直接對一顆 MCU 去做過設定的經驗,因為我根本就不了解 MCU 底層到底是怎麼工作的,經過 技術長的解釋,我漸漸摸出整個 MCU 運作的輪廓,而這一切就要從 datasheet 開始講起了。
來K datasheet吧!
在這次看SN8F5701 datasheet(載點)之前,我沒有完整看過任何一份 datasheet(這份 96 頁我基本上全部看完了),之前頂多是查最大容忍電流電壓,而且通常是出問題的時候才查,因為 Arduino 基本上都包好,除非有跟別的板子做搭配,才會有這方面的需求。
SN8F5701 的功能方塊圖(圖片來源)
看好看滿 datasheet是因為我現在沒有 Arduino IDE,沒有一堆已經做好現成的 library 可以給我 call function,沒有設定好的所有該有的變數。那我該怎麼辦?事實上目前大多數的韌體嵌入式開發(單晶片),都是用 Keil或 IAR(這兩個應該就是大宗了),把寫好的程式碼做 compile 將程式碼燒錄進 MCU 內。
只是個 complier 有什麼了不起?簡單說,這兩家 compile 出來的東西比較瘦(較不佔記憶體),而且支援相當多市面上常見的一些單晶片型號,而目前的公司習慣用 Keil 的 uVision5,雖然 SN8F5701 並沒有送給 Keil 認證,還好官網可以直接載 driver。
了解 compile 工具後,那程式碼到底要怎麼寫?如果沒有 library 可以 call function,我個人推薦8051 的兩本書《8051單晶片徹底研究 – 入門篇 & 基礎篇》,特色是由簡入深,循序漸進。裡面是用組語,但還好用 C 就可以實作,不過就算知道 C 語言可以寫,那內容到底是什麼?
事實上,單晶片寫的 function ,底層都是一堆暫存器(register)設定出來的,如果用白話文解釋,想像裡面有很多開關,也有別的形式可以設定,用各式各樣的開關組合,排列出某個 pattern,創造某些特定的 function 或模式。
Timer單晶片體驗分享
就舉我第一次接觸這麼底層的單晶片時遇到兩個很炫的東西來當例子吧。
Timer 在我玩 Arduino 的時候就有遇過(我記得我 ISR 的文章有做相關的分享),記得 UNO因為怕會造成原因不明的 crash,只有 Timer1 可以讓開發者使用,但現在厲害了,所有操控權皆在我手。SN8F5701 有三個 Timer:Timer0 、 Timer1還有 Timer3。基本上 Timer0 和 Timer1 是一樣的東西,表示有兩個 Timer 可以用,另外 Timer3 用來做 PWM,這個下文會說明。
我這次用了 Timer0,他總共有 4 個 mode(詳見 datasheet),我用了 mode1 – 16-bit up counting timer,我最想分享的還是 TH0 和 TL0 的設定(因為真的被搞了一陣子)。我要用 timer0 來幫我計時,定時去執行 ISR(本次案件即是執行 PWM)。
因為 mode1 是 16-bit 的 counting timer,需要兩個 8 bits 來計算數字,而 TH0 即是所謂的 high-byte register,TL0 則是 low-byte register,兩者搭配就可以做出最高 65536 的數值(2 的 16 次方,即是 16-bit timer 的最大值)。聽起來很簡單,up-counting 表示從小算到 65536,接著溢位 -> flag 變 1 -> interrupt(如果有開的話),但我發現每顆 MCU 對於這裡的設定的方式都不太一樣,光爬文就超過 5 種,最後終於試到一個對的。
首先,我得說這裡做實驗最快,第一,datasheet 可能沒有寫得很詳細;第二,網路上的作法不一定適用(eg. TI,ARM based,microchip 都有些許差異),我實驗的結果其實很簡單,只是有個關鍵我當時沒理解。這顆 SN8F5701,以切 10 millisecs 為例,datasheet 裡面表示 ftimer0 = fcpu/12,如果 fcpu 沒有特別設定,那 fcpu = fosc。
而這 MCU 的 fosc 是 32MHz,所以 ftimer0 = 32MHz/12,timer0 計數(count)一次即為 ftimer0 的倒數(假設為變數 A,單位時間),為了切 10 millisecs 執行一次 timer0 的 interrupt,所以 0.01s(10 millisecs) / A 極為計數次數 = 26667,又因為是 up-counting,所以 65536 – 26667 才是最後答案 = 38869。換成 hex code得到 0x97D5,那 TH0(high-byte) 即 0x97,TL0(low-byte)即 0xD5。
https://gist.github.com/Ryanhu1015/d3012694c184900414d88d1f122c0f60#file-main-c
不過上述還差了一個最重要的東西,後來才想通的是 datasheet 裡面 Timer0 的 mode2,8-bit Up Counting Timer with Specified Reload Value Support,這個 reload 就是 reload 我在 intial function 裡的設定的初值,而我現在用的 mode1,16-bit counting timer 是沒有這個東西的,因此,我每進一次 timer0 的 interrupt function 我都要重新給一次 TH0 和 TL0 值,這樣才會順利 work。
p.s:文章幅度長度考量,下篇會提 fosc、fcpu 和 PWM,還有非常非常重要的程式碼架構,待續……。btw,回歸寫文的感覺真好。
只需不到短短一分鐘...
輸入您的信箱與ID註冊即可享有一切福利!
會員福利
免費電子報
會員搶先看
主題訂閱
好文收藏