貧弱なマイコンでもなんとかWS2812を動かす

今回はWS2812BとWS2812Cに代表される、データ線一本で駆動するタイプの賢いLEDの駆動の話。 コイツラはたくさんつないでたくさん信号を送る都合上、例えば「1」を送るときに出すパルスのHighが220ns~380ns、Lowが580ns~1µsとだいぶ幅が短い。 貧弱なマイコンで愚直に実装すると基本動かないので、工夫が必要になってくる。今回はPIC16F1705で工夫した。

実装

今回使うPIC16F1705の最大クロックは32MHzなので、1命令にかかる時間は125ns*1である。 つまり、WS2812での「1」の信号を送るとすると、Highが3Cycle、Lowが8Cycleだけで、その11Cycleでなんとか24bit分の信号の処理をしつつきれいなパルス幅を出してやらねばならない。 ・・・なんてことはほぼ無理である。もしかしたら脳みそこねこねしてアセンブラを吐き出せば行けるかもしれないが、私の脳みそでは無理だった。

じゃあどうするのかというと、妥協をする。 実はWS2812、パルスを送るとき、Highの期間さえちゃんとしていれば、Lowの期間が多少データシート上の要求値より長くなっても正しく動作してくれる。なので、そこだけアセンブリを書いてループ処理はC言語(と言っても最適化がかかりやすそうな方法)で実装すればそれっぽく動く。 ちなみにこれは一粒だけの時の話であり、本来想定された数珠つなぎでこれをやるとどうなるかは検証していないのでわからない。

というわけで実装してみる。 全部貼ると長くなりすぎるので一部抜粋という形で掲載する。これと同じように赤と青分の処理を追加すれば完成になる。 今回はRA4をLEDへの出力に当てているので、LATAの4bit目をsetしたりclearしたりしている。 真似される場合は各自読み替えてほしい。

void set_WS2812(uint8_t red, uint8_t green, uint8_t blue) {
    uint8_t i;
    for (i = 0b10000000; i; i >>= 1) {
        if (green & i) {
            asm("movlb 2   ; select bank2");
            asm("bsf   LATA,4");
            asm("nop");
            asm("nop");
            asm("nop");
            asm("bcf   LATA,4");
        } else {
            asm("movlb 2   ; select bank2");
            asm("bsf   LATA,4");
            asm("nop");
            asm("bcf   LATA,4");
        }
    }
……
}

ちなみにアセンブリは1から考えるよりも、とりあえずC言語で愚直に実装したものをコンパイルして出てきたものをこねたほうが早い。 [プロジェクトフォルダ]>dist>default>production>[プロジェクト名].X.production.lstを読めばそれっぽいところがあるので、そこから解釈するのが良いと思う。

で、出てきたものの一部がこれ。とりあえずhigh期間は守れそうだ。

  4812                           ;main.c: 18:     uint8_t i;;main.c: 19:     for (i = 0b10000000; i; i >>= 1) {
  4813  06BE  3080                movlw    128
  4814  06BF  00F3                  movwf    ??_set_LED
  4815  06C0  0873                movf ??_set_LED,w
  4816  06C1  00F5                  movwf    set_LED@i
  4817  06C2                     l2705:
  4818  06C2  0875                movf set_LED@i,w
  4819  06C3  1903                btfsc    3,2
  4820  06C4  2ED7                 goto l2707
  4821                           
  4822                           ;main.c: 20:         if (green & i) {
  4823  06C5  0871                movf set_LED@green,w
  4824  06C6  0575                andwf    set_LED@i,w
  4825  06C7  1903                btfsc    3,2
  4826  06C8  2ED0                 goto l452
  4827  06C9  0022                movlb    2  ; select bank2 ;# 
  4828  06CA  160C                 bsf  268,4 ;# 
  4829  06CB  0000                nop  ;# 
  4830  06CC  0000                nop  ;# 
  4831  06CD  0000                nop  ;# 
  4832  06CE  120C                 bcf  268,4 ;# 
  4833                           
  4834                           ;main.c: 27:         } else {
  4835  06CF  2ED4                 goto l2703
  4836  06D0                     l452:
  4837  06D0  0022                movlb    2  ; select bank2 ;# 
  4838  06D1  160C                 bsf  268,4 ;# 
  4839  06D2  0000                nop  ;# 
  4840  06D3  120C                 bcf  268,4 ;# 
  4841  06D4                     l2703:
  4842                           
  4843                           ;main.c: 35:     }
  4844  06D4  1003                clrc
  4845  06D5  0CF5                  rrf  set_LED@i,f
  4846  06D6  2EC2                 goto l2705
  4847  06D7                     l2707:
……

動作の様子

とりあえず動く証拠という意味で。

www.youtube.com

出てくるパルスを見てみるとこんな感じになっている。

f:id:asi_a:20211005105248p:plain
Low期間が適当でも割となんとかなっている

類似品に関して

PL9823などのWS2811を内蔵しているやつやSK6812なども、似たような制御のLEDである。ただし、これらは2812とは微妙にパルスタイミングが違うので、このコードそのままでは動かないと思うし、2812みたいにHigh期間だけ合わしても動かないかもしれないので、検証の必要がある(誰かして)。

このLEDを1粒だけ使う価値があるのか?

さて、このLEDの真価は、たくさんのフルカラーLEDを制御したいと言うときに数珠つなぎで用いることができることであるが、果たして今回のように貧弱なマイコンで苦労してまで1粒だけ使う価値があるのだろうかというツッコミがありそうなので、釈明しておく。 まず、その価値はあると思う。多少苦労が伴うとはいえ、部品点数1つの追加とGPIO一つの専有で調光機能付きフルカラーLEDが使えるのは大きな魅力であるし、その値段も秋月電子で購入すると2812Bで一粒25円、2812Cにいたっては一粒18円と機能にしてはとても安い。実装も最悪手ハンダできるし、DIP化されてるのもあるので試作もしやすい。 なので、その価値はあると個人的に考えている。

余談

今回試作(と言ってもLEDの検証が主目的ではない)に表面実装用のユニバーサル基板を使ったが、なんかこう、微妙なはんだ付けしかできなかった。 他の人はどんなふうに作っているんだろうか。作例を見たくてインターネットを彷徨ったが見当たらなかったので、みんな微妙なデキにしかならないのかと思い始めている。 それともアマゾンの奥地まで調査に行かなければならないんだろうか。うーん……。

それと本来某チカに上げようと思っていたのだが、割と検証してないことが多いので突っ込まれると怖いのでこっちにひっそり上げた。

*1:1[s]/(32M/4)[Hz]、PIC16Fは4クロックで1命令処理