今回はマイコンを使ってマイコン内蔵のLEDであるWS2812Bを制御します。今回紹介するLED WS2812Bの特徴はマイコンなどから信号を与えることで各LEDを個別で制御できかつ安価である(AliExpress1つ当たり10円を切る価格)で売られているという所です。数珠接続で個別制御できるため安価なLEDテープを大進化させたLEDともいえると思います。

 一見最強そうに見えるこのLED実はマイコンから制御するのが一筋縄にはできないという問題があります。(本来は専用のコントローラを使います)今回はこのWS2812B搭載のLEDテープをAVRマイコンとSTM32マイコンで制御する方法を考えていきます。

 

 

制御信号の形

 まずはWS2812Bのデータシートを見てみます。

http://www.world-semi.com/DownLoadFile/108

こちらが製造元の新しいバージョンとみられるデータシート

https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf

そしてこちらがadafruitとか秋月に掲載されているデータシートです。

ややこしいことに、信号生成に関わる箇所のパラメータがこの2者で違うことを覚えておいてください。
 データシートを見ていくとUARTでもSPIでもI2Cでも無い、専用のデータ形式であることがわかります。具体的な信号の形(1bit)は下図のような感じです。

 1

そしてこのbitが以下のように連続して送られることで1つのLEDの制御信号になります。
2 

bitを緑、赤、青の順に連続的に並べたのみでスタートビットやストップビットといったものは無いのが特徴と言えます。

 そして複数のLEDの制御はこの24bitのデータを連続的に並べることで行います。LEDの接続と信号の順番を図で表すと以下のようになります。
3

 Amazonとかの販売ページにはアドレス指定可能とか書かれていますが、実際にはアドレスを指定して制御するわけではなく、手前のLEDから順番にデータを送る形式となっています。

 このLEDの制御における注意点としてはLEDの点灯状態を変える際にはLOWが一定時間以上(50us ot 280us)続くリセット信号を送らないといけないことです。逆に言い換えるとリセットされない時間であればLOWが続いても大丈夫ということです。実はこの仕様のおかげでAVRマイコンでも制御することができます。

 

制御信号の作り方

 続いてどうやってこの信号を作り出すかを考えていきます。制御信号の形上UARTとかI2CとかSPIをそのまま使うことはもちろんできません。また、信号速度的にArduinodigitalWriteとかは使えないのはもちろん、レジスタを叩いてもdelayとか割り込みでの処理も困難です。アセンブラを使えばクロック単位で処理できるので信号生成できますが、難しいし処理も重い、でもクロック単位レベルの高速な信号生成が必要。調べてみるとSPIの複数bitを使って信号生成してる例があったので、今回はSPIを使ってAVRSTM32で信号生成をしてみることにします。

 

 まずはSPI通信の仕様を軽く説明します。

SPI通信はUARTと違って4つのピンを使って制御します。そのピンの内訳はクロック(SCK) マスターアウトプットスレーブインプット(MOSI) マスターインプットスレーブアウトプット(MISO) スレーブセレクト(SS(ーー))です。通信を行うときはSSーーピンをLOWにして、SCKピンにクロックを掛けてクロックと同期した信号を送信します。タイミングチャートを下に示してみます。

4

 ここで、ややこしいことが起きてしまいます。それはMOSI(マスター送信ピン)がアイドル時に状態が不定になるということです。その他SCKピンのアイドル時のLOW,HIGHの差や先行端、後行端を採るかの差はマイコン間などでSPI通信を行うときは合わさないといけませんが、今回のLED制御には本質的には関わりません。 

 今回WS2812Bを制御するにあたって使うピンはMOSIピンのみです。他のピンは使いません。送信するデータの工夫でWS2812Bの制御信号を作り出します。

 

今回はSPIのデータの4bitWS2812B1bitとして使います。送り出すパケット形状より下図のようにHIGHになるbitの数を調整することで0のパケットと1のパケットを作り出します。

5

この形状の出力を作り出すデータは0パケットの場合0b1000(0x80) 1パケットの場合0b1110(0xE)になります。ですが、そう単純ではありません。SPIのアイドル時の状態が不定であるためです。ここで、今回使用するマイコンのデータシートを見てみます。

 

AVRマイコン(ATmega328P) https://avr.jp/user/DS/PDF/mega328P.pdf

STM32マイコン(STM32F446)https://www.st.com/content/ccc/resource/technical/document/reference_manual/4d/ed/bc/89/b5/70/40/dc/DM00135183.pdf/files/DM00135183.pdf/jcr:content/translations/en.DM00135183.pdf

 

 まずはAVRマイコンで制御信号を作り出す方法を考えるため、データシートのSPIのタイミングチャートが書かれている121ページを見てみます。一応こちらにも引用しておきます。

AVRデータシート

 このデータシートを見るとAVRマイコンではMOSIのアイドル時の状態はHIGHになってるように見えます。確認のために実際にオシロスコープで出力波形を確認してみたいと思います。(ロジックアナライザー持ってないのでオシロを使います。)
AVR波形

 送ってるデータは0xEE(0x11101110)です。そして、信号が始まる前と後はHIGHになってることがわかります。これより、データシート通りアイドル時がHIGHになっていることがわかりました。

