ホームホーム  更新日: 2020-12-05  [日本語 / English]

MAX7219 使用ドットマトリックス LED の制御

はじめに

MAX7219 を使用したドットマトリックス LED モジュールを STM32 マイコンで制御してみます。

ドットマトリックス LED

用意したのは 8x8 のドットマトリックス LED が 4 つ連なったタイプで,8x8 のマトリックスごとに制御用の IC である MAX7219 が載っています。 amazon で 1000 円ちょっとで購入できます。商品説明には Arduino 用とありますが,汎用的なインタフェースなので Arduino 以外のマイコン等でも制御できます。

接続

マイコンボードは STM32F446RE を搭載した NUCLEO-F446RE を使用しました。

ドットマトリックス LED モジュールへの配線は電源が VCC と GND,信号線が DIN,CS,CLK の合計 5 本だけです。3 本の信号線(DIN,CS,CLK)はすべて LED モジュールへの入力となっています。

電源は 5 V を供給します。消費電流がどの程度か不明ですが,とりあえず USB バスパワーで駆動してみました。

信号レベルも 5 V 系が要求されますが,STM32 は 3.3 V 系なので,間にレベルシフタを入れました。

信号線(DIN,CS,CLK)のマイコン側は通常の GPIO(出力に設定)にしています。なお,LED モジュールの DIN に対応するマイコン側の信号名は DOUT としました。

ドットマトリックス LED モジュールへの配線

プログラム

制御用の IC である MAX7219 にコマンドを送ることによって,ドットマトリックス LED の表示を制御します。開発環境は STM32CubeIDE Version: 1.4.2 を使っています。

MAX7219 のインタフェースは基本的にはクロック同期式のシリアル通信です。MAX7219 は CLK 信号の立ち上がりエッジで DIN を読んで内部の 16 bit シフトレジスタに蓄えていきます。そして,CS 信号の立ち上がりエッジで最後に取り込んだ 16 bit をラッチし,コマンドとして実行します。

Wait 関数

今回はマイコンの GPIO を使って,3 本の信号線(DOUT→DIN,CS,CLK)の High,Low を制御しましたが,MXX7219 のデータシートによると,CLK 信号の High,Low のパルス幅はそれぞれ 50 ns 以上という規定があります。 この時間を作り出すために以下のような Wait(時間待ち)関数を定義しました。CPU は 180 MHz で動いているので,NOP 命令 9 個でちょうど 50 ns になります。

// 時間待ち(1 / 180 MHz * 9 = 50 ns)
inline void Wait()
{
    asm volatile("NOP");    asm volatile("NOP");    asm volatile("NOP");
    asm volatile("NOP");    asm volatile("NOP");    asm volatile("NOP");
    asm volatile("NOP");    asm volatile("NOP");    asm volatile("NOP");
}

コマンド送信関数

DOUT,CS,CLK の信号線を制御して MAX7219 にコマンドを送る関数は以下のようになりました。MAX7219 x 4 つぶんに対応して,4 x 16 bit のデータを順次送ったあと,CS を High にしています。

データの構造としては,16 bit のうち上位 8 bit がコマンドを示しており,下位 8 bit はコマンドに応じたデータになっています。

// コマンド送信サブ(MAX7219 x 1 つぶんのデータを送る)
void Led_SendCommandSub(uint16_t data)
{
    for (uint16_t mask = 0x8000 ; mask ; mask >>= 1)
    {
        HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET);
        Wait();
        HAL_GPIO_WritePin(DOUT_GPIO_Port, DOUT_Pin, (data & mask) ? GPIO_PIN_SET : GPIO_PIN_RESET);
        Wait();
        HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_SET);
        Wait();
    }
}

// コマンド送信(MAX7219 x 4 つぶんのデータを送る + CS の制御)
void Led_SendCommand(const uint16_t *data)
{
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
    Wait();

    for (int i=0 ; i < 4 ; i++)
    {
        Led_SendCommandSub(data[i]);
    }

    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
    Wait();

    HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET);
    Wait();
}

