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または互換
⑥プッシュスイッチ1:GPI011
プッシュスイッチ2:GPI012
#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秒待機
}
プログラム中に「RTCライブラリをインクルード」で使用しています。
このためRTC(DS1307)ライブラリをインストールしています。
参考 https://github.com/adafruit/RTClib
プログラムを書き込んで実行してみましょう。
シリアルモニタで結果を確認した結果、日時、時間、あとは時間、分、秒の十の位、一の位が表示されているのが分かります。
ここで時間、分、秒の各桁を抽出してバラバラにしています。
その理由は、ドットマトリックスに各数字を表示させるための仕込みになります。
プログラムの内容を見てみると、
DateTime now = rtc.now(); // 現在の日時を取得
この1行で時間データを取得してnowという変数に代入しています。
あとはそのデータを加工して表示させているだけですね。
ここは絵心スキルが必要なお時間です。
メインプログラムとは別ファイルにして、データをヘッダファイルにしています。
分けることでLEDデータを使いまわしたり、プログラムの見易さなどを考慮しています。
led_d1の変数に、8x8のデータを入れています。
0は消灯、1は点灯させたいところに数字を入れています。
薄目でみると、led_d1は数字の1に見えてきませんか?
#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番に戻る。
という流れです。
#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秒以下は確実に時間がずれますね。