Arduino Nanoで作る8×8 LEDマトリックスのフリップクロック - 自作リアルタイムクロック
必要な部品と材料
| Arduino Nano R3 | ×1 |
| Maxim Integrated DS3231M - ±5ppm, I2C リアルタイムクロック | ×1 |
| 8×8 LEDマトリックス(MAX7219ドライバー付き) | ×1 |
| RGBディフューズド コモンカソードLED | ×1 |
| 抵抗 220Ω | ×2 |
| ロータリーエンコーダー(プッシュボタン付き) | ×1 |
必要な工具と機器
- はんだごて(汎用)
アプリとオンラインサービス
- Arduino IDE
このプロジェクトについて
Adrian Jones氏によって開発された8×8 LEDマトリックスモジュールクロックを製作しました。時刻は左から右へスクロール表示され、ソフトウェア制御で上から下への垂直フリップ表示にも切り替え可能です。時刻の各桁が右側からスライドインし、中央で一瞬停止してわずかに明るくなり、その後左へスクロールアウトしながら次の桁がスクロールインします。このサイクルが短い遅延を挟んで繰り返されます。
「上から下へ」の垂直フリップ表示にするには、コード内の以下の行を変更してください:
static boolean top2bottom = false;
を
static boolean top2bottom = true;
に変更します。
コードのコメントにある通り、スクロール速度、文字間の空白行数、12/24時間表示、文字間のミリ秒、輝度など、多くのパラメータを変更可能です。
時刻設定はロータリーエンコーダーで簡単に行えます。2色ダイオードが設定モード(分、時、通常)を表示します。時計は2つの基本パーツで構成されるボックスに収納されており、内部へのアクセスが非常に便利で実用的です。

