給卡伯

這個部落格已經許久沒更新,有點半荒廢的狀態了…。雖然還是有繼續做自己喜歡的韌體,學新的技術,玩新的模組,但現在大多的事情的 priority 都已高過紀錄自己所學、和大家分享新知。

原本也沒有打算要寫這篇並且放在這,畢竟這個部落格應該是專注在韌體技術的分享,我的學習心得,但又想說,現在在做的東西何嘗不是一種學習,因此就把這些有感而發也紀錄在自造者萊恩吧。

這篇紀錄,是寫給卡伯的。

記得前陣子在系上學長姐要畢業時,有看到系學會會長都會代表應屆有一篇感人肺腑的致詞 (應該是系學會會長吧…嗎?),後來去查發現每年好像都有,好奇心驅動下,我就往前幾屆找,找到了讓我印象非常深刻『電機命』的那篇,只覺得這個詞真的形容得太好。

為什麼會從電機命開始講起,一切都是因為卡伯現在在做的事情,看起來和團隊幾個電機仔過去在學校學的東西,even 我們各自原先擅長的領域,實在沒什麼太大的關係,但因為電機命,好像也就合理了,我們的使命就是解決問題吧。

(這邊沒有要特別說電機命是啥,有興趣的人我附上連結)

之所以會做卡伯,當初原因很單純,主要是因為我自己也是受害者,再加上自己幾個身旁很好的朋友也有這樣的困擾,我們都一致認為信用卡優惠真的太雜亂,規則太多,一旦沒有符合某些即便很小的條件,就是拿不到原先預期的回饋,然後這種感覺認真很糟。

在這些聲音的驅使下我便開始思考,是否有機會運用自己既有的技術能力來解決這個問題,答案是肯定的。如果我可以把信用卡的優惠整理出來,根據不同使用者手上擁有的信用卡,讓他們在消費當下可以利用我做的應用得到個人化的推薦結果豈不是很好?

帶著這個簡單不過的想法,藉著台大創創學程的機會,找到了一群志同道合的人就開幹了。一年創創學程的課程,在下學期期中之前,我都沒有特別想過這個東西會在學程結束後繼續做下去,一方面因為過程中各種被 challenge (e.g. 商業模式),另一方面則是對於自己唸電機系的期待,兩者的層層交互作用下,難免會有自我懷疑,萌生放棄的念頭。

幸好撐下來了,可能因為電機命吧 (嗎),就讓我姑且認定是這樣,心裡總有個聲音告訴我還沒到頭,那個問題還沒被我解決阿,談何放棄?所以現在說放棄也未免太早,輕易放棄一直都不是我的風格。

幾個心臟很大的夥伴,願意相信我這個傻子,相信這個團隊,相信這個題目,相信這個東西是有價值的。從原本的學程專案直到錢從戶頭領出來,遞出去的那刻,卡伯變成了一個真槍實彈的創業題目。

這篇文發出去的同時,卡伯對外正式的第一個版本已經上線了。不免俗的還是要感謝一路以來給予指導的業師,最重要的還是一直都很相信我這個傻子的夥伴們和過去曾經幫助過這個題目的各位

沒有大家的幫忙卡伯無法在這短短幾個月就可以推出現在這個看起來還可以的版本,謝謝你們。當然,產品有很多地方有待加強,需要大家的反饋,以及使用上的建議,目前使用人數有雖有稍漲的趨勢,不過還需更努力才行。

講成這樣好像是在看什麼創業有成後的心路歷程分享,完全不然。這篇文只是一個心情的抒發,加上我們最近看到了一個機會,或許這個東西可以成為我們可靠的商業模式。

不過這項計畫的內容會需要更多志同道合的人一起加入才有可能在短時間內實現,總不可能永遠靠我和 Toby 或其他友人的義氣相挺,我們需要更多開發能量,若螢幕前的你有興趣,歡迎私訊我或是卡伯團隊中的任何夥伴。(怎麼突然變徵才哈)

我們現在的狀態還在爬,用力地往前爬,期待在山洞還沒坍掉前,能夠看到盡頭的一道光。不過說真的,不論接下來怎麽發展,這段時光都是我人生中很寶貴的回憶,回憶在辦公室和夥伴一起工作從早到深夜,寫 code 到凌晨再到早上,睡到一半接到電話不是叫我起床尿尿,而是太多人同時使用導致系統當機,種種種種都已在我記憶中留下痕跡。

講了這麼多,回到電機命本體,就像致詞文裡提到的,或許我們走的不是一個典型的電機仔路線,或許看起來很像在做一些有的沒的、一事無成,好像我們沒在為自己的未來做更有意義的事。不過我們的確就是一群會為了一個相同目標共同努力拼命的人。

我至始至終認為,電機仔不一定就得走研究、進大廠,到國外工作的路線,我們的發展應該可以是更多元的,反正電機命嘛,我們的存在就是要解決某個問題,創造某些價值,期許自己因為我們的存在可以讓這個世界有一點點的改變,那我想這個過程就不該是唯一解,但目的地卻可以一樣。