しかし、WS2812Bはアイドル時がLOWです。そのため、そのままではWS2812Bは動かせません。そこで、AVRマイコンでWS2812Bを制御する場合信号を反転(つまりNOTをかける)してアイドル時がLOWの信号を作りだします。

また、信号を反転させるので送るbitも反転します。つまり最終的に送る形が0b1000(WS2812B0)の場合0b0111 0b1110(WS2812B1)の場合0b0001となり16進数で表すとそれぞれ「0パケットは0b0111 0x7」「1パケットは 0x0001 0x1となります。最終的に出力した波形はこんな感じです。

AVR波形2

これによりAVRマイコンでWS2812Bを制御する信号が生成できました。また、これら図では少々わかりにくいですが、AVRの性能上8bit単位で信号に間隔が空いてしまい(LOWが長く続いてる)ます。ただし、WS2812Bの仕様上50usまでは許容されているので問題ありません。

 

 続いてSTM32マイコン(STM32F446)で制御信号を作り出すために、データシートのSPIのタイミングチャートが書かれている856ページを見てみます。こちらにも引用します。

STMデータシート

 このタイミングチャートを見るとMOSIのアイドル時の状態がHIGHLOWも両方書かれていてどちらかがわかりません。つまり、データシート上は不定というわけです。ですが、タイミングチャートのMISO(スレーブ側は出力になる)を見ると後行端採取(CPHA=1)の時はLSBが少し伸びるとみられる表記が見られるので、この条件で実験してみました。結果は下図のようにLSBの状態が次のデータの先頭まで続いていました。(他の条件での実験結果は別記事で紹介します)
STM32後端LSB0-1
STM32後端LSB0
STM32後端LSB1-1
STM32後端LSB1

WS2812Bの場合は必要な信号が「0パケットで0b1000 」「1パケットで0b1110」となっておりLSBが必ず0となります。そしてLSBのステートがアイドル時のステートとなるためアイドル時も必ず0となります。そのため、STM32マイコンで制御する場合はAVRと異なり信号の反転は不要です。また反転していないので、送り出すデータも出力波形と同様に「0パケットで0b1000 0x8」「1パケットで0x1110 0xEとなります。また、STM32マイコンではDMAが使えることもあり、8bitの間での信号の伸びが無く連続した信号が生成できています。(DMAを使わない場合でも伸びはほぼ見られませんでした)

 

最後にSPIの通信速度の設定です。

 AVRマイコンにおいてもSTM32マイコンにおいてもSPIの 通信速度(クロック)はマイコンのクロックに依存します。SPIのクロックはマイコンのクロックの1/2n(2 <= n <= 7(AVR),8(STM32)) となります。WS2812Bの場合1パケット(4bit)の長さが大体900nsから1400ns程度となっており、これから逆算するとSPIの通信速度は4.44Mbpsから2.86Mbps程度となります。筆者の環境では16MHzで駆動しているATmega328Pの場合SPIのクロックはマイコンのクロックの1/4(分周1/4)4MHz(4Mbps) で正常に作動しています。また、ペリフェラルクロック42MHzSTM32マイコンの場合はペリフェラルクロックの1/16(分周1/16)2.625MHz(2.625bps)と新版のデータシート範囲から少々ずれがありますが正常に動作しています。(ただし、個体により動かない可能性はあるので注意必要だと思います)

 

最後にWS2812BAVRマイコンとSTM32マイコンで使う場合のまとめです。

 

AVR(ATmega328P)

 アイドル時がHIGHのため信号にロジックICなどでNOTを掛ける必要がある。

 送信データ

0パケットで0x7 0b0111

1パケットで0x1 0b0001

 パケット間(SPI8bit8bitの間)に間隔が空く(LOWが長く続く) 

 

STM32(F446)

 後行端採取CPHA=1(CubeMXClockPhase : 2Edge)に設定

 アイドル時はLSBstate維持のため反転不要

 送信データ

0パケットで0x8 0b1000

1パケットで0xE 0b1110

 パケット間に間隔が空かずきれいな波形が出力される

 

共通

 SPIクロック(通信速度) 2.86Mbps~4.44Mbps程度

  多少の誤差は許容されるが、分周で合わせない場合マイコンのクロック調整も要検討

 SPIMSB First  SPIMaterモード

 SCKのアイドル時の状態はどちらでもOK

MOSIWS2812BDIN(通信ピン)に接続

データ順は緑8bit 8bit8bitの順の24bit 

複数LEDの場合は手前側のLEDのデータ(24bit)から送る

50us~280us以上のLOWが連続するとデータ転送終了

 

例:STM32マイコンで6個のLEDを赤緑青 赤緑青の順にフル点灯させる場合以下のような配列のデータをDMAで流します。

uint8_t data[72] = {0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE,

                    0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,

                    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88,

                    0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE,

                    0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,

                    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 

};

 

 2つのマイコンで比較すると多くのLEDを正確に制御する場合はDMAが使えるSTM32マイコンを使う方が良く、少ないLEDを簡単に制御する程度であればAVRマイコンでもできるといったところだと思います。必要な用途に応じて使い分けると良いでしょう。