LED モジュールの初期化

上で定義したコマンド送信関数を使って,LED モジュールの初期化関数を以下のように定義しました。

データシートの記載によると,Scan Limit の設定と Normal Operation モードへの遷移だけで良さそうですが,どうも起動時に Test Mode 等に入ってしまう(LED が全点灯する)ことがあるようなので,明示的にすべての設定を行っています。

// LED モジュールの初期化
void Led_Init()
{
    const uint16_t data1[] = { 0x0f00, 0x0f00, 0x0f00, 0x0f00 };    // Test Mode = off
    const uint16_t data2[] = { 0x0900, 0x0900, 0x0900, 0x0900 };    // Decode Mode = off
    const uint16_t data3[] = { 0x0a00, 0x0a00, 0x0a00, 0x0a00 };    // Intencity = 1/32 (minimum)
    const uint16_t data4[] = { 0x0b07, 0x0b07, 0x0b07, 0x0b07 };    // Scan Limit = 8
    const uint16_t data5[] = { 0x0c01, 0x0c01, 0x0c01, 0x0c01 };    // Normal Operation Mode

    Led_SendCommand(data1);
    Led_SendCommand(data2);
    Led_SendCommand(data3);
    Led_SendCommand(data4);
    Led_SendCommand(data5);
}

市松模様の表示

以下のようなプログラムで市松模様を表示することが出来ます。main 関数から App_Run 関数を呼び出します。これを実行した結果が,はじめの写真です。

extern "C" int App_Run(void)
{
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET);
    HAL_Delay(100);

    Led_Init();

    // コマンドの準備(上位 8 bit で行を指定し,下位 8 bit で各列の On/Off を指定する)
    const uint16_t data1[] = { 0x010f, 0x010f, 0x010f, 0x010f };    // 1 行目のコマンド □□□□■■■■□□□□■■■■□□□□■■■■□□□□■■■■
    const uint16_t data2[] = { 0x020f, 0x020f, 0x020f, 0x020f };    // 2 行目のコマンド □□□□■■■■□□□□■■■■□□□□■■■■□□□□■■■■
    const uint16_t data3[] = { 0x030f, 0x030f, 0x030f, 0x030f };    // 3 行目のコマンド □□□□■■■■□□□□■■■■□□□□■■■■□□□□■■■■
    const uint16_t data4[] = { 0x040f, 0x040f, 0x040f, 0x040f };    // 4 行目のコマンド □□□□■■■■□□□□■■■■□□□□■■■■□□□□■■■■
    const uint16_t data5[] = { 0x05f0, 0x05f0, 0x05f0, 0x05f0 };    // 5 行目のコマンド ■■■■□□□□■■■■□□□□■■■■□□□□■■■■□□□□
    const uint16_t data6[] = { 0x06f0, 0x06f0, 0x06f0, 0x06f0 };    // 6 行目のコマンド ■■■■□□□□■■■■□□□□■■■■□□□□■■■■□□□□
    const uint16_t data7[] = { 0x07f0, 0x07f0, 0x07f0, 0x07f0 };    // 7 行目のコマンド ■■■■□□□□■■■■□□□□■■■■□□□□■■■■□□□□
    const uint16_t data8[] = { 0x08f0, 0x08f0, 0x08f0, 0x08f0 };    // 8 行目のコマンド ■■■■□□□□■■■■□□□□■■■■□□□□■■■■□□□□

    // コマンド送信
    Led_SendCommand(data1);
    Led_SendCommand(data2);
    Led_SendCommand(data3);
    Led_SendCommand(data4);
    Led_SendCommand(data5);
    Led_SendCommand(data6);
    Led_SendCommand(data7);
    Led_SendCommand(data8);

    while (1)
    {
    }
}