در پروژه های قبلی ساخت ساعت با استفاده از Rtc های مختلف و همچنین نمایشگر های مختلف شرح داده شد . در این پروژه از یک ماژول تابلو روان 8 در 32 پیکسل با MAX7219 استفاده شده است که می تواند اعداد را بزرگ نمایش دهد . سونسگمنت های بزرگ معمولا قیمت بالایی دارند لذا استفاده از آن ها برای ساخت ساعت زیاد مقرون به صرفه نخواهد بود . همچنین تابلو روان امکان نمایش هر کاراکتر و شکل دیگری بجز اعداد را دارد . همچنین از ماژول DS3231 برای محاسبه دقیق زیمان و تاریخ استفاده شده است . این پروژه را می توانید به سلیقه خود گسترش دهید .
شماتیک مدار :
کد های پروژه :
هر کدام از کد های زیر را با اسم مشخص شده ذخیره و در یک محل مشخص در کنار هم ذخیره کنید . در پایان فایل main.cpp را در آردوینو IDE باز کنید و از منو Sketch->Add فایل های دیگر را به پروژه اضافه کنید . در آخر فایل main.cpp را بر روی برد خود آپلود کنید :
main.cpp :
/* FreeBSD License Copyright (c) 2019,2020 vikonix: [email protected] All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <Arduino.h> #include <EEPROM.h> #include "oclock.h" ////////////////////////////////////////////////////////////////////////////// // O'Clock main loop() with Clock Menu code //delay in automatic key repeat mode //0-without delay #define KEY_DELAY 1 //show pressure in mm Hg //#define PRESSURE_IN_MM //automaticaly show pressure, temperature and date #define SWITCH_MODE_PERIOD 2 //n minutes - showing period of Pressure/Temp/Date #define AUTO_SHOW_PRESSURE 2 //0-Off else duration in seconds #define AUTO_SHOW_TEMP 2 //0-Off else duration in seconds #define AUTO_SHOW_DATE 2 //0-Off else duration in seconds //at this seconds parameter was displayed #define SHOW_PRESSURE_ON_SECS 15 //seconds to show pressure #define SHOW_TEMP_ON_SECS 30 //seconds to show temperature #define SHOW_DATE_ON_SECS 45 //seconds to show date ////////////////////////////////////////////////////////////////////////////// #define BLINK_LOG 8 /* 8 -> 500ms */ #define IS_BLINK() (((millis() >> (BLINK_LOG + 1)) & 1) == 0) ////////////////////////////////////////////////////////////////////////////// //pictogramms //first byte - size const byte AlarmPic[] = {6, 0x20, 0x3e, 0xbf, 0xbf, 0x3e, 0x20}; //{6, 0x20, 0x3e, 0xa3, 0xa3, 0x3e, 0x20}; const byte GradC[] = {5, 0x02, 0x00, 0x1c, 0x22, 0x22}; ////////////////////////////////////////////////////////////////////////////// // Clock Configuration bool fConfigChanged = false; struct ClockConfig { //alarm uint8_t days; uint8_t hour; uint8_t mins; uint8_t melody; } config; void LoadConfig() { Serial.println(F("LoadConfig")); EEPROM.get(0, config); if(config.days != 0xff) //not inited EEPROM alarm.SetAlarm(config.days, config.hour, config.mins, config.melody); } void SaveConfig() { if(fConfigChanged) { Serial.println(F("SaveConfig")); fConfigChanged = false; alarm.GetAlarm(config.days, config.hour, config.mins, config.melody); EEPROM.put(0, config); } } void ConfigChanged() { fConfigChanged = true; } ////////////////////////////////////////////////////////////////////////////// // Clock Menu codes enum { //Show menu MODE_SHOW_BEGIN, MODE_SHOW_CLOCK, MODE_SHOW_TEMP, MODE_SHOW_PRESSURE, MODE_SHOW_DATE, MODE_SHOW_ALARM, MODE_SHOW_END, //Setup menu MODE_SET_BEGIN, MODE_SET_TIME, MODE_SET_ALARM, MODE_SHOW_VERSION, MODE_SET_END, //change time/date menu MODE_CH_BEGIN, MODE_CH_HOUR, MODE_CH_MIN, MODE_CH_SEC, MODE_CH_DAY, MODE_CH_MONTH, MODE_CH_YEAR, MODE_CH_END, //change alarm menu MODE_CH_ALARM_BEGIN, MODE_CH_ALARM_HOUR, MODE_CH_ALARM_MIN, MODE_CH_ALARM_DAY1, MODE_CH_ALARM_DAY2, MODE_CH_ALARM_DAY3, MODE_CH_ALARM_DAY4, MODE_CH_ALARM_DAY5, MODE_CH_ALARM_DAY6, MODE_CH_ALARM_DAY7, MODE_CH_ALARM_MELODY, MODE_CH_ALARM_END, MODE_UNUSED //move here unused menu Id }; ////////////////////////////////////////////////////////////////////////////// const char DaysSymbol[] = "MTWRFSU"; const char Months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; ////////////////////////////////////////////////////////////////////////////// //change time mode void ChangeTime(int Mode, int Increment) { int h = CurTime.hour(); int m = CurTime.minute(); int s = CurTime.second(); int D = CurTime.day(); int M = CurTime.month(); int Y = CurTime.year(); switch(Mode) { case MODE_CH_HOUR: h += Increment; if(h < 0) h = 23; else if(h > 23) h = 0; break; case MODE_CH_MIN: m += Increment; if(m < 0) m = 59; else if(m > 59) m = 0; break; case MODE_CH_SEC: s += Increment; if(s < 0) s = 59; else if(s > 59) s = 0; break; case MODE_CH_DAY: D += Increment; if(D < 1) D = 31; else if(D > 31) D = 1; break; case MODE_CH_MONTH: M += Increment; if(M < 1) M = 12; else if(M > 12) M = 1; break; case MODE_CH_YEAR: Y += Increment; if(Y < 2010) Y = 2099; else if(Y > 2099) Y = 2010; break; } char buff[16]; if(Mode == MODE_CH_HOUR || Mode == MODE_CH_MIN || Mode == MODE_CH_SEC) { sprintf(buff, "%02d:%02d.%02d", h, m, s); PrintTinyString(buff, 0, 1, true); int x = 0; if(Mode == MODE_CH_MIN) x = 12; else if(Mode == MODE_CH_SEC) x = 24; if(IS_BLINK()) { InverseBlock(x, 7, 7, 1); } } else { sprintf(buff, "%02d %s.%d", D, Months[M - 1], Y - 2000); PrintTinyString(buff, 0, 1); int x = 0; int sx = 7; if(Mode == MODE_CH_MONTH) { x = 10; sx = 10; } else if(Mode == MODE_CH_YEAR) { x = 24; sx = 6; } if(IS_BLINK()) { InverseBlock(x, 7, sx, 1); } } if(Increment) { SetTime(DateTime(Y, M, D, h, m, s)); GetCurTime(); } } ////////////////////////////////////////////////////////////////////////////// //change alarm mode void ChangeAlarm(int Mode, int Increment) { uint8_t d, uh, um, umelody; alarm.GetAlarm(d, uh, um, umelody); //we need signed numbers int h = uh; int m = um; int melody = umelody; switch(Mode) { case MODE_CH_ALARM_HOUR: h += Increment; if(h < 0) h = 23; else if(h > 23) h = 0; break; case MODE_CH_ALARM_MIN: m += Increment; if(m < 0) m = 59; else if(m > 59) m = 0; break; case MODE_CH_ALARM_DAY1: case MODE_CH_ALARM_DAY2: case MODE_CH_ALARM_DAY3: case MODE_CH_ALARM_DAY4: case MODE_CH_ALARM_DAY5: case MODE_CH_ALARM_DAY6: case MODE_CH_ALARM_DAY7: { uint8_t day = Mode - MODE_CH_ALARM_DAY1; uint8_t mask = 1 << day;; if(Increment != 0) d ^= mask; break; } case MODE_CH_ALARM_MELODY: melody += Increment; if(melody < 0) melody = MelodiesCount - 1; else if(melody >= MelodiesCount) melody = 0; break; } char buff[16]; if(Mode == MODE_CH_ALARM_HOUR || Mode == MODE_CH_ALARM_MIN) { sprintf(buff, "%02d:%02d", h, m); PrintTinyString(buff, 8, 1, true); PrintPictogram(0, &AlarmPic[1], AlarmPic[0]); int x = 8; if(Mode == MODE_CH_ALARM_MIN) x = 20; if(IS_BLINK()) { InverseBlock(x, 7, 7, 1); } } else if(Mode == MODE_CH_ALARM_MELODY) { sprintf(buff, "Melody %d", melody + 1); PrintTinyString(buff, 0, 1, true); if(IS_BLINK()) { InverseBlock(28, 7, 3, 1); } } else { PrintTinyString(DaysSymbol, 0, 0, true); uint8_t mask = 1; for(int i = 0; i < 7; ++i) { if((d & mask) != 0) InverseBlock(i * 4, 6, 3, 1); mask <<= 1; } if(IS_BLINK()) InverseBlock((Mode - MODE_CH_ALARM_DAY1) * 4, 7, 3, 1); } if(Increment != 0) { ConfigChanged(); alarm.SetAlarm(d, h, m, melody); if(Mode == MODE_CH_ALARM_MELODY) alarm.Play(); } } ////////////////////////////////////////////////////////////////////////////// #define KEY_TIMEOUT_PERIOD 5 //sec #define LONG_PRESSED_PERIOD 2000 //msec #define LOOP_SLEEP_TIME 50 //msec ////////////////////////////////////////////////////////////////////////////// void loop() { //current mode static int Mode = MODE_SHOW_CLOCK; static uint32_t ModeTimeout = 0; bool fBlink = false; static unsigned long blinkt = 0; unsigned long ms = millis(); if(ms - blinkt >= (1ul << BLINK_LOG)) { blinkt = ms; fBlink = true; } char buff[16]; //Update Params int TimeChanged = GetCurTime(); if(TimeChanged) AdjustBright(); bool fAlarm = false; if(Mode == MODE_SHOW_CLOCK || Mode == MODE_CH_ALARM_MELODY) fAlarm = alarm.CheckTime(CurTime); KeyMode.update(); KeyPlus.update(); KeyMinus.update(); // Check Keys pressing // only one key in time allowed bool fEvent = false; int increment = 0; if(KeyMode.fell()) { //Serial.println(F("Key mode")); if(fAlarm) alarm.Reset(); else { fEvent = true; if(Mode < MODE_SHOW_END) { ++Mode; if(Mode == MODE_SHOW_TEMP && !IsTPHpresent()) //BME280 not inited ++Mode; if(Mode <= MODE_SHOW_BEGIN || Mode >= MODE_SHOW_END) Mode = MODE_SHOW_BEGIN + 1; if(Mode == MODE_SHOW_CLOCK) TimeChanged = CHANGED_ALL; } else if(Mode < MODE_SET_END) { if(Mode == MODE_SET_TIME) Mode = MODE_CH_BEGIN + 1; else if(Mode == MODE_SET_ALARM) Mode = MODE_CH_ALARM_BEGIN + 1; else if(Mode == MODE_SHOW_VERSION) { Mode = MODE_SHOW_CLOCK; TimeChanged = CHANGED_ALL; } } else if(Mode < MODE_CH_END) { if(++Mode >= MODE_CH_END) Mode = MODE_SET_TIME; } else if(Mode < MODE_CH_ALARM_END) { if(Mode == MODE_CH_ALARM_MELODY) alarm.Reset(); if(++Mode >= MODE_CH_ALARM_END) Mode = MODE_SET_ALARM; } } } else if(KeyPlus.fell()) { //Serial.println(F("Key plus")); if(fAlarm) alarm.Reset(&CurTime); else { fEvent = true; if(Mode < MODE_SET_END) { if(Mode <= MODE_SET_BEGIN || ++Mode >= MODE_SET_END) Mode = MODE_SET_BEGIN + 1; } else { //change date/time ++increment; } } } else if(KeyMinus.fell()) { //Serial.println(F("Key minus")); if(fAlarm) alarm.Reset(&CurTime); else { fEvent = true; if(Mode < MODE_SET_END) { if(--Mode <= MODE_SET_BEGIN) Mode = MODE_SET_END - 1; } else { //change date/time --increment; } } } //check long key pressing if(Mode == MODE_CH_HOUR || Mode == MODE_CH_MIN || Mode == MODE_CH_SEC || Mode == MODE_CH_DAY || Mode == MODE_CH_YEAR || Mode == MODE_CH_ALARM_HOUR || Mode == MODE_CH_ALARM_MIN ) { static int KeyDelay = 0; if(!fEvent && !KeyPlus.read()) { if(KeyPlus.duration() >= LONG_PRESSED_PERIOD) { //long pressed if(++KeyDelay >= KEY_DELAY) { fEvent = true; increment = 1; KeyDelay = 0; } } } else if(!fEvent && !KeyMinus.read()) { if(KeyMinus.duration() >= LONG_PRESSED_PERIOD) { //long pressed if(++KeyDelay >= KEY_DELAY) { fEvent = true; increment = -1; KeyDelay = 0; } } } else { KeyDelay = 0; } } if(fEvent) { Beep(); ModeTimeout = CurTime.secondstime() + KEY_TIMEOUT_PERIOD; } // Check Mode Timeout else if(Mode != MODE_SHOW_CLOCK && CurTime.secondstime() > ModeTimeout) { //Serial.println(F("reset mode")); ModeTimeout = 0; TimeChanged = SCROLL_ALL;//CHANGED_ALL; if(Mode == MODE_CH_ALARM_MELODY) alarm.Reset(); Mode = MODE_SHOW_CLOCK; } // Apply Mode if(Mode == MODE_SHOW_CLOCK) { fBlink = false; if(TimeChanged) { uint8_t h, m, d, n; alarm.GetAlarm(d, h, m, n); bool alarm = (Alarm::DayOfTheWeek(CurTime.dayOfTheWeek()) & d) != 0; DisplayTime(CurHours, CurMins, CurSecs, alarm, TimeChanged, 0); SaveConfig(); #if AUTO_SHOW_PRESSURE != 0 if(CurSecs == SHOW_PRESSURE_ON_SECS) { Mode = MODE_SHOW_PRESSURE; fEvent = true; ModeTimeout = CurTime.secondstime() + AUTO_SHOW_TEMP; } else #endif #if AUTO_SHOW_TEMP != 0 if(CurSecs == SHOW_TEMP_ON_SECS) { Mode = MODE_SHOW_TEMP; fEvent = true; ModeTimeout = CurTime.secondstime() + AUTO_SHOW_TEMP; } else #endif #if AUTO_SHOW_DATE != 0 if(CurSecs == SHOW_DATE_ON_SECS) { Mode = MODE_SHOW_DATE; fEvent = true; ModeTimeout = CurTime.secondstime() + AUTO_SHOW_DATE; } else #endif { fEvent = false; } if(fEvent && SWITCH_MODE_PERIOD && 0 != CurMins % SWITCH_MODE_PERIOD) { //skip for SWITCH_MODE_PERIOD Mode = MODE_SHOW_CLOCK; fEvent = false; } } } if(fEvent || fBlink) { if(Mode == MODE_SHOW_DATE) { sprintf(buff, "%d %s. %d", CurTime.day(), Months[CurTime.month() - 1], CurTime.year() - 2000); PrintTinyString(buff, 0, 1); } else if(Mode == MODE_SHOW_TEMP || Mode == MODE_SHOW_PRESSURE) { float t, p, h; if(ReadTPH(t, p, h)) { if(Mode == MODE_SHOW_TEMP) { int temp = t; int rh = h; sprintf(buff, "%2d %2d%%H", temp, rh); PrintTinyString(buff, 0, 1, true); PrintPictogram(8, &GradC[1], GradC[0]); } else { #ifndef PRESSURE_IN_MM int hPa = p / 100.0f; sprintf(buff, "%d hPa", hPa); #else int mmHg = p / 133.332f; sprintf(buff, "%d mm", mmHg); #endif PrintTinyString(buff, 1, 1); } } else { sprintf(buff, "BME280 ?"); PrintTinyString(buff, 0, 1); } } else if(Mode == MODE_SHOW_ALARM) { uint8_t h, m, d, n; alarm.GetAlarm(d, h, m, n); sprintf(buff, "%02d:%02d", h, m); PrintTinyString(buff, 13, 1, true); PrintPictogram(3, &AlarmPic[1], AlarmPic[0]); PrintPictogram(0, &d, 1); uint8_t wd = Alarm::DayOfTheWeek(CurTime.dayOfTheWeek()); PrintPictogram(1, &wd, 1); } else if(Mode == MODE_SET_TIME) PrintTinyString("Set>Time", 0, 1); else if(Mode == MODE_SET_ALARM) PrintTinyString("Set>Alrm", 0, 1); else if(Mode > MODE_CH_BEGIN && Mode < MODE_CH_END) { ChangeTime(Mode, increment); if(increment) { //time changed: update timeout ModeTimeout = CurTime.secondstime() + KEY_TIMEOUT_PERIOD; } } else if(Mode > MODE_CH_ALARM_BEGIN && Mode < MODE_CH_ALARM_END) ChangeAlarm(Mode, increment); else //if(Mode == MODE_SHOW_VERSION) PrintTinyString("O'Clk- " VERSION, 0, 1); ShowBuffer(); } delay(LOOP_SLEEP_TIME); }
alarm.h :
/* FreeBSD License Copyright (c) 2019,2020 vikonix: [email protected] All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __ALARM_H__ #define __ALARM_H__ #include <Arduino.h> #include <RTClib.h> #include <NonBlockingRtttl.h> ////////////////////////////////////////////////////////////////////////////// // Class for alarm management and playing melody when alarm went off #define SNOOZE_TIME (5 * 60) //snooze period in seconds #define ALARM_TIME 30 //alarm time in seconds //#define DEBUG_ALARM class Alarm { protected: uint8_t m_Pin; const char* const* m_MelodyArray; uint8_t m_MelodyCount; //alarm config uint8_t m_Days; //bit mask for week days Mon->0x1 Sum->0x40 uint8_t m_Hour; uint8_t m_Min; uint8_t m_Melody; char* m_Buffer; bool m_Play; uint32_t m_Snooze; public: // melody array in RTTTL format in PROGMEM // example: "Popcorn:d=4,o=5,b=125:8c6,8a#,8c6,8g,8d#,8g,c,8c6,8a#,8c6,8g,8d#,8g,c,8c6,8d6,8d#6,16c6,8d#6,16c6,8d#6,8d6,16a#,8d6,16a#,8d6,8c6,8a#,8g,8a#,c6"; Alarm(uint8_t pin, const char* const* MelodyArray, uint8_t MelodyCount) : m_Pin(pin), m_MelodyArray(MelodyArray), m_MelodyCount(MelodyCount), m_Days(0), m_Hour(0), m_Min(0), m_Melody(0), m_Buffer(nullptr), m_Play(false), m_Snooze(0) {} ~Alarm() { delete m_Buffer; } //Convert from day number "0-Sun .. 6-Sat" to 1 bit "0-Mon .. 6-Sun" static uint8_t DayOfTheWeek(uint8_t day) { if(day == 0)//Sunday return 0x40; else return 1 << --day; } void SetAlarm(uint8_t days, uint8_t hour, uint8_t mins, uint8_t melody) { m_Days = days; m_Hour = hour; m_Min = mins; m_Melody = melody; if(m_Melody >= m_MelodyCount) m_Melody = 0; const char* pMelody = (const char*)pgm_read_word(&(m_MelodyArray[m_Melody])); size_t len = strlen_P(pMelody); delete m_Buffer; m_Buffer = new char[len + 1]; if(m_Buffer) strcpy_P(m_Buffer, pMelody); } void GetAlarm(uint8_t& days, uint8_t& hour, uint8_t& mins, uint8_t& melody) { days = m_Days; hour = m_Hour; mins = m_Min; melody = m_Melody; } void Play() { #ifdef DEBUG_ALARM Serial.println(F("Alarm.Play")); #endif if(m_Buffer) rtttl::begin(m_Pin, m_Buffer); } bool CheckTime(const DateTime& time) { rtttl::play(); //check for restarting melody if(m_Play) { if(rtttl::done()) { if(m_Snooze + ALARM_TIME > time.secondstime()) Play(); else //60 seconds timeout done m_Play = false; } } else if(m_Snooze == time.secondstime()) { #ifdef DEBUG_ALARM Serial.println(F("Snooze")); #endif m_Play = true; Play(); } else if( (m_Days & DayOfTheWeek(time.dayOfTheWeek())) && m_Hour == time.hour() && m_Min == time.minute() && 0 == time.second() ) { #ifdef DEBUG_ALARM Serial.println(F("Alarm")); #endif m_Play = true; m_Snooze = time.secondstime(); Play(); } return m_Play; } void Reset(const DateTime* time = nullptr) { #ifdef DEBUG_ALARM Serial.println(F("Alarm.Reset")); #endif m_Play = false; rtttl::stop(); if(!time) //end alarm m_Snooze = 0; else //add snooze period m_Snooze = time->secondstime() + SNOOZE_TIME; } }; #endif //__ALARM_H__
oclock.cpp :
/* FreeBSD License Copyright (c) 2019,2020 vikonix: [email protected] All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ ////////////////////////////////////////////////////////////////////////////// //O'Clock - simple matrix clock /* - Tested with Arduino Nano v3.0 - Screen - 8x32 led matrix (SPI) - Time - rtc DS3231 (I2C) - Sensor - BME280 (I2C) (Optional) - 3 keys ('+', '-', 'Mode') ('-' optional) - Brights adjustment with photoresistor (5528 Light Dependent Resistor LDR 5MM) - Passive piezo buzzer for melody playing */ #include <EEPROM.h> #include <LEDMatrixDriver.hpp> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include "oclock.h" // Define the ChipSelect pin for the led matrix (Dont use the SS or MISO pin of your Arduino!) // Other pins are Arduino specific SPI pins (MOSI=DIN, SCK=CLK of the LEDMatrix) // see https://www.arduino.cc/en/Reference/SPI // Connect DIN->D11 CLK->D13 const uint8_t LEDMATRIX_CS_PIN = 9; //D9 // buzzer pin const uint8_t Buzzer_Pin = 8; //D8 Passive piezo buzzer connected to +5V // photo resistor pin const uint8_t Photo_Pin = A3; //Photoresistor connected to +5V with 10KOm to -V const uint8_t KeyMode_Pin = 4; //D4 //Keys connected to -V const uint8_t KeyPlus_Pin = 5; //D5 -//- const uint8_t KeyMinus_Pin = 6; //D6 -//- (optional) // Number of 8x8 segments you are connecting const int LEDMATRIX_SEGMENTS = 4; const int LEDMATRIX_WIDTH = LEDMATRIX_SEGMENTS * 8; // screen buffers byte screen_buffer[LEDMATRIX_WIDTH]; // The display object LEDMatrixDriver lmd(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN); // The clock object // Connect SDA->A4 SCL->A5 RTC_DS3231 rtc; // I2C // BME 280 module (optional) // BME280 Address fixed in library Adafruit_BME280.h // original Address is 0x77, China module Address is 0x76. // Don't forgot to set next define with correct Address !!! #define MY_BME280_ADDRESS (0x76) #define SEALEVELPRESSURE_HPA (1013.25) // Connect SDA->A4 SCL->A5 Adafruit_BME280 bme; // I2C // Key objects Bounce KeyMode = Bounce(); Bounce KeyPlus = Bounce(); Bounce KeyMinus = Bounce(); int EepromSize = EEPROM.length(); ////////////////////////////////////////////////////////////////////////////// // alarm melody in RTTTL format const char melody1[] PROGMEM = "Aha:d=4,o=4,b=160:8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5,8f#5,8e5,8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5"; const char melody2[] PROGMEM = "Europe:d=4,o=5,b=125:p,8p,16b,16a,b,e,p,8p,16c6,16b,8c6,8b,a,p,8p,16c6,16b,c6,e,p,8p,16a,16g,8a,8g,8f#,8a,g.,16f#,16g,a.,16g,16a,8b,8a,8g,8f#,e,c6,2b.,16b,16c6,16b,16a,1b"; const char melody3[] PROGMEM = "IndianaJ:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6"; const char melody4[] PROGMEM = "Popcorn:d=4,o=5,b=125:8c6,8a#,8c6,8g,8d#,8g,c,8c6,8a#,8c6,8g,8d#,8g,c,8c6,8d6,8d#6,16c6,8d#6,16c6,8d#6,8d6,16a#,8d6,16a#,8d6,8c6,8a#,8g,8a#,c6"; const char melody5[] PROGMEM = "Axel-F:d=4,o=5,b=125:g,8a#.,16g,16p,16g,8c6,8g,8f,g,8d.6,16g,16p,16g,8d#6,8d6,8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g,p,16f6,8d6,8c6,8a#,g,8a#.,16g,16p,16g,8c6,8g,8f,g,8d.6,16g,16p,16g,8d#6,8d6,8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g"; const char* const MelodyArray[] PROGMEM = {melody1, melody2, melody3, melody4, melody5}; const int MelodiesCount = sizeof(MelodyArray) / sizeof(const char*); Alarm alarm(Buzzer_Pin, MelodyArray, MelodiesCount); ////////////////////////////////////////////////////////////////////////////// // current time DateTime CurTime; int CurHours = -1;//not inited int CurMins = 0; int CurSecs = 0; int GetCurTime() { int h = CurHours; int m = CurMins; int s = CurSecs; CurTime = rtc.now(); CurHours = CurTime.hour(); CurMins = CurTime.minute(); CurSecs = CurTime.second(); if(h == -1) { // first call return CHANGED_ALL; } // check modified digits int mod = CHANGED_NOTHING; if(h / 10 != CurHours / 10) mod = CHANGED_HOUR10; else if(h % 10 != CurHours % 10) mod = CHANGED_HOUR1; else if(m / 10 != CurMins / 10) mod = CHANGED_MIN10; else if(m % 10 != CurMins % 10) mod = CHANGED_MIN1; else if(s != CurSecs) mod = CHANGED_SEC; return mod; } ////////////////////////////////////////////////////////////////////////////// void SetTime(const DateTime& dt) { rtc.adjust(dt); } ////////////////////////////////////////////////////////////////////////////// void AdjustBright() { // current bright level static int CurBrightLevel = 0; // logarithmic level byte level[] = {0,0,0,0,0,0,1,2,4,8}; // 0 = low, 15 = high // maximal analog value const int MaxAnalogValue = 1023; int v = analogRead(Photo_Pin); //Serial.print(F("Light level=")); Serial.println(v, DEC); #if 0 //switch off LED in full dark if(v == 0) lmd.setEnabled(false); else lmd.setEnabled(true); #endif // calc linear level int l = (v * sizeof(level)) / MaxAnalogValue; // get logarithmic level l = level[l]; if(CurBrightLevel != l) { CurBrightLevel = l; lmd.setIntensity(CurBrightLevel); } } ////////////////////////////////////////////////////////////////////////////// void Beep(int n) { if(n < 1 || n > 5) n = 1; while(n--) { tone(Buzzer_Pin, 500, 10); if(n) delay(150); else delay(10); } noTone(Buzzer_Pin); } void BeepError() { tone(Buzzer_Pin, 500, 1000); delay(1000); noTone(Buzzer_Pin); } ////////////////////////////////////////////////////////////////////////////// void ShowBuffer(byte* buffer) { if(buffer && screen_buffer != buffer) memcpy(screen_buffer, buffer, LEDMATRIX_WIDTH); for(byte row = 0; row < LEDMATRIX_WIDTH; row++) lmd.setColumn(row, screen_buffer[row]); lmd.display(); } ///////////////////////////////////////////////////////////////////////////// bool fBME280present = false; bool IsTPHpresent() { return fBME280present; } bool ReadTPH(float& Temperature, float& Pressure, float& Humidity) { if(!fBME280present) return false; bme.takeForcedMeasurement(); Temperature = bme.readTemperature(); Pressure = bme.readPressure(); Humidity = bme.readHumidity(); return true; } ///////////////////////////////////////////////////////////////////////////// void setup() { pinMode(Buzzer_Pin, OUTPUT); // Set buzzer an output Beep(); // init the display lmd.setEnabled(true); lmd.setIntensity(0); // 0 = low, 15 = high lmd.clear(); Serial.begin(9600); Serial.println(F("Vikonix O'Clock Version:" VERSION)); //Serial.print(F("EEPROM size=")); Serial.println(EepromSize, DEC); if(!rtc.begin()) { Serial.println(F("Rtc ERROR")); BeepError(); } if(rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); Beep(2); } //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); KeyMode.attach(KeyMode_Pin, INPUT_PULLUP); KeyMode.interval(20); KeyPlus.attach(KeyPlus_Pin, INPUT_PULLUP); KeyPlus.interval(20); KeyMinus.attach(KeyMinus_Pin, INPUT_PULLUP); KeyMinus.interval(20); LoadConfig(); if(!bme.begin(MY_BME280_ADDRESS)) Serial.println(F("BME280 sensor not found")); else { Serial.println(F("BME280 sensor Ok")); fBME280present = true; bme.setSampling( Adafruit_BME280::MODE_FORCED, Adafruit_BME280::SAMPLING_X1, // temperature Adafruit_BME280::SAMPLING_X1, // pressure Adafruit_BME280::SAMPLING_X1, // humidity Adafruit_BME280::FILTER_OFF ); } }
screen.cpp :
/* FreeBSD License Copyright (c) 2019,2020 vikonix: [email protected] All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <Arduino.h> #include "screen.h" ////////////////////////////////////////////////////////////////////////////// // digits 6x8 mono #define BIG_COLON 0xa #define BIG_COLON1 0xb const byte font_digit_6x8[] PROGMEM = { 0x7e, 0xff, 0x81, 0x81, 0xff, 0x7e, //'0' 0 0x00, 0x82, 0xff, 0xff, 0x80, 0x00, //'1' 1 0x82, 0xc1, 0xa1, 0x91, 0xcf, 0xc6, //'2' 2 0x42, 0xc1, 0x89, 0x89, 0xff, 0x76, //'3' 3 0x38, 0x24, 0xa2, 0xff, 0xff, 0xa0, //'4' 4 0x4f, 0xcf, 0x89, 0x89, 0xf9, 0x71, //'5' 5 0x7c, 0xfe, 0x8b, 0x89, 0xf9, 0x70, //'6' 6 0x01, 0x81, 0xf1, 0xf9, 0x8f, 0x07, //'7' 7 0x76, 0xff, 0x89, 0x89, 0xff, 0x76, //'8' 8 0x0e, 0x9f, 0x91, 0xd1, 0x7f, 0x3e, //'9' 9 0x62, 0x62, 0x00, 0x00, 0x00, 0x00, //':' 0xa 0x46, 0x46, 0x00, 0x00, 0x00, 0x00 //':' 0xb }; // tiny font with my small modification // Modifications to Tom Thumb for improved readability are from Robey Pointer, // see: http://robey.lag.net/2010/01/23/tiny-monospace-font.html #define MIN_TINY 0x1e #define MAX_TINY 0x7f const unsigned short PROGMEM font_3x5[] = { //custom symbols 0b0000100000000000, //0x1e 'dot in the up' 0b0000000100000000, //0x1f 'dot in the middle' //standart symbols 0b0000000000000000, //' ' 0b0000010111000000, //'!' 0b0000100000000010, //'"' 0b1111101010111110, //'#' 0b0101011111001010, //'$' 0b0100100100100100, //'%' 0b0101010101110100, //'&' 0b0000000011000000, //''' 0b0000001110100010, //'(' 0b1000101110000000, //')' 0b0101000100010100, //'*' 0b0010001110001000, //'+' 0b1000001100000001, //',' 0b0010000100001000, //'-' 0b0000010000000000, //'.' 0b1100000100000110, //'/' #if 0 //rounded digits 0b1111010001011110, //'0' 0b0001011111000000, //'1' 0b1100110101100100, //'2' 0b1000110101010100, //'3' 0b0011100100111110, //'4' 0b1011110101010010, //'5' 0b1111010101111010, //'6' 0b1100100101000110, //'7' 0b1111110101111110, //'8' 0b1011110101011110, //'9' #else //quadrate digits 0b1111110001111110, //'0' 0b0001011111000000, //'1' 0b1110110101101110, //'2' 0b1000110101111110, //'3' 0b0011100100111110, //'4' 0b1011110101111010, //'5' 0b1111110101111010, //'6' 0b0000100001111110, //'7' 0b1111110101111110, //'8' 0b1011110101111110, //'9' #endif 0b0000001010000000, //':' 0b1000001010000000, //';' 0b0010001010100010, //'<' 0b0101001010010100, //'=' 0b1000101010001000, //'>' 0b0000110101000110, //'?' 0b0111010101101100, //'@' 0b1111000101111100, //'A' 0b1111110101010100, //'B' 0b0111010001100010, //'C' 0b1111110001011100, //'D' 0b1111110101101010, //'E' 0b1111100101001010, //'F' 0b0111010101111010, //'G' 0b1111100100111110, //'H' 0b1000111111100010, //'I' 0b0100010000011110, //'J' 0b1111100100110110, //'K' 0b1111110000100000, //'L' 0b1111100110111110, //'M' 0b1111101110111110, //'N' 0b0111010001011100, //'O' 0b1111100101000100, //'P' 0b0111010001111100, //'Q' 0b1111101101101100, //'R' 0b1001010101010010, //'S' 0b0000111111000010, //'T' 0b0111110000111110, //'U' 0b0011111000001110, //'V' 0b1111101100111110, //'W' 0b1101100100110110, //'X' 0b0001111100000110, //'Y' 0b1100110101100110, //'Z' 0b0000011111100010, //'[' 0b0001100100110000, //'\' 0b1000111111000000, //']' 0b0001000001000100, //'^' 0b1000010000100000, //'_' 0b0000100010000000, //'`' 0b1101010110111000, //'a' 0b1111110010011000, //'b' 0b0110010010100100, //'c' 0b0110010010111110, //'d' 0b0110011010101100, //'e' 0b0010011110001010, //'f' 0b0011010101011111, //'g' 0b1111100010111000, //'h' 0b0000011101000000, //'i' 0b1000001101000000, //'j' 0b1111101100100100, //'k' 0b1000111111100000, //'l' 0b1111001110111000, //'m' 0b1111000010111000, //'n' 0b0110010010011000, //'o' 0b1111101001001101, //'p' 0b0011001001111111, //'q' 0b1110000010000100, //'r' 0b1010011110010100, //'s' 0b0001011111100100, //'t' 0b0111010000111100, //'u' 0b0111011000011100, //'v' 0b0111011100111100, //'w' 0b1001001100100100, //'x' 0b0001110100011111, //'y' 0b1101011110101100, //'z' 0b0010011011100010, //'{' 0b0000011011000000, //'|' 0b1000111011001000, //'}' 0b0001000011000010, //'~' 0b1111111111111110 //other symbols }; ////////////////////////////////////////////////////////////////////////////// byte GetColumnMask(int size) { switch(size) { case 1: return B10000000; case 2: return B11000000; case 3: return B11100000; case 4: return B11110000; case 5: return B11111000; case 6: return B11111100; case 7: return B11111110; case 8: return B11111111; } return B11111111; } ////////////////////////////////////////////////////////////////////////////// void CopySymbol(byte* pBuffer, const byte* pFont, int Symbol, int x, int y, int fontx, int fonty, int sizex = 0) { byte mask = GetColumnMask(fonty); if(sizex == 0) sizex = fontx; for(int i = 0; i < sizex; ++i) { byte column = pgm_read_byte(pFont + Symbol * fontx + i); pBuffer[x + i] = (pBuffer[x + i] & ~(mask << y)) | (column << y); } } ////////////////////////////////////////////////////////////////////////////// int CopyTinySymbol(byte* pBuffer, int Symbol, int x, int y, bool fixed) { byte mask = B11111100; unsigned short s3 = pgm_read_word(font_3x5 + Symbol); unsigned short descender = s3 & 0x01; byte column1 = (byte)(((s3 >> 11) & 0b00011111) << descender); byte column2 = (byte)(((s3 >> 6) & 0b00011111) << descender); byte column3 = (byte)(((s3 >> 1) & 0b00011111) << descender); int n = 0; if(fixed || column1 != 0) { pBuffer[x+n] = (pBuffer[x+n] & ~(mask << y)) | (column1 << y); ++n; } if(fixed || column2 != 0 || (column1 != 0 && column3 != 0)) { pBuffer[x+n] = (pBuffer[x+n] & ~(mask << y)) | (column2 << y); ++n; } if(fixed || column3 != 0) { pBuffer[x+n] = (pBuffer[x+n] & ~(mask << y)) | (column3 << y); ++n; } return n + 1; } ////////////////////////////////////////////////////////////////////////////// void ScrollVerticalOneRow(byte* buffer, byte from, byte to, boolean fUp) { for(byte n = from; n <= to; ++n) { if(fUp) { screen_buffer[n] >>= 1; bitWrite(screen_buffer[n], 7, bitRead(buffer[n], 0)); buffer[n] >>= 1; } else { screen_buffer[n] <<= 1; bitWrite(screen_buffer[n], 0, bitRead(buffer[n], 7)); buffer[n] <<= 1; } } } ////////////////////////////////////////////////////////////////////////////// void ScrollVertical(byte* buffer, byte from, byte to, boolean fUp) { for(byte i = 0; i < 8; i++) { ScrollVerticalOneRow(buffer, from, to, fUp); ShowBuffer(); delay(50); } } ////////////////////////////////////////////////////////////////////////////// //clock digits positions #define POS_DIGIT1 1 //0 #define POS_DIGIT2 (POS_DIGIT1 + 7) //7 #define POS_COLON (POS_DIGIT2 + 7) //14 #define POS_DIGIT3 (POS_COLON + 3) //17 #define POS_DIGIT4 (POS_DIGIT3 + 7) //24 void DisplayTime(int hours, int mins, int secs, bool alarm, byte scroll_mode, boolean fUp) { byte sprite_buffer[LEDMATRIX_WIDTH]; memset(sprite_buffer, 0, LEDMATRIX_WIDTH); // if(hours / 10) CopySymbol(sprite_buffer, font_digit_6x8, hours / 10, POS_DIGIT1, 0, 6, 8); CopySymbol(sprite_buffer, font_digit_6x8, hours % 10, POS_DIGIT2, 0, 6, 8); CopySymbol(sprite_buffer, font_digit_6x8, BIG_COLON + (secs & 1), POS_COLON, 0, 6, 8, 2); CopySymbol(sprite_buffer, font_digit_6x8, mins / 10, POS_DIGIT3, 0, 6, 8); CopySymbol(sprite_buffer, font_digit_6x8, mins % 10, POS_DIGIT4, 0, 6, 8); if(alarm) { sprite_buffer[LEDMATRIX_WIDTH - 1] = 0b10000000; } switch(scroll_mode) { case CHANGED_ALL: default: ShowBuffer(sprite_buffer); break; case CHANGED_MIN1: ScrollVertical(sprite_buffer, POS_DIGIT4, POS_DIGIT4+5, fUp); break; case CHANGED_MIN10: ScrollVertical(sprite_buffer, POS_DIGIT4, POS_DIGIT4+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT3, POS_DIGIT3+5, fUp); break; case CHANGED_HOUR1: ScrollVertical(sprite_buffer, POS_DIGIT4, POS_DIGIT4+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT3, POS_DIGIT3+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT2, POS_DIGIT2+5, fUp); break; case CHANGED_HOUR10: ScrollVertical(sprite_buffer, POS_DIGIT4, POS_DIGIT4+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT3, POS_DIGIT3+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT2, POS_DIGIT2+5, fUp); ScrollVertical(sprite_buffer, POS_DIGIT1, POS_DIGIT1+5, fUp); break; case SCROLL_ALL: ScrollVertical(sprite_buffer, 0, 31, fUp); break; } } ////////////////////////////////////////////////////////////////////////////// void PrintTinyString(const char* str, int x, int y, bool fixed) { byte sprite_buffer[LEDMATRIX_WIDTH+3]; memset(sprite_buffer, 0, sizeof(sprite_buffer)); char c; while((c = *str++) != 0 && (unsigned int)x < sizeof(sprite_buffer)) { if(c < MIN_TINY || c > MAX_TINY) c = MAX_TINY; c -= MIN_TINY; x += CopyTinySymbol(sprite_buffer, c, x, y, fixed); } memcpy(screen_buffer, sprite_buffer, LEDMATRIX_WIDTH); } ///////////////////////////////////////////////////////////////////////////// void InverseBlock(int x, int y, int sizex, int sizey) { byte mask = GetColumnMask(sizey) >> (8 - y - sizey); for(int i = 0; i < sizex; ++i) { screen_buffer[x + i] ^= mask; } } ///////////////////////////////////////////////////////////////////////////// void PrintPictogram(int x, const byte* buff, int size) { memcpy(screen_buffer + x, buff, size); }
screen.h :
/* FreeBSD License Copyright (c) 2019,2020 vikonix: [email protected] All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __SCREEN_H__ #define __SCREEN_H__ ////////////////////////////////////////////////////////////////////////////// //fonts and functions for working with matrix screen 8x32 ////////////////////////////////////////////////////////////////////////////// //imported parameters and functions for working with screen extern const int LEDMATRIX_WIDTH; extern byte screen_buffer[]; void ShowBuffer(byte* buffer = NULL); ////////////////////////////////////////////////////////////////////////////// #define CHANGED_NOTHING 0 #define CHANGED_ALL 6 #define CHANGED_SEC CHANGED_ALL #define CHANGED_MIN1 1 #define CHANGED_MIN10 2 #define CHANGED_HOUR1 3 #define CHANGED_HOUR10 4 #define SCROLL_ALL 5 ///////////////////////////////////////////////////////////////////////////// void DisplayTime(int hours, int mins, int secs, bool alarm, byte scroll_mode, boolean fUp); void PrintTinyString(const char* str, int x, int y, bool fixed = false); void InverseBlock(int x, int y, int sizex, int sizey); void PrintPictogram(int x, const byte* buff, int size); #endif //__SCREEN_H__
منبع : https://create.arduino.cc/projecthub/vikonix/o-clock-3d24a1?ref=search&ref_id=clock&offset=13