MAX7219とArduinoのLEDマトリクス:MD_Parolaで文字をスクロール表示
MAX7219とArduinoのガイド。8x8と4連8x32のLEDマトリクスモジュールの配線、MD_Parolaでのスクロール表示、FC16_HWで鏡文字を直す方法、時計とESP32のコードも紹介します。
MAX7219は、64個のLEDを単独で駆動できる安価なドライバICで、ArduinoでLEDマトリクスを使ういちばん簡単な方法です。よく見かけるのは2種類で、8x8のドットマトリクスが1個だけ載ったモジュールと、4個のマトリクスが横に並んだ4連(4-in-1)8x32ドットマトリクスモジュールです。MAX7219がArduinoから受け取る信号線はわずか3本(DIN、CS、CLK)で、行の走査を毎秒数百回行い、明るさを設定し、すべてのLEDの状態を自分で記憶します。そのぶんArduinoはスクロール表示、時計、スコアボード、アニメーションなどの処理に専念でき、モジュールを端から端へ連結すれば表示をいくらでも長くできます。
このガイドでは、MAX7219のしくみ、モジュールをArduino Uno、ESP32、Raspberry Pi PicoにSPIでつなぐ方法、モジュールの連結、そしてMD_Parolaライブラリで最初のスクロール表示を出す方法を学びます。続いて、初心者が最もつまずく文字が上下逆さや鏡文字になる問題を正しいハードウェアタイプ(FC16_HW)を選んで解決し、明るさと消費電流を計算し、アクセント付きの文字や自作の文字を表示し、RTCモジュールを使った時計と1ピクセルずつ描くアニメーションを作ります。最後の章では、ESP32でスマートフォンからメッセージを送る方法、Pico用のMicroPythonコード、よくある失敗の対処法を紹介します。

MAX7219とは?しくみ
64個のLEDをArduinoのピン1本ずつにつなぐことはできないため、LEDマトリクスではダイナミック点灯(マルチプレクス)を使います。8x8マトリクスのLEDは、8行×8列の格子状に配線されています。MAX7219はある瞬間には1つのLEDグループ(データシートでいう「digit」で、マトリクスでは1行または1列)だけを有効にし、残りの線を使ってそのグループの8個のLEDのどれを点灯させるかを決め、次のグループへ移ります。8グループ全体は毎秒約800回走査されます。目はこの速さについていけないので、画像は止まって見えます。その間チップは、各グループのパターンを内部の8x8ビットのRAMに保持し、1本の抵抗でLEDの電流を制限し、明るさを16段階で設定します。
ArduinoとMAX7219は、SPIとほぼ同じように動く3線式のシリアルインターフェースで通信します。コマンドはすべて16ビットのパケットで、上位バイトが書き込むレジスタを選び、下位バイトが値を運びます。CS(データシートではLOAD)がLOWの間、CLKの立ち上がりエッジごとにDINからビットが送り込まれ、CSが再びHIGHになるとパケットがレジスタに書き込まれます。74HC595シフトレジスタと同じ考え方ですが、MAX7219は走査と電流制限も引き受けてくれるので、手間がずっと少なくて済みます。SPI全般のしくみはSPIとは?で解説しています。
| 項目 | 値 |
|---|---|
| 電源電圧 | 4.0〜5.5 V(実際には5 V) |
| インターフェース | 3線式シリアル(DIN、CLK、CS)、最大10 MHz |
| 駆動できるLED | 64個(8x8マトリクス1個、または7セグメント8桁) |
| 走査周波数 | 標準800 Hz(8桁) |
| セグメント電流 | RSET抵抗で設定、最大40 mA(モジュールでは通常10 kΩ) |
| 明るさ | 16段階、デューティ比1/32〜31/32 |
| シャットダウン時の電流 | 約150 µA |
| 入力HIGHのしきい値 | 3.5 V以上(5 V電源時) |
| 縦続接続(デイジーチェーン) | DOUT出力を次のチップのDIN入力へ |
MAX7219にはアドレス指定できるレジスタが14個ありますが、LEDマトリクスに必要なのは下の表のものです。設定はライブラリがやってくれますが、それぞれの役割を知っているとデバッグのときに大いに役立ちます。たとえば、チップは電源を入れた直後は表示が消えたシャットダウンモードで起動するので、初期化コードが実行されないとモジュールが完全に壊れているように見えます。兄弟品のMAX7221はレジスタは同じですが、本物のSPI互換CSピンと、電磁ノイズを減らすスルーレート制限付きのセグメントドライバを備えています。
| アドレス | レジスタ | 役割 |
|---|---|---|
| 0x00 | No-Op(何もしない) | チェーン内の別のチップにパケットを送る間、このチップの状態を変えない |
| 0x01–0x08 | Digit 0–7(桁0〜7) | 8行(または8列)それぞれの8ビットのLEDパターン |
| 0x09 | Decode Mode(デコードモード) | 7セグメント用のBCDデコード。マトリクスではオフ(0x00) |
| 0x0A | Intensity(輝度) | 明るさ、0x0〜0xF |
| 0x0B | Scan Limit(走査範囲) | 走査する桁数。マトリクスでは7(8桁すべて) |
| 0x0C | Shutdown(シャットダウン) | 0:シャットダウンモード、1:通常動作 |
| 0x0F | Display Test(表示テスト) | 1:すべてのLEDが最大の明るさで点灯 |
8x8単体モジュールと4連8x32モジュール、どちらを選ぶ?
よく見かける2つのモジュールは回路は同じで、違うのはLEDマトリクスとMAX7219の配線のしかたと基板の形です。緑色の基板の単体モジュールには、DIP(24ピン)のMAX7219と1088ASなどの8x8マトリクスが載っていて、片側に入力ピン(VCC、GND、DIN、CS、CLK)、反対側に出力ピン(VCC、GND、DOUT、CS、CLK)があります。青い4連モジュールでは、4個のSMDのMAX7219がマトリクスの下にあり、4つのブロックは工場で連結済みです。基板には通常FC-16と書かれています。長い文字をスクロールさせるなら、4連モジュールのほうが安くて見た目もすっきりします。