總之期待聽到更多同好的聲音和興趣,我們可以賣你一個夢想,歡迎一起來實踐它。

電子標籤 (三) – Modbus over TCP 實作 (上)

大致解釋完 Modbus portocol 後,接下來就是實作的部分。記得過去在談 Modbus protocol 第一篇的時候有提到,這次要實現系統架構的長相如下圖,客戶希望可以用 PC 透過指定某個特定 IP 的 Modbus TCP Slave 使其再去控制底下的 Modbus RTU Slave。

比方說,總共有 3 組同網域中不同的 IP (即 Mobus TCP Slave,eg. 192.168.1.3 and .4 and .5),接著 PC (Modbus TCP Master) 透過 Modbus TCP 傳輸自定義的 data content 給特定 IP (eg. 192.168.1.3) 的 Modbus TCP Slave,而 Modbus TCP Slave 接到訊息後根據自定義的 spec 去 decode 來自 Master 的 data content,接著 decode 完的資訊 Modbus RTU Master (和 Modbus TCP Slave 是同一人) 利用 Modbus RTU 透過 RS485 傳送資料給特定 address 的 Modbus RTU Slave,完成控制。

螢幕快照 2019-10-20 上午12.14.43
圖片出處:Fun and Easy Modbus RTU protocol

而實作的 (上) 篇,就會從上圖的右半邊 Modbus TCP (Slave) <——Ethernet——> Modbus TCP (Master) 的部分開始說起,而 (下) 篇就會接續實作剩下左半邊,也就是 Modbus RTU (Master) 對 Modbus RTU (Slave) (即這次的主角 – 電子標籤)。為了更清楚的展示要實驗的內容,我做了簡圖以幫助大家快速理解:

螢幕快照 2019-11-11 下午1.51.51

整個硬體配置就如同上圖所述,因為我是用我的雷蛇筆電做開發,其本身不包含 RJ45 的網路孔,因此有兩個選擇,第一是利用 USB 外接網路卡神器,第二則是利用 WiFi,由於在同個網域底下測試,所以 WiFi 是完全可行的。

另一邊 ENC28J60 本身就是一個 Arduino 用來使用 Ethernet 很好的外掛 module,那當然就是利用它搭起 AP 和 Uno 之間 Ethernet 的橋樑。

至於軟體方面,透過點亮 or 熄滅 UNO 上外掛的一顆 LED 作為實驗 Modbus TCP 的依據,用 LED 來評判 Modbus protocol 是否被打通。畢竟一條 IP 打通了,客戶要接個 10 條也不是問題,只要 AP (或用 switch) LAN 孔夠多就行。

Modbus TCP Slave – UNO R3

那我們就從 Modbus TCP slave 端開始吧。slave 端我是利用 mac 作為開發環境。

ENC28J60 在網路上有相當多的資源可以參考,在此附上我使用的 github repo 連結:EtherCard。repo README.md 中還清楚的附上硬體接線,利用 SPI 和 UNO 做溝通,這部分我想照著做應該是不會出現太大的問題。當然,還要準備 RJ45 網路線將 EtherCard 和家裡的 AP 接上。

軟體部分,我建議若是 Mac 開發者,直接利用 Arduino 內建的 library manager 下載 EtherCard 這個 library 即可。下載後開啟 library 底下的 testDHCP sample code,編譯燒錄,打開 console 看看是否有抓到 MAC address 和 My IP,如下圖:

螢幕快照 2019-11-11 下午8.53.42

這個資訊很重要,是等等要作為 Modbus TCP slave 的資訊並讓 TCP Master 辨別的依據。

確定 ENC28J60 可以順利運作後,接著是重頭戲 – Modbus protcol library for Arduino。由於網路上也滿多資源,再經過測試後我認為最好用的 library 也在此附上其 github repo:modbus-arduino。這個 library 包山包海,不過我們只會用到 ModbusIP_ENC28J60 這個 directory 裡面的東西,此 dir 底下有四個 sample,我們會利用 Lamp 做實驗,看能不能順利透過 Modbus protocol (的 single bit coil) 將 LED 點亮。

Lamp  的 sample code 中,可以看到 setup function 內的 mac 和 ip byte array,如下圖:

螢幕快照 2019-11-11 下午2.23.27.png

將這兩個 byte array 內容改成剛剛在 testDHCP sample 中得到的 MAC address 和 My ip,這是等等要註冊給 Modbus object (如圖片中最後那行 configuration) 的資訊。

另外在 Lamp sample code 中也有一個值得分享的東西 – LAMP1_COIL 這個常數。如同上篇介紹 Modbus protocol 時,提到 registers type 的概念,00001 – 09999 屬於 Coils 的部分,code 中 LAMP1_COIL default 是 100,事實上在有效的範圍要設多少都是沒問題的。

slave 端的最後是最簡單的 LED,我將 GPIO 7 作為我的 LED pin,編譯燒錄後一切就緒,保持接電狀態,接著搞定 Master 端後就萬事俱備了。

75521923_2668731343173937_8198605415693418496_n

Modbus TCP Master – Razer blade

