SSブログ

Raspberry PiでフルカラーLED [Raspberry Pi]

たまたま面白いものを見かけた。赤緑青のLEDとそのドライブ用ICがひとつのパッケージに入っているPL9823。秋月通商でひとつ40円だった。
1128pl9823.jpg

「マイコン内蔵」などと称されてるけど、それはちょっと大げさというか看板に偽り。おそらく中身はマイクロコードではなく、シフトレジスタアレイでハードウェア的にコードされていると思われる。これをRaspberry Piで制御してみた。ちょっと細工がある。その説明も含めて、今日は量が多いけど、何日かに分けてもしょうがないので一気に書いてしまう。おつきあいのほどを....

worldsemiという中国のメーカの製品。LED用のドライバICを得意にしているらしいんだけど、半導体プロセスそのものはどうもアウトソースしているように見える。いや、それはべつにどうでもいいんだけど、このPL9823のなにが面白いと言って、5Vの電源を与えると、あとはPWM風のデジタル制御でRGBそれぞれの発光強度、つまりは色を制御できる。しかもそれを数珠つなぎにして4本の端子でいくつでもLEDを増やせて、ひとつひとつ個別に発色を制御できる。

その制御の仕方が独特で面白い。こういう電飾とかの分野では普通なのかもしれないけど、僕は全然知らなかった。僕なら例えばRS485やI2Cみたいなマルチドロップのシリアルバスの標準を使おうと考えてしまう。そうすると使うほうは簡単でいいけど、LEDを実装する側は大変で、結局は高価なものになってしまう。

ここの人たちはそんな硬直した発想は持っていない。しょせんLEDの色指定なので、そもそも多少ビットが化けても大問題というほどではない。パッケージの中ではLEDをPWMで変調すればいいので、そのクロックをデータのやりとりにも使いまわして、シフトレジスタの回路設計も使いまわそう、数珠繋ぎになった後続のLEDへのデータの制御もシフトレジスタとゲートで実装できる、という発想なんだろう。

ということで、デューティで0、1を区別してビットシリアルにデータを受け付けて、自分用のデータが揃った後もデータが来るようだったら数珠つなぎの後ろにまわす、というなんとも不思議なプロトコルになってる。中身は発振機とシフトレジスタの塊と電流ドライバぐらいでできる、そうすればグランドと電源とデータ入力と数珠つなぎ用のデータ出力の4本ピンの、カソード共通3色LEDと同じですむ、ということなんだろう。いや、さすがである。面白い。

他の人も面白いと思ったらしくて、Arduinoなんかのリアルタイム制御しやすいハードを使ってドライブしている人がいる。なんでArduinoかというと、このLEDのシリアルデータは結構速くてタイミングが難しい、ということなんだろう。ぼくはこれをRaspberry Piで動かしてみようと思う。

1  PL9823

このフルカラーLEDは普通のLEDと同じϕ5あるいはϕ8砲弾型のパッケージに入っていて、足が4本ある(他に表面実装用のパッケージもあるらしい)。4本の足の長さが全部違っていて、長い方から
1 グランド
2 データ出力(数珠つなぎ用)
3 VDD(電源)
4 データ入力
になっている。一番長いグランドのピンにチップが全部乗ってる。そのピンが端にはなっていないので間違わないようにする。電源は仕様書によるとDC4.5V〜6Vとなっていて、5V駆動を前提にしているらしい。

入力データは図-1のようにデューティの違いでビットの0と1を区別する。
1128fig1.png
一番上の黒線がLEDの仕様書にあるパターンである。つまりレベルが高い部分が長い(デューティが高い)ときを1として、短い(デューティが低い)とき0とみなすということらしい。

その下のふたつは、このLEDの中でドライブに使われているというWL2811の仕様書にあるパターンである。WL8211にはLow Speed ModeとHigh Speed Modeのふたつ(仕様書にはLow Speed Modeしか値は入ってなくて、High Speed ModeはLow Speed Modeのそれぞれ半分だ、と読める注釈がある。が、英語がなんだか変でそれで正しいのかどうかよくわからない)があってモード切り替え用のピンがあるが、PL9823にはそのピンが出てないので、決めうちなんだろう。

でもなんだか微妙に違っているし、1.36ってなんだよ、1.35じゃいけないのかよ、という感じもして、怪しげな英語とも相まって不安がつのる、というか味のある仕様書ではある。

WL8211でシフトレジスタがいくつか死んでる不良品をLEDのほうに使いまわしているのかもしれない。

これでビットを区別して、あとは高いビットから低いビットへ順に(MSB First)8ビット送ってそれが3バイト並んでRGBの順にそれぞれの明るさの設定値となる。したがって全部で24ビットでひとつのLEDの色と明るさが決まる、ということである。