| 項目 | 8x8単体モジュール | 4連8x32モジュール(FC-16) |
|---|---|---|
| 解像度 | 8x8 = 64個のLED | 32x8 = 256個のLED |
| MAX7219の数 | 1個(DIPパッケージ) | 4個(SMDパッケージ) |
| コードでのハードウェアタイプ | 通常はGENERIC_HW | 通常はFC16_HW |
| 一度に見える文字数 | 1文字 | 4〜6文字(文字幅による) |
| 最大の明るさでの最大電流 | 約330 mA | 約1.3 A |
| 向いている用途 | アイコン、サイコロ、スマイルマーク、小さなゲーム | スクロール表示、時計、スコアボード |
MAX7219の配線:Arduino Uno、ESP32、Pico
モジュールのピンは5本です。電源用のVCCとGND、データ用のDIN、クロック用のCLK、チップセレクト(ロード)用のCSです。MD_ParolaとMD_MAX72XXライブラリは標準でハードウェアSPIを使うので、DINはボードのMOSIピンに、CLKはSCKピンにつなぐ必要があります。CSはどのデジタルピンでも構いません。ボードのハードウェアSPIピンの位置はピン配置ツールで確認できます。配線は必ずモジュールの入力(IN)側、つまりDINと書かれた端に差してください。DOUTと書かれた端は次のモジュール用です。
| モジュールのピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | 5V (VIN) | VBUS (5 V) |
| GND | GND | GND | GND |
| DIN | D11 (MOSI) | GPIO 23 (MOSI) | GP19 (SPI0 TX) |
| CS | D10 | GPIO 5 | GP17 |
| CLK | D13 (SCK) | GPIO 18 (SCK) | GP18 (SPI0 SCK) |
ESP32とPicoのピンは3.3 Vですが、MAX7219のデータシートでは5 V電源時のHIGHレベルとして3.5 V以上が求められています。つまり3.3 Vの信号は、厳密には上限にわずかに届きません。それでもたいていのモジュールは動きますが、配線が長い場合や特定のロットのチップでは、ランダムな点や乱れた画像が出ることがあります。確実な対策は、3.3 VをHIGHとして受け付けて5 Vを出力する74HCT125などのバッファか、DIN、CLK、CSの各線に入れる高速なレベル変換モジュールです。信号線を20 cm未満に抑えるだけで解決することもよくあります。
モジュールの連結(デイジーチェーン)
MAX7219のDOUTピンは、DINに入ったデータを16.5クロック後に出力します。あるモジュールのOUT側を次のモジュールのIN側につなぐと、DOUTが次のチップのDINに入り、CLKとCSはすべてのチップで共有されます。こうすればArduinoから出る信号線は3本のままで、表示を8、16、32ブロックと長くできます。ライブラリは更新のたびにチェーン全体へパケットを送り、CSが立ち上がった瞬間に全チップが同時に表示を更新します。4連モジュールではこの配線が基板上で済んでいます。4連モジュール2個で8ブロック(64x8)の表示を作るなら、1個目のOUTピンを2個目のINピンにつなぎ、コードのMAX_DEVICESを8にするだけです。
ライブラリでは、ブロック番号はDIN入力にいちばん近いモジュールから始まり、左へ向かって増えていきます。MD_MAX72XXの原点は表示の右上の角です。そのため、文字を正しく読めるようにするには、DIN入力を右側に置く必要があります。チェーンが長くなると、本当の制約になるのは電源です。4〜8ブロックごとに太い線で5 VとGNDをチェーンに追加で供給し、各モジュールの電源入力に10 µFの電解コンデンサと100 nFのセラミックコンデンサを入れてください。LEDの電流パルスによるリップルを抑えるため、データシートでもこの組み合わせが推奨されています。
ライブラリのインストール:MD_MAX72XXとMD_Parola
MAX7219用のライブラリで特に人気のある2つは、同じ作者(majicDesigns)によるもので、組み合わせて使います。MD_MAX72XXは低レベルの処理を担当し、モジュールの初期化、ピクセルや列の書き込み、フォントの管理を行います。MD_Parolaはその上に、スクロール表示、エフェクト、文字揃え、ゾーンの機能を加えます。古いサンプルでよく見かけるLedControlライブラリも使えますが、スクロールは自分で書く必要があります。ライブラリをインストールしたことがない場合は、先にArduinoライブラリのインストール方法を読んでください。
- Arduino IDEでライブラリマネージャ(「ツール」>「ライブラリを管理」)を開き、MD_Parolaを検索します。
- majicDesignsのMD_Parolaライブラリをインストールします。依存ライブラリについて聞かれたら、全てをインストールを選んでMD_MAX72XXも一緒に入れます。
- 上の表のとおりにモジュールを配線し、4連モジュールならコードのHARDWARE_TYPEをFC16_HWに、MAX_DEVICESを4にします。
- スケッチを書き込みます。文字が上下逆さ、鏡文字、横倒しに見えても心配はいりません。下のハードウェアタイプの章で、1行変えるだけで直せます。
最初のプログラム:MD_Parolaで文字をスクロール表示
1// MAX7219 4'lü modülde kayan yazı (MD_Parola) / Scrolling text on a 4-in-1 MAX7219 module (MD_Parola)
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // mavi PCB'li 4'lü modül / blue-PCB 4-in-1 module
7#define MAX_DEVICES 4 // zincirdeki 8x8 blok sayısı / number of 8x8 blocks in the chain
8#define CS_PIN 10 // DIN = D11 (MOSI), CLK = D13 (SCK)
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12void setup() {
13 display.begin();
14 display.setIntensity(4); // parlaklık 0–15 / brightness 0–15
15 display.displayClear();
16 // metin, hizalama, hız (ms/kare), bekleme (ms), giriş efekti, çıkış efekti
17 // text, alignment, speed (ms/frame), pause (ms), entry effect, exit effect
18 display.displayText("Merhaba BlueGrays!", PA_CENTER, 50, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
19}
20
21void loop() {
22 if (display.displayAnimate()) { // efekt bittiğinde true döner / returns true when the effect ends
23 display.displayReset(); // aynı yazıyı baştan başlat / restart the same text
24 }
25}いちばん大事な行はdisplayText()です。引数は順に、テキスト、文字揃え(PA_LEFT、PA_CENTER、PA_RIGHT)、フレーム間の時間(ミリ秒)、文字が止まったあとの停止時間、表示開始と終了のエフェクトです。速度の値が小さいほど文字は速く動き、25〜60 msなら読みやすいスクロールになります。displayAnimate()は呼ばれるたびに最大1フレームだけ進めて、すぐに戻ります。そのためloop()の中で絶えず呼び出す必要があり、コードのほかの部分でdelay()を使ってはいけません。使うと文字の動きがカクつきます。プログラムを止めずに待つ方法はArduinoのmillis()とdelay()で解説しています。
| 関数 | 働き |
|---|---|
| begin() | モジュールを初期化し、シャットダウンから起こして表示を消去する |
| displayText(テキスト, 文字揃え, 速度, 停止時間, 開始エフェクト, 終了エフェクト) | 表示するテキストとエフェクトを設定し、アニメーションを開始する |
| displayAnimate() | 次のフレームを描く。エフェクトが終わるとtrueを返す |
| displayReset() | 同じテキストとエフェクトを最初からもう一度再生する |
| print("...") | エフェクトなしで文字を固定表示する |
| setIntensity(0–15) | すべてのモジュールの明るさを設定する |
| setInvert(true) | 点灯と消灯のLEDを入れ替える(反転表示) |
| displayShutdown(true) | 表示をシャットダウンモードにする。falseで復帰する |
| begin(2)とsetZone(0, 0, 1) | 表示を独立したゾーンに分け、ゾーンごとに別のテキストとエフェクトを使う |
PA_SCROLL_LEFTのほかにも、表示開始と終了のエフェクトはたくさんあります。PA_PRINT(文字が一度に現れる)、PA_SCROLL_UPとPA_SCROLL_DOWN(縦スクロール)、PA_WIPE、PA_OPENINGとPA_CLOSING(中央から開く・中央へ閉じる)、PA_FADE(明るさを変えてフェード)、PA_DISSOLVE、PA_BLINDS、PA_RANDOM、PA_GROW_UPなどです。すべてを見るには、「ファイル」>「スケッチ例」>「MD_Parola」からParola_Animation_Catalogスケッチを書き込んでください。HARDWARE_TYPEとMAX_DEVICESの行をモジュールに合わせて変えるのを忘れないようにしましょう。
ハードウェアタイプ(FC16_HW)の選び方と鏡文字の直し方
8x8マトリクスとMAX7219の配線はメーカーによって異なります。チップの「digit」線が行につながっている基板もあれば列につながっている基板もあり、行や列の順番が逆のこともあります。ライブラリはこの違いをHARDWARE_TYPEの設定で吸収します。タイプを間違えてもモジュールが故障しているわけではありませんが、8x8の各ブロックの文字が鏡文字、上下逆さ、または90度回転して見えます。ネット上の古いコードの多くはPAROLA_HWかGENERIC_HWを使っているため、今売られている青い4連モジュールではこれがいちばん多いトラブルです。下の表は症状ごとに変えるべき点をまとめたもので、4連のFC-16モジュールを前提にしています。
| 見え方 | 考えられる原因 | 対処 |
|---|---|---|
| どのブロックでも文字が左右反転している | PAROLA_HWを選択しており、列の順番が逆 | FC16_HWを使う |
| どのブロックでも文字が90度回転している | GENERIC_HWを選択しており、行と列が入れ替わっている | FC16_HWを使う |
| どのブロックでも文字が上下逆さ | ICSTATION_HWを選択しており、行と列の順番が両方逆 | FC16_HWを使う |
| テキスト全体が上下逆さで、左から右へ流れる | モジュールの向きが逆で、DIN入力が左側にある | モジュールを180度回してDIN入力を右側にする |
| 一部のブロックだけ正しく、残りは空白か乱れている | MAX_DEVICESが実際のブロック数と合っていない | MAX_DEVICESをブロック数に合わせる |
明るさと消費電流
明るさは2つの要素で決まります。1つ目はモジュール上のRSET抵抗です。チップはISETピンから流れ出る電流の約100倍の電流をLEDに流します。モジュールでは通常10 kΩが使われており、赤色LEDでは40 mAを少し超え、青や白のLEDでは約40 mAのピーク電流になります。2つ目はソフトウェアで設定するIntensityレジスタです。0〜15の値nを設定すると、各走査期間のうち(2n+1)/32の時間だけLEDが点灯するので、0が最も暗く(1/32)、15が最も明るく(31/32)なります。さらに各行は時間の8分の1しか点灯しないため、最大の明るさでもLED1個の平均電流は約5 mAです。LEDの色ごとの順方向電圧はLEDの電圧ツールで確認できます。
モジュールの平均電流は、I ≈ 8 mA + 5 mA × (2n+1)/32 × Lでおおよそ見積もれます。8 mAはチップ自体の消費電流、nは明るさの値、Lはそのモジュールで点灯しているLEDの数(0〜64)です。5 mAは40 mAのピーク電流の8分の1です。たとえば明るさ15で64個すべてを点灯させると、8 + 5 × 0.97 × 64 ≈ 318 mAとなり、データシートの330 mAとほぼ一致します。スクロール表示では点灯するLEDはふつう25〜35パーセントだけなので、実際の消費電流はずっと少なくなります。下の表は4連モジュールの標準的な値です。
| 条件 | 1モジュールあたり | 4連モジュール(32x8) |
|---|---|---|
| 全LED点灯、明るさ15 | ≈ 320 mA | ≈ 1.3 A |
| 全LED点灯、明るさ7 | ≈ 160 mA | ≈ 630 mA |
| スクロール表示(LEDの約30%)、明るさ7 | ≈ 55 mA | ≈ 210 mA |
| スクロール表示(LEDの約30%)、明るさ2 | ≈ 25 mA | ≈ 90 mA |
| シャットダウンモード(displayShutdown) | ≈ 0.15 mA | ≈ 0.6 mA |
夜に表示を暗くしたいなら、LDR光センサーで明るさを自動調整し、人がいないときはdisplayShutdown(true)で表示をスリープさせられます。電池で動く表示板を設計する場合は、上の値を電池寿命計算ツールに入力すれば何時間もつかがわかります。屋内なら明るさは15ではなく2〜4程度で十分なことが多く、目にもやさしく、LEDの電流もおよそ3分の1から6分の1に減ります。
アクセント付きの文字と特殊文字(UTF-8)
Arduino IDEはコードをUTF-8で保存するため、「é」や「ü」のような文字はテキストの中で1バイトではなく2バイトになります。MD_Parolaは1バイトずつをフォントの文字番号として探すので、アクセント付きの文字は意味のない記号に化けてしまいます。ライブラリの標準フォントにはLatin-1の文字(é、è、à、ñ、ç、ä、ö、ü、ßなど)がすべて入っているので、必要なのはUTF-8をフォントのコードに変換する小さな関数だけです。下の例にあるトルコ語のğ、ı、şのようにLatin-1にない文字は、1ピクセルずつ描いてaddChar()で追加する必要があります。下のスケッチはこの2つの処理を両方行います。なお、日本語のかなや漢字は標準フォントに含まれておらず、高さ8ピクセルで表すにはドットを自分で設計する必要があります。
1// MAX7219 + MD_Parola: Türkçe karakterli kayan yazı / Scrolling text with Turkish letters
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
7#define MAX_DEVICES 4
8#define CS_PIN 10
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12// Fontta olmayan 6 harf: ilk bayt genişlik, sonrakiler sütunlar (bit 0 = en üst satır)
13// 6 letters missing from the font: first byte is the width, then the columns (bit 0 = top row)
14const uint8_t trG[] = {5, 56, 69, 70, 85, 116}; // Ğ
15const uint8_t trg[] = {4, 25, 166, 166, 125}; // ğ
16const uint8_t trI[] = {3, 68, 125, 68}; // İ
17const uint8_t tri[] = {1, 124}; // ı
18const uint8_t trS[] = {5, 70, 73, 201, 73, 49}; // Ş
19const uint8_t trs[] = {4, 88, 212, 84, 52}; // ş
20
21// UTF-8 metni fontun kodlarına çevirir / converts UTF-8 text to the font's codes
22void utf8ToFont(const char *src, char *dst, size_t size) {
23 size_t n = 0;
24 while (*src && n < size - 1) {
25 uint8_t c = *src++;
26 if (c < 0x80) { dst[n++] = c; continue; } // ASCII aynen kalır / ASCII stays as is
27 if ((c & 0xE0) == 0xC0 && *src) { // 2 baytlık harf / 2-byte letter
28 uint16_t u = ((c & 0x1F) << 6) | (*src++ & 0x3F);
29 switch (u) {
30 case 0x11E: dst[n++] = (char)0x80; break; // Ğ
31 case 0x11F: dst[n++] = (char)0x81; break; // ğ
32 case 0x130: dst[n++] = (char)0x82; break; // İ
33 case 0x131: dst[n++] = (char)0x83; break; // ı
34 case 0x15E: dst[n++] = (char)0x84; break; // Ş
35 case 0x15F: dst[n++] = (char)0x85; break; // ş
36 default: dst[n++] = (u < 0x100) ? (char)u : '?'; // ç ö ü Ç Ö Ü ° fontta var / already in the font
37 }
38 continue;
39 }
40 while ((*src & 0xC0) == 0x80) src++; // desteklenmeyen karakteri atla / skip an unsupported character
41 dst[n++] = '?';
42 }
43 dst[n] = '\0';
44}
45
46char message[96];
47
48void setup() {
49 display.begin();
50 display.setIntensity(3);
51 display.addChar(0x80, trG); // addChar begin()'den sonra / addChar after begin()
52 display.addChar(0x81, trg);
53 display.addChar(0x82, trI);
54 display.addChar(0x83, tri);
55 display.addChar(0x84, trS);
56 display.addChar(0x85, trs);
57 utf8ToFont("Günaydın İstanbul! Şehirde hava 22°C, çok güzel bir gün.", message, sizeof(message));
58 display.displayText(message, PA_LEFT, 40, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
59}
60
61void loop() {
62 if (display.displayAnimate()) display.displayReset();
63}自作文字の各配列では、最初のバイトが文字の幅(列数)で、続くバイトが左から右への各列です。各バイトのビット0がいちばん上の行です。たとえば点のないıは1列だけの文字で、値124(0b01111100)で2行目から6行目が点灯します。普通のiとの違いは上の点がないことだけです。大文字のĞとİは、アクセント記号を置く場所を作るため、文字の本体を2行低く(7行ではなく5行に)しています。addChar()はデータをコピーせずアドレスだけを記録するので、配列はグローバルに置き、begin()のあとで追加する必要があります。コード0x80〜0x85を選んだのは、普通のテキストには出てこないからです。同じ方法で、度の記号、矢印、ハートなど好きな記号を追加できます。
時計の例:RTCモジュールでデジタル時計

Arduinoのmillis()のカウンターは電源が切れるとリセットされ、Unoのセラミック発振子のせいで1日に最大数分ずれることもあります。本格的な時計にはRTC時計モジュールを使いましょう。DS1307モジュールをI2Cバスにつなぎます(SDA → A4、SCL → A5、VCC → 5V、GND → GND)。MAX7219はSPIのピンを使うので、2つのモジュールはぶつかりません。コードのために、ライブラリマネージャからAdafruitのRTClibライブラリをインストールしてください。
1// MAX7219 + DS1307 RTC ile dijital saat / Digital clock with MAX7219 + DS1307 RTC
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5#include <RTClib.h> // Adafruit RTClib
6
7#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
8#define MAX_DEVICES 4
9#define CS_PIN 10
10
11MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
12RTC_DS1307 rtc; // SDA = A4, SCL = A5
13
14char timeText[8]; // "12:34" + sonlandırıcı / + terminator
15uint8_t lastSecond = 99;
16
17void setup() {
18 display.begin();
19 display.setIntensity(2);
20 display.setTextAlignment(PA_CENTER); // print() bu hizalamayı kullanır / print() uses this alignment
21 if (!rtc.begin()) {
22 display.print("RTC?"); // modül bulunamadı / module not found
23 while (true) {}
24 }
25 if (!rtc.isrunning()) {
26 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // ilk kurulumda derleme saati / compile time on first run
27 }
28}
29
30void loop() {
31 DateTime now = rtc.now();
32 if (now.second() != lastSecond) { // saniyede bir güncelle / update once a second
33 lastSecond = now.second();
34 // ':' ve boşluk aynı genişlikte, yazı kaymaz / ':' and space are equally wide, nothing shifts
35 sprintf(timeText, (lastSecond % 2) ? "%02d %02d" : "%02d:%02d", now.hour(), now.minute());
36 display.print(timeText);
37 }
38}このスケッチは1秒に1回時刻を読み取り、コロンを点滅させます。標準フォントではコロン(:)とスペースが同じ幅(2列)なので、数字の位置がずれません。RTCを初めて動かしたときは、スケッチをコンパイルした時刻に設定されます。DS1307は1か月に1分ほどずれることがあります。もっと正確な時計にしたいなら、DS3231モジュールを用意し、RTC_DS1307の代わりにRTC_DS3231を使い、条件if (!rtc.isrunning())をif (rtc.lostPower())に変えてください。DS3231は温度補償付きの水晶を使っているので、ずれは1年に1分ほどしかありません。表示を2つのゾーンに分けて、片側に時刻、もう片側に温度を表示することもできます。
アニメーション:MD_MAX72XXで1ピクセルずつ描く
スクロール表示から一歩進むなら、MD_MAX72XXライブラリを直接使えます。このライブラリはチェーン全体を1つのピクセルディスプレイとして扱い、4連モジュールなら32列×8行になります。setPoint(row, column, state)はLEDを1個だけ点灯・消灯し、setColumn()は1列の8ビットを一度に書き込み、clear()は表示を消去します。原点(0, 0)は右上の角で、列番号は左へ、行番号は下へ向かって増えます。下のコードは、表示の端で跳ね返るボールを描きます。
1// MD_MAX72XX ile seken top animasyonu / Bouncing ball animation with MD_MAX72XX
2#include <MD_MAX72xx.h>
3#include <SPI.h>
4
5#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
6#define MAX_DEVICES 4
7#define CS_PIN 10
8
9MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
10
11int x = 0, y = 0; // topun konumu (sütun, satır) / ball position (column, row)
12int dx = 1, dy = 1; // hareket yönü / direction of travel
13
14void setup() {
15 mx.begin();
16 mx.control(MD_MAX72XX::INTENSITY, 2);
17 mx.clear();
18}
19
20void loop() {
21 const int lastCol = mx.getColumnCount() - 1; // 4 modülde 31 / 31 with 4 modules
22 mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); // çizim bitene kadar gönderme / hold updates while drawing
23 mx.setPoint(y, x, false); // eski konumu sil / erase the old position
24 x += dx;
25 y += dy;
26 if (x <= 0 || x >= lastCol) dx = -dx; // kenara çarpınca dön / bounce off the edge
27 if (y <= 0 || y >= 7) dy = -dy;
28 mx.setPoint(y, x, true); // yeni konumu yak / light the new position
29 mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); // hepsini tek seferde gönder / send everything at once
30 delay(40);
31}各フレームでは、まず自動更新(UPDATE)をオフにし、古いピクセルを消して新しいピクセルを描いてから、UPDATEをオンに戻して変更を一度にモジュールへ送ります。こうすることで、描きかけのフレームが表示に出るのを防げます。同じやり方で、サイコロ、スマイルマーク、ヘビゲーム、音量レベルのバーなども描けます。MD_MAX72XXのスケッチ例には、パックマン、ロボットの目、砂時計といった既成のアニメーションも入っています。MD_Parolaを使っているときは、getGraphicObject()で同じ描画関数を呼び出せます。
ESP32でインターネット経由のメッセージ:スマートフォンから文字をスクロール表示
ESP32を使えば、スクロール表示板をワイヤレスにできます。下のコードは自宅のネットワークに接続し、まず自分のIPアドレスを表示にスクロールさせ、次にインターネット(NTP)から取得した時刻を表示します。同じWi-Fiにつないだスマートフォンのブラウザでそのアドレスを開くとテキストボックスが表示され、入力したメッセージが次の周回からスクロールを始め、時計と交互に表示されます。モジュールは表のとおりVSPIのピンにつなぎます。CLKをGPIO 18、DINをGPIO 23、CSをGPIO 5です。ESP32が初めての場合は、先にESP32とは?を読んでください。
1// ESP32: telefondan mesaj + internet saati / ESP32: message from your phone + internet time
2#include <WiFi.h>
3#include <WebServer.h>
4#include <time.h>
5#include <MD_Parola.h>
6#include <MD_MAX72xx.h>
7#include <SPI.h>
8
9#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
10#define MAX_DEVICES 4
11#define CS_PIN 5 // VSPI: CLK = GPIO 18, DIN = GPIO 23
12
13const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
14const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
15
16MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
17WebServer server(80);
18
19char message[128];
20char nextMessage[128];
21char clockText[8] = "--:--";
22bool newMessage = false;
23bool showClock = false;
24
25const char PAGE[] = R"rawliteral(<!DOCTYPE html><html><head><meta charset="utf-8">
26<meta name="viewport" content="width=device-width"><title>MAX7219</title></head><body>
27<h2>Kayan yazı / Scrolling text</h2>
28<form action="/"><input name="m" maxlength="100"> <button>Gönder / Send</button></form>
29</body></html>)rawliteral";
30
31void handleRoot() {
32 if (server.hasArg("m")) { // formdan gelen mesaj / message from the form
33 server.arg("m").toCharArray(nextMessage, sizeof(nextMessage));
34 newMessage = true;
35 }
36 server.send(200, "text/html; charset=utf-8", PAGE);
37}
38
39void setup() {
40 display.begin();
41 display.setIntensity(3);
42 display.print("WiFi..");
43 WiFi.begin(ssid, password);
44 while (WiFi.status() != WL_CONNECTED) delay(500);
45 configTime(3 * 3600, 0, "pool.ntp.org"); // Türkiye UTC+3 / Turkey is UTC+3
46 snprintf(message, sizeof(message), "IP: %s", WiFi.localIP().toString().c_str());
47 server.on("/", handleRoot);
48 server.begin();
49 display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
50}
51
52void loop() {
53 server.handleClient();
54 if (display.displayAnimate()) { // önceki gösterim bitti / the previous show has ended
55 showClock = !showClock;
56 if (newMessage) { // yeni mesaj sırayı keser / a new message jumps the queue
57 strlcpy(message, nextMessage, sizeof(message));
58 newMessage = false;
59 showClock = false;
60 }
61 if (showClock) {
62 struct tm t;
63 if (getLocalTime(&t, 10)) strftime(clockText, sizeof(clockText), "%H:%M", &t);
64 display.displayText(clockText, PA_CENTER, 40, 3000, PA_SCROLL_DOWN, PA_SCROLL_UP);
65 } else {
66 display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
67 }
68 }
69}handleRoot()はフォームから届いたメッセージをそのまま表示に送るのではなく、いったんnextMessageに預けます。メッセージがコピーされるのは今のアニメーションが終わったとき(displayAnimate()がtrueを返したとき)だけなので、MD_Parolaが読んでいるテキストが途中で変わることはありません。configTime()の3 × 3600という値は、トルコで使われているタイムゾーン(UTC+3)です。日本(UTC+9)なら9 × 3600に変えてください。日本には夏時間がないので、これだけで済みます。イギリスの場合はこの行をconfigTzTime("GMT0BST,M3.5.0/1,M10.5.0", "pool.ntp.org")に置き換えれば、夏時間にも対応できます。アクセント付きのメッセージを表示するには、前の章のaddChar()の行とutf8ToFont()関数をこのスケッチに加え、strlcpy()の行をutf8ToFont(nextMessage, message, sizeof(message))に置き換えます。インターネットの時刻やリアルタイムのWebダッシュボードについては、ESP32のNTP時計とESP32のWebサーバーとWebSocketを参照してください。
Raspberry Pi PicoとMicroPythonでMAX7219を使う
MicroPythonのPicoでは、オープンソースのmax7219.pyドライバ(micropython-max7219)をThonnyでPicoにコピーするのがいちばん手軽です。このドライバはMicroPythonのframebufモジュールを使っているので、text()、pixel()、line()、rect()などの描画関数がすぐに使え、4連のFC-16モジュールでも正しい向きで表示されます。モジュールはVBUS(5 V)から給電し、DINをGP19、CLKをGP18、CSをGP17につなぎます。MicroPythonはSPI0のMISOピン(GP16)も確保するので、このピンはほかの用途に使わないでください。framebufのフォントは8x8ピクセルでASCII文字だけなので、4連モジュールに一度に表示できるのは4文字で、アクセント付きの文字は表示されません。セットアップについてはRaspberry Pi Picoとは?とMicroPythonの基本を参照してください。
1# Pico + 4'lü MAX7219 modül: kayan yazı / Pico + 4-in-1 MAX7219 module: scrolling text
2from machine import Pin, SPI
3import time
4import max7219 # max7219.py Pico'da olmalı / must be on the Pico
5
6# GP18 = SCK -> CLK, GP19 = MOSI -> DIN, GP17 -> CS; GP16 (MISO) boş kalır / GP16 (MISO) stays free
7spi = SPI(0, baudrate=5_000_000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19))
8display = max7219.Matrix8x8(spi, Pin(17, Pin.OUT), 4)
9display.brightness(3) # parlaklık 0–15 / brightness 0–15
10
11def scroll(text, delay_ms=35):
12 width = len(text) * 8 # her harf 8 piksel / each letter is 8 px wide
13 for x in range(32, -width, -1): # sağdan girip solda kaybolur / enters right, leaves left
14 display.fill(0)
15 display.text(text, x, 0, 1)
16 display.show()
17 time.sleep_ms(delay_ms)
18
19while True:
20 scroll("BlueGrays + Pico")
21 display.fill(0)
22 display.text("Pico", 0, 0, 1) # 4 harf tam sığar / exactly 4 letters fit
23 display.show()
24 time.sleep(1)
25 display.fill(0)
26 display.rect(0, 0, 32, 8, 1) # ekran çerçevesi / display frame
27 display.show()
28 time.sleep_ms(500)MAX7219のプロジェクトアイデア
- スクロールするメッセージボード:お店や教室、自分の部屋のドア用にメッセージボードを作り、HC-05 Bluetoothモジュールを使ってスマートフォンからメッセージを変えます。
- 時計・カレンダー・温度:RTCの時刻と日付、DHT11センサーの温度と湿度を交互に表示します。
- インターネット時計と天気:ESP32を使って、NTPの時刻と天気APIから取得した気温をスクロール表示します。
- スコアボード:赤外線リモコンで操作する、卓球やバスケットボールの試合用の2ゾーン表示を作ります。
- ミニゲーム:ジョイスティックを使って、32x8の表示でヘビゲーム、Pong、障害物よけゲームを遊べます。ほかのアイデアはArduinoのゲームプロジェクトにあります。
- 音量レベルメーター:音センサーの読み取り値を32列の棒グラフで表示します。
- 32x32のアニメーションパネル:4連モジュールを4個縦に積み重ね、雨、星、花火などのアニメーションを再生します。
よくある失敗と対処法
| 症状 | 考えられる原因 | 対処 |
|---|---|---|
| LEDがまったく点灯しない | 電源が来ていない、配線がモジュールのOUT側にある、またはCSピンがコードと合っていない | DINと書かれたIN側に配線を差し、CS_PINと5 V電源を確認する |
| すべてのLEDが全点灯したまま変わらない | データが届いていない:DINとCLKが入れ替わっているか、チップがテストモードのまま | DINをMOSIに、CLKをSCKにつなぎ、モジュールの電源を入れ直す |
| 文字が鏡文字、上下逆さ、または90度回転している | HARDWARE_TYPEが間違っている | 4連モジュールではFC16_HW、単体モジュールではGENERIC_HWを試す |
| テキスト全体が上下逆さで、左から右へ流れる | モジュールが逆向きに取り付けられ、DIN入力が左側にある | モジュールを180度回す。DIN入力は右側にする |
| 1ブロックしか動かない、または文字が途中で切れる | MAX_DEVICESが間違っている、またはモジュール間の接続が切れている | MAX_DEVICESをブロック数に合わせ、OUT → INの配線を確認する |
| 明るさを上げるとArduinoがリセットする、または表示がちらつく | USBの500 mAの上限を超えて電圧が下がっている | 外部の5 V 2 AのACアダプターを使い、GNDを共通にして明るさを下げる |
| ESP32でランダムな点や文字化けが出る | 3.3 Vの信号がHIGHのしきい値3.5 Vに届かず、配線も長い | 74HCT125でレベルを上げ、配線を短くする |
| アクセント付きの文字が意味のない記号になる | UTF-8のバイトがそのままフォントに渡されている | utf8ToFont()とaddChar()の方法を使う |
| スクロールする文字がカクついたり引っかかったりする | loop()の中にdelay()や時間のかかる処理がある | displayAnimate()を頻繁に呼び、待ち時間はmillis()で処理する |
MAX7219は、わずかな配線で目を引く表示を作れるいちばん安価な方法のひとつです。走査、電流制限、明るさはチップが引き受けてくれるので、何を表示するかに集中できます。まずは8x8単体モジュールでアイコンを描くところから始め、次に4連モジュールでスクロール表示、時計、ESP32を使ったワイヤレスのメッセージへと進みましょう。カラフルで1個ずつ制御できるLEDがほしいならWS2812B NeoPixelのLEDがよい選択で、数字だけを表示するプロジェクトなら7セグメントディスプレイが向いています。BlueGraysアプリでは、ドットマトリクスの時計とカレンダー、気象ステーション、32x8のアニメーション、スクロール表示などのMAX7219のプロジェクトを、回路図とコード付きでステップごとに見られます。
よくある質問
MAX7219モジュールの文字が上下逆さや鏡文字に見えるのはなぜですか?
8x8マトリクスとMAX7219の配線はメーカーによって異なり、ライブラリはHARDWARE_TYPEの設定でその違いを補正します。今売られている青い4連モジュールのほとんどはFC16_HWで、緑色の単体モジュールはGENERIC_HWで正しく表示されます。それでも表示がおかしければ、PAROLA_HWとICSTATION_HWも試してください。変えるのはコードの1行だけです。
MAX7219モジュールは何個まで連結できますか?
チップ自体に上限はなく、MD_MAX72XXはモジュールの数を1バイトで保持するので、ソフトウェア上は最大255個まで使えます。実際の上限は電源と信号の品質で決まります。4〜8ブロックごとに5 VとGNDを追加で供給し、配線を短くし、各モジュールに10 µFと100 nFのコンデンサを入れてください。チェーンが長いほど、1回の更新にかかる時間と使うRAMが増えます。
MAX7219は3.3 VのESP32で動きますか?
MAX7219には4 V以上の電源が必要なので、モジュールはESP32の5V(VIN)ピンから給電してください。データシートでは5 V電源時のHIGHレベルとして3.5 Vが求められています。ESP32の3.3 Vの信号でもたいていのモジュールは動きますが、この値には届きません。確実に動かすには、DIN、CLK、CSの各線に74HCT125などのレベル変換ICを入れてください。
MAX7219のLEDマトリクスの消費電流はどれくらいですか?
8x8単体モジュールで全LEDを最大の明るさで点灯させると約330 mA、4連8x32モジュールでは最大1.3 Aになります。スクロール表示では一部のLEDしか点灯せず、明るさもふつう2〜7程度に抑えるので、4連モジュールの消費電流は通常100〜250 mAほどです。シャットダウンモードでは、各チップの消費電流はわずか約150 µAです。
MAX7219にアクセント付きの文字や特殊文字を表示するには?
MD_Parolaの標準フォントには、é、ñ、ä、ö、ü、ßなどのLatin-1の文字が入っていますが、Arduino IDEはこれらをUTF-8の2バイトで保存します。表示する前に、小さな関数でテキストをフォントのコードに変換してください。Latin-1にない文字は、自分でドットを描いてaddChar()で追加できます。
MAX7219で7セグメントディスプレイを駆動できますか?
できます。MAX7219はもともと最大8桁のカソードコモン7セグメントディスプレイ用に設計されたチップで、数字を自動でデコードするBCDモードも備えています。市販の8桁MAX7219表示モジュールは、LedControlライブラリのsetDigit()関数で簡単に使えます。アノードコモンのディスプレイは、このチップに直接つなぐことはできません。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
ドットマトリクス時計とカレンダー(MAX7219、DS1307)
机や壁に置き、時刻と日付をスクロール表示する光るパネルです。8x32のLEDディスプレイには大きな数字の時計が表示され、コロンの2つの点が毎秒点滅します。毎分の初めに曜日と日付がスクロールし、その後、時計表示に戻ります。時計モジュールには専用の電池があるため、電源を抜いても時刻を保持します。

