亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

元件与材料

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
SparkFun Arduino Pro Mini 328 - 5V/16MHz
× 1
用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
实时时钟模块 (RTC)
购买 “DS1302 Real Time Clock Module for Arduino” 并拆下 IC 插座与晶振
× 1
CR1220 纽扣电池及座
搜索 “5pcs,Black Housing CR1220 CR1225 Button Cell Battery Socket Holder BS-1220-1”
× 1
0.1μF 1206 SMD 电容
× 1
10μF 1206 SMD 陶瓷电容
× 1
用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
轻触开关 (顶部按压)
12mm 带帽款
× 4
3A 迷你 DC-DC 降压模块
× 1
0.28 英寸共阴四位七段数码管
× 36
MAX7219 DIP 封装 IC
× 18
用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
蜂鸣器
× 1
机针排针/排母
需公母各 8 条,通常 40PIN 一条
× 1
DC 电源插座
搜索 “12V DC Power Supply Jack Socket Female Panel Mount Connector 5.5 x 2.ODZ2”
× 1

必备工具与设备

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
电烙铁 (通用)
用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
3D 打印机 (通用)

应用与在线服务

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
Arduino IDE

项目简介

在 Hackaday.io 上看到 Frugha 用七段数码管阵列做的精美时钟,但他用 0.56 英寸显示器体积较大。本设计采用 0.28 英寸显示器,体积仅为原版四分之一。

软件重写,增加了闹钟功能和配置界面,可设置时间、日期、闹铃、亮度等。

视频

配置界面

四个按键控制:最上方 SELECT 进入配置菜单,当前项闪烁。再按 SELECT 循环切换:时间、日期、闹钟、曲目、亮度、日期格式、时制。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

按第二个键 ENTER 进入修改,多项设置时再按 ENTER 循环,当前项闪烁。最下两键 UP/DOWN 调整数值。

日期界面

时钟界面下按 ENTER 显示日期 5 秒。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

配置菜单中的 “日期格式” 可设置 DD-MM 或 MM-DD 及字体风格。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

设计考量

为简化板级设计,改用四位一体数码管而非原版单个数码管。因 0.28 英寸体积小,MAX7219 单独做一块板,用机针排针/排母互连简化布线。提供 Eagle 源文件,可自行打板或委托制板,我用热转印法自制。

列板 (Column Boards)

需制作 6 块列板,每块承载 6 个四位数码管。焊接公排针时从中间向两边,用火柴棍垫高方便焊接,用细尖烙铁头和 0.5mm 锡丝。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

建议用哑光黑漆涂刷数码管边框,避免对齐不佳露白边。用记号笔给每块列板编号,插入主板时对应位置。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

MAX7219 主板

设计时未将 MAX7219 的段/位引脚按标准映射,而是优先简化 PCB 布局,软件里适配即可。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

焊接母排针时,建议先插在列板的公针上,再整体焊上主板,保证对齐。拆卸时按编号复位即可。

MPU 板 (微处理器板)

承载除显示外所有元件。四按键和两个 SMD 电容贴板背面,其余贴正面。为降低高度,Arduino Pro Mini 的 FTDI 排针直接焊在 PCB 上,DTR 和 VCC 飞线到板上。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

外壳制作

附带前后盖 STL 文件,0.2mm 层高、带裙边打印,前盖需支撑(仅接触热床)。

MAX7219 主板连同列板从前盖滑入,若松动可用美纹纸填实。MPU 板热熔胶固定在后盖。

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

软件

需在 Arduino IDE 安装 MD_MAX72XX 库。提供 Hardware_Test_V1 测试程序,全段点亮检查短路/断路。通过后烧录 Clock_V4 正式固件。

代码

