上面圖片沒轉過去可以點這裡
前言
因為工作的關係,目前正在學習Python GUI的設計,剛好手上有之前買的STM32開發版,於是就翻了之前買的小玩具來接下去玩。
由於我還不太了解ChibiOS的使用方法,於是我開始求助ChatGPT。嘗試用問答的方式來寫出控制WS2812的程式。
沒想到在產出程式的那一步,ChatGPT因為產出錯的程式碼而導致後續無法運作。在後面有邀糗ChatGPT重新產生一遍程式碼,但光靠我現在的知識量,很難讓他產生出正確的程式。
而且想到要花更多時間邊Debug邊找出正確的寫法,可能這個連假就這樣子過去了…QQ
後續我改變想法:先使用Arduino Core嘗試寫出可以動的Demo,先把基本的輪子(框架)造出來,後續要移植到其他的生態系(ex: ChibiOS)會方便許多。
使用元件
Arduino Uno R3
這是義大利原廠在2018年出的版本,但其實功能上沒什麼改變,所以用藍色的那一塊也可以。WS2812B
我買的是這種環形的LED,如果要用長條型的,一樣可以使用。
WS2812控制方式
WS2812B的控制方式是採用OneWire發送訊號的方式來控制LED內部暫存器,進而讓LED發出對應的光芒。
但因為他的波形非常的短暫,就可以想到的作法,操作方式會有很多種:
- Firmware Delay(效率會不好,建議僅用在初期開發)
- Timer 計時器(效率佳)
- Timer + DMA(沒試過,但相較於前兩個應該會好很多)
- Etc…
當然,現在有很多大神/團隊把上面複雜的操作方式包成Library (函式庫),如果要快速成型的話,可以直接到Github / Google搜一圈,找一個符合你的需求來使用即可。
我使用的是FastLED這個函式庫,在這裡感謝製作這個函式庫的大神們。
架構圖
- 使用者會透過GUI調整WS2812的顏色與各種燈泡的亮滅,GUI會透過UART方式傳送「指令」至Arduino Uno / 其他MCU,再透過封裝好的Library控制WS2812。
- 指令方面,目前先規劃為6碼(意即6個8位元的資料)來當作GUI與Uno的溝通橋樑。後續會再根據需求擴充。指令格式如下:
Arduino 程式
初始化
1 |
|
- 前三行會引入需要的函式庫,讓編譯器知道我們後續需要的函式。分別為:
- Arduino Core函式庫
Arduino.h
- 以及FastLED函式庫
FastLED.h
- 標準函式庫
String.h
- Arduino Core函式庫
- 之後會透過
#define
定義三個巨集,分別是:- 指令的長度
SERIAL_BYTE
- WS2812接的PIN腳位置
LED_PIN
- 串接WS2812的個數
LED_NUM
- 指令的長度
- 再來會宣告四個變數,並將它們都初始化:
- 取代
delay()
的millis(詳細在這裡有介紹過) - 指令字元陣列
serial_bytes
(後續都是HEX進行操控,用char
即可) - 一個操作鎖
set_led
:作用是在控制LED的時候不會因為其他指令送進來而被干擾 - CRGB結構的陣列
leds
:裡面記載每顆LED的顏色配置。
- 取代
setup()
1 | void setup() |
- 首先使用
FastLED.addLeds()
增加LED配置,在泛型裡面填入LED的型號,LED的腳位與顏色定義。之後在括號裡面填入接WS2812 的腳位。- 為了怕每次上電都會有前一次的記錄造成顯示不正常,在這裡我使用
FastLED.clearData()
清掉記憶體內的值,並使用FastLED.show()
顯示新的狀態。
- 為了怕每次上電都會有前一次的記錄造成顯示不正常,在這裡我使用
- 之後使用
Serial.begin()
打開UART通訊功能,括號裡面填入你想設定的鮑率。- 由於會很快速的輸入指令,為了怕後續沒收到,這裡使用
Serial.setTimeout()
設定最久的讀取時間為100ms。
- 由於會很快速的輸入指令,為了怕後續沒收到,這裡使用
剛買的WS2812沒意外都會是GRB方式配置,在撰寫這部分的時候一定要去查看該LED的應用手冊/說明書。
loop()
1 | void loop() |
- 這邊我使用自己事先寫好的
ReadSerial()
讀取指令,並且回傳一個布林值,代表有無收到新的指令。 - 如果有收到指令,那會跳至
SetupLED()
進行更改LED的操作,以此循環。
ReadSerial()
1 | bool ReadSerial() |
- 使用
Serial.readBytes()
讀取透過UART輸入進來的資料- 如果接收到的資料長度等於事先設定的長度,且最後的校驗碼為正確,則會清除Serial裡面的Buffer,並且回傳
True
,反之回傳False
- 如果接收到的資料長度等於事先設定的長度,且最後的校驗碼為正確,則會清除Serial裡面的Buffer,並且回傳
- 在這裡用到之前使用Timer計時來取代Delay的技巧,讓執行更有效率(詳細在這裡有介紹過)
SetupLED()
1 | void SetupLED() |
- 這裡判斷接收進來的資料欄位,透過
CRGB()
去設定每個LED的顏色,之後使用FastLED.show()
顯示出對應的顏色。
GUI 程式
GUI我使用Python與Flet框架來撰寫,目前的功能還算簡單,畢竟還僅僅是驗證這些功能可不可行。
其中讀取設定檔的程式碼如下:
1 | start = time.time() |
在這裡也同樣用到上面的講過的技巧,利用Timer來去讀取秒數並做對比,之後再去讀取設定檔的指令並送給控制器作LED的輸出。
設定檔格式如下:
1 | [ |
之後也會嘗試實現編輯器的功能:若有表演的需求,可以對准MP3的時間點,在相對應的時間來變化LED的顏色與亮度。
過程與問題
ChatGPT
在一開始嘗試使用ChibiOS的時候,發現到ChatGPT產生出來的DMA程式段其實無法正常使用。由於產生出來的程式貌似有些老舊,需要經過一定的調整,將列舉的變數調整成目前ChibiOS版本定義的才可讓編譯器成功編譯。
1 | void ws2812Start(WS2812Driver *ws2812p, const WS2812Config *config) { |
但不是說ChatGPT就是垃圾,ChatGPT倒是幫忙我在了解並學期其他領域的時候有一定的貢獻。雖然找出來的東西有些還是有點問題,但只要Google一下,基本上都可以找出正確或者可以使用的解答。
後續我先放棄ChibiOS的方法,先嘗試使用Arduino Uno + FastLED的方案,讓LED亮起來再說。
操控到一半,LED卡住
目前GUI的指令傳送部分沒有做優化,只是單純的指令傳輸,但GUI使用多執行緒方式實現。若動作一快,很容易讓指令傳送單元卡住。
後續我在傳送單元內部多寫了一個變數self._isoperate = False
當作目前有在傳送指令的一道鎖。
1 | def sendSerial(self, |
當前端調整拉桿時,若後端的指令還在傳送,則不會做傳送的動作,等到上一道指令都傳送完畢,才會進行下一次指令的傳送。
但,這不會是最好的方法,因為可能傳送的會是一連串讓LED漸變顏色的指令,若發送的動作一快,很容易因為指令還沒傳送完畢,而讓LED變化的不是很順暢。還有很多優化的空間。
結論
當初這個專案只是某天突然想利用ChibiOS來操控看看WS2812,雖然過程中因為ChatGPT正常發揮,以及我對ChibiOS還不是這麼了解,後續改用Arduino Core來實現。但目前已經可以正常的可以根據我想要的模式來讓WS2812呈現對應的亮度。

Comments