データが24ビット以上来たら、それは数珠つなぎの後ろ用のデータである、とみなしてデータ出力に送り出す。当然それまでの自分用のデータを受け取っている間は出力には何も出ない、ということだろう。

そうやって自分の分をとっては残りを後ろにわたす「横取りバケツリレー」でつながっているLEDすべてにデータを行き渡らせる、という仕組みである。したがって20万個つなぐと、最後のLEDのデータ書き換えは最初のLEDが変わってから1秒以上あとになる、ということになる。

また、データが余れば捨てられるだけだけど、足りないときは後ろのLEDにはデータがなくて光らない、ということになる。

しばらくデータが来なくて久しぶりに来たとき、具体的にはローレベルが50μsec以上続いたとき、その先頭は自分用のデータだとみなしてバケツリレーをやり直す。

2  ドライブ方法

このLEDを光らすために、なぜみなさんArduinoなんかの比較的小さめのハードを使っているか、というと600kHz近いデータレートで、その中のデューティを制御しないといけない。仕様書で指定されたトレランスの中に入れるためには最悪でも2MHzのクロック(分解能)を持ってないといけない。これは結構厳しい。

LED側としても数珠繋ぎの間の配線が長いとEMIや反射なんかで信号が劣化するので、そもそも長距離での制御は全く考えられていない(距離を延ばしたかったら途中にもLEDを光らせろ、というに違いない)。じゃあもっと遅くすればいいじゃないか、とも思うんだけど仕様を決めた彼らがどう考えたのかはよくわからない。

例えばArduino用のコードを見てみると、どうやら実行中にCPUそのもののクロックを数えて切り替えることでデューティを制御するという荒技を使ってるらしい。ようするに、こういうやり方ではこのLEDを光らせるためにCPUの全精力を注ぎ込むことになって、Arduinoでは許されるけど、Raspberry Piではリソースの無駄遣いになってしまう。

ましてやRaspberry PiのOSのRaspbianでは、OSが勝手に割り込んでループの途中で他のプロセスにCPUを割り振ってしまうのでこういうやりかたでは、うまくいくこともあるけどダメなときもある、というような信頼性の低い作業になってしまう。

2.1  Raspberry Piでどうやってドライブするか

Raspberry Piでどうするか。Raspberry PiにはPWM出力もあるけど、パルスごとにデューティを制御する機能は持ってないので、これは残念ながら使えない。

あとはシリアルインターフェイスである。Raspberry Piには
  • UART
  • I2C
  • SPI
と三種類あって、Raspberry Piはシリアルインターフェイスの宝庫である。どれも最終的にはハードウェアによってシリアルに変換されて送り出し受け取りをするので、バッファサイズさえ十分なら、ユーザプロセスからデータを与えてしまえば、そのプロセスがどうなっていようと途切れずにデータを送り出すことができる。ただしもちろんRaspbianもunixなので与えたデータがいつ送り出されるかは厳密にはわからない。そのときのCPUの負荷やカーネルの忙しさ加減でミリ秒以上の遅れが発生する可能性はある。

しかし、ようするに図-1にある波形を作るようなビットパターンをシリアルでLEDに対して連続的に送り出すことができればいい。ひとつのLEDを光らせるためには24ビット必要なので、たとえばそのビットパターンを1バイトで表現したとすると、24バイトを約4.7MHzのビットクロックで送り出せばいい。かなりのスピードだけど、この辺が使えないか考えてみる。

2.1.1  UART

UARTはデータをシリアルに変換して外に送り出す。これにビットパターンを乗せてLEDの入力にすれば動くはずである。

Raspberry PiのUARTは基本的にはターミナル用で、あとはIRDAなんかを想定している。どちらにしても比較的遠距離(数m)にあるかなり低速のデバイスとの通信を考えたものである。

Raspberry Piでボーレートがどこまでいけるのかドキュメントを探したんだけどよくわからない。termios.hヘッダには4Mボーまで定数は定義されてるけど、ARMのUARTに関するドキュメントにはUARTCLKとして外部から与えることになっていて、絶対値として最大のボーレートがいくつになるかわからない。Raspberry Piでどうなってるのかよくわからない。知ってる人いたら教えて。うちにオシロでもあれば実際に動かして出てるかどうか見ればいいんだけど、そんなものはない。

また、UARTではRS-232Cに使えるような、スタートビット、ストップビット、パリティビットの追加がかならずあるので、それを含めてビットパターンを組まなければいけない。つまり例えばストップビットが1ビットパリティ無し(スタートビットは必ず必要)の場合に1ビットが0.35μsecになるようにできれば1バイト分でLEDデータの2ビット分が転送できるけど、その場合2800000ボーなんていう超高速通信に対応できてないといけない。この値はUARTでは普通なら想定されていない。