由於客戶打算自己設計 PC 端程式,而我只是為了打通 Modbus over TCP,因此便在網路上尋找是否有免費的 Modbus 軟體能讓我做打通的測試,結果發現還不少,最終在眾多選擇中找到了一個比較好用的,稱作 simply Modbus

在 simply Modbus 官網上,可從裡面看到許多先前在第二篇提到的名詞 (eg. RTU, ASCII, TCP),我們在這裡要選用 TCP Client 的版本作為我們實驗的媒介,在此附上下載連結:SimplyModbusTCPclient7.1.2

(注意!目前 simply Modbus 的軟體只有支援 Windows,Mac 並不支援喔!)

如上述 slave 的部分中提到,slave 端利用 single bit coil 對 Modbus 註冊 LED 的狀態,由於 LED 也就兩個狀態 ON (HIGH) or OFF (LOW),用 coil 綽綽有餘,而在 Master 端直接 Write Coils 即可控制 LED 亮滅!對原理比較沒興趣的人也不用了解沒關係,因為 simply Modbus 有提供 UI 介面 (雖然頗不好看),選一選就可以了。

下載後打開 Modbus TCP client 會看到下圖:

75439252_484532572407666_3805791433181364224_n

在此僅說明必要操作的部分,其他部分有興趣的可以自行研究。首先,在 IP address 的 textbox 輸入剛剛在準備 Modbus TCP slave 端時,利用 testDHCP sample code 所獲得的 My IP (我的是 192.168.0.20),port 502 是 Modbus default port 不用動,即可按下 CONNECT,若無誤,會如上圖顯示,變為 DISCONNECT,表示確實連上了 Modbus TCP slave (即 UNO R3)。

然而,這個 UI 介面是給 read function 使用的,而我們要做的是 Write Coil,因此得按下如上圖紅圈圈起來的 Write 的鍵,看到的畫面如下:

74912296_2200554526911460_8227985736482160640_n.png

在分享 Modbus TCP slave 時有提到,由於 00001 – 09999 皆屬於 Coil 之有效範圍,所以我在這也做了一個小更改,將原先 default 的 100 改成如上圖圈圈內的 11 (即 First Register 的 textbox),另外,其旁的 values to write 並不為對某個 register 進行值的設定,而是要對多少個 register 進行動作 (詳見 function code 介紹的部分),而在此我只對 number 11 這個 Coil 動作,因此設為 1。

接著就是比較需要注意的地方,如圖可以看到我有圈 minus offset 的部分,必須為 0,若有值會導致位置錯誤而不會動。

最後就是真正的 value to write (最接近底部的圈圈),設定要 write 進 coil 的值,非 0 即 1,在此我設定了 1 準備要寫進去,終於,萬事備齊了,只差按下下方的 SEND 鍵,看到底會不會動。

幸好…能夠如我所願控制 LED 亮滅,Modbus TCP 的部分順利打通了。

75247403_1101022236955717_8264185589163622400_n.jpg

在此給一個小提醒,若是照著上方所有的步驟一步步將 Modbus 打通,在使用 simply Modbus 按下 SEND 的瞬間,LED 就會動作沒錯,不過 simply Modbus 會顯示 error,說是 connection 的問題,這是正常的!因為 arduino uno 內的 sample code 所使用的 library 是 default 一旦收到東西就會先 close 那次的 connection。

談完整個 Modbus TCP 實作的過程後,下篇將介紹本次專案主角的部分 – Modbus RTU 實作,近請期待。

本文同步刊登於 MakerPRO

電子標籤 (二) – Modbus protocol (下)

上篇只有稍微介紹完 Modbus 的種類,接下來就從 Modbus protocol packet format 繼續吧!

Modbus RTU protocol 傳輸資料格式 (packet format)

如同上篇所述,我主要會專注在目前主流 Modbus – Modbus RTU 做介紹,我們就直接來看 master 傳出去的資料以及格式是什麼吧!(Modbus TCP 傳出去的資料會在實作過程中稍微提及)

螢幕快照 2019-08-21 下午3.27.11

圖片出處:Modbus 101 – Introduction to Modbus

這個 format 剛開始看到一定會霧颯颯,因為包括 function code,和使用到的 register type,都屬於 Modbus protocol 自定義的一部份。就一個個來看吧。

Device address 應該不難懂,即 master 要將這整個 packet 傳給哪個 slave,slave address 則可以事先定義好,在我的 use case,RTU slave 就是電子標籤本身,所以可以事前在 MCU 中 define slave address。

而要講 function code 之前,一定要先瞭解 Modbus 自定義的 register type。

螢幕快照 2019-08-21 下午5.07.02.png

圖片出處:Modbus protocol over RS485

嚴格來說,不能稱以上這些被定義的 register 為 register,最多稱之為 “Object",只是有四種不同的形式,即上圖所示的 Discrete Inputs, Coils, Input Registers 和 Holding Registers。

之所以不能稱之為 register 的原因,相信如果有接觸單晶片開發的同好朋友一定知道,因為 MCU (device) 本身內部就是由一堆 registers 的設定,才能做出不同的功能表現。而這裡和實體那些擁有特定 address 的 register 不同,此為軟體模擬,和硬體不直接相關,因此用 Object 稱之會較為準確。

