A8106 (8051 2.4G RF 無線調光) – RF 通訊實作 receiver & sleep mode (下)

繼上篇 RF 通訊實作 trasmitter 的部分大致分享完後,接著就是 receiver。如果單就 “接收" 這件事來說,實作並沒有太困難,因為和發射 (transmit) 的部分非常類似,所以若搞懂了發射的機制,那接收不會是問題。

不過這次的專案在接收端 (receiver) 不只是要做接收,還有一個非常重要的功能 – 學習模式。據我得到的 spec 指示,一個控燈的 receiver 要能 (也最多只能) 記住三隻發射器 (也就是 transmitter),透過 receiver 上的三個 buttons 操作特定的流程 (eg. 長按 button 1),進入學習模式;進入學習模式後,再依特定的流程記錄某一隻特定的發射器 (eg. 進入學習模式後,某發射器發射訊號,receiver 收到後若 button 2 有被 clicked 兩下,那就拉出 ID 資訊記在 flash 裡),最後再操作離開學習模式的流程。

學習模式的大方向如上所述,實際上要注意的事項還真不少,嚴格來說要完成 receiver 的開發相較於 transmitter 是難上許多的。另外,transmitter 上篇也有個大重點尚未介紹 – 睡眠模式 (sleep mode)。睡眠模式 (sleep mode) 之所以重要是因為 RF 發射是相當耗電的,比 normal mode 時的耗電還要高上好幾倍,如果不想一直換電池的話這是必須的功能,下方介紹時再一併說明。

以當時 Winson 根據客戶驗收需求給我的指示,開發的先後順序是:基本 RF 收發功能並且能控模擬的 LED 輸出 -> 睡眠模式 (sleep mode) -> 學習模式 (很可惜,由於當時案子似乎沒談清楚,以及介於我快要離職之際,學習模式並沒有碰到)。

那就照順序從 receiver 的接收功能繼續下去吧!

接收端 (receiver)

如果大家對於上篇文章有印象,要切換到接收端的 code,首先 P3_5 要先設為 0 (或也可用硬體直接控制),而接收部分的程式碼如下:

提個外話,相較於前篇把 transmit 的 code 寫在 main,這裡的做法我個人比較喜歡,再寫一個 function 讓 main 看起來比較乾淨 (我習慣分檔案)。

言歸正傳,由於 RF 的發射是廣播 (broadcast) 的方式傳送資料,也就是說當按下發射器並且觸發 RF 發射時,所有通電的 receiver 都會收到這個訊號 (這裡不論強弱),至於該不該收 (也就是該不該接收某發射器傳來的 data 並做相對應的動作) 是根據開發者用程式碼所定義的,就是所謂 ID 的功用。

還記得 transmitter 將 8 個 bytes 的 data 透過 Packet_Tx[8] 傳出去,裡頭包含了自定義的資料,即每個 byte 所代表的意義都可以自訂,裡頭就包含了 ID (eg. 第 7 個 byte 代表 ID) 以及一些其他重要的資訊。而 light_condition_receiver 這個 function 最主要就是在 “聽空氣中 RF 的訊號”,確定收到後再執行 RxPacket 這個 function 去分析收到的 packet 的內容。

把兩個主要的 function 分別細看,先講 light_condition_receiver() 的部分,如同 transmitter 的 code,這裡也是利用 sample code 寫好的 function – RFLIB_StrobeCmd 做處理,只要把 TX 改成 RX (接收端收資料),RX 改成 TX (接收端發出 ack),其實就大功告成,就不浪費篇幅了。

至於 RxPacket(),上圖的 code 不包含 error checking 因此相對簡單許多 (比如說錯幾個 bit 以上要做什麼相對應的處理),先利用全域的 array – tmpbuf[128],去存從 packet 讀出來的東西,再利用 tmpbuf 裡的資料回 light_condition_receiver 做下一步的執行 (即註解的 light control 區段)。

大致上單純接收並解析 RF packet 的實作介紹至此,一樣我相信還有許多功能我沒玩到,不過若有問題或發現,歡迎各位同好朋友可以提出來一同討論,指教交流!

基本上專案進行到這裡,已經可以順利控燈了。不過如同文章開頭所說,若不想要一直更換發射器的電池,那編寫發射端的睡眠模式勢在必行 (接收端倒是沒差,因為控燈的裝置是可以持續插著電的)。

睡眠模式 (Sleep mode)

睡眠模式很複雜,至少比我一開始想像的還要困難許多。在分享我開發睡眠模式的過程前,先講有睡眠模式和沒有睡眠模式耗電量的差別,這樣看下去可能會比較有感。根據 A8106 的 datasheet,在一般板子通電預設的狀態下,MCU 會在 normal mode,RF 則會在 sleep mode,在此情形下的耗電量為 5.5 mA (我測的時候的確在 5. 多 mA)。

而 RF 在 TX mode (MCU 仍在 normal mode) 時更是飆高至 17-24 mA 不等。照發射器所使用的電池,如果我沒記錯是 CR2032,容量是 225 mAh,姑且不論電池本身的特性,例如內阻損耗我直接忽略不計 (我甚至記得這種鈕扣電池最大放電電流應該是沒辦法到 17 mA 這麼高…),在不發射 RF 的情況 (RF in sleep mode),225 / 5.5 ~= 41 h (小時),意思是說不到兩天的時間什麼都不做就沒電了,未免太過荒謬…。

