SSブログ

Raspberry Piで(もうひとつの)フルカラーLED [Raspberry Pi]

世間はクリスマスで、近所では浮かれ電飾がピカピカして、おそらく今頃はそういったうちの中では皆、敬虔にバッハのオラトリオBWV248でも聞いているんだろうと他人事ながら想像している。

ところで、このところずっと新しい開発製品のデモを来年の展示会に向けて準備しているけど、また例によって手作り感満載、見た目はごちゃごちゃした黒いかたまりの隅っこからレーザ光が出る、というようなものになってしまいそうなので、すこし色っぽくしたいと思っていた。もう3年前になるけど「マイコン内蔵」フルカラーLEDをRaspberry Piで光らせたことがあって、これで少し色気と浮かれ気分を黒い塊に追加しようと思った。

その時買った秋月通商のサイトを見ると新しい別の「IC内蔵の5mmRGBフルカラーLED」というのがあった。データシートを見るとやはり数珠つなぎのできるRGB3色が1パッケージに入ったものだけど、通信方式が違っている(それ以前にメーカが違ってるんだけど、出どころは同じに思える)。こんどのはタイミングはすごく緩いけど、データを3値で与えないといけない。なんとも好き勝手なことを考えるものだ。その思考の跡がわかるような気がして面白い。ウェブではあまり光らせた情報がなさそうなので、また例によって手に入れてRaspberry Piでドライブできるようにしようと考えた。

忙しいと言いながら、また全然余計なところに時間を費やしてしまう...

LEDの仕様

LEDはOST4ML5B32Aという型番で、5Vで駆動してシリアルなデータを入れてやるとそれに従ってRGBそれぞれのLEDの輝度を制御する、というもの。データシートの制御に関する部分をコピーしてくると
1220fromdatasheet.png

同じ型番で違うデータシートも見つかる。こっちのほうが若干情報量が多い。さらに違うのもある。発光素子そのものに関する仕様はそこそこタイトなんだけど、それ以外はかなり怪しげな素人っぽいものになってる。例えば、外形は4.9なのか5.0なのかわからないし、他にもおかしなところがいっぱいある(「Transmit data」がなぜか9ビットある、「電圧」を「pressure」と書いてある、MSBと書いたビットが立っているのにデータは0000001、まだまだある)けど、現物はそれなりだろうと踏んで仕様のおかしなところはとりあえず無視する。一番の特徴は普通のシリアルデータではなく、3値になっているところ。

DIN_HとDIN_Lで「1」と「0」を区別するんだけど、中間のVDD/2という電位からの立ち上がり立ち下がりでDIN_HとDIN_Lを区別するようになっている。3値にしたおかげでタイミングはゆるふんにすることができて、電位を保持する時間が1$\mu$秒〜100$\mu$秒とすごく大きな幅でかまわない、ということになっている(それ以外にもう一つ、3値の重要な目的があるようなんだけど、それは後で)。データシートの別の場所を見るとその中間の電位VDD/2を認識するためにデータ投入の前に3m秒保持せよ、と書いてある。

前のモデルでは仕様書上のタイミングのトレランスが$\pm 150$n秒だったことを考えるとドライブする方からすればかなり楽になった、といえる。前のモデルではタイミングが厳しいので、僕はハードウェアSPIバスの信号を無理やり使って実現した(他にはArduinoでクロックを数えながらタイミングを作ってる人がいる)。こんどのではpigpioライブラリを使える可能性がある。

このデータを8ビットぶんでひとつのコンポーネント、3コンポーネントでRGBとなってひとつのLEDぶんになるようである。それをさらに続けると数珠つなぎになったうしろのLEDのデータになる、ということだろう。この辺は前のLEDと同じ思想で作られていると考えていいだろう。

通信時間

ざっくりと通信による時間を考えてみる。最短で1ビットが2$\mu$秒なのでLEDひとつあたり48$\mu$秒、100個繋げば約5m秒かかる。VDD/2保持のために最低3m秒必要なので、100個のデータを更新するには8m秒、125回/秒の更新が可能なので目で見る分には全く問題がない。この通信フォーマットではビットがずれた場合の検出手段がないので、現実的にはLEDの必要な個数と毎秒の更新回数(すくなくとも30回)から1ビットあたりのパルス幅を決めた方が電気的には無難だろう。

例えば、このLEDを格子に並べてディスプレイにする場合、例えばVGAだと640$\times$480=307200個並べたとして最短でも1フレームが約15秒かかることになる。動画は難しい。