ドットマトリクス時計・カレンダー・気象ステーション(MAX7219、DS1307)
机や壁に置いて、時刻、日付、室内の温湿度を順番に表示する、文字がスクロールする表示板です。8x32のLEDディスプレイには、まず摂氏と華氏の温度、次に湿度、そして2つの点が1秒ごとに点滅する時計が表示されます。分が変わると、曜日と日付がスクロール表示されます。時計モジュールが時刻を保持し、DHT22センサーが温度と湿度を測定します。

32x8 LEDマトリクス4枚でアニメーションと文字表示
横長のLEDマトリクスモジュールを4枚上下に並べると、1,000個以上の光の点からなる正方形のディスプレイになります。まず映画のように文字が上から雨のように降り、続いてカーテンが開いて2行の文字がスクロールし、その間を波やひし形が流れていきます。一連の演出はちらつかず滑らかに動き、終わると自動で最初から繰り返します。

32x8 LEDマトリックス4枚で作る6種のアニメーション
32x8 LEDパネル4枚を縦に並べた正方形のディスプレイで、6種類のアニメーションが順番に再生されます。降り注ぐデジタルの雨、広がる渦巻き、自ら生まれては消える細胞、広がる波紋、手前に飛んでくる星、そして花火です。それぞれ約8秒続き、ピクセルがゆっくり消えながら次のアニメーションに移ります。すべてを1台のArduino Unoで動かします。