為了解決這個棘手的問題,A8106 設計了三個 RF in sleep mode (此時 MCU 是 stop mode) 的狀態,分別為 PM1,PM2 和 PM3 (先不算 PM3 without sleep timer 的部分,此三種 mode 皆是有 sleep timer 的情形),就拿我使用的 PM2 做例子 (各個 mode 有些差距在此先不提),耗電量只有 3 uA,整整比原先少了 1700 多倍…,很明顯的高下立判。

而在實作方面,睡眠模式要顧的不外乎兩件事:1. 進入睡眠模式,2. 從睡眠模式醒來。而在我實作的應用中,醒來的唯一目的就是發射 RF,亦即沒要發射就睡,以此方式省電。

然而進入睡眠模式簡單,醒來做事反而難。A8106 有個很有趣的特色 – key interrupt,其三個 port P0, P1, P3 的任何一隻 GPIO 都可以作為外部中斷的媒介,此稱為 key input,可以透過 key interrupt (即外部中斷) 喚醒 A8106 (stop mode -> normal mode) 接著執行這個外部中斷想要執行的程式碼。

若套用到我的實作應用上,上述的 key input 即為使用者按了發射器上的任一按鍵,喚醒 A8106 並且 RF 發射那個按鍵所代表的功能訊息。又若將整個睡眠模式放進原先的程式碼,流程就會變成以下:

通電 -> 進入 RF sleep mode, MCU stop mode -> key input -> key interrupt (RF sleep mode, MCU normal mode) -> 發射 RF -> 進入 RF sleep mode, MCU stop mode。

流程看起來簡明扼要,但每一步所要注意的事情可多了…,包括 key input 的設定 (WUN – wake up enable),RF mode 和 MCU mode 設定的先後順序,有沒有足夠的時間給 mode 轉換等等都是問題。由於一開始我一直沒有搞清楚整個睡眠模式的流程,導致我到離職那天都尚未破關,現在也沒東西讓我繼續試,因此我最多只能分享到這,實在有點可惜,若後續的專案有這類的需求 (應該很常會有),屆時我會再分享我的程式碼,並深入解釋其中奧秘 (據說睡眠模式真的很麻煩,各家埋控場奇異的程度比一般的功能都還要高出許多)。

安可爾系列也就到此告一的段落,接下來會是全新也是我正在進行的個人專案,下次見!

本文同步刊登於 MakerPRO

廣告

A8106 (8051 2.4G RF 無線調光) – RF 通訊實作 transmitter (中)

由於篇幅關係,最終決定將 transmitter 和 receiver 各自一篇,不然怎麼切割都好像怪怪的…。廢話不多說,直接開始吧!

發射端 (transmitter)

發射端有兩個有趣的重點值得提出來分享。第一,button 按下去多久以及怎麽樣算是一個 “有效” 的 “按下”;第二,RF 傳輸的邏輯。

偵測 button 被按這件事我以前是有經驗的,不過還是停留在我知道因為空氣中有很多雜訊,可能導致 input pull-up 的 button state 變成 LOW,明明沒人按,但卻執行被按之後的行為,以前是用 “秒數” 去決定是否 “成功” 被按,比如說 state 在 LOW 時維持了 1 秒才算有效。

不過業界不是這樣操作的,直接利用部分程式碼解釋,如下:

上方的 code 是 timer0 的 ISR,透過 t0hrel 的設定可以將其設為 1 millisec 進來一次,重點是下方的 button1_time_cnt 變數,unsigned int 在此是 2 個 bytes,每 1 millisec 讓這個變數向左平移一個 bit,並且和 Button1_SW1 做 OR 的邏輯運算,如果翻譯成白話就是:

根據 Button1_SW1 (P3_2) 的 state 為 input pull-up 為 HIGH,因此 button1_time_cnt 為 16 個 1,而因為 timer ISR 的設計,每 1 millisec 就會去檢查一次 Button1_SW1 的 state 並且將其值 assign 給 button1_time_cnt 的 LSB,下次進來 ISR 時又會向左平移,依序執行下去,也就是說,在 main 裡面可以利用 if statement 判斷,button1_time_cnt == 0x0F 時,表示 button1 真的被按了。

這是一個極具彈性的寫法,利用 ISR 有效且精準的掌控 button 被按多久才算是有效,在此為遇到 0x0F 波形時成立 (利用示波器真的可以看到)。不過這個寫法是用在短按或是一次性按下的功能,比如說單純 on/off,也就是遙控器最上面那個 button 的功能。

至於長按的部分,目前的寫法是用一開始提到的方式,利用 flag 隨者每 1 millisec ISR 檢查 (eg. 判斷 Button2_SW2 是否為 state LOW) 而增加,如果 flag 的值比某個值大 (自己設定怎樣才算有效的一次長按,在此我是設 1 sec,意即 flag 的值如果 >= 1000 表示長按一次成功) 就成立。

講到長按 button 的功能就可以順勢帶到第二個重頭戲 – RF 傳輸邏輯。

當長按一次成功時,transmitter 就會執行發射一次 RF 的動作。首先,發射前總得要知道要挾帶的 data 是什麼,因此得先用個 array 將 data 放進去,以 datasheet 內提到 FIFO mode 的傳輸 packet format 的 playload 最大是 256 bytes,其他像是 preamable,ID code,CRC 在官方的定義的 protocol 都有說明,只不過我要實作的應用不需要用到這麼多功能,我只需要 8 個 bytes 就可以處理完 playload 以及 ID 的需求,以下用部分程式碼來解釋:

