2015年4月24日 星期五

用 AVR 控制 WS2812 LED



最近看到這款新型的 LED
LED 只有亮和暗兩種狀態, 所以如果要半亮或微亮就必須用 PWM 快速的開關來產生半亮效果
一顆 LED 不難做, 但是如果要彩色那就是個機車的問題
彩色 LED 是三種顏色的 LED 做在一起, 也就是紅藍綠三色
然後透過 PWM 改變三種顏色的亮度來產生多種顏色, 三種顏色就要三通道 PWM
PWM 不一定要硬體 PWM, 可以用一個 timer 加上無限迴圈一直監看數字然後設 GPIO
但是有幾通道就要有幾隻腳, 一個燈就要三隻, 多燈共用又有亮度問題
若分到的時間太少就不夠亮, 這問題是很機車的
所以 WS2812 這顆只需要一隻腳就能串接無限顆的彩色 LED 簡直是救星XD
不過因為只有一隻腳, 不像 SPI 那樣有同步 clock, 因此時間就要算得很準才行



這顆的訊號傳輸是這樣的:

送一個表示 0 的 bit, 先將信號腳拉高 350 nS, 然後拉低 800 nS
表示 1 的 bit 則是拉高 700 nS 後拉低 600 nS
然後送出 24-bit 即設定完成一燈的顏色, 綠色先送, 然後是紅色和藍色, 各顏色的高位元先送
所有燈都送完後拉低等 50 uS 顏色值就會套用到燈上, 若一直送都不等, 顏色將不會改變!
WS2812 會認為還有資料要傳而不會改變狀態

這信號單位是 nS, 10^-9 秒, 速度要求蠻高的, 也就是 CPU 時脈最好要有 10MHz
不過對於一個指令要多個 clock 才能完成的處理器有可能會惹麻煩
這次我試了兩種 MCU: atmega8 和 cc2540
atmega8 的 AVR 系列大多數指令都是一個 clock 就能做完
不過以我的 11.0952 MHz 來做還是有些吃緊
如果用迴圈送每個 bit 就會不夠快, 還在想該不會要動用組合語言
後來找了一些資料發現不用, 用 C 還夠用
作法是迴圈展開, 像這樣:

m = 0x80;
for(i=0; i<8; i++){
    if((b & m) == 0){
        PORTB |= (1<<0);
        ... // 350 nS
        PORTB &= ~(1<<0);
        ... // 800 nS
    }else{
        ...
    }
    m = m >> 1;
}

m 是遮罩, b 是要送出的 byte, for 迴圈會包含遞增, 判斷, 然後跳躍
(add, branch, jump, 請參考計算機組織書籍XD)
這都是相當昂貴的指令, 也就是耗時間, b & m 那裡也會花時間
如果是沒有最佳化的編譯器 PORTB |= (1<<0) 那裡也很恐怖
先把 PORTB 資料放入暫存器, 然後做 OR, 再寫回
如果照這樣做下去 10MHz 根本不夠快, 這堆做完就 1000 nS 了
可是代表 0 的 bit 只能等 350 nS
所以我的寫法是 for loop 拔掉, 就手動輸入 0x80 0x40 0x20 ... 等
b & m 那裡沒門, 留著, PORTB |= (1<<0) 則沒關係
有興趣可再編譯後打開 lst 檔, 可以看到:

27:main.c        ****     WS_OUT_BYTE(g)
22                       .loc 1 27 0
23 0000 6623              tst r22
24 0002 04F0              brlt .L2
25                       .loc 1 27 0 is_stmt 0 discriminator 1
26 0004 C09A              sbi 0x18,0
27                   /* #APP */
28                    ;  27 "main.c" 1
29 0006 0000              nop
30                     
31                    ;  0 "" 2
32                    ;  27 "main.c" 1
33 0008 0000              nop
34                     
35                    ;  0 "" 2
36                   /* #NOAPP */
37 000a C098              cbi 0x18,0
38                   /* #APP */
39                    ;  27 "main.c" 1
40 000c 0000              nop

