WokwiでMAX7219ドットマトリクスLEDをモードスイッチ連動表示する方法|FRONT LEFT・CENTER・RIGHTを順番に切り替える

C言語関連

AVRやArduinoでスイッチによるモード切り替えを作っている場合、MAX7219などのドットマトリクスLEDを追加して「現在選択しているモード名」を表示すると、操作状態が非常に分かりやすくなります。

たとえばモードスイッチを押すたびに、FRONT LEFT → FRONT CENTER → FRONT RIGHT → FRONT LEFT…と表示を切り替えたい場合、基本的な考え方は難しくありません。スイッチ処理とLED表示処理を別々に作り、現在のモード番号を使って表示文字列を選択します。

実際のWokwiプロジェクトでは、すでにmode変数があり、PB0側のモード+スイッチを押すとmode = (mode + 1) % 14;によって0~13を順番に切り替える構造になっています。この既存の仕組みを利用すれば、ドットマトリクス表示を後付けしやすくなります。[参照] 対象のWokwiプロジェクト

まず「スイッチ」「状態」「表示」を3つに分けて考える

モード切り替えプログラムを作るときに重要なのは、スイッチを押した瞬間に大量の表示処理を直接書き込むのではなく、現在の状態を表す変数を変更し、その状態を表示関数へ渡すという構造にすることです。

たとえば最初は次の3状態だけを考えます。

mode 表示
0 FRONT LEFT
1 FRONT CENTER
2 FRONT RIGHT

モード+スイッチを押したらmodeを1増やし、3になったら0へ戻します。C言語ならmode = (mode + 1) % 3;だけで実現できます。

この方法なら、あとから「SURROUND LEFT」「SURROUND RIGHT」などを増やす場合も、状態と文字列を追加するだけで拡張できます。

現在のWokwiプログラムにはすでにモード切り替え処理がある

対象プロジェクトを見ると、uint8_t mode;という変数が用意され、メインループ内でスイッチの前回値と今回値を比較しています。

モード+スイッチについては、概念的に次の処理がすでに入っています。

if (bis(sw, 8) && bic(sw, 0)) {
    mode = (mode + 1) % 14;
    ...
}

bis(sw, 8)が前回のPB0がHIGHだったこと、bic(sw, 0)が現在LOWであることを確認しているため、押した瞬間の立ち下がりを検出する構造です。

したがってドットマトリクスを追加するときも、スイッチ判定を新しく作り直す必要はありません。この場所でmodeが変化したあと、LED表示を更新するという考え方が最も自然です。

最初の練習では3モードだけにすると分かりやすい

ドットマトリクスの表示処理を確認する段階では、一度14モード全部を考えず、3モードだけでテストすると原因を切り分けやすくなります。

たとえば次のようにします。

uint8_t mode = 0;

if (bis(sw, 8) && bic(sw, 0)) {
    mode = (mode + 1) % 3;
    matrix_update(mode);
}

この場合、電源投入時が0なら「FRONT LEFT」、1回押すと1になって「FRONT CENTER」、もう1回押すと2になって「FRONT RIGHT」、さらに押すと0へ戻ります。

動作確認が終わったら、元の% 14へ戻し、0~13の各モード名を用意すれば同じ考え方で拡張できます。

文字列は配列にするとif文を大量に書かずに済む

表示文字列をモードごとにifswitchで書くこともできますが、文字列の一覧を配列にしたほうが後から追加しやすくなります。

3種類だけなら、次のような形です。

const char *mode_text[] = {
    "FRONT LEFT",
    "FRONT CENTER",
    "FRONT RIGHT"
};

表示するときはmode_text[mode]を使えば、現在のモードに対応する文字列を取得できます。

たとえばmode == 0ならmode_text[0]なので「FRONT LEFT」、mode == 2なら「FRONT RIGHT」です。この「数値を状態として保持し、配列から名称を引く」という構造は、組み込みプログラムでも非常によく使います。

MAX7219のドットマトリクスならDIN・CLK・CSの3信号で制御できる

WokwiのMAX7219ドットマトリクスは、主にDINCLKCSの3本の制御信号を使います。このほか電源のVCCとGNDが必要です。

複数の8×8マトリクスを横につないだモジュールでは、MAX7219をデイジーチェーンして横長の表示器として利用できます。Wokwiでもchain属性によって複数ユニットを連結できます。[参照] Wokwi MAX7219 Dot Matrix Reference

なお、Wokwiにはparolafc16などのレイアウト設定があります。実機でもモジュールの配線方式によって文字が左右反転したり90度回転したりするため、使うMAX7219モジュールに合った設定を選ぶ必要があります。

既存回路では空いているピンを確認してから接続する

対象のプログラムはすでに多数のI/Oを使用しています。PORTBのPB0~PB4はスイッチ、PORTCのPC0~PC2は7セグメント表示、PORTDのPD2~PD7はリレーやミュート表示に使われています。

そのため、一般的なArduino UnoのMAX7219作例でよく使われるD11、D13などをそのまま真似すると、既存回路との競合が起こる可能性があります。