コード
code (Arduino)
//*****************************************************************************************//
// MATRIX CLOCK
// Adrian Jones, March 2014
// - allows left-to-right and top-to-bottom scrolling
//*****************************************************************************************//
#include <Wire.h> // I2C-WIRE library
#include <RTClib.h> // RTC-Library
// MAX7219レジスタと制御ピンの定義
#define max7219_reg_noop 0x00
#define max7219_reg_digit0 0x01
#define max7219_reg_digit1 0x02
#define max7219_reg_digit2 0x03
#define max7219_reg_digit3 0x04
#define max7219_reg_digit4 0x05
#define max7219_reg_digit5 0x06
#define max7219_reg_digit6 0x07
#define max7219_reg_digit7 0x08
#define max7219_reg_decodeMode 0x09
#define max7219_reg_intensity 0x0a
#define max7219_reg_scanLimit 0x0b
#define max7219_reg_shutdown 0x0c
#define max7219_reg_displayTest 0x0f
#define dataIn 12 // DIN
#define load 10 // CS
#define clock 11 // CLK
// アルファベット・数字フォントデータ(8×8ドット)
char alphanum[][8] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // space
{0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10}, // !
// ... (以降、全文字のフォントデータが続く)
// 0-9, A-Z, a-z, 記号など
};
// RTC操作の定義
RTC_DS1307 RTC; // Tiny RTC (DS1307) モジュール (SDA - A4, SCL - A5)
// ロータリーエンコーダー、スイッチ、LED制御
#define enc_PinA 2 // エンコーダーAをピン2 (割り込み0)
#define enc_PinB 4 // エンコーダーBをピン4
#define enc_Switch 3 // エンコーダースイッチをピン3 (割り込み1)
#define mode_Pin 8 // モードLEDピン
#define min_Pin 9 // 分LEDピン
unsigned char enc_A, enc_B, enc_A_prev=0;
static boolean rotating = false;
static boolean clockwise = false;
static boolean updateFlag = false;
static int mode = 0; // 0 - なし, 1 - 時設定, 2 - 分設定
// 表示文字列
#define max_array_size 100
char ac[max_array_size] = {};
byte rc[8] = {};
String display_message = "";
int arraylen;
// 動作パラメータ
#define delay_line 75 // 行シフト間のms
#define delay_char 400 // 文字間のms
#define delay_mess 500 // メッセージ間のms
#define cblanks 1 // 文字間の空白行数
#define eblanks 0 // メッセージ末尾の追加空白行数(8行以上)
// 表示機能
static boolean top2bottom = false; // 表示方向(上から下、または右から左)
static boolean hour24 = false; // 24時間表示?
static boolean charHI = true; // 文字全体をハイライト?
static boolean doSerial = true; // シリアル出力?
//*****************************************************************************************//
// 初期セットアップ
//*****************************************************************************************//
void setup() {
Wire.begin();
Serial.begin(57600);
if(doSerial) Serial.print("MATRIX Clock - Adrian Jones, Mar. 2014");
// 8×8 LEDマトリックス制御ピン
pinMode(dataIn, OUTPUT);
pinMode(clock, OUTPUT);
pinMode(load, OUTPUT);
initMatrix(); // LEDマトリックス初期化
// LEDピン
pinMode(mode_Pin, OUTPUT);
digitalWrite(mode_Pin, 1);
pinMode(min_Pin, OUTPUT);
digitalWrite(min_Pin, 1);
// エンコーダー制御
pinMode(enc_PinA, INPUT_PULLUP);
digitalWrite(enc_PinA, HIGH);
pinMode(enc_PinB, INPUT_PULLUP);
digitalWrite(enc_PinB, HIGH);
pinMode(enc_Switch, INPUT_PULLUP);
digitalWrite(enc_Switch, HIGH);
attachInterrupt(0, rotEncoder, CHANGE); // 時刻設定
attachInterrupt(1, swEncoder, CHANGE); // 分/時
// RTC
RTC.begin();
if (!RTC.isrunning()) {
RTC.adjust(DateTime(__DATE__, __TIME__));
if(doSerial) Serial.println(" (RTC reset)");
} else {
if(doSerial) Serial.println(" (RTC running)");
}
}
//*****************************************************************************************//
// メインループ
//*****************************************************************************************//
void loop() {
DateTime now = RTC.now();
display_message = createMessage(now);
arraylen = initDisplayString(display_message);
if(updateFlag) {
show_time_and_date(now);
updateFlag = false;
}
while(rotating) {
delay(1);
adjTime(now, clockwise);
show_time_and_date(RTC.now());
display_message = createMessage(now);
arraylen = initDisplayString(display_message);
delay(1);
rotating = false;
}
delay(5);
for (int i = 0; i < (arraylen-7); i++) {
for (int j = 1; j < 9; j++) {
maxSingle(j, ac[i+8-j]);
}
if(i % (8+cblanks) == 0) {
if(charHI) maxSingle(max7219_reg_intensity, 0x01);
newDelay(delay_char);
} else {
maxSingle(max7219_reg_intensity, 0x00);
newDelay(delay_line);
}
}
if(mode == 0) newDelay(delay_mess);
}
//*****************************************************************************************//
// 割り込みルーチン
//*****************************************************************************************//
// エンコーダー回転時のISR
void rotEncoder() {
delay(1);
enc_A = digitalRead(enc_PinA);
enc_B = digitalRead(enc_PinB);
if(!enc_A && enc_A_prev) {
clockwise = (!enc_A && enc_B) ? true : false;
if(mode != 0) rotating = true;
}
enc_A_prev = enc_A;
}
// エンコーダーボタン押下時のISR
void swEncoder() {
delay(1);
if(digitalRead(enc_Switch) != LOW) return;
delay(1);
if(digitalRead(enc_Switch) != LOW) return;
mode++;
mode = mode % 3;
digitalWrite(mode_Pin, !(mode == 1));
digitalWrite(min_Pin, !(mode == 2));
updateFlag = true;
}
//*****************************************************************************************//
// 動作ルーチン
//*****************************************************************************************//
void newDelay(int dly) {
for (int z=1; z < dly; z++) {
delay(1);
if(rotating || updateFlag) break;
}
}
void initMatrix() {
maxSingle(max7219_reg_scanLimit, 0x07);
maxSingle(max7219_reg_decodeMode, 0x00);
maxSingle(max7219_reg_shutdown, 0x01);
maxSingle(max7219_reg_displayTest, 0x00);
for (int e=1; e<=8; e++) maxSingle(e, 0);
maxSingle(max7219_reg_intensity, 0x00);
}
void adjTime(DateTime now, boolean dir) {
if(mode == 1) {
int adj_hrs = now.hour();
if(dir) {
if(++adj_hrs >= 25) adj_hrs = 1;
} else {
if(adj_hrs == 0) adj_hrs = 24;
if(--adj_hrs <= 0) adj_hrs = 24;
}
RTC.adjust(DateTime(now.year(), now.month(), now.day(), adj_hrs, now.minute(), now.second()));
}
if(mode == 2) {
int adj_mins = now.minute();
if(dir) {
if(++adj_mins >= 60) adj_mins = 0;
} else {
if(--adj_mins < 0) adj_mins = 59;
}
RTC.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), adj_mins, now.second()));
}
}
byte rotChar(char inLetter) {
int ind = int(inLetter) - 0x20;
for (int col = 0; col < 8; col++) {
byte mask = 0x01 << (7-col);
for (int row = 0; row < 8; row++) {
bitWrite(rc[col], 7-row, (alphanum[ind][row] & mask));
}
}
}
void show_time_and_date(DateTime datetime) {
if(doSerial) {
int minutes = datetime.minute();
int hours = datetime.hour();
if(hours == 0) hours = 24;
int seconds = datetime.second();
char delim = '/';
char dend = ' ';
String te = "Current date/time: ";
te = te + datetime.year() + delim + datetime.month() + delim + datetime.day() + dend;
Serial.print(te);
if(hours < 10) Serial.print(0);
Serial.print(hours, DEC);
Serial.print(":");
if(minutes < 10) Serial.print(0);
Serial.print(minutes, DEC);
Serial.print(":");
if(seconds < 10) Serial.print(0);
Serial.print(seconds, DEC);
Serial.println("");
}
}
String createMessage(DateTime datetime) {
String new_mess = " ";
int hr = datetime.hour() % 24;
if(hr == 0) hr = 24;
int mn = datetime.minute();
if(mode == 0) {
if(hour24) new_mess += hr;
else new_mess += (hr > 12) ? hr - 12 : hr;
new_mess += ':';
if(mn < 10) new_mess += '0';
new_mess += mn;
if(!hour24) new_mess += (hr > 12) ? "~" : "^";
}
if(mode == 1) new_mess += hr;
if(mode == 2) {
if(mn < 10) new_mess += '0';
new_mess += mn;
}
return new_mess;
}
int initDisplayString(String message) {
int x = 0;
for (int y = 0; y < message.length(); y++) {
char thisCh = message.charAt(y);
int ind = int(thisCh) - 0x20;
if(!top2bottom) rotChar(thisCh);
for (int row = 0; row < 8 + cblanks; row++) {
if (row <= 7) {
ac[x] = (top2bottom) ? alphanum[ind][7-row] : rc[row];
} else {
ac[x] = 0;
}
x++;
}
}
for(int y = 0; y < 7+eblanks; y++) { ac[x] = 0; x++; }
return x;
}
void maxSingle(byte reg, byte col) {
digitalWrite(load, LOW);
putByte(reg);
putByte(col);
digitalWrite(load, HIGH);
}
void putByte(byte data) {
byte i = 8;
byte mask;
while(i > 0) {
mask = 0x01 << (i - 1);
digitalWrite(clock, LOW);
if (data & mask) digitalWrite(dataIn, HIGH);
else digitalWrite(dataIn, LOW);
digitalWrite(clock, HIGH);
--i;
}
}
回路図

製造プロセス
- Arduino POVクロック:残像効果で浮かび上がるLED時計を自作する
- Arduinoで作るレトロNumitron管時計:シンプル、信頼性高く、省エネ
- 12メートル先から見える大型LEDマスタークロック(アラーム機能付き)を自作しよう
- 自作16×8 LEDマトリックスでテトリスゲーム:Arduino Nano実装
- MotionSense: ArduinoとESP8266で作るスマート侵入検知システム
- PIRセンサー連動 インタラクティブLEDマトリクスドアディスプレイ - ホリデーグリーティング
- スマートArduino四足歩行ロボットの製作:ステップバイステップガイド
- Bolt IoTとArduino UNOでLED輝度をシームレス制御
- Arduino、Yaler、IFTTTでWeb対応IoTゲージを構築する
- Arduinoで作る斥力式磁気浮上装置: シンプル回路とDIYガイド
- 高性能 Arduino 気象観測気球データロガーの製作ガイド