從 TXFIFO 這個 register 開始往下數 8 bytes 就是我會先放我要傳的 data 的地方 (從 0x900 ~ 0x93F 都是可以放 data 的位置,共 64 bytes,實際要如何操作到上述最大 playload 256 bytes 我並沒有研究),一旦 RFLIB_StrobeCmd(CMD_TX) 執行,東西就會被打出去 (RFLIB_StrobeCmd 這個 function 是原先 sample code 就有寫好,也是去設定某 SFR 讓 RF 動作),接著是我覺得最有趣的部分。

由於並不知道 RFLIB_StrobeCmd 這個 function 裡面到底怎麼寫,因此 while ((RFLIB_ReadReg(MODEC1_REG) & 0x80) == 0x80); 這行會顯得有些莫名其妙。從 datasheet 只知道 MODEC1 這個 register 如果值為 0x80 時為 sleep mode,那表面上看來在我設定完 RFLIB_StrobeCmd(CMD_TX) 後,MODEC1 的值應該已經要是 0xC0 的 TX mode 了,這個 while 怎麼樣也不會成立。我後來給自己的解釋是時間差,即真的要將 MODEC1 的值設定成 0xC0 這件事是需要時間的,也就是 transmit 的時間,真的變成 0xC0 才算傳輸完成。

而在 A8106 的 RF protocol 中,transmitter 傳 data 出去後,會把自己設定為 RX mode,去接收是否有來自 receiver 的 ACK bit,如果 Timeout 到了還沒收到就重新在 transmit 一次。

以上是 RF transmit 介紹的精簡版,只挑出了幾個比較大的重點出來,事實上有很多小的眉角需要注意,而且我相信我做到的部分只是 A8016 的冰山一角,這顆埋控能做到的事情絕不如此,有礙於篇幅先停在這裡,下篇會繼續將接收端一併解釋清楚,分享出來,幫自己紀錄。

本文同步刊登於 MakerPRO

A8106 (8051 2.4G RF 無線調光) – RF 通訊實作 (上)

在台大電機的第一個學期終於結束,也撐過了地獄的兩個月,過年期間我會將過去一年的種種在 facebook 網誌上好好整理一番,在此就讓我直接開始那個從 12 月初就積欠到現在的專案文章吧!

前些時間,基於學校課業考量,又加上外務真的太多,因此在 11 月底時我向公司提了離職,結束了我在安可爾科技短暫的四個月旅程。學到的東西很多,不過時間很趕有些東西來不及紀錄,文章內容若有問題,歡迎交流指教。

上篇文章以埋控開發必備的 debugger 作為開頭,事實上跟 A8106 沒有絕對的關係,只是做了幾種 8051 單晶片的開發後,對於 debugger 之於開發的重要性有感而發,因而放置在此系列的第一個部分。 如同上篇文末所言,此篇就會介紹 A8106 是如何進行 RF 通訊以及實作方式。

要談 RF 實作,必須要先瞭解 A8106 的特性,那就得從 datasheet 下手,網路上能找到的是瑋忠 A8106 RF module 規格書,這裡面只有記載一些比較基本的資訊,像是 electrical specification,哪些 GPIO 在這個版本有被拉出來 (interface descriptions),一些簡略的 features 介紹等等。

實際上如果要真的使用 A8106 實作 RF 通訊,還是要回歸到最原始 Amiccom A8106 datasheet 清楚瞭解各個 SFR (special function register) 和其他像是 timer,port 等等的功用,才有辦法成功實現。不過網路上並沒有相關文件 (應該是沒有釋出),所以我只能將我用到的部分分享給大家參考。

上篇文章也已經提到,此專案會利用遙控器 (transmitter) 發出 2.4G 訊號來控制燈具 (receiver),並且利用硬體 PWM 的方式進行燈光控制。若在功能上定義得更清楚些,從接收端講起,由於接收器燈具的部分客戶會提供,我只負責將控制的部分寫好,收 RF 訊號,控制 8 顆燈 (8 段亮度,每顆燈分別不做 PWM 只有 on/off 行為) 的亮滅,實現硬體 PWM 的感覺。

另外,發射端遙控器的部分,總共有三個按鍵,上下鍵各一,再外加一個可以記憶此時 PWM 狀態,做出關和亮 (亮起來時的狀態是上次全關前的亮度) 的功能,遙控器如下圖。

螢幕快照 2019-01-27 上午10.28.37.png上圖即為三鍵式遙控器

其中 “+" 按鈕預計短按控制單一顆燈的 on,長按做亮度遞增的效果,"-" 按鈕即為 “+" 的反向。而最上方的按鈕的功能就如同前述的一樣,可以記錄上一次關燈時的狀態。

而不論發射端或是接收端,公司還是得搭配 A8106 設計我們自己的電路板,為了成本以及開發的便利性,基本上 transmitter 和 receiver 的板子是一模一樣的 (至少在測試的時候是,不過抱歉電路圖是公司資產我無法放上來),要模擬真實情況的應用,因此板上有三個 button (模擬發射器的三個鍵),以及由 A8106 拉出來的 8 隻 GPIO 以及一隻 GND 作為 output 給燈使用,如下圖:

圖片 1.png此圖中的遙控器已是實際樣品的版本 (新版本的 PCB 設計),不為前述給測試之用。

功能確定那接著就來看要如何從 datasheet 提供的資訊,利用 code 將 RF 做出來。首先得先搞定前置作業再說。

和之前的狀況一樣,為了讓開發者更容易上手他們家的單晶片,廠商都會釋出他們的 sample code 給下游做參考,以利下游公司的開發速度,這次也不例外,基本上所有基礎功能的 function 都己經寫好,像是 timer,interrupt,uart 等等 (不過看不看得懂又是另外一回事了…像這次的 code 真的很髒…,要不就是沒 comment,test code 糞 code 沒標註,要不就是變數名稱取的很難理解…)。