ピクセルスネーク:8x8マトリクス
定番のスネークゲームを自分の手で作って遊びましょう。カラフルな8x8のLEDマトリクスで、ジョイスティックを使って緑のヘビを動かし、赤いリンゴを食べます。リンゴを食べるたびにヘビが長くなり、少しずつ速くなって、小さな数字ディスプレイのスコアも増えます。壁や自分のしっぽにぶつかると画面が赤く3回点滅し、ボタンを押すと再び始められます。

ESP32とNTPでインターネットから正確な時刻を取得
ESP32をWi-Fiに接続し、NTP(Network Time Protocol)サーバーからミリ秒精度の現在時刻を取得します。configTzTime()関数でタイムゾーンを自動適用し、夏時間を採用する国ではPOSIX TZ文字列で夏時間と標準時間の切り替えも自動管理します。時刻はシリアルモニタと、必要に応じて接続するI2C LCDに表示します。configTzTime()がSNTPサービスを開始し、ネットワークにアクセスできる間は定期的に再同期します。

ESP32とDS1307 RTCのデジタル時計(I2C + LCD)
このプロジェクトでは、DS1307リアルタイムクロック(RTC)モジュールを使って16x2 I2C LCDに時刻と日付を表示します。DS1307は電池でバックアップされるRTCで、ESP32の電源を切っても時刻を保持します。両方のモジュールが同じI2Cバス(SDA/SCL)を共有するため、2本の信号線だけで複数の機器を制御する方法を学べます。I2C通信プロトコルやRTCモジュールの動作原理、LCDの表示データを随時更新する方法を実践的に学ぶプロジェクトです。

4桁の7セグメントディスプレイで作るデジタル時計
キッチンのオーブンの時計のように、4桁の数字が光るデジタル時計です。DS1302時計モジュールが時刻を保持し、Arduinoが毎秒、時と分を読み取って4桁のディスプレイに表示します。時と分の間には小さな点が点灯します。時刻を保持するモジュールとディスプレイを組み合わせる方法を学びます。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

