RP2040マイコンでドットマトリックスを制御しようボードのサンプルプログラム②

- ## 商品概要

 RP2040マイコンを使って、8x8LED ドットマトリックスを制御して楽しもうというコンセプトです。
 RTCも簡単につながるので、時間表示なども楽しんで知的好奇心を満たせれば嬉しいです。

 - ## サンプルサンプルプログラム内容
 I2C接続されたRTC（時計機能）から日付、時間データを読み出してシリアルモニタに表示させるプログラムです。
 RTCの時刻を設定していない場合や、ボタン電池を入れ替えたときに日時、時間データを再設定したい場合などにも使えます。

 - ## プログラム環境
 ArduinoIDE バージョン：2.3.2
 RTCライブラリ　version to 2.1.4
 
 - ## ハードウェアの主な仕様
　①マイコン:PR2040-ZERO（RaspberryPiPico系）
　②I2C接続ピン　
　 SDA：GPIO8（GPIO4）
　 SDL：GPI09（GPIO5）
　③RTC　DS1307RTCモジュール
　④Lチカ用LED　：GPIO10
　⑤マトリクスLEDドライバ　HT16K33-28Wまたは互換
　⑥プッシュスイッチ１：GPI011
　 プッシュスイッチ２：GPI012

- ## サンプルプログラム1

```c++

#include <Wire.h>  // I2C通信ライブラリをインクルード
#include "RTClib.h"  // RTCライブラリをインクルード

RTC_DS1307 rtc;  // RTCオブジェクトの作成

void setup() {
  Serial.begin(9600);  // シリアル通信を9600bpsで開始
  Wire.setClock(400000L);  // I2Cクロックを400kHzに設定
  Wire.begin();  // I2Cバスを初期化

  if (!rtc.begin()) {  // RTCが見つからない場合
    Serial.println("Couldn't find RTC");
    while (1);  // 無限ループで停止
  }

  if (!rtc.isrunning()) {  // RTCが動作していない場合
    Serial.println("RTC is NOT running, setting the time!");
    // 現在の日時を手動で設定 (2024年7月28日 20時53分0秒)
    rtc.adjust(DateTime(2024, 7, 28, 20, 53, 0));
  }
}

void loop() {
  DateTime now = rtc.now();  // 現在の日時を取得

  // 時間、分、秒を10の位と1の位に分割
  int hourTens = now.hour() / 10;
  int hourOnes = now.hour() % 10;
  int minuteTens = now.minute() / 10;
  int minuteOnes = now.minute() % 10;
  int secondTens = now.second() / 10;
  int secondOnes = now.second() % 10;

  // 現在の日時をシリアルモニタに表示
  Serial.print("Current Time: ");
  Serial.print(now.year(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.day(), DEC);
  Serial.print(" ");
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.println();

  // 各時間、分、秒の位をシリアルモニタに表示
  Serial.print("Hour Tens: ");
  Serial.println(hourTens);
  Serial.print("Hour Ones: ");
  Serial.println(hourOnes);
  Serial.print("Minute Tens: ");
  Serial.println(minuteTens);
  Serial.print("Minute Ones: ");
  Serial.println(minuteOnes);
  Serial.print("Second Tens: ");
  Serial.println(secondTens);
  Serial.print("Second Ones: ");
  Serial.println(secondOnes);

  delay(1000);  // 1秒待機
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782127/0e090137-68f2-7f19-4668-769213aa2a0e.png)

プログラム中に「RTCライブラリをインクルード」で使用しています。
このためRTC（DS1307）ライブラリをインストールしています。

参考　https://github.com/adafruit/RTClib

プログラムを書き込んで実行してみましょう。
シリアルモニタで結果を確認した結果、日時、時間、あとは時間、分、秒の十の位、一の位が表示されているのが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782127/f7c42f5a-f292-52d2-8388-4ababe6b2081.png)

ここで時間、分、秒の各桁を抽出してバラバラにしています。
その理由は、ドットマトリックスに各数字を表示させるための仕込みになります。

プログラムの内容を見てみると、
```c++
DateTime now = rtc.now();  // 現在の日時を取得
```
この1行で時間データを取得してnowという変数に代入しています。
あとはそのデータを加工して表示させているだけですね。

- ## サンプルプログラム2

ここは絵心スキルが必要なお時間です。
メインプログラムとは別ファイルにして、データをヘッダファイルにしています。
分けることでLEDデータを使いまわしたり、プログラムの見易さなどを考慮しています。

led_d1の変数に、8x8のデータを入れています。
0は消灯、1は点灯させたいところに数字を入れています。
薄目でみると、led_d1は数字の1に見えてきませんか？

```c++
#ifndef LED_DATA_H
#define LED_DATA_H