根據 sample code 的指示,我自己整理出了主要的功能脈絡,將 sample code 改寫成我要的東西。在 main 裡面,很乾淨的利用 define P3_5 (*註 1),1 時為 transmitter,0 時為 receiver,不過有些東西是不論 transmitter 或是 receiver 都要設定好的初始值 – port 的設定。

A8106 在 GPIO 方面,總共有三個 port,任一 port 皆由 8 bits 組成,每個 bit 代表一隻腳位 (所以共 24 隻 I/O),像上述提到的 P3_5 即為一例。在設定方面,A8106 有一般的 logic high/low (*註 2)(eg. P0 = 0x00 -> port 0 所有 pin 腳皆是 LOW state),OE (output enable, eg. P0OE = 0x00 -> port 0 所有 pin 腳皆為 input mode),PUN (pull-up, eg. P0PUN = ~P0OE -> input pin 才需 pull-up),WUN (wake-up enable, 這個部分牽涉到 sleep mode,因此會留到介紹 sleep mode 時一併說明)。

根據需求以及上述說明來設定 register,除此之外,在 A8106 中,有三隻特殊給 RF 專用的腳位,分別為 GPIO1 (P1_2),GPIO2(P1_3),CKO (P0_7),原先看 datasheet 以為這是 RF 運作時會用到的三隻腳位,不能讓我將其使用於一般 GPIO,不過後來經查證發現事實不然,RF 早就被 module 本身做掉了,何必要在使用三隻額外的 GPIO 做傳輸呢?果然,此三隻腳位如果在 IOSEL 這個 register 為 xxxx 1111 時,可以用來對 RF 做一些設定或者說控制,並不是 RF 在傳輸時會利用到這三隻腳位,因此對我來說,此三腳位完全可以當作 GPIO 做使用 (且瑋忠甚至沒有把 P1_2 拉出來)。

接著,timer0,RF 等的初始化,一些重要 flag (eg. TR0, EA) 的設定,RF mode 的選擇等等做完後終於可以進入 transmitter 的部分。

*註 1:後來發現,瑋忠科技的版本因為有把 P3_5 這隻 GPIO 拉出來,既不是控制 8 個燈的其中任何一個 output pin,也不是 button input pin (因為公司設計的板子根本沒拉出 P3_5),所以實際上可以利用硬體的一個切換紐去決定現在板子要是發射端或是接收端,不需要每次 upload 時都手動去更改 code 裡面 define P3_5 所代表的值。

*註 2:稍微提醒一下,我發現不少人會在實作的過程中將 logic HIGH / LOW 和 INPUT / OUTPUT 混在一起,且本人自己也犯過類似的錯,很神奇,但就是有可能會錯在這種地方…。

本文同步刊登於 MakerPRO

A8106 (8051 2.4G RF 無線調光) – debugger (JTAG or SWD)

一如既往,先解釋一下這次來到手邊的專案要做什麼。簡單來說,要利用 Amiccom A8106 這顆 8051 based 的 RF 模組,做一對遙控器和接收器,發射端廣播 2.4G 的訊號,接收端收到 packet 並解析後做出相對應調光的動作,這次的調光是硬體調光,並不是軟體的 PWM。

2(A8106 瑋忠科技版本)

不僅如此,客戶最終希望能夠多支遙控器都能控制同一個接收端,所以勢必需要實作學習模式,將遙控器的資訊 (eg. id) 記在接收端的 flash 裡面,讓接收端認得這個空中的 broadcast 是誰發出來的。

這是我在安可爾的第二個案子,我得說,真的滿難的,以前在 Arduino 上玩的 WiFi,BLE (都是 2.4G) 都是第三方 library 寫好,直接 call function 來用就可以,但這次,我沒有 function 可以 call 只有下游廠商莫名又難懂的 sample code (很多真的有寫跟沒寫一樣…) 讓我去研究 2.4G RF 的傳輸方式。

經過幾個星期的努力,終於破關 (只破了最主要的功能,週邊還沒研究清楚,繼續努力),整理實作的過程後產出了新的一個系列文章,接下來將會一一分享出來。

debugger 的秘密

做韌體的同好朋友一定都知道,無論要開發的專案是什麼,燒錄這件事絕對是最基本也最重要的事。

上個專案是我第一次使用 keil C51 做開發,當時有個叫做 SN-LINK 的硬體說是 SN8F5701 這個模組開發的 debugger,我不疑有他,接上去後照著步驟設定的確就可以執行燒錄的動作。

當下的我沒有多想,因為 Arduino 並不需要這樣東西,不需要在模組和電腦之間還要連一個 debugger 才可以燒錄,覺得是特例,直到這次的專案,debugger (這次叫做 ICE,Amiccom 自己家的 debugger) 又出現了,完全激起了我的好奇心。

為什麼這個 debugger 需要存在?他不就是燒錄器嗎?先講結論,他主要的功能不是燒錄器,如果你不想執行 debug mode 那他其實可以不用存在。

故事的源頭,要從 bootloader 開始講起…

bootloader 的意義

我們都知道燒錄絕對和 bootloader 脫離不了關係。

Bird 曾經跟我說過:everything can be called bootloader before user code.

一個模組 (開發版) 中,bootloader 可以有很多段,例如,若是確定為燒錄模式,就會執行接下來的 bootloader 去等 UART 的資料,接著寫到 program flash (中間什麼 erase block 等等),動作過後,燒錄完成。