Clock_V4.ino C/C++
/*--------------------------------------------------- * 7 Segment array clock * * Based on clock by Frugha (https://hackaday.io/project/169632-7-segment-display-array-clock) * * Modifications by John Bradnam (jbrad2089@gmail.com) * - Changed hardware to use 0.28" 7 segment displays. * - MAX7219 device, digit and segments order has been changed to simply an otherwise * complex PCB board routing. * - Hardware uses Arduino Mini Pro, DS1302 RTC and 4 push buttons * - Added Alarm function with selectable alarm usic * - Added Brightness setting * - Added Setup screen to set time, date, alarm, music and brightness * - Added Date screen (displays for two seconds when ENTER button is pressed * Update 2020-06-16 * - Added date format option on configuration screen * - Increased date screen timeout from 2 to 5 seconds * - Added a configuarble choice of font for date display *--------------------------------------------------*/ #include <MD_MAX72xx.h> #include <SPI.h> #include <TimeLib.h> #include <DS1302RTC.h> #include <EEPROM.h> #include "button.h" #include "Tunes.h" #include "Digits.h" #define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW #define MAX_DEVICES 18 #define LED_CLK 13 // or SCK (WHI) #define LED_DATA 11 // or MOSI (BRN) #define LED_CS 10 // or SS (YEL) #define SPEAKER 2 #define SW_UP 3 #define SW_DOWN 4 #define RTC_CLK 5 #define RTC_IO 6 #define SW_ENTER 7 #define RTC_CE 8 #define SW_SELECT 9 DS1302RTC rtc(RTC_CE, RTC_IO, RTC_CLK); // SPI hardware interface MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, LED_CS, MAX_DEVICES); // Arbitrary pins //MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, LED_DATA, LED_CLK, LED_CS, MAX_DEVICES); //EEPROM handling #define EEPROM_ADDRESS 0 #define EEPROM_MAGIC 0x0BAD0DAD typedef struct { uint32_t magic; bool alarm; uint8_t minutes; uint8_t hours; bool format12hr; uint8_t brightness; uint8_t tune; bool formatDmy; bool squareFont; } EEPROM_DATA; EEPROM_DATA EepromData; //Current EEPROM settings void clockButtonPressed(void); void enterButtonPressed(void); void downButtonPressed(void); void upButtonPressed(void); Button* clockButton; Button* enterButton; Button* downButton; Button* upButton; //Operating modes bool inSubMenu = false; #define SETUP_FLASH_RATE 200; unsigned long setupTimeout; bool setupDisplayState = false; enum ClockButtonModesEnum { CLOCK, TIME_SET, DATE_SET, ALARM_SET, TUNE_SET, BRIGHT_SET, FORMAT_SET }; ClockButtonModesEnum clockMode = CLOCK; enum TimeSetMenuEnum { TIME_HOUR, TIME_MIN, TIME_FORMAT }; TimeSetMenuEnum timeSetMode = TIME_HOUR; enum DateSetMenuEnum { DATE_YEAR, DATE_MONTH, DATE_DAY }; DateSetMenuEnum dateSetMode = DATE_YEAR; enum AlarmSetMenuEnum { ALARM_HOUR, ALARM_MIN, ALARM_STATE }; AlarmSetMenuEnum alarmSetMode = ALARM_HOUR; enum FormatSetMenuEnum { DAY_MONTH, FONT_STYLE }; FormatSetMenuEnum formatSetMode = DAY_MONTH; int lastSeconds = -1; bool alarmRinging = false; //true when alarm is on bool alarmCancelled = false; //alarm cancelled by user bool musicPlaying = false; //true if playing a song bool clockColon = false; //show/hide colon int8_t dom[] = {31,28,31,30,31,30,31,31,30,31,30,31}; tmElements_t newTime; //Used to store new time void setup() { Serial.begin(115200); //Eprom readEepromData(); //Initialise buttons clockButton = new Button(SW_SELECT); enterButton = new Button(SW_ENTER); downButton = new Button(SW_DOWN); downButton->Repeat(downButtonPressed); upButton = new Button(SW_UP); upButton->Repeat(upButtonPressed); //Initialise sound pinMode(SPEAKER,OUTPUT); mx.begin(); mx.control(MD_MAX72XX::INTENSITY, EepromData.brightness); //0..MAX_INTENSITY mx.update(MD_MAX72XX::OFF) ; //No Auto updating setSyncProvider(rtc.get); // the function to get the time from the RTC if(timeStatus() != timeSet) { Serial.println("RTC Sync Bad"); newTime.Year = CalendarYrToTm(2020); newTime.Month = 5; newTime.Day = 30; newTime.Hour = 14; newTime.Minute = 53; newTime.Second = 0; time_t t = makeTime(newTime); setTime(t); rtc.set(t); if (rtc.set(t) != 0) { Serial.println("Set Time Failed"); } } newTime.Year = CalendarYrToTm(year()); newTime.Month = month(); newTime.Day = day(); newTime.Hour = hour(); newTime.Minute = minute(); newTime.Second = second(); Serial.println("Time: " + String(newTime.Hour) + ":" + String(newTime.Minute) + ":" + String(newTime.Second)); Serial.println("Date: " + String(newTime.Day) + "/" + String(newTime.Month) + "/" + String(tmYearToCalendar(newTime.Year))); clockMode = CLOCK; showTime(true); } void loop() { testButtons(); if (clockMode == CLOCK) { showTime(false); if (EepromData.alarm && EepromData.hours == hour() && EepromData.minutes == minute()) { if (!alarmCancelled) { alarmRinging = true; playSong(melodies[EepromData.tune]); } } else { alarmCancelled = false; alarmRinging = false; } } else { showSetup(false); } delay(100); } //--------------------------------------------------------------- //Test if any buttons have been pressed void testButtons() { //Single press buttons if (clockButton->Pressed()) { clockButtonPressed(); } if (enterButton->Pressed()) { enterButtonPressed(); } //Don't need to check result of pressed since the button handler will invoke its repeat function upButton->Pressed(); downButton->Pressed(); } //--------------------------------------------------------------- //Handle CLOCK btton void clockButtonPressed() { if (cancelAlarm()) return; inSubMenu = false; clockMode = (clockMode == FORMAT_SET) ? CLOCK : (ClockButtonModesEnum)((int)clockMode + 1); if (clockMode == CLOCK) { Serial.println("Saving Time: " + String(newTime.Hour) + ":" + String(newTime.Minute)); Serial.println(" From: " + String(hour()) + ":" + String(minute())); if (newTime.Year != CalendarYrToTm(year()) || newTime.Month != month() || newTime.Day != day() || newTime.Hour != hour() || newTime.Minute != minute()) { //Update time Serial.println("Updating RTC"); newTime.Second = second(); time_t t = makeTime(newTime); setTime(t); rtc.set(t); if (rtc.set(t) != 0) { Serial.println("Set Time Failed"); } } writeEepromData(); showTime(true); } else { if (clockMode == TIME_SET) { newTime.Year = CalendarYrToTm(year()); newTime.Month = month(); newTime.Day = day(); newTime.Hour = hour(); newTime.Minute = minute(); newTime.Second = second(); Serial.println("Loading Time: " + String(hour()) + ":" + String(minute())); } showSetup(true); } } //--------------------------------------------------------------- //Handle ENTER btton void enterButtonPressed() { if (cancelAlarm()) return; if (clockMode != CLOCK) { if (!inSubMenu) { timeSetMode = TIME_HOUR; dateSetMode = DATE_YEAR; alarmSetMode = ALARM_HOUR; formatSetMode = DAY_MONTH; inSubMenu = true; } else { switch (clockMode) { case TIME_SET: timeSetMode = (timeSetMode == TIME_FORMAT) ? TIME_HOUR : (TimeSetMenuEnum)((int)timeSetMode + 1); break; case DATE_SET: dateSetMode = (dateSetMode == DATE_DAY) ? DATE_YEAR : (DateSetMenuEnum)((int)dateSetMode + 1); break; case ALARM_SET: alarmSetMode = (alarmSetMode == ALARM_STATE) ? ALARM_HOUR : (AlarmSetMenuEnum)((int)alarmSetMode + 1); break; case FORMAT_SET: formatSetMode = (formatSetMode == FONT_STYLE) ? DAY_MONTH : (FormatSetMenuEnum)((int)formatSetMode + 1); break; } } showSetup(true); } else { showDate(day(), month(), year()); delay(5000); } } //--------------------------------------------------------------- //Handle DOWN btton void downButtonPressed() { if (cancelAlarm()) return; switch (clockMode) { case TIME_SET: if (inSubMenu) { switch(timeSetMode) { case TIME_HOUR: newTime.Hour = (newTime.Hour + 24 - 1) % 24; break; case TIME_MIN: newTime.Minute = (newTime.Minute + 60 - 1) % 60; break; case TIME_FORMAT: EepromData.format12hr = !EepromData.format12hr; break; } showSetup(true); } break; case DATE_SET: if (inSubMenu) { switch(dateSetMode) { case DATE_YEAR: newTime.Year = ((newTime.Year - 30 + 100) - 1) % 100 + 30; break; case DATE_MONTH: newTime.Month = ((newTime.Month - 1 + 12) - 1) % 12 + 1; break; case DATE_DAY: uint8_t md = daysInMonth(newTime.Year, newTime.Month); newTime.Day = ((newTime.Day - 1 + md) - 1) % md + 1; break; } showSetup(true); } break; case ALARM_SET: if (inSubMenu) { switch(alarmSetMode) { case ALARM_HOUR: EepromData.hours = (EepromData.hours + 24 - 1) % 24; break; case ALARM_MIN: EepromData.minutes = (EepromData.minutes + 60 - 1) % 60; break; case ALARM_STATE: EepromData.alarm = !EepromData.alarm; break; } showSetup(true); } break; case TUNE_SET: EepromData.tune = (EepromData.tune + NUM_OF_MELODIES - 1) % NUM_OF_MELODIES; showSetup(true); break; case BRIGHT_SET: EepromData.brightness = (EepromData.brightness + MAX_INTENSITY - 1) % MAX_INTENSITY; mx.control(MD_MAX72XX::INTENSITY, EepromData.brightness); //0..MAX_INTENSITY showSetup(true); break; case FORMAT_SET: if (inSubMenu) { switch (formatSetMode) { case DAY_MONTH: EepromData.formatDmy = !EepromData.formatDmy; break; case FONT_STYLE: EepromData.squareFont = !EepromData.squareFont; break; } } showSetup(true); break; } } //--------------------------------------------------------------- //Handle UP btton void upButtonPressed() { if (cancelAlarm()) return; switch (clockMode) { case TIME_SET: if (inSubMenu) { switch(timeSetMode) { case TIME_HOUR: newTime.Hour = (newTime.Hour + 1) % 24; break; case TIME_MIN: newTime.Minute = (newTime.Minute + 1) % 60; break; case TIME_FORMAT: EepromData.format12hr = !EepromData.format12hr; break; } showSetup(true); } break; case DATE_SET: if (inSubMenu) { switch(dateSetMode) { case DATE_YEAR: newTime.Year = ((newTime.Year - 30) + 1) % 100 + 30; break; case DATE_MONTH: newTime.Month = ((newTime.Month - 1) + 1) % 12 + 1; break; case DATE_DAY: uint8_t md = daysInMonth(newTime.Year, newTime.Month); newTime.Day = (newTime.Day % md) + 1; break; } showSetup(true); } break; case ALARM_SET: if (inSubMenu) { switch(alarmSetMode) { case ALARM_HOUR: EepromData.hours = (EepromData.hours + 1) % 24; break; case ALARM_MIN: EepromData.minutes = (EepromData.minutes + 1) % 60; break; case ALARM_STATE: EepromData.alarm = !EepromData.alarm; break; } showSetup(true); } break; case TUNE_SET: EepromData.tune = (EepromData.tune + 1) % NUM_OF_MELODIES; showSetup(true); break; case BRIGHT_SET: EepromData.brightness = (EepromData.brightness + 1) % MAX_INTENSITY; mx.control(MD_MAX72XX::INTENSITY, EepromData.brightness); //0..MAX_INTENSITY showSetup(true); break; case FORMAT_SET: if (inSubMenu) { switch (formatSetMode) { case DAY_MONTH: EepromData.formatDmy = !EepromData.formatDmy; break; case FONT_STYLE: EepromData.squareFont = !EepromData.squareFont; break; } } showSetup(true); break; } } //--------------------------------------------------------------- //Turn off the alarm if it is playing a tune bool cancelAlarm() { if (musicPlaying) { musicPlaying = false; alarmCancelled = alarmRinging; return true; } yield(); return false; } //--------------------------------------------------------------- //Show the Setup menu and flash current item selected void showSetup(bool force) { setupDisplayState = setupDisplayState | force; force = force || (millis() > setupTimeout); if (force) { setupTimeout = millis() + SETUP_FLASH_RATE; bool on = setupDisplayState; setupDisplayState = !setupDisplayState; mx.clear(); if (on || !(clockMode == TIME_SET && !inSubMenu)) displayString(0,7,"TINE"); if (on || !(clockMode == TIME_SET && inSubMenu && timeSetMode == TIME_HOUR)) displayNumber(0,13,newTime.Hour,2,true); if (on || !(clockMode == TIME_SET && inSubMenu && timeSetMode == TIME_MIN)) displayNumber(0,16,newTime.Minute,2,true); if (on || !(clockMode == TIME_SET && inSubMenu && timeSetMode == TIME_FORMAT)) displayString(0,19,(EepromData.format12hr) ? "12HR" : "24HR"); if (on || !(clockMode == DATE_SET && !inSubMenu)) displayString(1,7,"DATE"); if (on || !(clockMode == DATE_SET && inSubMenu && dateSetMode == DATE_YEAR)) displayNumber(1,13,tmYearToCalendar(newTime.Year),4,true); if (on || !(clockMode == DATE_SET && inSubMenu && dateSetMode == DATE_MONTH)) displayNumber(1,18,newTime.Month,2,true); if (on || !(clockMode == DATE_SET && inSubMenu && dateSetMode == DATE_DAY)) displayNumber(1,21,newTime.Day,2,true); if (on || !(clockMode == ALARM_SET && !inSubMenu)) displayString(2,6,"ALARN"); if (on || !(clockMode == ALARM_SET && inSubMenu && alarmSetMode == ALARM_HOUR)) displayNumber(2,13,EepromData.hours,2,true); if (on || !(clockMode == ALARM_SET && inSubMenu && alarmSetMode == ALARM_MIN)) displayNumber(2,16,EepromData.minutes,2,true); if (on || !(clockMode == ALARM_SET && inSubMenu && alarmSetMode == ALARM_STATE)) displayString(2,19,(EepromData.alarm) ? "ON" : "OFF"); if (on || !(clockMode == TUNE_SET && !inSubMenu)) displayString(3,7,"TUNE"); if (on || !(clockMode == TUNE_SET && inSubMenu)) { switch(EepromData.tune) { case 0: displayString(3,13,"ELISE"); break; case 1: displayString(3,13,"RANGE"); break; case 2: displayString(3,13,"SUNSHINE"); break; } } if (on || !(clockMode == BRIGHT_SET && !inSubMenu)) displayString(4,1,"BRIGHTNESS"); if (on || !(clockMode == BRIGHT_SET && inSubMenu)) displayNumber(4,13,EepromData.brightness,0,false); if (on || !(clockMode == FORMAT_SET && !inSubMenu)) displayString(5,0,"DATE FORNAT"); if (on || !(clockMode == FORMAT_SET && inSubMenu && formatSetMode == DAY_MONTH)) displayString(5,13,(EepromData.formatDmy) ? "DD-NN" : "NN-DD"); if (on || !(clockMode == FORMAT_SET && inSubMenu && formatSetMode == FONT_STYLE)) displayString(5,19,(EepromData.squareFont) ? "FONT1" : "FONT2"); mx.update(); } } //--------------------------------------------------------------- //Display the current time on the display if changed // force - always show time even if not changed void showTime(bool force) { force = force || (lastSeconds != second()); if (force) { lastSeconds = second(); showTime(hour(), minute(), true, true, (second() & 0x01), (clockMode != CLOCK || !EepromData.format12hr)); } } //--------------------------------------------------------------- //Display the time on the display // h - hour // m - minute // he - hour enable // me - minute enable // ce - colon enable void showTime(int h, int m, bool he, bool me, bool ce, bool f24) { mx.clear(); if (he) { if (!f24 && h > 12) { h = h - 12; } if (h > 9 || f24) { displayLargeDigit(0, h / 10); } displayLargeDigit(5, h % 10); } if (me) { displayLargeDigit(13, m / 10); displayLargeDigit(18, m % 10); } if (ce) { displayLargeDigit(11, 10); } mx.update(); } //--------------------------------------------------------------- void showDate(int d, int m, int y) { #define XOFS 3 mx.clear(); if (EepromData.formatDmy) { displayDateDigit(XOFS+0, d / 10); displayDateDigit(XOFS+4, d % 10); displayDateDigit(XOFS+8, 10); //colon displayDateDigit(XOFS+12, m / 10); displayDateDigit(XOFS+16, m % 10); } else { displayDateDigit(XOFS+0, m / 10); displayDateDigit(XOFS+4, m % 10); displayDateDigit(XOFS+8, 10); //colon displayDateDigit(XOFS+12, d / 10); displayDateDigit(XOFS+16, d % 10); } displayNumber(5, 10, y, 0, false); mx.update(); } //--------------------------------------------------------------- // Write a square or small digit based of configuration setting // x = 0 to 23 // v = large or square digit to display (0..10) void displayDateDigit(uint8_t x, uint8_t v) { if (EepromData.squareFont) { displaySquareDigit(x, v); } else { displaySmallDigit(x, v); } } //--------------------------------------------------------------- // Write a segment bit mask // x = 0 to 23 // v = large digit to display (0..10) void displayLargeDigit(uint8_t x, uint8_t v) { if (x < 24 && v < 11) { for (uint8_t row = 0; row < 6; row++) { for (uint8_t col = 0; col < 6; col++) { writePhysicalDigit(row, col + x, pgm_read_byte(&largeDigits[v][row][col]), false); } } } } //--------------------------------------------------------------- // Write a segment bit mask // x = 0 to 23 // v = large digit to display (0..10) void displaySmallDigit(uint8_t x, uint8_t v) { if (x < 24 && v < 11) { for (uint8_t row = 0; row < 5; row++) { for (uint8_t col = 0; col < 3; col++) { writePhysicalDigit(row, col + x, pgm_read_byte(&smallDigits[v][row][col]), false); } } } } //--------------------------------------------------------------- // Write a segment bit mask // x = 0 to 23 // v = large digit to display (0..10) void displaySquareDigit(uint8_t col, uint8_t v) { //Segment order d e f b c a _ g uint8_t mask = ascii[v]; if (mask == B00000000) { //Hyphen mask = B00000001; } if (mask & B00000100) { //seg A writePhysicalDigit(0, col + 0, 0xff, false); writePhysicalDigit(0, col + 1, 0xff, false); writePhysicalDigit(0, col + 2, 0xff, false); } if (mask & B00010000) { //seg B writePhysicalDigit(0, col + 2, 0xff, false); writePhysicalDigit(1, col + 2, 0xff, false); writePhysicalDigit(2, col + 2, 0xff, false); } if (mask & B00001000) { //seg C writePhysicalDigit(2, col + 2, 0xff, false); writePhysicalDigit(3, col + 2, 0xff, false); writePhysicalDigit(4, col + 2, 0xff, false); } if (mask & B10000000) { //seg D writePhysicalDigit(4, col + 0, 0xff, false); writePhysicalDigit(4, col + 1, 0xff, false); writePhysicalDigit(4, col + 2, 0xff, false); } if (mask & B01000000) { //seg C writePhysicalDigit(2, col, 0xff, false); writePhysicalDigit(3, col, 0xff, false); writePhysicalDigit(4, col, 0xff, false); } if (mask & B00100000) { //seg E writePhysicalDigit(0, col, 0xff, false); writePhysicalDigit(1, col, 0xff, false); writePhysicalDigit(2, col, 0xff, false); } if (mask & B00000001) { //seg D writePhysicalDigit(2, col + 0, 0xff, false); writePhysicalDigit(2, col + 1, 0xff, false); writePhysicalDigit(2, col + 2, 0xff, false); } } //--------------------------------------------------------------- // Write string of characters uint8_t displayString(uint8_t row, uint8_t col, String s) { for (int i = 0; i < s.length(); i++) { byte c = (byte)s.charAt(i); if (c == 0x2D) { c = 0x3D; } if (c < 0x30 || c > 0x5F) { c = 0x3F; //Used as a space character } c = c - 0x30; writePhysicalDigit(row, col, ascii[c], true); col = col + 1; } } //--------------------------------------------------------------- // Write a number uint8_t displayNumber(uint8_t row, uint8_t col, int number, int padding, bool leadingZeros) { if (padding == 0) { padding = (number > 0) ? floor(log10(number)) + 1 : 1; } col = col + padding; bool first = true; for (int i = 0; i < padding; i++) { col--; if (number != 0 || first) { writePhysicalDigit(row, col, ascii[number % 10], true); number = number / 10; first = false; } else { writePhysicalDigit(row, col, ascii[(leadingZeros) ? 0 : 0x0F], true); } } } //--------------------------------------------------------------- // Write a segment bit mask // row = 0 to 5 // col = 0 to 23 // v = segment mask // erase = true will replace all old pattern, false will OR with existing void writePhysicalDigit(uint8_t row, uint8_t col, uint8_t v, bool erase) { if (row < 6 && col < 24) { uint8_t dig = (col & 0x03) + ((row && 0x01) ? 4 : 0); uint8_t dev = (col >> 2) * 3 + (row >> 1); uint16_t c = ((uint16_t)dev << 3) | digitMap[dig]; if (!erase) { v = v | mx.getColumn(c); } mx.setColumn(c, v); } } //--------------------------------------------------------------- //Return the days in a given year and month //Feb has 28 unless leap year or the turn of a century uint8_t daysInMonth(int y, int m) { return dom[m - 1] + ((m == 2 && (y % 4) == 0 && (y % 100) != 0) ? 1 : 0); } //--------------------------------------------------------------- //Write the EepromData structure to EEPROM void writeEepromData() { //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change. EEPROM.put(EEPROM_ADDRESS,EepromData); } //--------------------------------------------------------------- //Read the EepromData structure from EEPROM, initialise if necessary void readEepromData() { //Eprom EEPROM.get(EEPROM_ADDRESS,EepromData); //Serial.println("magic:" + String(EepromData.magic, 16) + ", alarm: " + String(EepromData.alarm) + ", time: " + String(EepromData.hours) + ":" + String(EepromData.minutes) + ", 12hr: " + String(EepromData.format12hr) + ", brightness: " + String(EepromData.brightness)); if (EepromData.magic != EEPROM_MAGIC) { Serial.println("Initialising EEPROM ..."); EepromData.magic = EEPROM_MAGIC; EepromData.alarm = false; EepromData.minutes = 30; EepromData.hours = 5; EepromData.format12hr = false; EepromData.brightness = 8; EepromData.tune = 0; EepromData.formatDmy = false; writeEepromData(); } Serial.println("alarm: " + String(EepromData.alarm) + ", time: " + String(EepromData.hours) + ":" + String(EepromData.minutes) + ", 12hr: " + String(EepromData.format12hr) + ", brightness: " + String(EepromData.brightness)); } //--------------------------------------------------------------- //Play a melody int playSong(const uint16_t* melody) { //Play each note in the melody until the END_OF_TUNE note is encountered musicPlaying = true; int thisNote = 0; uint16_t noteRaw = pgm_read_word(&melody[thisNote++]); while (musicPlaying && noteRaw != END_OF_TUNE) { testButtons(); yield(); playNote(noteRaw); noteRaw = pgm_read_word(&melody[thisNote++]); } //while delay(50); } //--------------------------------------------------------------- //Play a single note void playNote(uint16_t noteRaw) { // to calculate the note duration, take one second divided by the note type. // e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. uint16_t frequency = noteRaw & 0x1FFF; uint16_t duration = (noteRaw & 0xE000) >> 13; if (duration == 7) duration = 8; uint16_t noteDuration = 1800 / duration; if (frequency != REST) { tone(SPEAKER, frequency, noteDuration); } // to distinguish the notes, set a minimum time between them. // the note's duration + 30% seems to work well: uint16_t pauseBetweenNotes = (noteDuration * 13) / 10; delay(pauseBetweenNotes); if (frequency != REST) { // stop the tone playing: noTone(SPEAKER); } } 
Digits.h C/C++
/*--------------------------------------------------- * 7 Segment array clock * define each digit in a 6x6 byte array *--------------------------------------------------*/ #pragma once //--------------------------------------------------- // Standard MAX7219 wiring // Digit order 0,1,2,3,4,5 /* // Segment order _ a b c d e f g #define _______ B00000000 #define __cd___ B00011000 #define _bcde_g B00111101 #define abcdefg B01111111 #define __cdefg B00011111 #define __c____ B00010000 #define ab__efg B01100111 #define abcd_fg B01111011 #define abcde_g B01111101 #define ab_defg B01101111 #define _bc____ B00110000 #define a__defg B01001111 #define ____ef_ B00000110 #define ___defg B00001111 #define abc__fg B01110011 #define a___ef_ B01000110 #define _b_____ B00100000 #define ab___f_ B01100010 #define _bcd___ B00111000 #define a___ef_ B01000110 #define ab_____ B01100000 #define ____e__ B00000100 #define __cde_g B00011101 #define a____f_ B01000010 #define a_cdefg B01011111 #define ___de__ B00001100 #define _____f_ B00000010 #define ab___fg B01100011 #define a__def_ B01001110 #define __cde__ B00011100 #define a___efg B01000111 #define a__d___ B01001000 #define abc____ B01110000 #define _bc_ef_ B00110110 #define ___def_ B00001110 #define abc_ef_ B01110110 #define _bcdef_ B00111110 #define a__def_ B01001110 #define abcd___ B01111000 */ //Segment order d e f b c a _ g #define _______ B00000000 #define __cd___ B10001000 #define _bcde_g B11011001 #define abcdefg B11111101 #define __cdefg B11101001 #define __c____ B00001000 #define ab__efg B01110101 #define abcd_fg B10111101 #define abcde_g B11011101 #define ab_defg B11110101 #define _bc____ B00011000 #define a__defg B11100101 #define ____ef_ B01100000 #define ___defg B11100001 #define abc__fg B00111101 #define a___ef_ B01100100 #define _b_____ B00010000 #define ab___f_ B00110100 #define _bcd___ B10011000 #define a___ef_ B01100100 #define ab_____ B00010100 #define ____e__ B01000000 #define __cde_g B11001001 #define a____f_ B00100100 #define a_cdefg B11101101 #define ___de__ B11000000 #define _____f_ B00100000 #define ab___fg B00110101 #define a__def_ B11100100 #define __cde__ B11001000 #define a___efg B01100101 #define a__d___ B10000100 #define abc____ B00011100 #define _bc_ef_ B01111000 #define ___def_ B11100000 #define abc_ef_ B01111100 #define _bcdef_ B11111000 #define a__def_ B11100100 #define abcd___ B10011100 //Square Numbers //ASCII Character Set //Numbers 0 - 9 //Letters A - Z //Segment order d e f b c a _ g uint8_t ascii[] = { B11111100, B00011000, B11010101, B10011101, B00111001, B10101101, B11101101, B00011100, B11111101, B10111101, B00000000, B00000000, B00000000, B00000001, B00000000, B00000000, B00000000, B01111101, B11101001, B11100100, B11011001, B11100101, B01100101, B10111101, B01111001, B00011000, B11011000, B00000000, B11100000, B00000000, B01001001, B11001001, B01110101, B00000000, B01000001, B10101101, B11100001, B11001000, B00000000, B00000000, B00000000, B10111001, B00000000, B11001100, B01010001, B10011100, B00000000, B10000000 }; //Digit sequence for each device (MAX7219) uint8_t digitMap[] = {5, 2, 6, 4, 1, 7, 3, 0}; //------------------------------------------------------------ // Digits using logical coordinates //------------------------------------------------------------ const int8_t largeDigits[11][6][6] PROGMEM = { { //0 { _______, _______, __cd___, _bcde_g, abcdefg, __cdefg }, { _______, __c____, abcdefg, ab__efg, abcd_fg, abcdefg }, { _______, abcde_g, ab_defg, _______, _bc____, a__defg }, { _______, abcdefg, ____ef_, __c____, abcdefg, ____ef_ }, { _______, abcdefg, __cdefg, abcdefg, a___efg, _______ }, { _______, abc__fg, abcdefg, ab__efg, _______, _______ } }, { //1 { _______, _______, _______, __c____, abcdefg, __cdefg }, { _______, _______, _______, abcdefg, abcdefg, ____ef_ }, { _______, _______, _bcde_g, abcdefg, a__defg, _______ }, { _______, _bc____, abcdefg, abcdefg, ____ef_, _______ }, { _______, abcdefg, abcdefg, a___ef_, _______, _______ }, { _b_____, abcdefg, abcdefg, _______, _______, _______ } }, { //2 { _______, _______, __cd___, _bcde_g, abcdefg, __cdefg }, { _______, _bc____, abcdefg, ab___f_, abcd_fg, abcdefg }, { _______, _______, _______, _bcd___, abcdefg, a___ef_ }, { _______, _______, _bcde_g, abcdefg, a___ef_, _______ }, { __c____, abcdefg, abcdefg, a___ef_, _______, _______ }, { _b_____, abc__fg, abcdefg, abcdefg, ab__efg, _______ } }, { //3 { _______, _______, __cd___, abcdefg, abcdefg, __cdefg }, { _______, _b_____, abcdefg, ab___f_, abcd_fg, abcdefg }, { _______, _______, __cd___, _bcde_g, abcdefg, a___ef_ }, { _______, _______, ab_____, abc__fg, abcdefg, ____e__ }, { _______, __cd___, __cde_g, _bcde_g, abcdefg, ____ef_ }, { _b_____, abc__fg, abcdefg, abcdefg, a____f_, _______ } }, { //4 { _______, _______, _bcd___, abcdefg, __c____, abcdefg }, { _______, __c____, abcdefg, a___ef_, abcde_g, a__defg }, { _______, abcde_g, abcdefg, _bcd___, abcdefg, ____ef_ }, { __c____, abcdefg, abcdefg, abcdefg, abcdefg, _______ }, { _______, _______, __c____, abcdefg, ____ef_, _______ }, { _______, _______, abcdefg, ab__efg, _______, _______ } }, { //5 { _______, _______, _bcde_g, abcdefg, abcdefg, abcdefg }, { _______, _bc____, abcdefg, a_cdefg, ___de__, _______ }, { _______, _______, abc__fg, abcdefg, abcdefg, ____ef_ }, { _______, _______, _______, _bc____, abcdefg, ____ef_ }, { _______, __cde_g, __cde_g, _bcde_g, abcdefg, _____f_ }, { _b_____, abcdefg, abcdefg, ab__efg, a____f_, _______ } }, { //6 { _______, _______, _______, __cd___, abcdefg, ____ef_ }, { _______, _______, _bcde_g, abcdefg, a____f_, _______ }, { _______, _bcd___, abcdefg, abcdefg, abcdefg, ____e__ }, { __c____, abcdefg, a___ef_, ab_____, abcdefg, ____ef_ }, { _bc____, abcdefg, __cdefg, _bcde_g, abcdefg, _______ }, { _______, abc__fg, abcdefg, ab__efg, _______, _______ } }, { //7 { _______, _bc____, abcdefg, abcdefg, abcdefg, __cdefg }, { _______, _b_____, ab___fg, ab___fg, abcdefg, abcdefg }, { _______, _______, _______, _bcde_g, abcdefg, a___ef_ }, { _______, _______, _bcde_g, abcdefg, ab__efg, _______ }, { _______, _bcde_g, abcdefg, a___ef_, _______, _______ }, { _b_____, abcdefg, abcdefg, _______, _______, _______ } }, { //8 { _______, _______, __cd___, abcdefg, abcdefg, __cdefg }, { _______, _bc____, abcdefg, ab___f_, abcd_fg, abcdefg }, { _______, _b_____, abcdefg, _bcde_g, abcdefg, a___ef_ }, { _______, _bcde_g, ab__efg, abc__fg, abcdefg, ____e__ }, { _bc____, abcdefg, __cde_g, _bcde_g, abcdefg, ____ef_ }, { _______, abc__fg, abcdefg, abcdefg, a____f_, _______ } }, { //9 { _______, _______, __cde_g, abcdefg, __cdefg, ___de__ }, { _______, _bcd___, abcdefg, ab___fg, abcd_fg, abcdefg }, { _______, abcdefg, a_cdefg, __cde__, abcdefg, a__defg }, { _______, ab_____, abcd_fg, abcdefg, abcdefg, _____f_ }, { _______, __cd___, abcdefg, ab__efg, _______, _______ }, { _b_____, abcdefg, a___ef_, _______, _______, _______ } }, { //Colon { _______, _______, _______, _______, _______, _______ }, { _______, __cde_g, _______, _______, _______, _______ }, { _______, _______, _______, _______, _______, _______ }, { _______, _______, _______, _______, _______, _______ }, { __cde_g, _______, _______, _______, _______, _______ }, { _______, _______, _______, _______, _______, _______ } } }; const int8_t smallDigits[11][5][3] PROGMEM = { { //0 { a___ef_, a__d___, abc____ }, { _bc_ef_, _______, _bc_ef_ }, { _bc_ef_, _______, _bc_ef_ }, { _bc_ef_, _______, _bc_ef_ }, { ___def_, a__d___, _bcd___ } }, { //1 { _______, abc_ef_, _______ }, { _______, _bc_ef_, _______ }, { _______, _bc_ef_, _______ }, { _______, _bc_ef_, _______ }, { _______, _bcdef_, _______ } }, { //2 { a__def_, a__d___, abc____ }, { _______, _______, _bc_ef_ }, { a___ef_, a__d___, _bcd___ }, { _bc_ef_, _______, _______ }, { ___def_, a__d___, abcd___ } }, { //3 { a__def_, a__d___, abc____ }, { _______, _______, _bc_ef_ }, { _______, a__def_, _bc____ }, { _______, _______, _bc_ef_ }, { a__def_, a__d___, _bcd___ } }, { //4 { abc_ef_, _______, abc_ef_ }, { _bc_ef_, _______, _bc_ef_ }, { ___def_, a__d___, _bc____ }, { _______, _______, _bc_ef_ }, { _______, _______, _bcdef_ } }, { //5 { a___ef_, a__d___, abcd___ }, { _bc_ef_, _______, _______ }, { ___def_, a__d___, abc____ }, { _______, _______, _bc_ef_ }, { a__def_, a__d___, _bcd___ } }, { //6 { a___ef_, a__d___, abcd___ }, { _bc_ef_, _______, _______ }, { ____ef_, a__d___, abc____ }, { _bc_ef_, _______, _bc_ef_ }, { ___def_, a__d___, _bcd___ } }, { //7 { a__def_, a__d___, abc____ }, { _______, _______, _bc_ef_ }, { _______, _bc_ef_, _______ }, { _______, _bc_ef_, _______ }, { _______, _bcdef_, _______ }, }, { //8 { a___ef_, a__d___, abc____ }, { _bc_ef_, _______, _bc_ef_ }, { ____ef_, a__d___, _bc____ }, { _bc_ef_, _______, _bc_ef_ }, { ___def_, a__d___, _bcd___ } }, { //9 { a___ef_, a__d___, abc____ }, { _bc_ef_, _______, _bc_ef_ }, { ___def_, a__d___, _bc____ }, { _______, _______, _bc_ef_ }, { a__def_, a__d___, _bcd___ } }, { //Hyphen { _______, _______, _______ }, { _______, _______, _______ }, { a__def_, a__d___, abcd___ }, { _______, _______, _______ }, { _______, _______, _______ } } }; 
Tunes.h C/C++
No preview (download only).
Button.h C/C++
/* Class: Button Author: John Bradnam (jbrad2089@gmail.com) Purpose: Arduino library to handle buttons */ #pragma once #include "Arduino.h" #define DEBOUNCE_DELAY 10 //Repeat speed #define REPEAT_START_SPEED 500 #define REPEAT_INCREASE_SPEED 50 #define REPEAT_MAX_SPEED 50 class Button { public: //Simple constructor Button(int pin); Button(int name, int pin); Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true); //Background function called when in a wait or repeat loop void Background(void (*pBackgroundFunction)()); //Repeat function called when button is pressed void Repeat(void (*pRepeatFunction)()); //Test if button is pressed bool IsDown(void); //Test whether button is pressed and released //Will call repeat function if one is provided bool Pressed(); //Return button state (HIGH or LOW) - LOW = Pressed int State(); //Return button name int Name(); private: int _name; int _pin; bool _range; int _low; int _high; bool _activeLow; void (*_repeatCallback)(void); void (*_backgroundCallback)(void); }; 
Button.cpp C/C++
/* Class: Button Author: John Bradnam (jbrad2089@gmail.com) Purpose: Arduino library to handle buttons */ #include "Button.h" Button::Button(int pin) { _name = pin; _pin = pin; _range = false; _low = 0; _high = 0; _backgroundCallback = NULL; _repeatCallback = NULL; pinMode(_pin, INPUT_PULLUP); } Button::Button(int name, int pin) { _name = name; _pin = pin; _range = false; _low = 0; _high = 0; _backgroundCallback = NULL; _repeatCallback = NULL; pinMode(_pin, INPUT_PULLUP); } Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow) { _name = name; _pin = pin; _range = true; _low = analogLow; _high = analogHigh; _activeLow = activeLow; _backgroundCallback = NULL; _repeatCallback = NULL; pinMode(_pin, INPUT); } //Set function to invoke in a delay or repeat loop void Button::Background(void (*pBackgroundFunction)()) { _backgroundCallback = pBackgroundFunction; } //Set function to invoke if repeat system required void Button::Repeat(void (*pRepeatFunction)()) { _repeatCallback = pRepeatFunction; } bool Button::IsDown() { if (_range) { int value = analogRead(_pin); return (value >= _low && value < _high); } else { return (digitalRead(_pin) == LOW); } } //Tests if a button is pressed and released // returns true if the button was pressed and released // if repeat callback supplied, the callback is called while the key is pressed bool Button::Pressed() { bool pressed = false; if (IsDown()) { unsigned long wait = millis() + DEBOUNCE_DELAY; while (millis() < wait) { if (_backgroundCallback != NULL) { _backgroundCallback(); } } if (IsDown()) { //Set up for repeat loop if (_repeatCallback != NULL) { _repeatCallback(); } unsigned long speed = REPEAT_START_SPEED; unsigned long time = millis() + speed; while (IsDown()) { if (_backgroundCallback != NULL) { _backgroundCallback(); } if (_repeatCallback != NULL && millis() >= time) { _repeatCallback(); unsigned long faster = speed - REPEAT_INCREASE_SPEED; if (faster >= REPEAT_MAX_SPEED) { speed = faster; } time = millis() + speed; } } pressed = true; } } return pressed; } //Return current button state int Button::State() { if (_range) { int value = analogRead(_pin); if (_activeLow) { return (value >= _low && value < _high) ? LOW : HIGH; } else { return (value >= _low && value < _high) ? HIGH : LOW; } } else { return digitalRead(_pin); } } //Return current button name int Button::Name() { return _name; } 
Hardware_Test_V1.zip C/C++