3値のための付加回路

しかし3値にするためにはRaspberry Pi(もちろんArduinoでも)では工夫が必要になる。

無理やり3値回路

ひとつのやりかたはGPIOピンの読み込みと書き込みのインピーダンスの違いを利用する手。例えば
1220gpiodirect.png

みたいにする。中間電位のときはピンのモードを読み込みにすると入力インピーダンスは高いので、抵抗で分割された電位になる。ただしこの場合、Hが3.3Vまでしか上がらないのと、書き込み/読み込みの切り替え時間は保証されていないし、データシートにDINのトレランスがないので、これで動作するかどうかはやってみないとわからない。また分割抵抗の値は適切に選ばないと1$\mu$秒の幅は案外厳しいし、電位保持時間はソフトウェアで制御するしかないので、CPUコアひとつ分が張り付きになる。Raspberry Piではあまり賢いやり方ではない。

もうちょっとマジメな3値回路

どのみちGPIO直結だけでは不可能で、なんらかの付加回路が必要になる。そして3値だと、真面目に制御しようとするとGPIOのピンは少なくともふたつ占有することになる。しかしまあ付加回路も電気回路をいじったことのある人ならすぐ思いつくようなレベルだし、もしpigpioライブラリが使えればほぼどのピンも使うことができるので、悪い話ではない。

まずどんな回路で3値を作るか考えてみる。入力信号の与え方によってざっくりふた通り考えられる。まずはGPIOの二つのピンをそれぞれVIN_H用とVIN_L用にして、どちらも立ってない時には中間電位になるようにするもの。回路は例えばこんな
1220digitra.png

デジトラ(あるいは普通のトランジスタと電流制限抵抗)ふたつで、V-側をオンすると電位が低くなって、V+をオフすると高くなるというもの。デジトラはそこそこ速いものを選ぶ必要がある。抵抗が非対称なのはデジトラのオン時VCE分のゲタを履かせるためのもので、そこまで気を使わないといけないか、はよくわからない。

他にも例えばこんな
1220digitra2.png

とかだと入力が両方とも正論理にできる。ただし、両方をHにすると電源をショートするので「両方Hにならない」ことが保証できるようにしないとこれは危ない。

もうひとつはGPIOふたつのピンのうちひとつをデータに、もうひとつを中間電位のオンオフに使うというもの。さっきのGPIOの読み書きを切り替えるやりかたを外部回路で実現すればいい。例えば
1220tristatebuffer.png
みたいにしてTTLの3ステートバッファをそのまま使って作る。これだとV+/-にデータビットを入れて、VMIDをHにすると(TTLそのものの使い勝手としてはLにするとデータが通ると解釈するので図にはロジック反転の○印が付いている)出力がHi-Zになって抵抗分割の電位がDIN になる。TTLだと切り替え時間が仕様化されてるし、素人が手半田で組むことを考えれば十分速いので安心できる。

しかしいまどきTTLなんか手に入るのか、と思っていたら秋月通商で一個30円で売ってた(RSにもあった)。こういうインターフェイスに使うようなICはまだ需要があるのかな。

pigpioライブラリを使う

外付け回路はとりあえずどれでもいいとして、それを制御するソフトウェアをどうするか。Arduinoみたいに1コア張り付いたとしてもRaspbianではいつコンテクストスイッチが起こるかわからないので、満足に動作しない。僕が今Raspberry Piで一番使っている、というかこれなしに書くことはないライブラリがpigpioライブラリ(2年近く前にまとめを書いた)で、これにちょうどいい機能を持っている。

pigpioのWave機能

pigpioにWaveという機能がある。これは任意の時間間隔で任意のGPIOピンに任意の状態を書き込むことができる、という機能である。ある状態を書き込むには構造体
typedef struct {
    uint32_t gpioOn;
    uint32_t gpioOff;
    uint32_t usDelay;
} gpioPulse_t;
を使う。これは立てるピン位置(ビットの並びで指定する)と下げるピン位置と、次の遷移までの時間間隔($\mu$秒単位)を指定する。これを配列にして信号を作るというものである。例えば一つ目の構造体のgpioOnに0x1000、gpioOffに0x2000、usDelayに5を入れて次の構造体にgpioOnに0x2000、gpioOffに0x1000、usDelayに5として、このふたつを繰り返すように指定すると、BCM12とBCM13のピンにデューティ50%の100kHzの方形波が逆位相で出力される、というものである。こう書くと分かりにくかったな。