在模組通電的瞬間,模組內會先執行 MCU 廠商已經寫死在裡面的 bootloader,這是用來控制硬體的,像是這次的 A8106,datasheet 裡面有提到,P04 腳位如果是 LOW 時會進入燒錄模式,若為 HIGH 則為 normal mode,也就是說,通電後,bootloader 利用判斷式去偵測 P04 腳位的情況,根據判斷結果再塞對應的值進相關的暫存器,達成燒錄或是正常的模式。

相信以上,有在玩專案的 maker 同好或多或少都知道這件事,不過知道這些跟 debugger 有什麼關連?

一般的埋控開發,在與電腦連結前中間都會出現一個 middleware 我們稱之為 debugger,這個連接 debugger 的介面就得好好來介紹一下,因為 P04 若為 LOW,與 debugger 連接的其中兩條線 (此為 P14 和 P15) 將被設成 debug IO (可以進行燒錄或 debug),反之,P04 為 HIGH (normal mode),P14 和 P15 就會是普通的 GPIO。

此介面稱為 JTAG (or SWD),不過一般玩 Arduino 的 maker 不太會接觸到這個叫做 JTAG 的介面,而講到 JTAG 當然 SWD 也得一起說明才完整,不過其實簡單來說 SWD 就是簡化版的 JTAG。

JTAG 是非常常用,在一般的模組、開發版上必定會看到的 debug 介面,由四隻腳組成 (這裡講的是主要,市面上還有很多奇形怪狀的形式),分別是:

  1. TDI (data in)
  2. TDO (data out)
  3. CLK (clock)
  4. TMS (mode 切換)

*每家可能名字會有些許差異,但功能都一樣

它可以操縱 IC 內暫存器的讀取或寫入,聽起來沒有很厲害,依上次專案的經驗,我的 user code 也可以直接對 MCU 的暫存器進行設值的動作。

但 JTAG 厲害的地方在於,以這次專案使用的 A8106 作為例子 (雖然 ICE 是走 SWD,不過解釋上用 JTAG),A8106 是 MCU,只是一顆 IC,原廠的下游廠商會根據這顆 IC 做不同的功能釋放,做出市面上我們看到的模組 (開發版),由此可知不同廠商很可能都利用 A8106 這個 MCU 兜出不一樣功能取向的模組,意即若某廠商覺得某些腳位並不重要,他可能就不將此腳位拉出來,作為模組的 GPIO pin,或是其他功能的 pin 腳位。

螢幕快照 2018-12-11 上午11.54.08
(Amiccom ICE 的 SWD 示意圖)

也就是說 user code 可以控制的部分在於 “廠商有拉出來的腳位",而 JTAG 可以做到的是,整個 MCU (甚至周邊的 IC) 他都可以操控!之所以說周邊 IC 也可以,是因為到底行不行,都是根據事先被定義好的 JTAG mapping,去做讀取或寫入的動作,偵測那些腳位是否有出問題,這些是軟體碰不到的部分。

相信眼尖的同好朋友有發現,JTAG 也才四個腳位,要怎麼樣才能去讀取或寫入這麼多的腳位?其實,JTAG 用了 shift register 的概念,serial to prallel,用 buffer 吃值的方式,將序列的 data 轉成並列的形式 (CLK 腳位就是在控制這個把 buffer 值倒出來的速度有多快)。

介紹完 JTAG 的強大,但還是沒解釋這跟燒錄有什麼關係?

就像我剛開始提到的結論一樣,如果不用 debug mode 那真的沒什麼用,因為要不要進燒入模式的確是 bootloader 決定的,不過如果要 debug,在此也只有 JTAG (or SWD) 做得到。

相信有在開發 web 的朋友一定知道,在 debug 的時候,可以 “一步一步” run program 看到底是在 code 的哪個環節出了問題,而 Keil C51 debug mode 也可以做到完全一樣的事情。而這整件事歸功於這個 “debugger" 的存在 (事實上就是 JTAG),拜 JTAG 可以操控暫存器所賜,可以直接控制 MCU 裡面的 clock,而我們知道 MCU 可以動起來就是因為 clock 在震,也就是說 JTAG 可以控制 MCU,動一步,clock 停住,達成我們所看到一步步偵錯的效果。

因此他叫 debugger 不是沒有原因,因為它主要的功能並不是燒錄而是執行 debug。

不過!不知道有沒有朋友發現,剛剛上方提到 bootloader 也只是將判斷的結果塞進某些特定的暫存器。那讀取或寫入暫存器不就是 JTAG 的本能嗎!也就是說 JTAG 可以直接設值讓模組進入燒錄模式!也難怪常常聽到前輩說 JTAG 是整個模組的最後一道防線 (一般常說的 “ MCU 死了” 通常就是指 bootloader 當掉)。

談完以上的種種,以及 debugger 實際的定位後,那反觀 Arduino 為什麼沒有 debugger?

經過師父 Bird 解釋後才了解到,Arduino compile 後並不是可執行的 machine code,燒錄前會再經過中間一層 Arduino 自家的東西,才會成功變 machine code 燒錄進板子,至於中間那層到底是什麼,我目前沒有特別去研究。

搞懂了 debugger 以及燒錄的種種後,下一篇文章將會開始談 A8106 的 RF 到底怎麼傳資料,拭目以待。

本文同步刊登於 MakerPRO

SN8F5701 – 8051 ADC 功能介紹

