ESP32 + HUB75を使った発車標と方向幕

· Updated: · Haru

「電子工作でできること」でよくイメージに出てくるのが、LEDマトリックスディスプレイではないでしょうか?

今回は、HUB75規格のLEDマトリックスディスプレイを使うことで電車で通勤・通学する際に必ず目にするものを作製してみました。

HUB75規格とは?

HUB75は国際標準化団体などが定めた公式の規格ではありません。中国製のRGB LEDマトリクスモジュールで広く使われるようになった事実上の業界標準(デファクトスタンダード)で、メーカーごとに細部の実装が微妙に違うことがあります(例:G/Bの配線が逆、GNDピンの代わりにアドレス線Eが来ている等)。

一般的なHUB75パネルの入力コネクタは、16ピン(2×8)のIDCボックスヘッダーです。標準的なピン配列は次の通りです。

PIN

信号名

役割

1

R1

上半分の行のR

2

G1

上半分の行のG

3

B1

上半分の行のB

4

GND

5

R2

下半分の行のR

6

G2

下半分の行のG

7

B2

下半分の行のB

8

GND

9

A

行アドレス選択

10

B

行アドレス選択

11

C

行アドレス選択

12

D

行アドレス選択

13

CLK

シフトクロック

14

LAT

ラッチ

15

OE

出力イネーブル

16

GND

動作原理としては、2行同時駆動のシフトレジスタ方式になっています。HUB75パネルの中身は、行ごとに定電流LEDドライバIC(MBI5124やICN2038系が定番)が乗ったシフトレジスタの塊です。

  1. CLKの立ち上がりごとに、R1/G1/B1(上半分の行)とR2/G2/B2(下半分の行)のデータを1ビットずつシフトレジスタに送り込む
  2. 1行分のデータを送り終えたら**LAT**をパルスして、シフトレジスタの内容を出力用レジスタに確定(ラッチ)する
  3. OEでLEDの点灯・消灯を切り替える。ここをPWMのように高速でON/OFFすることで明るさの階調を表現する
  4. A・B・C・Dの組み合わせで「今どの行ペアを光らせているか」を選択し、全行を高速に順次スキャンする

つまり全ピクセルを常時光らせているわけではなく、行を高速に切り替えながら人間の目には全体が点灯して見えるようにしている、という点はダイナミック点灯の液晶や7セグLEDと同じ考え方ですね。

アドレス線の本数はパネルの行数(スキャン方式)で変わり、A〜Cのみなら1/8スキャン、A〜Dなら1/16スキャン、A〜Eまであると1/32スキャン(64行パネルなど)になります。この64行対応版を指して「HUB75E」と呼ぶこともあります。

成果物

今回取り組んだのは、ESP32-S3-DevKitC-1-N8R8をマイコンに採用し、Waveshare製HUB75 64×32パネルを2枚チェーン接続してJR東日本の方向幕/発車標を実機で再現するプロジェクトです。

マイコンの選定理由ですが、CLK/LAT/OEをソフトウェアでビットバンギングすると実用的なリフレッシュレートが出ないため、ESP32-HUB75-MatrixPanel-I2S-DMAのようにI2Sペリフェラルを使ってDMA転送するライブラリを利用することが前提になります。このライブラリはESP32/ESP32-S2/ESP32-S3系統にしか対応しておらず、候補に挙がっていたESP32-C6やArduino Uno R4は非対応と判明し除外しました。

また、HUB75パネルを光らせるために必要な信号線は13本(R1/G1/B1/R2/G2/B2/A/B/C/D/CLK/LAT/OE)必要になります。デイジーチェーン接続に対応しているので、1枚でも2枚でも必要な信号線の本数は変わりません。あまりにもGPIOが少ないマイコンを選ぶと信号線が足りなくなる可能性がありますが、ESP32-S3ならこれを問題なく確保できます。

さらに、時刻表データの同期やNTPでの時刻合わせにWi-Fiが必要ですが、ESP32-S3は802.11 b/g/nを標準搭載しています。また、Wi-Fiが使えることから簡単なHTTPサーバ機能を書いてあげれば、ブラウザ経由でディスプレイを制御することもできます。

表示に使っている文字ですが、基本的に全て手打ちしています。今回は32*64のディスプレイを2枚横に繋げて使用しているので、サイズは32*128です。

この32*128ディスプレイに任意のものを表示させるために、独自の打ち込みツールを実装しました。列車の種別に関しては32*50で、行き先に関しては32*78であらかじめ打ち込んだものを保存しておき、種別と行き先はそれぞれ独立して切り替えることができます。

打ち込んだものは`maku.h`というヘッダファイルにして、ESP32で動かすCプログラムと一緒にコンパイルします。

#pragma once
#include <Arduino.h>

#define MAKU_SPLIT_X 50   // 種別と行き先の境界

struct MakuImg {
  const uint16_t* data;  // w*h, RGB565
  uint16_t        w, h;
  const char*     name;  // UTF-8
};

// maku_type_0: E233-快速[橙] (50x32)
const uint16_t PROGMEM maku_type_0[] = {
  0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60,
  0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60, 0xFC60,

  // 省略
 
};

参考までに、`maku.h`の内部ではこのような形式で保存されています。

打ち込み次第では実際に存在しない組み合わせの方向幕も作れるので楽しいです。今後も空き時間を見つけて色々打ち込んでいきたいと思います。


方向幕だけではもったいないので、せっかくならJRの駅のホームに設置されている発車標も作ってみました。方向幕と発車標はシリアルモニタからコマンドを送信してあげることで自在に切り替えられるようにしてあります。

$ maku
$ dep

ESP32では現在時刻を取得できるので、時刻表の情報をあらかじめ入れておけば現在時刻に基づいた情報を表示させることができます。家にいながら最寄駅にいる気分になれるわけですね笑

画像は試験動作させている時の様子ですが、発車標にするには32*128では横幅が足りないかもしれません。実際に駅に設置されているものは32*192ぐらいでしょうか?

発車標モードにも対応すべく、発車標用の打ち込みツールも実装しました。

発車標では、方向幕とは違って同じ文字が複数回登場することがあります。また、文字の並びも規則的なので、いちいち打ち込んだものを丸ごと表示するよりも、文字ごとにデータを保存しておいてそこから文字を呼び出して組み立ててから表示させる方式の方が圧倒的に効率が良いです。

このツールでは、一通り使いそうな文字を打ち込んで登録しておき、先ほどと同様ヘッダファイル`font.h`として保存しておきます。この打ち込みツールの役割としては、プレビュー表示で確認しながら1文字ずつ打ち込んでいき、フォントデータをヘッダファイルとして書き出せるようにする、といったところでしょうか。発車標としてのレイアウト自体はCプログラムの方で組み立てています。


このプロジェクトの成果物は11月の学祭にも展示すると思いますので、ぜひ見にきてください。

それでは、今回はここらへんで。