ということでUARTを使うのはとりあえずは諦めて、ボーレートの上限がわかれば改めてやってみることにする。

2.1.2  I2C

I2CもシリアルインターフェイスだけどUARTよりはずっと近距離通信を想定していて、クロックと信号線の2本で通信する(もちろん電源とグランドは必要である)。クロックを使わずに信号線の方にビットパータンを乗せればいいはずである。

I2Cのクロック周波数は規格で決まっていて
  • 10kbps(low-speed mode)
  • 100kbps(Standard mode)
  • 400kbps(Fast mode)
  • 3.4Mbps(high-speed mode)
の4通りがあるらしい。3.4MbpsならLEDの入力の基本クロックとして使える。でもこれは規格であってRaspberry Piがどこまでサポートしてるかはわからない。僕がGPIOをアクセスするためにずっと使ってるbcm2835ライブラリには1.689MHzのクロックまでの定数しか定義されていない。元のクロックは250MHzでずっと高いんだけど、なんでかよくわからない。1.689MHzでは最短のパルスが表現できないのでアウトである。

2.1.3  SPI

SPIもI2Cと基本的な考え方は同じ。I2Cでは入出力をホストもデバイスもシェアすることになっていてデータ線は1本しかなかったけど、SPIは入力と出力に分かれている。I2Cが半二重通信だったのがSPIでは全二重になっている、ということである。またI2Cはデバイスの指定をアドレスで行っていたけど、SPIはチップセレクト信号を使う。したがって信号線の数はずっと多いけど、通信されるデータはI2Cに比べると構造はずっとシンプルでバイトシーケンスとみなしていい。

つまりどちらかといえばI2Cはスピードよりも信号線の少なさを優先するデータ量の少ないアプリケーションに向いていて、SPIはその逆だということである。狭い基板の中で独立したチップのデバイスがたくさんあるときはI2Cで、1、2個ぐらいならSPIで、というふうに考えられる。しかしスピード優先とは言ってもSPIでGbpsのやりとりをしようというわけではなく、せいぜい基板のなかをぐるぐると引っ張り回しても問題がない程度のスピードしかサポートしない。配線が長くなると配線端での反射が問題になるので普通は終端抵抗の大きさなんかを決めるけど、SPIでは考慮されてない。ようするに反射が問題にならないような配線長とクロックの組み合わせでしか使わない、ということである。

そのSPIのクロックだけど、I2Cに比べるとかなりいい加減で規格にはっきり決まっているわけではないらしい。まあそりゃそうだろう。基板の上のチップ同士の通信用でしかも通信相手をチップセレクト信号で選ぶので、相手が初めから決まっていてお互い都合のいいスピードを適当に決めればいい、ということだろう。

2.1.4  Raspberry PiでのSPI

Raspberry Pi側はI2Cと同じ250MHz(周期4nsec)の元クロックを分周している。bcm2835ライブラリでは2のベキの値がずらっと定義されていて、一番短いクロックは8nsecの125MHzから使えるようになっている。

分周回路が対応しているかどうかわからないけど2のベキ以外の値も取れるなら、88分周では幅352nsecで仕様書の値に近くできる。I2Cでは2のベキ以外の値も使えるし、I2CとSPIで同じクロックを使っておきながら分周回路を違う設計にすることはあまりないと思うので、おそらく分周カウンタいっぱいまでの全ての整数が値としてとれるはずである。

さすがに125MHzは引っ張り回すわけにはいかないけど、256nsecの3.9MHzが定義されていて、これを使えば仕様書にあるトレランスの範囲内のパルスが作れる。

ところがこれでパルスを作ってLEDを光らせてみようとしたらうまくいかなかった。BroadcomのBCM2835のIOに関する仕様書を見てもよくわからなかった。Motorolaの規格書みると、詳しくはわからないけど、マルチバイト転送では(ハード的にはビット連続のモードとマルチバイトのモードがあって、さらにプロトコルが微妙に違うモードにわかれているらしい)どうやらまったくのビット列ではなくて、バイト境界があるらしい。

いくつかのSPI用のチップの仕様書にはバイトごとにガードビットが入ってそのときはクロックがお休みする、と書いてある。したがってタイミング的には1バイトの転送には9クロックかかる(でも立ち上がりエッジは8回しかない)、ということらしい。その辺はモードにもよるらしいけど、バイト数の多い転送でエラーを最小限にするのための工夫のようである。BCM2835がどういう実装をしているかとか、bcm2835ライブラリでどういうパラメータ設定になっているかよくわからない。

しょうがないので会社に持って行ってオシロで実際にMOSIの信号を見てみるとガードビットらしいものが挿入されて、前後の値がどうであれ、ガードビットのところでMOSI信号はLに落ちてしまっていた。極性や位相のモードを変えてもそれは同じだった。

