Arduino 8×8 LED 矩阵翻页时钟 - DIY 实时时钟项目
组件与材料
| Arduino Nano R3 | ×1 |
| Maxim 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; // 改为 true 启用垂直翻转
代码注释中提供了丰富的可调参数:滚动速度、字符间空行数、12/24 小时制、字符间延时、亮度等。
时间设置
使用旋转编码器轻松设置时间。双色 LED 指示设置模式(小时、分钟或正常模式)。
时钟外壳由两个基础部件组成,内部访问非常方便实用。

核心代码
//*****************************************************************************************//
// MATRIX CLOCK - Adrian Jones, March 2014
// 支持左右滚动和上下翻转显示
//*****************************************************************************************//
#include
#include
// MAX7219 寄存器定义
#define max7219_reg_noop 0x00
#define max7219_reg_digit0 0x01
// ... (其余寄存器定义省略)
#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}, // 空格
{0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10}, // !
// ... (完整字模数据省略,含数字、字母、符号)
};
// RTC 设置
RTC_DS1307 RTC;
// 旋转编码器、按键与 LED 控制引脚
#define enc_PinA 2
#define enc_PinB 4
#define enc_Switch 3
#define mode_Pin 8
#define min_Pin 9
// 运行参数
#define delay_line 75 // 行间延时(ms)
#define delay_char 400 // 字符间延时(ms)
#define delay_mess 500 // 消息间延时(ms)
#define cblanks 1 // 字符间空行数
#define eblanks 0 // 消息末尾额外空行
// 显示特性
static boolean top2bottom = false; // 显示方向
static boolean hour24 = false; // 24小时制
static boolean charHI = true; // 字符高亮
static boolean doSerial = true; // 串口输出
void setup() {
Wire.begin();
Serial.begin(57600);
// LED 矩阵控制引脚
pinMode(dataIn, OUTPUT);
pinMode(clock, OUTPUT);
pinMode(load, OUTPUT);
initMatrix();
// 模式指示 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.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);
}
// 中断服务程序:编码器旋转
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;
}
// 中断服务程序:编码器按键
void swEncoder() {
delay(1);
if(digitalRead(enc_Switch) != LOW) return;
delay(1);
if(digitalRead(enc_Switch) != LOW) return;
mode = (mode + 1) % 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;
}
}
// MAX7219 初始化
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()));
}
}
// 字符旋转90度(垂直显示用)
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) {
// 格式化输出年月日时分秒
}
}
// 生成显示消息字符串
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;
}
// MAX7219 单字节写入
void maxSingle(byte reg, byte col) {
digitalWrite(load, LOW);
putByte(reg);
putByte(col);
digitalWrite(load, HIGH);
}
// SPI 位发送
void putByte(byte data) {
byte i = 8, 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 显示器
- 复古 Numitron 管时钟:Arduino Nano 驱动,低温可靠省电
- 自制大尺寸LED主时钟带闹钟功能 — 12米外清晰可见
- 自制 16×8 LED 矩阵俄罗斯方块游戏机:Arduino Nano + 74HC595 驱动
- MotionSense:基于 Arduino 与 ESP8266 的智能入侵检测系统
- 互动 LED 矩阵门显:PIR 触发节日问候 + 待机特效
- 从零构建智能 Arduino 四足机器人:完整指南
- Bolt IoT + Arduino UNO 实现 6 路 LED 无级调光
- 基于 Arduino、Yaler 与 IFTTT 构建 Web 化 IoT 仪表盘
- Arduino 简易排斥式磁悬浮:电路与 DIY 指南
- 高性能 Arduino 气象气球数据记录器
- Arduino Uno驱动MAX7219级联8×8 LED矩阵:从入门到动画显示