Discrete Inputs 和 Coils 只有 single byte,很明顯就是用來做 I/O 的 digital INPUT/OUTPUT,由表中也可得知此二種不同的 typologies 的差別:Coil 是 R/W,Discrete Inputs 則是只能 Read。

而 Input Registers 和 Holding Registers 皆是可存 0-65535 值 (2 bytes) 的 typologies。最常見的就是 analog 值的應用以及其他客製化所需的資料,像是進入 ISR 的次數等等。

講到這,應該還是很難想像那到底要怎麼使用這些 typologies?slave 的 code 到底要設定哪些東西才能讓 master 在對應的位置抓到或是寫入有效的資料呢?下圖見分曉。

螢幕快照 2019-08-21 下午4.52.04.png

圖片出處:Modbus protocol over RS485

既然已經定義了各種 typologies (Discrete Inputs, Coils, Input Registers 和 Holding Registers),那給不同 typologies 不同的軟體位置定義其實也就搞定了。如上圖,00001 – 09999 就屬於 Coils 的部分,或者也可以用 prefix 的角度,如果是 0 開頭的就一定是 Coils,以此類推做出四種 typologies 的區別,至於 PDU address 的部分,下方會和 function code 的部分一起做解釋。

截至目前,應該對於 register type (以免搞混我這不用 Object 就用原先的講法吧) 有稍微的理解,現在可以進一步前進至 function code 的部分了。

螢幕快照 2019-08-21 下午5.39.15.png

圖片出處:Modbus protocol over RS485

由於解釋完了四種 typologies,上方 related area 這個 column 的內容應該就可以對得起來了。也就是說,我們退回到 Modbus RTU packet format 來看,device address 後便是 function code,假設 master 期望對 slave 的某個 GPIO (eg. LED) 做 ON/OFF 的動作,那 function code 就會植入 5,目的是告訴 slave 我 (master) 要來 “寫” 你的 Single Coil 了。

在講下去之前,要先認識一個詞 – PDU (protocol data unit),整個 Modbus RTU packet 中 PDU 的部分包含 function code 和 data (包含 register number 和 register count),如下結構:

function code
Starting Address HI
Starting Address LO
Count of Register HI
Count of Register LO

而一組 PDU 最多 (maximum) 是 256 bytes。

回到主線,因為不同的 function code 就會對不同的 typologies 做各式的動作,這也是為什麼在講 typologies 時的那張圖,最後一個 column – PDU address,不論typologies 為何,皆是 0000 – 9998。舉例來說,如果 function code 為 1,接下來的 byte 就是填要從哪個 register 開始 (eg. 0x00),然後從開始的 register 數起共多少個 registers (eg. 0x0A),一次最多讀取 2000 的 coils。

至於 CRC error checking 就是大家常見的標準款,沒什麼特別 (雖是這樣說,我還是覺得發明的人真的太強…)。master 傳過去的 packet format 大致介紹是這樣,接下來,slave 在得到 master 的 packet 後回應的部分,我覺得也相當有趣,下方和大家分享。

Modbus RTU slave response

Modbus RTU slave 在收到 master 訊息後,只有兩種情況,正確接收和出現錯誤。

在正確接受的情況下,slave 會回的東西很簡單,exactly 就是 master 傳來的東西,packet 的結構完全和 master 的 packet 相同,包含 function code + data + CRC error check。

比較有趣的是出現錯誤的時候,slave 會將來自 master packet 裡的 function code 的 MSB,變為 logic 1,除此之外,原先來自 master data 位置的內容物會被 Modbus protocol 所定義的 exception code 所取代,如下圖:

螢幕快照 2019-08-26 下午5.05.43.png

eg. 從 master 來的 function code 假設是 3 (0000 0011),若有錯誤產生,slave 丟回給 master 的 packet中的 function code 會是 131 (1000 0011),data 的部分則是上圖中某個數字,端看錯誤的情形決定。

以上快速交代了 Modbus RTU protocol master 和 slave 互傳的資料內容,若有誤歡迎指教交流,下篇就會進入實作的部分,從透過 Modbus TCP 將 PC (master) 和 Ardunio UNO + ENC28J60 (slave) 打通的故事說起。

本文同步刊登於 MakerPRO

電子標籤 (一) – Modbus protocol (上)

我又跳票了。

Johnny Five clock 只進行到 part 1,後來的確有對 Johnny Five 繼續把玩了一番,REPL 是否會被綁架的實驗做完了,不過 STM32F7 才研究到一半,我想重新洗掉 default 的 os (應該是 mbed),灌自己的 linux distro,一邊了解整個 embedded os 的流程,一邊精進自已 linux command 的能力,不過前些日子還沒完全破關,未能整理成完整的文章,接著就又必須向現實低頭,停止玩耍,賺點生活費。

估計又要一陣子才能繼續 Johnny Five 和 F7 的動作了,先讓我欠著我一定會寫成文章。

這次到手邊的案子是客戶希望利用他們自家設計的硬體實作 Modbus 通訊,簡單來說,整個架構就如下圖:

