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

提個外話,相較於前篇把 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