設定 PORTB 的指令只有一條, 分別為 sbi 0x18,0 和 cbi 0x18,0
以前 avr-gcc 要用一個 macro 才能做, 新版似乎已經內建, 所以沒這問題
如果要搬移到其他平台需要注意這, 別的平台不一定會轉成 set bit 這種指令
然後 if((b & m) == 0) 這判斷式必須是這樣
理由是代表 0 的 bit 只有 350 nS, 讓它緊跟著判斷式可以不用跳躍
如果放到 else 後面就會經過跳躍, 會多一條指令, 當處理器慢一點時像是 8MHz 就會在邊緣
寫完後接著就拿示波器多調整幾次, 要讓誤差落在 150 nS 以內 (datasheet 有寫)
這樣能動了, 原始碼:

ws2812.zip



本實驗室的 WT-03


信號線只有一條, 接在 PB0 上


很刺眼!


降低 ISO 以保留 LED 中央的細節XD
這比較接近眼睛看到的


接著我換試 cc2540, 德州儀器的 BLE 平台

驗證階段, 外觀有點噁心不要介意XD
它是 3V 的 MCU, WS2812 至少要 4V 以上才會亮, 所以 IO 要準位轉換
用一個 AND 閘即可, 近代 AND 閘可以有 100MHz 的速度, 沒問題
但是 cc2540 有問題!
它是顆 32MHz 的 8051, 大多數指令也都是一個 clock 就完成, 但是!
它的判斷和跳躍感覺很慢, 要好幾個 clock 才能完成
我檢視 IAR 編出的組合語言指令數都很少
判斷後也有 set bit 功能, 但是示波器量出來就是運算時間都 1000 nS 上下
後來翻德州儀器的使用手冊發現這段


和時間有關的迴圈可能要修改, 雖然沒明說但應該就是這兩指令可能明顯較慢
附上程式供參考:

ws2812.c 這程式僅供參考, 它不能用!

所以跑出來就變成...

雖然我送的是彩色可是只有三個亮, 而且是全白的
所以時脈高不一定能跑, 雖然也有可能是我沒寫好
但是那指令已經少到只剩幾條還是點不起來我也沒轍了
或許有高手能點起來, 但若換作我可能還是再多打一顆 MCU 上去唄