68610251_2414983268608565_922549259964776448_n.png

圖片出處:Fun and Easy Modbus RTU protocol

基本上這張圖就完全是這次要實現的系統架構。(不要急,等等下面提到看起來很可怕的東西我都會再解釋。)

PC (master) 透過 Modbus TCP 經由 Ethernet 給特定 IP 的 device (slave) 訊息,這裡的 device 在我實現的過程中我是利用 Arduino UNO + ENC28J60 Ethernet module。

而這個 Arduino UNO 又會被拿來當 Modbus RTU (remote terminal unit) 的 master,而電子標籤就是在這整個架構下做為 Modbus RTU 的 slave。

兩段都是利用 RJ45 連接,只是第一段是真的 Ethernet,第二段則是利用 RJ45 來實現 RS485 通訊。之所以要用 RS485 是因為客戶希望可以連接多個電子標籤,畢竟我們都知道 RS485 相對 RS232 更穩定。

我會從 Modbus 的介紹開始,接著實作過程,最後介紹電子標籤所使用的 MCU 作為此系列的結尾。

Modbus Protocol

Modbus protocol 事實上行之有年,早就不是個新東西,以往被應用的場域就屬 PLC 最為廣泛,只是菜鳥如我當然是第一次聽到這個東西,因此決定來研究研究。由於篇幅,這裡會省去所有歷史的部分,有興趣的同好朋友可以自行 google。

首先,Modbus protocol 走的是最常見的 master / slave 通訊架構,值得注意的是,在 Modbus protocol 中 slave 是無法主動發起通訊 (requirement),也就是說,slave 一定要等到收到 master 的訊息才有機會送出資料。

接著開始解釋文章上方,在敘述這次要實現的系統架構時所提到的一些名詞,像是 Modbus TCP 和 Modbus RTU,事實上這些的確都屬於 Modbus 的範疇。簡單來說,Modbus 共有三個種類,分別為:

  1. Modbus ASCII
  2. Modbus RTU (remote terminal unit)
  3. Modbus TCP

** 題外話,如果想瞭解 RTU 和 PLC 差異的同好朋友可以參考此文章

顯然第一項並沒有提到過。Modbus ASCII 其實是 Modbus 剛被設計出來時,這個通訊協定傳輸資料使用的編碼方式 (ASCII encoding),至今已經鮮少人使用,不特別介紹。

目前最常見,也最主流的則是使用 binary encoding,加上 CRC error checking 的 Modbus RTU,搭配的 interface 就是大家所熟悉的 RS232,RS485 或 RS422。當然,這其中最受歡迎的還是 RS485,畢竟他的表現備受肯定:理論上如果 baud rate 都相同,在沒有 repeater (就想像是一個中繼站即可) 的情況下,最多能連貫 32 個 devices,而在有 repeater 的情形下更是可以連貫 247 個 devices,距離遠達 1200 meters。

由於 Modbus RTU 的主流,接下來所介紹的 Modbus 資料傳輸格式 (format) 以及內容 (content) 都是環繞在這個種類的 Modbus,即 Modbus RTU protocol。

最後一個 Modbus TCP,嚴格來說,他並不是 master / slave 的通訊架構,而是 client / server,雖然小弟我覺得根本沒有太大差別,而且的確網路上很多資源都顯示,可以將 client 比擬為 master,server 則比擬為 slave (直覺來想可能會覺得我打錯了,不過並沒有)。

Modbus TCP 和 Modbus RTU 最大的差別在於兩個部分。

第一,Modbus TCP 會在把 Modbus RTU 的一段 chunk of data 再封裝 (encapsulate) 一層,變成 TCP packet,作為傳輸的單位。

第二,Modbus TCP 所使用的 error checking (eg. checksum) 是使用 TCP 的,不是使用 RTU 本身的 (這也很直覺,畢竟都用 TCP packet 傳了)。

另外,Modbus TCP 是可以同時運行多個 client (master) 和多個 server (slave) 的。

由於篇幅,被迫先停在這裡不然會很奇怪,下篇開始會從 Modbus RTU protocol packet format 開始解釋。想先瞭解更多的朋友,Youtube 上有個頻道 RealPars,小弟我認為他講解的 Modbus 非常清楚,可以參考看看,以下為影片連結:What is Modbus and How does it work?

下篇見。

本文同步刊登於 MakerPRO

Nodebot – Johnny Five servo clock (part 1)

繼上篇文章所述,雖然 STM32F4 已經到我手邊 (好想快點開始玩,把 Linux 弄上去架個 server 之類的練練功,不然該忘的也差不多都忘了…),不過答應自己要做的東西得真的做出來才行。這篇文章就是要分享利用上次提到 Johnny-Five node package 做的一個小時鐘。

這其實是我在國外網站看到的一個小專案,在沒有看到相關 C/C++ repo 或是 source code download link 的情況下,決定自己手幹一個。另外,由於之前太懶,做的東西都沒放上 github,現在也懶得整理過去的專案 (因為真的太多了…),不過從現在開始的專案都會丟上去 (誰叫用學校帳號辦這麼爽),所以有興趣的同好朋友們可以自己抓下來玩玩。