上次由於篇幅的緣故,和大家分享 8051 的 UART 之後沒能繼續把 ADC 講完 (每次都說最後一篇,然後每次都有最最後一篇哈哈…),所以今天這篇真的真的要終結這顆埋控了,文末也會稍微提到目前正在進行的專案,與 2.4G RF 有關。

ADC (analog to digital converter)

我們都知道現實世界是 “類比” 的,不過類比實在太複雜,聰明的人類因此創造出 “數位” 來簡化問題,如果不夠清楚,就想像類比是 “連續數值的變化” 而數位就只有 0 或 1,而這之間的轉換牽涉到很多數學的複雜運算,比如說傅立葉轉換 (fourier tranform),將 time domain 的 data 轉成 frequency domain,原本看似沒意義的 function curve 突然之間有了生命。

本篇就用最簡單的例子 – 電壓大小變化,來實作這顆 SN8F5701 埋控的 ADC 功能。

這顆埋控 ADC 的部分是用 SAR 架構 (聽說還有很多種,但我都還沒遇到過或是我不知道我遇過了…),6 隻可以設為 analog input 的 pin 腳位,解析度有到 4096 steps,在腦中想像一下長條圖 y 軸的刻度,就是 4096 階,也就是說如果 4096 階這個值不變,在 reference high voltage 和 VSS (通常都是接地 0V) 相差的越小  (下方會說明),解析度相對就會提高,但有利有弊,除非進來的 analog 訊號數值和變化真的不大,否則這個情況很難發生。

上述提到的 reference high voltage (參考高電位) 還有 clock rate 是 ADC 中相當重要的一環,這顆埋控有四個 clock rate 可以選擇,以 datasheet 裡面提到的 converting rate,我目前的理解是每次每個數值轉換 (analog to digital) 的速率,所以說如果類比訊號進來的很快,但我 MCU ADC 的 converting rate 設太低,用膝蓋想都知道讀出來的數值可能會出問題。

剛剛之所以說以我目前的理解是因為我在別顆埋控的 datasheet 裡面有看過 ADC sampling rate,至於這和 converting rate 是否相同,經過詢問 Bird 後得知,在大部分的狀況下是一樣的,但有些進階的 ADC 上會不一樣 (就我所知 sampling rate 越高表示越能模擬出真正 analog 訊號的全貌)。

再來談到參考高電位, SN8F5701 提供 4 個 sources 給開發者選擇,分別是 VDD, 4V, 3V, 2V,拿 3V 舉例,因為埋控規定 VSS (0V) <= sampled input voltage 一定要 <= 參考高電位,意思是說如果 reference high voltage 我設定為 3V 我只能去測量 0-3V 之間的數值,而 4096 這個解析度,就是將 0-3V 分割成 4096 階,去定義進來的 analog 訊號該對應到什麼二進位的數值,用下圖搭配著看會更清楚:

螢幕快照 2018-10-20 下午10.32.32.png
圖片出處:類比數位資料轉換器 ADC 介紹

接著在講 initiate ADC 之間,先談談整個轉換流程在這顆埋控中是怎麼進行的。首先要讓 ADC enable (ADENB=1),接下來要讓 ADC 開始可以轉換數值 (ADS=1),當轉換結束後硬體自行會將 ADS 變為 0 表示轉換結束,硬體也會把其他兩個 flag – EOC 和 ADCF 拉 HIGH 為 1,另外轉換好的數值就存在 ADB[11:4] 和 ADR[3:0] 兩個 registers。

而上方提到的 ADCF 其實是和 ADC 的 interrput 做搭配,ADCF 為 1 時,如果開發者有 enable adc interrput (EADC=1),這時 ISR 會被觸發,觸發 “前” ADCF flag 就會被硬體清掉。但我並沒有用 ADC interrupt 的功能,原因下方我會說明。

以下程式碼是 ADC 功能的初始設定:

要作為 ADC pin 腳位,首先要將 pin 設定為 input mode,再用 POCON 設為 pure analog input pin,重點是一次同時間只能有一隻腳位為 analog pin,因為這顆埋控 analog 和 digital I/O 腳位是共用,所以非 analog pin 的腳位一律要設定好是 digital 不然會有問題。

根據四種不同的 clock rate (ADR 的 ADCKS 設定) 會對應出不同的 converting time (converting time 的時間是指從開始轉換 <ADS=1> 到結束 <EOC=1> 這段時間) 我是直接開到最大,用 fosc / 1 的 clock rate 在跑,至於 reference high voltage 我是用內部 VDD (5V) 作為參考高電位,因此可以利用電源供應器給 P01 這隻 pin 腳 0-5V 的類比訊號做為測試。

以下是部分程式碼:

上方程式碼片段總共有三個重點,第一,要讀出來總要看得到是多少,所以搭配 UART 顯示在我的 console 上 (所以也是為什麼我上次覺得 UART 和 ADC 要寫在同一篇的緣故)。第二,我之所以沒有開 ADC 的 interrupt 就是因為我不需要這麼常取值 (但還是很看應用所以也不一定),我反而是用 timer0 去計時,在 main 裡面每隔 1 秒去讀一次 ADB 和 ADR 裡的值。第三,因為轉換結束後不僅 EOC 會被系統拉 HIGH,還有 ADS 也會拉 LOW,所以我為了要啟動下一次的轉換,我勢必得清掉 EOC 並且拉 HIGH ADS。

以上搞懂且成功運行後,就可以在 PC 的 console 上面看到,隨著電源供應器的旋鈕被轉動,console 上顯示的數值就會跟著做改變,而 ADC 的功能測試也到這告一個段落,SN8F5701 大致玩完,雖然沒有到很深,但該摸的基本上都有摸到,繼續前進下一顆埋控!

