در پروژه های قبلی ساخت ساعت با استفاده از Rtc های مختلف و همچنین نمایشگر های مختلف شرح داده شد . در این پروژه از یک ماژول تابلو روان 8 در 32 پیکسل با MAX7219 استفاده شده است که می تواند اعداد را بزرگ نمایش دهد . سونسگمنت های بزرگ معمولا قیمت بالایی دارند لذا استفاده از آن ها برای ساخت ساعت زیاد مقرون به صرفه نخواهد بود . همچنین تابلو روان امکان نمایش هر کاراکتر و شکل دیگری بجز اعداد را دارد . همچنین از ماژول DS3231 برای محاسبه دقیق زیمان و تاریخ استفاده شده است . این پروژه را می توانید به سلیقه خود گسترش دهید .

 

شماتیک مدار :

 

کد های پروژه :

هر کدام از کد های زیر را با اسم مشخص شده ذخیره و در یک محل مشخص در کنار هم ذخیره کنید . در پایان فایل main.cpp را در آردوینو IDE باز کنید و از منو Sketch->Add فایل های دیگر را به پروژه اضافه کنید . در آخر فایل main.cpp را بر روی برد خود آپلود کنید :

main.cpp :

/*
FreeBSD License

Copyright (c) 2019,2020 vikonix: valeriy.kovalev.software@gmail.com
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: valeriy.kovalev.software@gmail.com
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: valeriy.kovalev.software@gmail.com
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: valeriy.kovalev.software@gmail.com
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: valeriy.kovalev.software@gmail.com
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

Tags:
About Author: USER_4