在此附上 github repo 連結:https://github.com/4D616B6572/Johnny-Five-clock

進入主題,就先從硬體開始吧。

硬體周邊需求

  1. Arduino UNO x 1
  2. 麵包板 x 1 (非必須,也可以很霸氣的直接焊起來)
  3. SG90 伺福馬達 x 7 (因為我想做成 7 段顯示器的樣子)
  4. 跳線 -> ∞
  5. 各種紙板,泡綿膠等美工用具

如標題所示,這只是本專案的 part 1,上方所列的硬體需求只是單一個數字的實作,既然要變成時鐘,那少說也得準備四個數字,不過硬體方面就得再思考要怎麼準備,畢竟 UNO 腳位有限,我也不可能拿四塊板子,除非我準備用來和 UNO 溝通的主機 (eg. STM32F4) 有這麼多 USB 埠口,屆時想到會在下篇文章分享,若有想法的同好朋友也歡迎提出討論。

軟體執行操作

事實上,整份 code 不到 100 行,而且也不難,只是要一直嘗試一些原先在 Arduino 上很直覺的事情但在 nodebot 上卻不是如此,畢竟 nodebot 在操作上本身就跟一般單晶片專案的思維不太一樣,講得有點籠統,不過沒關係下方會一併說明。

在整個過程中,不知道是我 google 能力還有待加強還是真的確實如此,我在網路上並沒有找到太多有關 Johnny-Five 實作實際專案的文章,多數都是如何使用、起頭、點燈之類的初階分享。

因此我會針對和 Arduino 真的很不一樣的點做說明,其他有寫過 nodejs 的同好一定看得懂程式碼本身在做的事情。