文章最後提一下我目前手邊正在進行的專案,是利用 Amiccom A8106 2.4G RF module 實作發射與接收器,RF 比我想像中還要複雜許多,不僅僅是廠商自定義的 protocol,還要 based on 這個 protocol 去做不同的 use case (比如說學習模式,接收器可以認得三隻發射器等等),屆時再來分享瞜!

*本文同步刊登 MakerPRO

SN8F5701 – 8051 UART 功能介紹

這篇雖說是簡易雷達調光的最終章,不過事實上 UART 和 ADC 並沒有應用在這次的專案之上,所以文章的主題是針對這顆埋控做功能上的測試和介紹,我們就直接進入主題吧!

UART

剛開始玩 Arduino 的時候,只知道 Serial.println() 可以在 console 中印出某些字串,變數的值等等,沒有去探究怎麼實現的,玩了一陣子後,瞭解到 USB 轉 TTL 的事實,TXD,RXD 背後的UART protocol 是怎麼運作的,即便知道背後原因,但因為 Arduino 把整個東西做掉因此當時我沒機會了解得非常深入,應該說沒機會真的去親身實作背後原理。

因為這次專案的機會,雖說的確不會用到 UART 的部分 (松翰的 SN-LINK-V3 處理了燒錄的部分),不過 Winson 希望我把所有功能都玩一遍,因此頭一次自己寫 UART 的機會就來了。

看了 datasheet,說真的,松翰這份文件內 sample code 的部分真的寫得比較不完整,而且有些地方是有問題的,另外就是一些細節的部分沒有交代清楚,因而必須自己做實驗來驗證。

不知道是不是自己功力還不夠的問題,我在測試 SN8F5701 這顆埋控有關於 UART 部分的時候,發現了它竟然不是全雙工 (Full Duplex)!(但文件明明清楚表示 UART 是全雙工的沒錯),接下來就來分享我做實驗的過程。

實驗是這樣,透過像是 coolterm 這樣的串口軟體工具 (就是 Arduino serial console 的概念),我希望在鍵盤上輸入文字或是數字 (RXD),SN8F5701 能夠吐回來到 console 中顯示 (TXD)。

而講到 UART 不論哪顆埋控一定都會有一個或多個 buffer 來接目標物 (不論是要送出去還是丟進來都是),SN8F5701 當然也不例外,S0BUF 就是那個 buffer,表示他要用來接我鍵盤輸入的文字或數字,也要用來接要丟出去的東西,那…那豈不是會打架嗎?此時心想,既然文件都肯定是全雙工了,或許有什麼東西是沒寫出來但是能夠順利 work 的。

首先,我先做了個基本的輸送實驗,以下是部分的程式碼 (就不寫數字部分的了,文字會數字一定會):

稍微解釋一下上方的程式碼,uart_init() 作為 uart 功能的初始設定,tx, rx 分別寫成兩個一般的 function,chstring[] 這個字元陣列是在開機 (reset) 後,會先顯示在 coolterm console 上的字串。

我覺得這裡最值得分享的還是 while (TI0==0) {},這個寫法比較不直覺,不過卻很必要。TI0 (和 RI0) 是個 flag,如果 S0BUF 滿了,也就是說東西可以丟出去 (或接進來),TI0 (或是 RI0) 會變成 1,必須手動清除 flag 將其變回 0 才能再收 (或丟) 下一批東西,而之所以要這樣寫,是因為 MCU 的速度太快,還來不及清除 flag 就已經跑過 TI0=0 這個指令了。這樣講或許有點模糊,以上方的程式碼舉例,如果沒有 while (TI0==0) {} 這行,因為 MCU (fcpu) 太快,可能再 S0BUF = da 後,在 TI0 還沒變成 1 之前,就已經先跑完了 TI0=0 這行程式碼,導致 TI0 清不掉,無法繼續有效運作。

雖然這個寫法是對的但卻很不合理,一方面,我花了所有 main 的資源只做了把我的輸入印在 console 上,做實驗可以但實際與其他功能配合運用時就是個災難。另一方面,大家應該很疑惑在 rxreceivedata() 裡面,WDTR = 0x5A 的用意,事實上不加這行又在有開 watch dog 的情形下,只要使用者沒有在 watch dog 所設定的時間內真的輸入東西 (相信我,一般人是不可能跟上的),系統就會直接判定當機而進行 reset,而為了避免這種窘況,WDTR = 0x5A 就是用來清 watch dog 的指令,好讓 rxreceivedata() 可以順利等下去,不過說真的沒有人會這樣寫,沒東西來就卡在那個 while 裡面一直等,什麼都不用做了…。

另外,我用了個 flag (enableWrite) 讓 txsenddata() 在沒有東西進來的情況不會送東西出去。

既然上述兩個 function 都放在 main 的情形這麼不優雅,那就是時候派 ISR 上場了。UART 當然也有 interrupt 的機制,據 Winson 說,UART 通常會把 rx 放在 ISR,tx 放在 main,我覺得很合理,試想如果把 rx 放在 main,tx 放在 ISR,那上述的問題就沒獲得解法,依舊在等。寫法就變以下 (程式碼片段):

rx 在 ISR,文件明確表示啟動的機制是 RI0 變 1 時便 trigger ISR,意思是說我不用用 while 再繼續等,進來的時候把 RI0 flag 清掉就好,也因為全雙工,不用太在意收丟交錯的情形,有東西進來把 enableWrite flag 打開就行。

