今回はマイコンを使ってマイコン内蔵のLEDであるWS2812Bを制御します。今回紹介するLED WS2812Bの特徴はマイコンなどから信号を与えることで各LEDを個別で制御できかつ安価である(AliExpressで1つ当たり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者で違うことを覚えておいてください。
そしてこのbitが以下のように連続して送られることで1つのLEDの制御信号になります。
各bitを緑、赤、青の順に連続的に並べたのみでスタートビットやストップビットといったものは無いのが特徴と言えます。
そして複数のLEDの制御はこの24bitのデータを連続的に並べることで行います。LEDの接続と信号の順番を図で表すと以下のようになります。
Amazonとかの販売ページにはアドレス指定可能とか書かれていますが、実際にはアドレスを指定して制御するわけではなく、手前のLEDから順番にデータを送る形式となっています。
このLEDの制御における注意点としてはLEDの点灯状態を変える際にはLOWが一定時間以上(50us ot 280us)続くリセット信号を送らないといけないことです。逆に言い換えるとリセットされない時間であればLOWが続いても大丈夫ということです。実はこの仕様のおかげでAVRマイコンでも制御することができます。
制御信号の作り方
続いてどうやってこの信号を作り出すかを考えていきます。制御信号の形上UARTとかI2CとかSPIをそのまま使うことはもちろんできません。また、信号速度的にArduinoのdigitalWriteとかは使えないのはもちろん、レジスタを叩いてもdelayとか割り込みでの処理も困難です。アセンブラを使えばクロック単位で処理できるので信号生成できますが、難しいし処理も重い、でもクロック単位レベルの高速な信号生成が必要。調べてみるとSPIの複数bitを使って信号生成してる例があったので、今回はSPIを使ってAVRとSTM32で信号生成をしてみることにします。
まずはSPI通信の仕様を軽く説明します。
SPI通信はUARTと違って4つのピンを使って制御します。そのピンの内訳はクロック(SCK) マスターアウトプットスレーブインプット(MOSI)
マスターインプットスレーブアウトプット(MISO) スレーブセレクト(SS)です。通信を行うときはSSピンをLOWにして、SCKピンにクロックを掛けてクロックと同期した信号を送信します。タイミングチャートを下に示してみます。
ここで、ややこしいことが起きてしまいます。それはMOSI(マスター送信ピン)がアイドル時に状態が不定になるということです。その他SCKピンのアイドル時のLOW,HIGHの差や先行端、後行端を採るかの差はマイコン間などでSPI通信を行うときは合わさないといけませんが、今回のLED制御には本質的には関わりません。
今回WS2812Bを制御するにあたって使うピンはMOSIピンのみです。他のピンは使いません。送信するデータの工夫でWS2812Bの制御信号を作り出します。
今回はSPIのデータの4bitをWS2812Bの1bitとして使います。送り出すパケット形状より下図のようにHIGHになるbitの数を調整することで0のパケットと1のパケットを作り出します。
この形状の出力を作り出すデータは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マイコンではMOSIのアイドル時の状態はHIGHになってるように見えます。確認のために実際にオシロスコープで出力波形を確認してみたいと思います。(ロジックアナライザー持ってないのでオシロを使います。)
送ってるデータは0xEE(0x11101110)です。そして、信号が始まる前と後はHIGHになってることがわかります。これより、データシート通りアイドル時がHIGHになっていることがわかりました。
しかし、WS2812Bはアイドル時がLOWです。そのため、そのままではWS2812Bは動かせません。そこで、AVRマイコンでWS2812Bを制御する場合信号を反転(つまりNOTをかける)してアイドル時がLOWの信号を作りだします。
また、信号を反転させるので送るbitも反転します。つまり最終的に送る形が0b1000(WS2812Bの0)の場合0b0111
、0b1110(WS2812Bの1)の場合0b0001となり16進数で表すとそれぞれ「0パケットは0b0111 0x7」「1パケットは
0x0001 0x1」となります。最終的に出力した波形はこんな感じです。
これによりAVRマイコンでWS2812Bを制御する信号が生成できました。また、これら図では少々わかりにくいですが、AVRの性能上8bit単位で信号に間隔が空いてしまい(LOWが長く続いてる)ます。ただし、WS2812Bの仕様上50usまでは許容されているので問題ありません。
続いてSTM32マイコン(STM32F446)で制御信号を作り出すために、データシートのSPIのタイミングチャートが書かれている856ページを見てみます。こちらにも引用します。
このタイミングチャートを見るとMOSIのアイドル時の状態がHIGHもLOWも両方書かれていてどちらかがわかりません。つまり、データシート上は不定というわけです。ですが、タイミングチャートのMISO(スレーブ側は出力になる)を見ると後行端採取(CPHA=1)の時はLSBが少し伸びるとみられる表記が見られるので、この条件で実験してみました。結果は下図のようにLSBの状態が次のデータの先頭まで続いていました。(他の条件での実験結果は別記事で紹介します)
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)
で正常に作動しています。また、ペリフェラルクロック42MHzのSTM32マイコンの場合はペリフェラルクロックの1/16(分周1/16)で2.625MHz(2.625bps)と新版のデータシート範囲から少々ずれがありますが正常に動作しています。(ただし、個体により動かない可能性はあるので注意必要だと思います)
最後にWS2812BをAVRマイコンとSTM32マイコンで使う場合のまとめです。
AVR(ATmega328P)
アイドル時がHIGHのため信号にロジックICなどでNOTを掛ける必要がある。
送信データ
0パケットで0x7 0b0111
1パケットで0x1 0b0001
パケット間(SPIの8bitと8bitの間)に間隔が空く(LOWが長く続く)
STM32(F446)
後行端採取CPHA=1(CubeMXのClockPhase : 2Edge)に設定
アイドル時はLSBのstate維持のため反転不要
送信データ
0パケットで0x8 0b1000
1パケットで0xE 0b1110
パケット間に間隔が空かずきれいな波形が出力される
共通
SPIクロック(通信速度) 2.86Mbps~4.44Mbps程度
多少の誤差は許容されるが、分周で合わせない場合マイコンのクロック調整も要検討
SPIはMSB First SPIはMaterモード
SCKのアイドル時の状態はどちらでもOK
MOSIをWS2812BのDIN(通信ピン)に接続
データ順は緑8bit 赤8bit青8bitの順の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マイコンでもできるといったところだと思います。必要な用途に応じて使い分けると良いでしょう。