現在の構成を見る限り、たとえばPC3、PC4、PC5などの未使用端子をDIN、CLK、CSとしてソフトウェアで制御する方法を検討できます。ただし最終的な実機では、ATmega328Pのピン配置、基板の配線、他の周辺回路を確認したうえで決定してください。

MD_Parolaを使うと英文字表示はかなり簡単になる

MAX7219へ直接文字を表示する場合、本来は「F」「R」「O」など各文字の5×7や8×8ビットパターンを用意し、MAX7219へ1列ずつ送信する処理が必要です。

それを簡単にしてくれる代表的なArduinoライブラリがMD_ParolaMD_MAX72XXです。文字表示、左右・中央寄せ、スクロールなどを扱えます。[参照] MD_Parola公式GitHub

たとえば通常のArduinoスケッチとして新規に動作確認するなら、4連MAX7219で次のような考え方にできます。

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 4
#define CS_PIN 10

MD_Parola matrix(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

const char *modeText[] = {
  "FRONT LEFT",
  "FRONT CENTER",
  "FRONT RIGHT"
};

uint8_t mode = 0;

void showMode()
{
  matrix.displayClear();
  matrix.displayText(modeText[mode], PA_CENTER, 50, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

displayText()では文字列、位置、速度、停止時間、表示効果などを指定できます。MD_Parola公式ドキュメントでも静止表示やスクロール表示を行うAPIが提供されています。[参照] MD_Parola Class Reference

「FRONT CENTER」は4連32×8ではスクロール表示が扱いやすい

8×8マトリクスを4個横につなぐと表示領域は32×8ドットになります。しかし一般的な5ドット幅程度の英字フォントでは、「FRONT CENTER」のような長い文字列を一度に全部表示することはできません。

そのため、4連程度のモジュールであればスクロール表示にするのが簡単です。「FRONT LEFT」が左から流れ、「FRONT CENTER」に切り替えたら新しい文字列が流れる、といった動作にできます。

もし全文を静止表示したいなら、MAX7219の連結数を増やす、小さいフォントを作る、「FL」「FC」「FR」のような略称を使う、といった方法があります。

オーディオのチャンネル表示器として使うのであれば、通常時は「FL」「FC」「FR」を固定表示し、モード変更時だけ「FRONT LEFT」などのフル名称をスクロールさせる設計も見やすくなります。

スイッチを押した瞬間だけ表示内容を変更するのがポイント

初心者向けのプログラムでは、メインループを通るたびにdisplayClear()と文字列設定を繰り返してしまうことがあります。しかし表示文字列を毎回初期化すると、スクロールが先へ進まなかったり、ちらついたりする原因になります。

基本は、モードが変化した瞬間だけ新しい文字列を設定し、その後はアニメーション更新だけを繰り返すことです。

Arduino+MD_Parolaなら概念的には次のようになります。

void loop()
{
  bool pressed = /* モードスイッチの押下判定 */;

  if (pressed) {
    mode = (mode + 1) % 3;
    showMode();
  }

  if (matrix.displayAnimate()) {
    matrix.displayReset();
  }
}

この分離をしておくと、リレー制御や7セグ表示が増えても、それぞれの処理を整理しやすくなります。

現在のプログラムへ組み込むならmode変更直後が分かりやすい

対象のWokwiコードでは、PB0の立ち下がりを検出したあとにmodeを書き換えています。そのため、最終的にはその直後にドットマトリクスの表示変更関数を呼ぶ構造が分かりやすいです。

イメージとしては次のようになります。

if (bis(sw, 8) && bic(sw, 0)) {
    mode = (mode + 1) % 14;

    matrix_set_mode(mode);

    if (mute_state) {
        mute_state = 0;
        disp(7);
    } else {
        disp(3);
    }
}

matrix_set_mode()の中では、0なら「FRONT LEFT」、1なら「FRONT CENTER」、2なら「FRONT RIGHT」というように表示文字列を決定します。

こうしておけば、既存の7セグ・リレー・ミュート処理へ大きく手を加えず、ドットマトリクス表示を追加機能として分離できます。

14モードまで増やすなら表示名を14個のテーブルにする

現在のコードではモードが0~13まであります。そのため最終形では14個の名前を配列として持つ方法が使いやすくなります。

const char *mode_text[14] = {
    "FRONT LEFT",
    "FRONT CENTER",
    "FRONT RIGHT",
    "FRONT HEIGHT LEFT",
    "FRONT HEIGHT CENTER",
    "FRONT HEIGHT RIGHT",
    "FRONT WIDE LEFT",
    "FRONT WIDE RIGHT",
    "SURROUND LEFT",
    "SURROUND RIGHT",
    "SURROUND BACK LEFT",
    "SURROUND BACK RIGHT",
    "TOP MIDDLE",
    "TOP SURROUND"
};

実際のモード割り当ては既存プログラムの仕様に合わせて変更します。ここで重要なのは名称そのものではなく、mode_text[mode]だけで表示名を取り出せる構造です。

あとから15番目のモードを追加する場合も、配列へ文字列を追加し、モード数を変更するだけで済みます。

現在の直書きAVRプログラムとArduinoライブラリを混ぜる場合は注意する

対象コードはdigitalRead()digitalWrite()を中心にした一般的なArduinoスケッチではなく、PORTBPORTCPORTDDDRCなどを直接操作し、int main(void)を中心に書かれています。

一方、MD_ParolaはArduino向けライブラリです。そのため既存コードへ何も考えずに#include <MD_Parola.h>だけ追加するより、既存の低レベルAVR方式を維持してMAX7219も直接制御するか、全体をArduinoのsetup()/loop()方式へ整理してMD_Parolaを使うかを決めたほうが保守しやすくなります。

まず表示方法を理解する目的なら、MAX7219+MD_Parolaだけの小さな別プロジェクトで「ボタンを押す→3つの文字列が変わる」動作を完成させ、その後で既存プログラムへ統合する方法がおすすめです。

低レベル方式を維持するならMAX7219送信関数を独立させる

現在のようにAVRのポートを直接操作する方式を維持したい場合は、MAX7219用に「16ビットを送る関数」「初期化関数」「文字表示関数」を独立して作ると整理できます。

MAX7219への基本送信は、CSをLOWにし、レジスタ番号8ビットとデータ8ビットをDINへ送りながらCLKを16回動かし、最後にCSをHIGHへ戻すという構成です。

たとえば関数構成だけを先に決めるなら、次のようにできます。

void max7219_send(uint8_t address, uint8_t data);
void matrix_init(void);
void matrix_clear(void);
void matrix_set_mode(uint8_t mode);
void matrix_scroll(void);

メインループ側はMAX7219の細かな送信方法を意識せず、matrix_set_mode(mode);だけを呼べるようにするのがポイントです。

ボタンのチャタリング対策も忘れない

実機の押しボタンは、1回押しても電気的にはHIGHとLOWが短時間に何度か揺れる「チャタリング」が発生します。そのまま読むと、1回押したのにFRONT LEFTから一気にFRONT RIGHTまで進んだように見える場合があります。

対象コードは前回値と現在値から立ち下がりを検出し、ループ末尾に_delay_ms(10)も入っているため、単純に現在値がLOWなら毎回加算するコードよりは連続カウントしにくい構造になっています。

ただし実機で誤動作する場合には、20~50ms程度安定したことを確認してから押下確定するデバウンス処理を追加するとさらに確実です。

表示処理には長いdelayを入れないほうがよい

ドットマトリクスでスクロール文字を作るとき、delay(3000)のような長い待ち時間を入れる実装は簡単ですが、その間はモードスイッチやミュートスイッチを読み取れなくなることがあります。

今回のようにリレーや複数スイッチも同時に扱う装置では、表示処理だけのためにCPUを長時間止めないほうが適しています。

MD_ParolaのdisplayAnimate()のような非ブロッキング型の更新方式や、AVRであればmillis()相当のタイマー・カウンタを使って一定時間ごとに1列進める方式にすると、文字を流しながらスイッチ入力も継続できます。

最初に作るべきテストプログラム

いきなり現在の大きなプログラムへすべて組み込むより、次の順番で確認するとトラブルが少なくなります。

  1. ドットマトリクスに「TEST」など固定文字を表示する
  2. 「FRONT LEFT」をスクロール表示する
  3. 1個のボタンで0→1→2→0と変数を切り替える
  4. mode_text配列を使って3種類の文字を切り替える
  5. 既存プログラムのmodeスイッチ処理と結合する
  6. 最終的に14モード分の文字列を登録する

この順番なら、「文字が出ない」のが配線の問題なのか、「ボタンで変わらない」のがスイッチ処理の問題なのかを切り分けられます。

組み込み開発では機能を一つずつ確認してから統合するほうが、最終的には短時間で完成しやすくなります。

まとめ:mode変数と表示文字列を結び付ければ拡張しやすい

モードスイッチを押すたびにドットマトリクスLEDへ「FRONT LEFT」「FRONT CENTER」「FRONT RIGHT」を順番に表示する場合、基本構造はスイッチでmodeを変更する→modeに対応した文字列を選ぶ→ドットマトリクスの表示だけ更新するという3段階です。

対象のWokwiプログラムには、すでにmode変数とPB0の立ち下がり検出が実装されています。そのためスイッチ制御を一から書き直すより、mode変更直後にmatrix_set_mode(mode)のような表示更新関数を追加するのが自然です。

MAX7219を簡単に文字表示したい場合はMD_Parolaが便利ですが、現在のコードはAVRレジスタを直接操作する低レベルな構成なので、ライブラリを使うならArduino方式へ整理する選択肢もあります。既存方式を維持するなら、空いているポートを使ってMAX7219通信と文字描画を独立した関数として実装するとよいでしょう。

最初は3モードだけで完成させ、動作したあとにmode_text[]へ表示名を追加していけば、現在の14モードや将来追加するチャンネル表示にも同じ仕組みをそのまま応用できます。

コメント

タイトルとURLをコピーしました