そうすると、どうしてもLEDのパルスをSPIの1バイトにわりあてないといけない。LEDの仕様のパルス周期が標準で1.71μ秒なので、分周数をDとすると
1128eq01.png
で、整数に丸めると48分周で、その場合図-2のように±150nsecのトレランス仕様を満足する。
1128fig2.png
でもそもそも仕様書の数字にぴったりにしたとしても、LEDがその通り動いてくれるかわからない(仕様を満足したら動く、というのはグローバルスタンダードな考え方であって、それが通用するかどうかはわからない)。また、非常に速くて短いパルスになるので引き回し方によってはナマってしまって動かないということもありえる。電気的な実装はかなり注意しないといけない。

それとは別に、バッファサイズの問題がある。SPIでやりとりするデータは全く構造化されていないので、たとえば画像のような大きなデータをやりとりすることは全く考えられていない。バッファの最終的なサイズはハードウェアで決まるはずだけど、どこがボトルネックになるのかによっては小さなバッファサイズしか使えないことがありえる。

でもこれでは絶対ダメというのは今の所ないので、とりあえずSPIを使ってこのLEDをドライブしてみよう。

3  ドライブ回路

ちょっとだけ回路が必要になる。仕様ではSPIのチップセレクトがでてないときはデータはどうなってるかわからない、ということになってる。したがってチップセレクトが出てるときだけMOSIのデータを通過させるような付加回路が必要になる。

SPIのチップセレクト信号は(普通は)負論理で、データは正論理になっている。一方のLEDはおそらく立ち上がりを検出してるだろうから、正論理とみなしていい(LEDとしては論理は関係ない)。チップセレクト信号がLになったときにパルスが立ち上がるようにすればいいので、チップセレクトの反転とデータのANDを取るような回路を作ればいい。
1128fig3.png
ということで図-3みたいな回路を付け加える。回路とも言えないようなもので、たとえば古いTTLの74LS00の中の1回路を使えば済むようなものである。しかしTTLも完全にObsoleteで、こういうちょこっとした回路を近頃みんなはどうしてるんだろう、と思う。

ところでこの回路ではSPIのデータが反転してしまう。でも回路でやらなくてもソフトで始めから反転したパターンを作ればいいだけなので、わざわざめんどくさくする必要はない。

LEDの仕様にはデータ線がどのくらい電流を食うのか書いてないのでわからないけど常識的なレベルだろうと考えた(制御チップの仕様書にはIIという項目があってこれがそうだろうと思われるけどよくわからない書き方になっている)。

4  やってみた

ということでやってみた。ところが全然動かない。データはソフトウェア的にはちゃんとできてるので、SPI以降の問題らしい。どうしようもないので、また会社に持って行ってオシロで見てみると、チップセレクトとのANDを取る回路で波形がナマって短いパルスが出てなかった。

うちに転がってたトランジスタと適当な抵抗とコンデンサで作ったのが、あまりに適当すぎたらしい。このくらい速いと、ちゃんとしたスイッチング用のトランジスタを使って、スピードアップコンデンサを最適化しないといけない。ちょっと(いや、かなり)安易に考えすぎていた。

ということで、AND回路の高速化はあとでやることにして(って大抵やらないけど)、SPIのMOSI出力を直接LEDにつないで試すことにした。Raspberry PiのMOSI出力は3.3Vだけど、LEDのほうはTTLコンパチだろうと踏んだ。さらにSPIではMOSIが高インピーダンスになったりすることはない。そうすると状態が定義されていないとはいえ、お行儀よくLowレベルに落ちてるはずだ、と考えられる。これらの仮定は普通のグローバスルタンダードな考えに基づけば、正しいはずである。

LEDの電源の5VもRaspberry Piから取ることにすると、付加回路はいらなくなって直接GPIO端子にLEDをつなげばいい。いや、たいしたもんだ。で、やってみると動いた。

ここにYouTubeのiframeを埋め込んだのに表示されない。なんでだろ?
とりあえずYouTube動画へのリンク
1128YouTube.jpg


とりあえず、白、赤、緑、青の順に光らせたあと、HUEをぐるっと2πまわるようにして、最後に白を表示して終わるようにした。それっぽくは見えるけど、カラーバランス的にはかなりあやしい。どこかでノイズが乗ってデータが化けてる可能性もあるけど、何度やってもあやしさは変わらないので、こういうLEDなんだろう。

ちゃんと数珠繋ぎもできた。ふたつだけど。

ここまで書けばあとは誰でもコーディングできると思うけど、とりあえず試してみたい人用に YouTubeにあげたデモのソースを置いておく。ご参考まで。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。