// 自作数字のデータ
byte led_d1[8] = { 
  B00001000, 
  B00011000, 
  B00101000, 
  B00001000, 
  B00001000, 
  B00001000, 
  B00001000, 
  B00011100 
};

byte led_d2[8] = { 
  B00111100, 
  B01000010, 
  B00000010, 
  B00000100, 
  B00001000, 
  B00010000, 
  B00100010, 
  B01111110
};

byte led_d3[8] = { 
  B00111110, 
  B01000001, 
  B00000001, 
  B00011110, 
  B00000001, 
  B00000001,
  B01000001, 
  B00111110
};

byte led_d4[8] = { 
  B00001000, 
  B00011000, 
  B00101000, 
  B01001000, 
  B11111110, 
  B00001000, 
  B00001000, 
  B00011100
};

byte led_d5[8] = { 
  B11111110, 
  B10000000, 
  B11111100, 
  B10000010, 
  B00000010, 
  B00000010, 
  B10000010, 
  B01111100
};

byte led_d6[8] = { 
  B00111100, 
  B01000000, 
  B10000000, 
  B11111100, 
  B10000010, 
  B10000010, 
  B10000010, 
  B01111100, 
};

byte led_d7[8] = { 
  B01111110, 
  B00000010, 
  B00000010, 
  B00000100, 
  B00001000, 
  B00010000, 
  B00100000, 
  B01000000 
};

byte led_d8[8] = { 
  B01111110, 
  B01000010, 
  B01000010, 
  B01111110, 
  B01000010, 
  B01000010, 
  B01000010, 
  B01111110 
};

byte led_d9[8] = { 
  B00111100, 
  B01000010, 
  B01000010, 
  B00111110, 
  B00000010, 
  B00000010, 
  B01000010, 
  B00111100 
};

byte led_d0[8] = { 
  B00111100, 
  B01000010, 
  B01000010, 
  B01000010, 
  B01000010, 
  B01000010, 
  B01000010, 
  B00111100 
};

byte led_da[8] = { 
  B00100000, 
  B11110000, 
  B00100111, 
  B11111101, 
  B01000111, 
  B11111101, 
  B01010111, 
  B01100000 
};//アットマーク＠のつもりです。

byte led_dq[8] = { 
  B00011000, 
  B00100100, 
  B01000010, 
  B10111101, 
  B01001000, 
  B01001000, 
  B01000100, 
  B00100100 
};

#endif

 ```
 数字データですが、バランスが悪いので、絵心スキルで書き換えを楽しんでもらえればと思います。
 上記を”led_data.h”ファイル名にして、メインプログラムと同じところにファイルを置くことで読み出すことができるようになります。

 本体のプログラムになります。


 プログラムの大まかな流れですが、
1.数値データをled_data.hからドットマトリックスを光らせるデータを取得。
2.led_data.hのデータは人間が分かりやすいデータ形式なので左右を反転させる。
3.LEDドライバ設定する。
4.表示を全部0で全部消灯させる。
5.日時、時間データを取得してnowという変数に代入する。
6.nowの値から、時間、分、秒を1桁の数字にデータを加工する。
7.取得した時間データをドットマトリックスにデータを送信。
8.1秒待って5番に戻る。
という流れです。

 

```c++
#include <Wire.h>
#include "RTClib.h"
#include "led_data.h" // 自作ヘッダーファイル
RTC_DS1307 rtc; // RTC（リアルタイムクロック）オブジェクトの作成

//########## LED Matrix Control IC ############
#define LDaddrs1 (0x70)  // LEDドライバー左のアドレス
#define LDaddrs2 (0x71)  // LEDドライバー中央のアドレス
#define LDaddrs3 (0x73)  // LEDドライバー右のアドレス

// 自作数字のデータ（ポインタ配列）
byte* digits[10] = {
  led_d0, led_d1, led_d2, led_d3, led_d4, led_d5, led_d6, led_d7, led_d8, led_d9
};

// 左右にミラー処理（左右反転）をする関数
void mirrorHorizontal(byte input[8], byte output[8]) {
  for (int i = 0; i < 8; i++) {
    output[i] = 0;
    for (int j = 0; j < 8; j++) {
      if (input[i] & (1 << j)) {
        output[i] |= (1 << (7 - j));
      }
    }
  }
}