Johnny-Five 最重要的兩件事,第一,new 一個 five.Board() 的物件稱之為 board,再來 board.on (“ready", [callback]); callback function 在 “ready” 後被執行;第二,REPL (read-eval-print-loop),是重點中的重點,是 Johnny-Five 能讓 nodebot 活得像一般單晶片一樣的主要媒介

相信有看過介紹 Johnny-Five 套件相關文章的朋友,一定都對第一個重點不陌生,不然也不可能點起 UNO 版上的 LED。至於第二個,應該相對不熟悉,而這也我主要想分享的部分。

board.on (“ready", [callback]); 如同 Arduino 裡的 void setup(),兩者都是做些初始化以及 “一次性的操作 (eg. call function)”,那 REPL 是不是就相對應應該會是 Arduino 裡的 void loop()?目前我覺得不是,之所以說 “目前” 是因為我覺得以我現在對 Johnny-Five 的理解,我對於在此套件中要做到像一般單晶片 eg. Arduino void loop() 不斷執行定義在其內的所有 function 的可行性 (包括 ISR 的註冊) 打了一個大問號。

因為在 REPL 內,雖說稱作 loop,它也只不過是去聽某個 function 是否有被 call,雖然也可以在此註冊類似 ISR 的功能,但它本身並不會主動去執行某個程式,除非你呼叫它也就是說,若在進入 repl 後,不打出某個 function 按下 enter,nodebot 是不會理你的,如同我放在 github 上的 source code,在 this.repl.inject() 中有定義一個 function time,當在 repl 中打下 time() 才會回傳現在時間的秒數

舉個簡單的例子來說明,在一般的 Arduino 環境內如果想要讓 LED 不斷閃爍,從最基礎的 Blink 範例程式可以知道,只要 HIGH 和 LOW 加個 delay 交替放在 void loop() 中執行即可,但在 nodebot 我目前並不曉得要怎麼做到這件事,這次的專案是因為剛好我要抓的是時間,所以我可以註冊一個 setInterval,讓他每秒回傳一個數字回來,但如果不是固定時距,而是一個特別的 pattern 那豈不是糟了?

另外上方提到 nodebot 可以註冊類似 ISR 的功能,這件事對於有寫 js 的同好朋友一定也不意外,畢竟 js 就是 event driven 的語言,js 的 callback function 就是在處理 “某件事發生後”,去執行一個相對應的行為,而在 firmware 就像是 button.on(“down", [callback]),指 button 被按下時執行 callback,非常直覺且合理。

想要解決 nodebot 不能真正做到 loop 的行為,講到這不知道有沒有人跟我一樣有一個暴力的想法:那就寫一個 function 裡面有個 while(1) 不就得了?這樣我只要在進入 repl 後,執行某個 function 一次,他就如同 Arduino 的 loop() 一樣,能夠跑到天荒地老?

照理來說好像可以,但我直覺告訴我應該不行,感覺 repl 會直接被綁架,不能動作,待我實驗,下回揭曉!

最後就用 nodebot 時鐘單一數字成果的 GIF 作為本篇結尾吧!

本文同步刊登於 MakerPRO

JavaScript 統一世界 – node.js x Arduino

最近一次比較成型的專案距離現在已經有一段時間,由於在自己的心理狀態以及學校課業的拿捏 (花多少時間在課業) 上,一直沒有給自己一個好的交代,無法說服自己。幸好,這些困擾許久的煩悶,最近因為自己的調適加上下定決心嘗試,而有了個明確的方向。

包括上篇文末所提的全新專案 (剛開始不久…),以及我曾講過很想做的專案我都記在心中,雖然不知道多久能生出來不過我想我會盡力找到時間,畢竟興趣才是我的動力,不為什麼只是單純開心。

雖然專案還沒有個底,原本以為不知道可以幫自己紀錄點什麼,不過認真想想能寫的還是不少。

最近幾次和 Bird 聯絡剛好提到我打算用 STM32F4 來開發我的專案,加上聊到 RTOS,想要在 rpi 玩玩 Embedded Linux (我也想搞在 STM32F4 上看看),Bird 就說要幫我淘個板子讓我玩玩 (鳥爸無敵,太開心拉),不過板子還沒來,既然還沒來也沒東西可以分享,不如就來說說近期的發現吧 – 利用 node.js 進入硬體的世界。

JavaScript 統一世界

事實上也不算是近期的發現,隱隱約約早就知道 python, javascript 能用來寫 Arduino,不過從來沒試過,也不知道這個東西的能耐到底在哪。

因為過去就有在接觸 node.js,加上這學期的課程剛好修了網路服務程式設計,外加接案,寫 js 的時間已經超過了 C/C++,因此突然有個想用 node 去會一會 Arduino 的想法,而這一玩不得了,有種發現新大陸的感覺,也激發了我的新想法。

我算是 3/4 個 js 的愛好者,跨足 web programming,接著後起的 web app,加上較少人知道的 firmware 領域,js 基本上可以說是無所不能 (不論其他例如效能、穩定性的比較)。網路上討論 javascript x IOT 的文章也不在少數,開源的 library 數目更是多到讓我有點驚訝 (如下連結)。

文章參考:10 Javascript IoT Libraries To Use In Your Next Project

而本篇會針對 Johnny – Five 這個目前最受歡迎 javascript robotics framework 做分享,不會是以教學文的方式呈現,因為網路上可以找到一卡車。

Johnny – Five

螢幕快照 2019-04-21 上午2.38.07.png圖片出處:https://github.com/rwaldron/johnny-five

其實早在 2010 年網路上就開始有人提出 NodeBots 的概念 (我真的是孤陋寡聞…),從 node-serial 開始往下長,長成現在巨大的模樣。Johnny – Five 只是其中一個成員。

目前 johnny – Five 不論是硬體相容性,API 完整度,技術討論社群都是眾多 framework 中表現最亮眼的,若有興趣的同好朋友可以到其官網看看。

講到這裡,若是先前只有在 Arduino IDE 上寫過 C/C++ code 的朋友想必現在會一頭霧水,心中疑問:“蛤,那要怎麼讓電腦認得板子?”

事實上這個懷疑是對的,板子並不是 USB 插上電腦就可以動,要使用 johnny – Five 之前,必須先做些前置動作。如果用 Arduino UNO 作為例子 (實際上 Arduino 系列被支援的開發版都可以這樣操作),必須先打開 Arduino IDE 找到範例裡的 StandardFirmataPlus 並燒錄 (upload) 進去,這樣 Johnny – Five 才可以被使用。

之所以需要此步驟的原因是因為,要讓電腦上 (主機 host computer) 的軟體能夠 “認得” 並 “操控” 開發版 (MCU),這之間是透過一個叫做 Firmata 的 communicating protocol 做溝通,也就是 StandardFirmataPlus 這份 code 做的事情。

主機上軟體的訊息 (程式碼),包括使用哪個 USB port,baud rate 等等就可以由 node-serial 傳給 Arduino UNO。

如果說軟體的第一步是 print “Hello World!",那 MCU (SoC) 第一步就是讓 LED 燈亮!

燒錄完後接著,直接先讓 LED 亮起來再說!

螢幕快照 2019-04-21 下午11.55.36
此圖即為將 Johnny – Five github 上的範例 glone 下來在我的電腦上執行的結果。

程式碼的部分應該不是什麼大問題,重點有趣的是下方 terminal 顯示的訊息,執行 node strobe.js 後有個 REPL (Read-Eval-Print Loop),顧名思義是個 “讀取 – 求值 – 輸出的循環”,是不是有一種似曾相似的感覺?我個人覺得很像 Arduino 的 firmware code 燒錄後,利用 serial console 下 AT_command 的感覺,只是 node 弄得比較直觀好懂一點 (註 1)。

看完軟體的部分,再看看 UNO 版。可以看到 LED 13 的確每 1000 milliseconds 閃一次外,也可以看到 LED 13 旁的 RX LED 也以一個固定的頻率在閃爍,表示電腦一直有在送訊息給 Arduino UNO,如下圖:

57382416_1355530074600454_3777324978353995776_n.jpg

講到這裡,我自己其實一直存在著一個疑惑,既然 REPL 得透過 node-serial 才能和板子溝通,那一但 USB 拔了不就什麼都沒了嗎?確實,不像 Arduino 一旦燒錄進 flash,只要通電,都還是執行當初被燒進去那份程式碼的行為,這也是我覺得 nodeBot 非常大的一個缺點,即便同份程式碼,每次上電就得重新 setup 一次,而且得隨時和電腦溝通,所以他是離不開電腦的…。

若說要做 rpi 相關的應用倒是無妨 (畢竟 node.js 也可在 Linux 上運行),但像前一個專案的應用 (A8106 無線調光) 就不可能使用 nodeBot 執行了…。

以上是目前我個人對 nodeBot 和 Johnny – Five 理解,在 STM32F4 和 rpi 到手前我想我會用 Johnny-Five 搭配 express (node.js web framework) 做出點小東西來過過乾癮,屆時在寫篇文章紀錄吧!

註 1:有興趣但不想自己 setup 一些東西的朋友,又或是沒有想法想先玩玩看別人寫好的範例程式碼可以參考這個網站:node-ardx (比 johnny – Five 自己 github page 所提供的 git repo 還要豐富些) 所提供的 package,除了在 REPL 中有自定義的變數,讓使用者可以動態的和所定義的變數互動 (eg. LED,執行 led.on() 等等),裡面也有其他詳細的應用案例介紹,一樣,git clone 下來就可以直接執行了。

文章參考:NodeBots – The Rise of JS RoboticsJohnny – Five github repo

本文同步刊登於 MakerPRO

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 (或也可用硬體直接控制),而接收部分的程式碼如下:

unsigned char tmpbuf[128];
void light_condition_receiver(void);
void RxPacket(void);
void main(void)
{
//hardware initialization...
if(P3_5)
{
while(1)
{
// transmitter code...
}
}else
{
while(1)
{
light_condition_receiver();
}
}
}
void light_condition_receiver()
{
// the variable counts for error bit...
RFLIB_StrobeCmd(CMD_RX);
Delay10us(1);
while((RFLIB_ReadReg(MODEC1_REG) & 0x80)==0x80); //wait receive completed
RxPacket();
// do the light control here...
// status = tmpbuf[5]...;
// state = tmpbuf[6]...;
// if...else if...bunch of code...
WriteFIFO(); // write data to tx fifo
RFLIB_StrobeCmd(CMD_TX); // entry tx
Delay10us(1);
while((RFLIB_ReadReg(MODEC1_REG) & 0x80) == 0x80); //wait receive completed
}
void RxPacket()
{
unsigned char i,recv;
for(i=0; i<8; i++) // transmit only 8 bits in my case.
{
recv = RFLIB_ReadReg(RXFIFO_REG + i);
tmpbuf[i]=recv;
//error checking (bit, byte)...
}
}
view raw RF_recv.c hosted with ❤ by GitHub

提個外話,相較於前篇把 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 秒才算有效。

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

#define Button1_SW1 P3_2 // P3_2 is one of the input button pin
unsigned int button1_time_cnt; //global variable
void Timer0ISR (void) interrupt 1 // trigger per 1 ms
{
TF0 = 0; // Clear Timer0 interrupt
TH0 = (65536-t0hrel)>>8; // Reload Timer0 high byte,low byte
TL0 = 65536-t0hrel;
button1_time_cnt <<= 1 ;
button1_time_cnt |= Button1_SW1;
//there are also lots of functions below
//just skip that part...
}

上方的 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 的需求,以下用部分程式碼來解釋:

#include "globalvar.h" // button2_time_cnt is defined in the header file
#include "function.h"
#define P3_5 1 // if P3_5 is 1, it will be the transmitter
#define period 1000
unsigned char Packet_Tx[8]; // if initialize, this array will be a "constant", which can't be modified in code.
void WriteFIFO(void);
void WriteFIFO(void)
{
unsigned char i;
for (i = 0; i < 8; i++)
RFLIB_WriteReg(TXFIFO_REG + i, Packet_Tx[i]);
}
void main(void)
{
//hardware initialization
//timer0, rf, other settings are done here
if (P3_5)
{
while (1)
{
if (button2_time_cnt >= period)
{
//something here to store the state about the num of light are on
for (i = 0; i < 8; i++)
{
Packet_Tx[i] = 0x11 * (i + 1); // this can be modify, like specify the first two bytes to be the info of ID...
// and third byte will be the state of light...
}
WriteFIFO(); // load data in specific register and wait to transmit
RFLIB_StrobeCmd(CMD_TX); //entry tx & transmit
Delay10us(1);
while ((RFLIB_ReadReg(MODEC1_REG) & 0x80) == 0x80); //wait transmit completed
TimeoutFlag = 0;
RFLIB_StrobeCmd(CMD_RX); //entry rx
Delay10us(1);
while ((RFLIB_ReadReg(MODEC1_REG) & 0x80) == 0x80 && TimeoutFlag == 0); //wait receive completed
if (TimeoutFlag)
{
RFLIB_StrobeCmd(CMD_STBY); //exit rx mode
continue;
}
RxPacket();
Delay10us(1);
}
}
}
else
{
//the part of receiver
}
}
view raw RF.c hosted with ❤ by GitHub

從 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