21 則留言:

  1. 期待看到能把圖片放到一維燈條上XD

    回覆刪除
    回覆
    1. 組成圖像麼, 噢......那有點貴XD
      不過如果要圖像還是一次八條並行比較好, 速度較快
      說不定還可播 video ! (那貴爆了XD)

      刪除
  2. 恩.....我目前使用 ATMEGA8 (外部晶振 12M) 搭配 LTC106-F8 可程式 LED 送入的訊號皆為1且用示波器看過訊號是1(1300ns) 0 (350ns) 但是其並不會亮
    開頭與結尾都有 50us 的 Rst 訊號 想請問一下程式如何修改? 有關這款LED的範例有點少...
    輸入規格如下
    T0H 0.35us +-150ns
    T0L 1.36us +-150ns
    T1H 1.36us +-150ns
    T1L 0.35us +-150ns
    Rst 50us (訊號為0)


    Rst訊號是必須先拉高一個 nop 再拉低 50us 再拉高嗎?

    回覆刪除
    回覆
    1. 作者已經移除這則留言。

      刪除
    2. 我的上一回覆有錯, 刪除!
      我以為是 WS2812, 你選的是別家方案...
      若要修改就是改 NOP 的數量, 找到我程式的這裡:

      #define WS_T0H NOP;NOP;
      #define WS_T0L NOP;NOP;NOP;
      #define WS_T1H NOP;NOP;NOP;NOP;NOP;NOP;
      #define WS_T1L NOP;NOP;

      改變 NOP 數量即可改變信號長度
      這個我沒有範例可提供, 我沒有硬體沒法測

      刪除
    3. 結果我發現是給我Datasheet的人把接腳完全弄反(還手動把廠商的接腳畫掉自己寫) 正負DIN DOUT都反是要怎麼亮啦...
      現在會亮了 不過白色不太OK 好像DOUT要接VDD才會是白色(否則藍色恆亮).. 不然就是我產生的訊號有問題
      控制顏色的話你這個程式對我來說太慢 可能還要研究一下

      刪除
    4. LED 沒燒掉算很幸運了, 要是燒了, 拿故障的 LED 永遠也試不出來
      產生彩虹顏色, 每隔一秒變一色, 全白和全暗也要測, 然後試接 DOUT 和 VDD
      如果兩種接法有一種是全部顏色都正確的應該就是那了
      如果兩種接法顏色都和預期的不同......請再回去查訊號問題

      即使是直接對廠商都有可能拿到錯的資料了
      一般散戶透過賣家拿到錯的也是很常見的 (汗)
      總之, 有找到方案就好

      刪除
  3. 最近要用他,然後看到一些資料...
    http://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
    這篇看起來的總結是,datasheet僅供參考w
    可以猜一下2812的內部作法:訊號上拉時觸發事件/中斷,在觸發後大約500ns對訊號取樣一次,取到1就是1,反之為0。套用動作看起來最短只要6us就會發生(真不精確...不是說好50us嗎),然後如果讓訊號一直保持1,那不會發動套用效果,也不會再觸發下一個bit的正緣中斷
    換句話說,只要正脈衝夠短在500ns內(不要壓線),那就是0,否則是1,之後這個bit待在0的時間不能到達觸發套用動作的程度(就是不能一個bit傳完以後超過6us才傳下一個bit),那這樣時間要求就變鬆很多了

    接著是另外一篇
    http://hackaday.com/2014/02/04/ws2811-spi-driver-using-one-transistor-and-passives/
    這描述了怎麼用簡單的電路從spi訊號生出符合要求的2812訊號。然後可以參考上一篇的時序要求來設定spi的時脈...我想設定個500khz就好,不用多費力氣去生800khz的clk來符合要求,但是因為送1時的判斷時點問題,所以1mhz也許不能用

    第三是今天拿到的簡測
    說明書寫說邏輯1必須是0.7v+電壓,如果供應5v,那高準位必須達到3.5v......
    ...結果我今天直接用3.3v的mcu推就會動啦wwww(led v+接5v,訊號只到3v3)害我之前在想說要加什麼來解(有人把第一顆led串二極體,降壓到4.3v工作,讓邏輯1的電壓降低到3v就能判定)

    所以今天玩的感覺是,自己測一輪最好,datasheet僅供參考w

    對了,我的for迴圈是寫 for(m=0x80;m!=0;m=m>>1) 可以少用一個 i(真懶w

    回覆刪除
    回覆
    1. 多謝資料!
      老外還真是無聊去試這個XD 不過這拉大誤差說不定是有目的的
      像是不同溫度時的反應之類的, 振盪器也是會受溫度影響, 尤其 LED 會發熱
      雖然說改成寫 500ns 看起來是鬆了點, 但對於 10MHz 以下的 MCU 還是很嚴格啊
      >這描述了怎麼用簡單的電路從spi訊號生出符合要求的2812訊號。
      可是一條線能做的事改用兩條還要加料件感覺不太划算啊
      除非是要 DMA 影像, 影像輸出做大片的這樣就有意義
      因為大片的 LED 顯示都是數萬元台幣起跳, 多個幾塊錢的料沒差XD
      >datasheet僅供參考w
      樓上還有人拿到錯的資料XD
      不過通常還是照 datasheet 做, 尤其企業的, 這關係到能否找廠商來算帳XD
      >我的for迴圈是寫 for(m=0x80;m!=0;m=m>>1) 可以少用一個 i
      恭喜你節省了 1 byte 記憶體!XD

      刪除
    2. 10MHZ以下的mcu,或者有即時事件要處理的mcu(我的情況),就用spi掛電路吧w spi只要能跑到500kbps就能動了 說不定cc2540這樣就能用了XD
      在示波器看了第一顆led整形後的波型...傳0是320ns高電位,傳1是640ns高電位
      那張電路我後來小改了一下,讓他至少順便做位準調整,不然我也怕踩線的3.3v哪天會反噬XDDD 接法是SPICLK和SIG/DO之間的電阻,換成訊號用二極體(流向SPICLK)來墊高電壓(會變0.8~4V符合要求),SIG/DO另接提昇電阻到5V代替本來SPICLK的供電
      找廠商算帳這個學起來 感覺以後會用到w 感恩

      刪除
    3. >說不定cc2540這樣就能用了XD
      https://4.bp.blogspot.com/-K8__MmpIDHg/VvUujbMZs_I/AAAAAAAAJ3U/_91nTYF6xhgP7l-0xClEBvceQ6Wp0XyHw/s1600/DSC03529.JPG
      板子已經做了, 所以就暫時不用了XD
      不過對於無線燈泡的應用應該很有競爭力, 畢竟電晶體肯定是比 MCU 便宜的
      >不然我也怕踩線的3.3v哪天會反噬
      這時候就要摀住臉說: 呵!這麼快就開始反噬了麼...
      (出自 "如何做一個高貴冷豔的中二病")XD
      如果要好波形, 原來的電路過個邏輯閘應該就好了吧?
      看過那種只有單閘的, 五隻腳的 IC, 只有兩入一出的 AND 閘

      刪除
  4. 最近又用別的玩法惡搞了2812:用uart排bit
    當然這有限制,baud rate跑3Mbps的時候,一個bit的長度是0.33us,剛好符合需要。
    不過排bit要注意uart是lsb開始的,而且自備1bit的開始位元,更混亂的是,ttl uart平常閒置時是1
    所以一個位元組傳送的時候是這樣

    電腦裡的位元順序:ABCD EFGH
    (閒置1直到開始)0 HGFE DCBA 1(閒置)

    而我手邊有一條rs485的線,rs485則是半雙工差動,只當輸出用的話就是有一條反相的訊號能用!反相過的輸出長這樣,試著輸入0x31
    (閒置0)1 0111 0011 0(閒置0)

    我也希望不要掉速太多,所以一byte塞兩個bit,要排成這樣
    (閒置0)1 YY00 1ZZ0 0(閒置0)

    可動的位元組裡面有一個bit永遠固定是1,所以先塞0xEF進去
    (閒置0)1 0000 1000 0(閒置0)
    F在前面,E在後面,而且順序和相位都相反

    前方的bit要寫1的話
    (閒置0)1 1100 1000 0(閒置0)
    所以是把0xEF的最後兩位元改成0讓它反相,我用xor做就是0xEF^0x03

    後方bit寫1的話
    (閒置0)1 0000 1110 0(閒置0)
    就要把bit6和bit5改寫0進去(反相後為1),那就是0xEF^0x60

    把所有bit都準備好放在陣列裡面,然後一次把整個陣列寫出到uart,避免分多次傳送,分次的話會在波型看到電腦發呆幾毫秒(我不確定這是ms visual的問題還是整個os就是會這樣還怎樣的,畢竟不是mcu)

    回覆刪除
    回覆
    1. 怎麼花樣這麼多XD 有 PWM 風格的感覺XD
      uart 不是還有個 stop bit 嗎? 電腦上好像沒看過參數可以關
      而且 3Mbps 不是每家的 uart 界面都能用啊, 這操太猛了XD
      os 延遲應該是傳輸模式, 印象中看過別家方案在驅動會設臨界值
      當每次函數呼叫中傳輸量低於某個值就是每個 byte 都是 OS 去排程慢慢發
      而高過某個值時就會進 DMA 模式, 一次全丟出去自動完成
      如果一個一個 byte 去丟, 應該會是逐一排程, 不過如果通訊參數
      也就是 uart 參數, 印象中可以設定成全部庫存起來直到看到換行才一次丟
      不過那個一定會認換行字元用來觸發傳輸, 對你的應用不合適

      刪除
    2. 嗯,在c#有分一次塞整個陣列的,和一次塞一個byte的,應該就是os用不同操作法去動。換行通常都關掉了xd。stop bit因為就是回到idle電位,所以只要不太長就不礙事,我程式是設定1bit stop(雖然設定有0可是不能用
      以前pc還很組裝風格的時候,要自製介面卡或者數位界面似乎沒有現在這麼困難,現在的話,usb太方便,os又有內建類別、晶片又有好好支援的,大概就是uart了。只要包裝好,拿到其他電腦只要接上usb點開程式就可以開始嗨(?),也不用特別裝驅動(因為內建了)
      那個認換行字元真的滿討厭的,有這種功能卻不能自設觸發條件(所以更討厭了xd,還好可以不開

      刪除
    3. 以前的 ISA 好像還有在賣專用的洞洞板, 電料行還有看過XD
      雖然 uart 是內建但各家性能差很多啊, 像這個 3Mbps 就不是每家都保證可以的
      尤其 usb-serial, 印象中看過保證到 1Mbps 的, 再上去就要看運氣XD
      幾年前 arduino 剛出來時是 usb-serial, 應該就是和你想的一樣, 方便好用
      不過之後換成了 atmega8, 有神人用 GPIO 精密控制, 做出了軟體 usb device
      隨著這平台的爆量輸出, 可能 atmel 看到需求, 就變成現在的 atmega8u/16u/32u 系列
      以往會附 usb 的 mcu 通常是定位在高階品, 但是 atmel 做出了例外, 最低階的也有 usb
      只要把 usb class 的描述在 usb 界面溝通時丟出去, 這超低階的 mcu 可以偽裝成各種設備
      以 arduino 來說就是裝成 ACM, 這樣你就同時有了 usb-seral 以及 mcu 精準的 GPIO
      有興趣可以找 arduino pro micro, 用這貨去搞就不用再煩惱這些時間問題啦~

      不過這需要花時間, 要寫這種韌體, 要啃 usb spec...XD

      uart 的換行字元如果是要跑協議的, 像是 AT command 界面通常都吃這個
      不過我也是會把它關掉XD 因為會不知道到底有沒有發

      刪除
  5. http://www.everlight.com/file/SeriationFile/One-Wire_RGBIC_ApplicationNote_V1_TCH.pdf
    更新資料

    回覆刪除
    回覆
    1. 這是別家的產品了吧?
      億光的改 900/300ns 互補喔, 看起來差不多, 就差這零件不好買
      隨便搜尋一下只看到某北美零售商有

      刪除
    2. WS2812B已改成這樣波型了。億光只是仿一樣的。改成這樣的波型就可以用SPI通信埠直接驅動。對MCU來說控制就簡單得多。
      不然就就如同你的計算一樣,光是要做300ns的檢知,就生成一堆限制。

      刪除
    3. 噢!原來如此, 多謝情報!
      我那一捲 5 米舊版的還庫存著不知道怎麼用結果就更新了
      雖然說是簡單一些但...許多廠商的 MCU 在 SPI 傳輸時中間會有間隔
      這種型的 MCU 應該還是沒辦法, 所以這更新造福的應該不是低階 MCU
      而是那堆可以 DMA 連續灌資料的中高階 MCU, 以及跑 OS 的應用處理器
      這對廣告電視牆那種巨量用戶可以大幅簡化設計

      刪除
  6. 滿有用的補充資料,可實現無SRAM驅動RGB,且是用Attiny13
    https://github.com/wagiminator/ATtiny13-NeoController

    回覆刪除
    回覆
    1. 多謝!
      剛看到以為是要用 IR 發資料, 速度差太多應該只能放記憶體XD
      從 flash 直接取用的話現代 MCU 應該都辦得到, 只不過各家作法不同
      AVR 用 const 定義即可, arm 的話要改在 ld 腳本, 用 section 塞資料
      這專案用 type c, 應該加掛 USB 協議晶片和降壓晶片, 這樣可以帶起更多顆 LED
      這樣才能完整壓榨變壓器, 用到整顆超燙!XD

      刪除

注意:只有此網誌的成員可以留言。