前回のブザーに続いて、今回は3つ目、8x8のLEDマトリックスです。「光ったり鳴ったりする」シリーズの最後を飾るデバイスになります。今回はちょっと複雑です。
ハードウェア
ボードは前回・前々回同様 BeagleBone Green です。Grove コネクタまわりの説明ははじめてNerves(15)を参照してください。
LEDマトリックス基板
スイッチサイエンスで販売のI2C 8x8 LEDマトリックス基板を使いました。3.3V Grove I2Cコネクタ付きで、赤色LEDが8x8に並んでいます。チップは NXP の PCA9622 です。このボードの回路図 も公開されてます。
この基板、調達した時点で処分特価(¥2,090)の在庫限り販売になっており、残念ながら現在は在庫切れになってしまってます。
なお、このモジュールは元々はスイッチサイエンス版Eagletボード用です。Groveのコネクタが基板の裏面にオスコネクタが基板実装されており、通常のオス・オスのGroveケーブルではつながりません。私はGrove の I2C用Hubに挿して使ってます。これもスイッチサイエンスGrove Hubや秋月電子Grove Hubで調達できます。
ソフトウェア
今回のライブラリは前2つと違って GenServer で作ってあります。表示を維持するためにバックグラウンドで動き続ける必要があるためです。
表示の仕組み
PCA9622 は8x8=64個のLEDを個別には駆動できないので、光らせる列を高速に切り替えながら光らせる、いわゆるダイナミック点灯(ダイナミック駆動)をしています。ある瞬間には1列分のLEDしか光っていないのですが、これを列ごとに高速に切り替えることで、人の目には8列同時に光っているように見えます。
# handle_continue(:update_leds, ...) の中身(抜粋)
new_counter = rem(counter + 1, 8)
{row_high, row_low} = bit_expand(elem(mat, new_counter))
{col_high, col_low} = bit_expand(new_counter)
# 列を選択してから、その列の行データを書く
Circuits.I2C.write(ref, i2c_address, <<@ledout2, col_low::binary>>)
Circuits.I2C.write(ref, i2c_address, <<@ledout3, col_high::binary>>)
Circuits.I2C.write(ref, i2c_address, <<@ledout0, row_low::binary>>)
Circuits.I2C.write(ref, i2c_address, <<@ledout1, row_high::binary>>)
:reflesh_interval を0(デフォルト)にしておくとできるだけ速くこのループを回すので普通は残像でちらつきません。値をわざと大きくすると、列がひとつずつ切り替わっていく様子が観察できます。
文字を表示する
PCA9622Matrix8x8.CharGen という文字生成モジュールを同梱していて、0〜9の数字と(16進指定で)A〜Fを 8x8 のビットマップにしてくれます。
PCA9622Matrix8x8.start_link()
PCA9622Matrix8x8.CharGen.digit_strlist(15, 16) # "F" を表示
|> PCA9622Matrix8x8.CharGen.strlist_tuples()
|> PCA9622Matrix8x8.set_matrix()
PCA9622Matrix8x8.Demo という、1秒ごとにカウントアップして数字を表示するサンプルも同梱しています。
PCA9622Matrix8x8.Demo.start_link()
Nerves で実装
前回・前々回と同じ要領です。
% mix nerves.new pca9622test
% cd pca9622test
% vi mix.exs # deps に {:pca9622matrix8x8, "~> 0.1.0"} を追加する
% mix deps.get
% export MIX_TARGET=bbb
% mix firmware
% mix burn # SDカードに焼く
SDカードを BeagleBone Green に挿して、user ボタンを押しながら電源を入れてブートしてください。
テスト
以下でいごかしてみます。
iex(1)> PCA9622Matrix8x8.start_link()
{:ok, #PID<0.1234.0>}
iex(2)> PCA9622Matrix8x8.Demo.start_link()
{:ok, #PID<0.1235.0>}
この様に動作します。X-twitter の動画ではちらついてますが、肉眼ではちらつきがほぼ気になりません。
優先順位制御
なお、他のプロセスがガッツリ走ってるとちらつきが気になります。例えば Nerves 本体がブートした直後はLEDもつきますが、並行して他のプロセスの立ち上がりがあってCPUが忙しいようでチラつきます。あと mix upload とかしてもファームウェアの書き込みが起こるためチラつく…というか表示が止まります。
これを避けるためにプロセスの優先順位を変えられるようにしました。立ち上げる際に :priority をElixirのプロセス優先順位 を示す :high :normal :low にすると切り替えられます。例えば PCA9622Matrix8x8.start_link(priority: :high) とすると他のプロセスより優先順位が上がってチラつきにくくなります。デフォルトは :normal です。:low にするとチラつきをより楽しめます。優先順位についてははじめてな Elixir(26) プロセスの優先順位を調べてみるなどをご覧ください。
まとめ
今回はPCA9622を使った8x8 LEDマトリックスに16進数1桁を表示するところまで作りました。これで照度センサ・ブザー・LEDマトリックスの3つと、一通りご紹介できました。
いごいてるのをよく見ると、基板のシルク印刷がちゃんと読める上下とは別の方向から見るようになってました。これを東西南北どっちから見るようにするのか決めるオプションをつけたいです。これは8x8の行列を回転させるだけなので難しくないはずです。
CharGenは改良の余地がたくさんあります。
- 現在は16進しか対応してないので、アルファベット全般に広げる
- そのときは入力をASCIIにするなど
- などというと半角カナとか言う話も出てきそうで怖い
- そのときは入力をASCIIにするなど
- 文字自体は 5x7 で出来ており、周りをスペースで囲んであるので、LEDのどちら側にスペースを取るかを決められるようにする
- 複数枚を並べてもっと大きな表示にする
などなど。あと、肝心の基板が手に入らなくなってしまったので、他のデバイスでも出来るようにしたいところです。必要に迫られたら考えます。