// HT16K33のLED表示
void LED_Disp(byte Addrs, byte data1[8], byte data2[8]) {
  Wire.beginTransmission(Addrs);
  Wire.write(0x00); // データの開始アドレスを設定
  for (int i = 0; i < 8; i++) {
    Wire.write(data1[i]); // 偶数行のデータを書き込み
    Wire.write(data2[i]); // 奇数行のデータを書き込み
  }
  Wire.endTransmission();
}

// マトリクスLED制御ICの設定
void setupMatrix(byte Addrs) {
  LED_Driver_Setup(Addrs); // LEDドライバのセットアップ
  LED_Driver_Blink(Addrs); // LEDドライバの点滅設定
  LED_Driver_Brightness(Addrs, 4); // 明るさを設定（0～15）
  LED_Driver_DisplayInt(Addrs); // LED表示を0で初期化
}

// LEDドライバ　HT16K33　セットアップ
void LED_Driver_Setup(byte Addrs) {
  Wire.beginTransmission(Addrs);
  Wire.write(0x21); // HT16K33を起動
  Wire.endTransmission();
}

// LEDドライバ　HT16K33　明るさ設定
void LED_Driver_Brightness(byte Addrs, byte brightness) {
  Wire.beginTransmission(Addrs);
  Wire.write(0xE0 | brightness); // 明るさを設定
  Wire.endTransmission();
}

// LEDドライバ　HT16K33　点滅設定
void LED_Driver_Blink(byte LD_addrs) {
  Wire.beginTransmission(LD_addrs);
  Wire.write(0x81); // 点滅設定　点灯のみ
  Wire.endTransmission();
}

// LEDドライバ　HT16K33　画面初期化
void LED_Driver_DisplayInt(byte Addrs) {
  Wire.beginTransmission(Addrs);
  Wire.write(0x00); // データの開始アドレスを設定
  for (int i = 0; i < 8; i++) {
    Wire.write(B00000000); // 上段の8x8LEDに0を書き込み
    Wire.write(B00000000); // 下段の8x8LEDに0を書き込み
  }
  Wire.endTransmission();
}

void setup() {
  Serial.begin(9600); // シリアル通信を開始
  Wire.setClock(400000L); // I2Cの動作クロックを設定（400kHz）
  Wire.begin(); // I2C通信を開始
  rtc.begin(); // RTCを開始
  
  setupMatrix(LDaddrs1); // 左のLEDドライバをセットアップ
  setupMatrix(LDaddrs2); // 中央のLEDドライバをセットアップ
  setupMatrix(LDaddrs3); // 右のLEDドライバをセットアップ
}

void loop() {
  DateTime now = rtc.now();

  byte hourTens = now.hour() / 10;
  byte hourOnes = now.hour() % 10;
  byte minuteTens = now.minute() / 10;
  byte minuteOnes = now.minute() % 10;
  byte secondTens = now.second() / 10;
  byte secondOnes = now.second() % 10;

  byte hourTensMirrored[8], hourOnesMirrored[8];
  byte minuteTensMirrored[8], minuteOnesMirrored[8];
  byte secondTensMirrored[8], secondOnesMirrored[8];
  
  mirrorHorizontal(digits[hourTens], hourTensMirrored);
  mirrorHorizontal(digits[hourOnes], hourOnesMirrored);
  mirrorHorizontal(digits[minuteTens], minuteTensMirrored);
  mirrorHorizontal(digits[minuteOnes], minuteOnesMirrored);
  mirrorHorizontal(digits[secondTens], secondTensMirrored);
  mirrorHorizontal(digits[secondOnes], secondOnesMirrored);

  // 各マトリクスに数字を表示
  LED_Disp(LDaddrs3, hourTensMirrored, hourOnesMirrored);
  LED_Disp(LDaddrs2, minuteTensMirrored, minuteOnesMirrored);
  LED_Disp(LDaddrs1, secondTensMirrored, secondOnesMirrored);
  delay(1000); // 1秒表示
}

```
これを書き込むとRTCから読み出した時間データ
時間、分、秒に分けてその値をled_dataと結び付けて
ドットマトリックスに変換して1秒で更新しています。
0.5秒更新にしても見栄えの変わらない状態が予想されますので1秒待ったら更新にしています。
厳密にいうとこの1秒以下は確実に時間がずれますね。