この構造体ふたつでLED用のひとつのビット(立ち上がり立ち下がりと、中間電位レベル)を表すと48個のgpioPulse_t構造体でひとつのLEDのRGB輝度のデータにできる、ということになる。ただし、この信号を送り出す関数gpioWaveTxSend()は一度に1回しか呼べない(出力中にgpioWaveTxSend()を呼ぶとそれまでのがチャラになって、それ以降の出力は新しい関数呼び出しに取って代わられる)ので、LED数珠つなぎを2本以上ドライブしたい時には、タイミングを合わせてひとつのgpioWaveTxSend()関数で出力してやる必要がある。

このWave機能は時間分解能が1$\mu$秒で、LEDの仕様にある最短の電位保持時間と等しい。Wave機能に限らずハードがサポートしていない機能の時間間隔はソフトウェアで制御するので、優秀なpigpioライブラリといえども揺らぎがある。usDelayを1$\mu$秒に指定したとき、場合によっては1$\mu$秒を切ってしまう可能性がある。それを考えるとある程度長めのパルス幅にしてやらないといけないかもしれない。これはpigpioの揺らぎの分布(負荷にもよるが)とLED側のトレランス実力に依存するので、とりあえずはやってみるしかない。

ソフトウェア構造

さくっと設計してみる。まず数珠つなぎが複数本定義できるように考える。数珠つなぎ1本は
  • LEDの個数
  • 負荷回路の形式(Hi、Lo別ピンか、データピンと中間電位指定ピンのどちらか)
  • そのピン番号と論理(ActiveHi、Lo)
を指定する。そのうちのGPIOピンに関する設定は
#define activeHigh  true
#define activeLow   false
typedef struct _pinSetting {
    uint8_t     pinNumber;
    bool        polarity;
} pinSetting;
みたいにして、その数珠つなぎ一本を作る関数を
PFCLEDChain     *createChainAlternativeLogic(size_t     countOfLED,
                                             pinSetting high,
                                             pinSetting low);
PFCLEDChain     *createChainTristateLogic(size_t        countOfLED,
                                          pinSetting    data,
                                          pinSetting    middle);
みたいにする。PFCLEDChainはLEDの数珠つなぎ一本を表すopaque typeな構造体。関数のひとつめはHi、Lo別々のGPIOピンを使う場合で、ふたつめはデータ用ピンと中間電位用のピンに分けた場合。どのみちWave機能を呼び出すことになるので、どちらも成功すれば同じ構造体が返るようにする。

初期状態では数珠つなぎのLED全部が消灯状態で、
bool            setLEDColorToChain(PFCLEDChain  *ledChain,
                                   size_t       indexAt,
                                   PFCRGBColor  *rgb);
で、構造体を作ったあと、LED個別の色を設定できるようにする。PFCRGBColorはRGB色を表す構造体。

もうひとつ、この数珠つなぎをまとめて、Waveのパルス列に変換するのは
PFCLEDChainConverter    *createLEDChainConverter(uint32_t       pulseWidthInUSec,
                                                 PFCLEDChain    **chains,
                                                 size_t         numberOfChains);
とする。pulseWidthInUSecは電位を保持する$\mu$秒単位の期間で、chainsはLED数珠つなぎの(ポインタの)配列、numberOfChainsはその本数である。

これはそのまま
bool                    transferColors(PFCLEDChainConverter *conv);
とすればそれぞれの数珠つなぎにそれぞれの色設定データが同時スタートで流されて、
bool                    isNowTransferring(PFCLEDChainConverter *conv);
で、データ転送が続いているかどうかがわかるようにする。転送が始まってしまえばPFCLEDChain 構造体は関与しなくてもいいようにして、setLEDColorToChain関数で次の状態用に色設定を変える。オブジェクト(構造体)を作り替えなくても次の転送が準備できるようにする。なんだかいかにもCoreFoundationっぽいけど、設計としてはこんなもんだろ。

現物を手配

コーディングを始める前にブツを手に入れてとりあえずブレッドボードに配線してみた。外付け回路はとりあえず安心のTTLを使うことにした。僕がTTLを買うのはたぶん20年ぶりぐらいになる。
1222-breadboard.jpg

電源を入れただけでは光らない(前モデルと違って電源投入リセットがある)けど、DINを手で触ってやるとノイズでデタラメに光った。入力インピーダンスは結構高いらしい。

ちょっと長くなりすぎたので、続きは次回。
nice!(0)  コメント(0) 
共通テーマ:blog

nice! 0

コメント 0

コメントを書く

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

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