硬件测试草图 - 全段点亮并测试蜂鸣器

No preview (download only).

自定义零件与外壳

原理图

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

每个 MAX7219 的原理图 (重复 18 次)

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟 用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

承载六个四位七段数码管的列板 (需 6 块)

用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟 用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟

Eagle 格式 PCB 文件

eagle_files_KPhUOj6Ezv.zip

制造工艺

  1. Arduino POV 时钟:自制视觉暂留 LED 显示器
  2. DIY Arduino单词时钟 – 极简优雅的实时文字显示
  3. 自制 Arduino 伊斯兰祷告时间实时时钟
  4. 自制大尺寸LED主时钟带闹钟功能 — 12米外清晰可见
  5. Arduino 8×8 LED 矩阵翻页时钟 - DIY 实时时钟项目
  6. Arduino Uno驱动MAX7219级联8×8 LED矩阵:从入门到动画显示
  7. eDOT:基于 Arduino 的精密时钟与气象站,内置红外遥控
  8. 紧凑型Arduino雷达系统:HC-SR04超声波传感器配1.8英寸显示屏
  9. Arduino 实时天气时钟:OLED 显示时间、日期与温度
  10. 适用于 Arduino UNO/MEGA 的 Nixie 管时钟扩展板 NCS314 – 兼容 IN-14/IN-12 型号
  11. 用 Arduino UNO 自制迷你钢琴 — 图文教程
  12. 用 DS1302 RTC 和 Arduino UNO 制作可靠闹钟