不過可以看到上面我在 ISR 裡面多加了一個 if statement,這是因為我發現如果不加,竟然 tx 傳到螢幕上的字元陣列會漏字!於是我開始懷疑不是全雙工,寫了那個 if 就是當他是半雙工的意思 (收的時候不能丟,丟的時候不能收)。果然這樣寫,終於鍵盤輸入什麼,螢幕就會顯示什麼了,UART 的功能測試也就告一個段落。

我發現講到這裡篇幅已經太長,請原諒我下一篇再接著講 ADC!

*另外,我上個月開始有在 MakerPRO 共筆專欄,歡迎大家來交流!(鞭策小力一點拜託><)

本文同步發表於MakerPRO

簡易雷達調光 (二) – SN8F5701

延續上篇 – 簡易雷達調光 (一) – SN8F5701,本篇文章會繼續著重在 SN8F5701 的功能實踐。不過在繼續講其他功能之前,如 PWM, UART 等等實作,我想先補充一下上次 Timer 沒分享完的部分。

Timer 到底應該怎麼切?

記得上篇文章的切法,我是以 10 millisecs 為最小單位做切割,當時只是想說,客人的要求既然是 1 min,如果切 1 millisec,豈不是要設定變數值為 60000,因為是 8 bit 的 MCU,資料型態的決定上就需要更加小心,不然真的後患無窮阿!(溢位很容易就出問題),但後來發現這樣真的很不方便,因為刻度不夠細,沒有辦法到非常精準 (雖然肉眼看不太出來,但客人就是會拿示波器挑戰你阿哈哈)

從上面的觀點來看,好像要精準就沒辦法計時太久,而要計時久一點,就沒辦法太精準 (怎麼突然想到測不準原理…對不起不要打我…)。但其實是有解法的,Winson 有提到通常還是習慣用 1 millisec 做切割,如果需要比較長時間的計時,會設多個全域變數,如下片段程式碼:

以上的寫法只是用了三個全域變數做累加,分別區分毫秒,秒,分。至於實際執行 (ISR) 的程式碼片段,有人會放 main 裡面,也有人喜歡直接放在 ISR function 內做判斷,但現在刻度變得這麼細,還是放在 main 裡面保險一點 (雖然說 main 裡面盡可能乾淨沒錯),畢竟如果 ISR 執行時間超過 1 millisec,基本上程式就會開始出現問題 (會 crash,之前做 NTP 時鐘的時候就有出現過)。另外,因為現在是 1 millisec,所以 TH0 和 TL0 兩個暫存器的值也要做調整 (算法上一篇有)。

PWM

接下來是本章的重頭戲,PWM。記得當初剛接觸 Arduino 的時候,瓦力號系列的文章有分享 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 可以做的事情,在這裡需要自己設定所有東西,就用以下程式碼片段做介紹:

如上程式碼,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,頻率倍數調整…等等。不過這些這次我都還沒有機會實作到,如果之後有遇到會再分享出來。

fosc / fcpu

這兩個新名詞我第一次看到的時候真的是不知道代表什麼意思,直到全部看完 datasheet 才漸漸瞭解各別代表的意義。fosc 是震盪器的頻率 (system clock -> IHRC 為 32MHz),而 fcpu 是可以根據 CLKSEL 和 CLKCMD 兩個暫存器做調整,意思是說 fcpu <= fosc (固定值)。

程式碼架構 / 自己的 Library / 經驗問題

本文最後要來分享一下,截至目前為止,無關乎我對埋控理解的多寡,純粹是要講在開發的過程中哪些該注意,哪些可以省掉未來不論維護,或是往後再開發的速度都能夠有效掌控的方式。

就從上一篇文章提到的程式碼架構開始,因為客人需求的多變,可能今天開給我們的 spec 跟他現場真的看到的樣子有落差,就會再和我們討論調整,如果程式碼寫死 (意即照 spec freestyle 硬幹),客人要我們改點小東西 (對他們來說),我們在沒有架構的情況下,可能又要花上一天的時間把程式碼做修正。

為了不讓這麼蠢的事情發生,在開始照著 spec 下手寫 code 前,都習慣會先自己假定幾種客人 “可能會再改的部分" (很吃經驗),把這些會再改的部分一一變成參數的形式 (的確比較麻煩,程式碼會相對變比較肥,但好處絕對勝過沒架構的版本),這樣在完成後只需要做參數 (比如說 const 比如說 #define) 的調整,程式碼就能依照新的參數執行。

比如說上面的 pwmValue 就是一個 const int,使用者可以直接更改數字去做到不同 duty cycle 的 pwm 效果,只是個超小的範例,但就是這個感覺沒錯。

另外,Winson 也有跟我提到,“寫久了就會建自己的 Library”,比如說 PWM function 其實每顆埋控的實作都大同小異,所以把 pwm 模板寫好,等到下次遇到別顆埋控時,只要照 datasheet 改變參數就可以。但我發現其實沒這麼簡單,因為所謂 “模板”,程度上很難抓,會不知道到底要寫的多細節,或是多粗糙才算是有用的模板 (我試過,基本上寫到後來整個 pwm function 都移過去了…),我發現這個很看經驗,所以等我真的知道怎麼拿捏時再分享出來摟!

建自己的 library 真的很方便的感覺,同樣的東西就不用一直重寫,對於開發的人也比較好做後續的維護,因為不會每次都不一樣。

下篇文章是簡易雷達調光的最終章,要談 UART 和 ADC 的功能實作 。下回揭曉…