Еще одни часы на LCD1602 с крупными цифрами

v258
Offline
Зарегистрирован: 25.05.2020

[i]"Что бы вы ни делали на Ардуино, все равно получаются или часы, или метеостанция"[/i] (с) ArduinoLab

Так вышло, что у меня совпали два события - внезапно оказалось, что в комнате нет часов, и почта доставила посылку с али, в которой среди прочего был стартовый набор Ардуино. В общем, вопрос о первом изделии был очевиден.
Собственный опыт: лет 20 назад я делал некоторые вещи на рассыпной логике, потом подзабросил. Кое-какой опыт программирования тоже имеется, правда, не на C++. Честно говоря, сишный синтаксис пока еще вызывает у меня зубную боль)))
Делать пришлось на том, что было в наличии - модуль DS1302, LCD1602 с синей подсветкой и ардуино нано - UNO из набора решил оставить для дальнейших экспериментов. Ну и три тактовых кнопки с колпачками - тоже из набора.

v258
Offline
Зарегистрирован: 25.05.2020
Собственно, вот что в результате получилось.
 
 
 
Т.к. эти часы все-таки носят временный характер (жду, когда придут DS3231 и светодиодная матрица), поэтому заморачиваться с корпусом не стал, просто использовал подоходящую картонную коробочку. Кнопки даже наружу выводить не стал.
Часы отображают часы и минуты крупным шрифтом, вместо двоеточия мигают крест на крест два плюсика, кнопки позволяют настраивать и часы, и минуты, и секунды, переключение режимов настройки - кнопка "Настройка", плюс и минус, соответственно, увеличивают или уменьшают значения либо на единицу, либо, если удерживать кнопку более секунды, в ускоренном режиме. Ни будильника, ни программной коррекции хода делать не стал.
 
Скетч можно взять здесь. Схема и библиотеки в архиве
b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

Скетч можно взять здесь. Схема и библиотеки в архиве

в этом разделе принято выкладывать код в сообщении. В крайнем случае - давать ссылку на гитхаб

Архивы на ЯД категорически не приветсвуются

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

в этом разделе принято выкладывать код в сообщении. В крайнем случае - давать ссылку на гитхаб

Архивы на ЯД категорически не приветсвуются

Как выкладывать картинки разобрался, а как прикрепить файл? 

b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

Как выкладывать картинки разобрался, а как прикрепить файл? 

а какой файл вы хотите выложить? Если скетч - то не надо файлом, вставляйте код прямо в сообщение. Только правильно вставляйте, как программный код, а не как текст

v258
Offline
Зарегистрирован: 25.05.2020

Да к скетчу заголовочный файл прилагается. Библиотеки, опять же. Проще было бы архивом выложить. Ну если нельзя, то нельзя

Скетч


//  создание пользовательских символов для LCD-дисплея:
//  http://mikeyancey.com/hamcalc/lcd_characters.php

#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <virtuabotixRTC.h>
#include "clock_lcd_1602.h"

#define BTN_SET 2  // кнопка переключения режима настроек
#define BTN_UP 3   // кнопка увеличения значения
#define BTN_DOWN 4 // кнопка уменьшения значения

#define DISPLAY_SETTING_TIMER 10000 // время отображения настроек при отсутствии активности, мс
#define BLINK_TIMER 1000            // период мигания цифр при настройке значений и двоеточия в основном режиме, мс
#define BUTTON_DOWN_TIMER 1000      // задержка удержания нажатой кнопки до включения ускоренной смены значений, мс
#define DEBOUNCE_TIMER 50           // задержка на подавление дребезга контактов кнопок, мс

#define SHOW_TIME 0   // режим отображения времени, основной режим
#define SET_HOUR 1    // режим настройки часов
#define SET_MINUTE 2  // режим настройки минут
#define SHOW_SECOND 3 // режим отображения секунд
#define SET_SECOND 4  // режим настройки секунд

#define SHOW_HOUR 0   // показать часы
#define SHOW_MINUTE 1 // показать минуты
#define HIDE_HOUR 2   // скрыть часы
#define HIDE_MINUTE 3 // скрыть минуты

virtuabotixRTC myRTC(6, 7, 8);      // подключение часового модуля - CLK, DAT, RST
LiquidCrystal_I2C lcd(0x27, 16, 2); // установка LCD с адресом 0x27 для отображения 16 символов в двух строках

unsigned long blinkTimer = 0;          // таймер для работы с мигающими символами
unsigned long displaySettingTimer = 0; // таймер выхода из настроек при отсутствии активности
unsigned long buttonDownTimer;         // таймер удержания кнопки нажатой

byte displayMode = 0;           // текущий режим отображения
bool b_set, b_up, b_down = LOW; // состояние кнопок
bool time_checked = false;      // флаг изменения времени в настройках

// массивы для отрисовки сегментов цифр
byte LT[8] =
    {
        B00111,
        B01111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte UB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000};

byte RT[8] =
    {
        B11100,
        B11110,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte LL[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B01111,
        B00111};

byte LB[8] =
    {
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

byte LR[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11110,
        B11100};

byte MB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111};

byte BM[8] =
    {
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

bool checkblinkTimer()
{
  bool result = true;
  int cur = millis() - blinkTimer;
  if (cur > BLINK_TIMER / 2)
  {
    if (cur <= BLINK_TIMER)
    {
      result = false;
    }
    else
    {
      setTimerData(blinkTimer);
    }
  }
  return (result);
}

void setTimerData(unsigned long &val)
{
  val = millis();
}

bool checkTimeDisplay()
{
  return (millis() - displaySettingTimer < DISPLAY_SETTING_TIMER);
}

bool checkButtonDownTimer()
{
  return (millis() - buttonDownTimer > BUTTON_DOWN_TIMER);
}

bool checkButtonState(byte pin)
{
  bool cur = false;
  switch (pin)
  {
  case BTN_SET:
    cur = !digitalRead(BTN_SET);
    if (cur != b_set)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_SET);
    }
    break;
  case BTN_UP:
    cur = !digitalRead(BTN_UP);
    if (cur != b_up)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_UP);
    }
    break;
  case BTN_DOWN:
    cur = !digitalRead(BTN_DOWN);
    if (cur != b_down)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_DOWN);
    }
    break;
  }
  return (cur);
}

void custom_(byte x)
{ // пустая цифра - нужно для блинка
  lcd.setCursor(x, 0);
  lcd.print("   ");
  lcd.setCursor(x, 1);
  lcd.print("   ");
}

void custom0(byte x)
{ // Вывод цифры 0 в позиции х с использованием ранее созданных сегментов
  lcd.setCursor(x, 0);
  lcd.write(0);
  lcd.write(1); //
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}
void custom1(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(2);
  lcd.print(" ");
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(255);
  lcd.write(4);
}

void custom2(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(0);
  lcd.write(7);
  lcd.write(7);
}

void custom3(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom4(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(255);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(255);
}

void custom5(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(3);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom6(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom7(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(5);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.write(0);
  lcd.print(" ");
}

void custom8(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom9(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(0);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(7);
  lcd.write(7);
  lcd.write(5);
}

// Здесь сохранение измененного времени при выходе из режима настройки
void saveTimeToRTC(byte Hour, byte Minute, byte Second)
{
  myRTC.updateTime();
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  myRTC.setDS1302Time(Second, Minute, Hour, myRTC.dayofweek, myRTC.dayofmonth, myRTC.month, myRTC.year);
  time_checked = false;
}

void setHourMode()
{
  myRTC.updateTime();
  byte curHour = myRTC.hours;
  byte curMinute = myRTC.minutes;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  dpDisplay(false, false);

  for (;;)
  {
    // здесь отработка нажатия кнопок
    if (checkButtonState(BTN_SET))
    {
      if (!b_set)
      {
        b_set = HIGH;
        if (time_checked)
        {
          myRTC.updateTime();
          // сохранение значений, но только при условии, что они были изменены
          if ((myRTC.hours != curHour) || (myRTC.minutes != curMinute))
          {
            saveTimeToRTC(curHour, curMinute, myRTC.seconds);
          }
        }
        displayMode++;
        if (displayMode > SET_MINUTE)
        {
          break;
        }
        else
        {
          setTimerData(displaySettingTimer);
        }
      }
    }
    else
    {
      b_set = LOW;
    }
    if (checkButtonState(BTN_UP))
    {
      time_checked = true;
      setTimerData(displaySettingTimer);
      if (!b_up)
      {
        b_up = HIGH;
        setTimerData(buttonDownTimer);
        switch (displayMode)
        {
        case SET_HOUR:
          checkNumData(curHour, true, true);
          break;
        case SET_MINUTE:
          checkNumData(curMinute, true, false);
          break;
        }
      }
      else
      {
        // если кнопка удерживается нажатой, то менять значения только после истечения времени задержки
        if (checkButtonDownTimer())
        {
          switch (displayMode)
          {
          case SET_HOUR:
            checkNumData(curHour, true, true);
            break;
          case SET_MINUTE:
            checkNumData(curMinute, true, false);
            break;
          }
        }
      }
    }
    else
    {
      b_up = LOW;
    }
    if (checkButtonState(BTN_DOWN))
    {
      time_checked = true;
      setTimerData(displaySettingTimer);
      if (!b_down)
      {
        b_down = HIGH;
        setTimerData(buttonDownTimer);
        switch (displayMode)
        {
        case SET_HOUR:
          checkNumData(curHour, false, true);
          break;
        case SET_MINUTE:
          checkNumData(curMinute, false, false);
          break;
        }
      }
      else
      {
        // если кнопка удерживается нажатой, то менять значения только после истечения времени задержки
        if (checkButtonDownTimer())
        {
          switch (displayMode)
          {
          case SET_HOUR:
            checkNumData(curHour, false, true);
            break;
          case SET_MINUTE:
            checkNumData(curMinute, false, false);
            break;
          }
        }
      }
    }
    else
    {
      b_down = LOW;
    }

    switch (displayMode)
    {
    case SET_HOUR:
      // мигание только при условии не удерживаемых нажатыми кнопках "+" или "-"
      if (b_up || b_down || checkblinkTimer())
      {
        digitalClockDisplay(curHour, SHOW_HOUR);
      }
      else
      {
        digitalClockDisplay(curHour, HIDE_HOUR);
      }
      digitalClockDisplay(curMinute, SHOW_MINUTE);
      break;
    case SET_MINUTE:
      if (b_up || b_down || checkblinkTimer())
      {
        digitalClockDisplay(curMinute, SHOW_MINUTE);
      }
      else
      {
        digitalClockDisplay(curMinute, HIDE_MINUTE);
      }
      digitalClockDisplay(curHour, SHOW_HOUR);
      break;
    }

    if (!checkTimeDisplay())
    {
      if (time_checked)
      {
        myRTC.updateTime();
        // сохранение значений, но только при условии, что они были изменены
        if ((myRTC.hours != curHour) || (myRTC.minutes != curMinute))
        {
          saveTimeToRTC(curHour, curMinute, myRTC.seconds);
        }
      }
      displayMode = SHOW_TIME;
      break;
    }
    // задержка нужна для уменьшения скорости смены значений при удержании кнопок "+" и "-"
    delay(100);
  }
}

void setSecondMode()
{
  myRTC.updateTime();
  byte curSecond = myRTC.seconds;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  digitalClockDisplay(0, HIDE_HOUR);
  dpDisplay(false, false);

  for (;;)
  {
    // здесь отработка нажатия кнопок
    if (checkButtonState(BTN_SET))
    {
      if (!b_set)
      {
        b_set = HIGH;
        if (displayMode == SET_SECOND)
        {
          if (time_checked)
          {
            // сохранение значений только в случае, если они менялись
            myRTC.updateTime();
            if (myRTC.seconds != curSecond)
            {
              saveTimeToRTC(myRTC.hours, myRTC.minutes, curSecond);
            }
          }
        }
        displayMode = SHOW_TIME;
        break;
      }
    }
    else
    {
      b_set = LOW;
    }
    if (checkButtonState(BTN_UP))
    {
      time_checked = true;
      setTimerData(displaySettingTimer);
      displayMode = SET_SECOND;
      if (!b_up)
      {
        b_up = HIGH;
        setTimerData(buttonDownTimer);
        checkNumData(curSecond, true, false);
      }
      else
      {
        // если кнопка удерживается нажатой, то менять значения только после истечения времени задержки
        if (checkButtonDownTimer())
        {
          checkNumData(curSecond, true, false);
        }
      }
    }
    else
    {
      b_up = LOW;
    }
    if (checkButtonState(BTN_DOWN))
    {
      time_checked = true;
      setTimerData(displaySettingTimer);
      displayMode = SET_SECOND;
      if (!b_down)
      {
        setTimerData(buttonDownTimer);
        b_down = true;
        checkNumData(curSecond, false, false);
      }
      else
      {
        // если кнопка удерживается нажатой, то менять значения только после истечения времени задержки
        if (checkButtonDownTimer())
        {
          checkNumData(curSecond, false, false);
        }
      }
    }
    else
    {
      b_down = LOW;
    }

    switch (displayMode)
    {
    case SHOW_SECOND: // пока не начали настраивать, секунды не мигают и отображается их нормальный ход
      myRTC.updateTime();
      curSecond = myRTC.seconds;
      digitalClockDisplay(curSecond, 1);
      break;
    case SET_SECOND: // как только начали настраивать, секунды начинают мигать и изменяются только по кнопкам
      // мигание только при условии не удерживаемых нажатыми кнопках "+" или "-"
      if (b_up || b_down || checkblinkTimer())
      {
        digitalClockDisplay(curSecond, SHOW_MINUTE);
      }
      else
      {
        digitalClockDisplay(curSecond, HIDE_MINUTE);
      };
      break;
    }

    if (!checkTimeDisplay())
    {
      displayMode = SHOW_TIME;
      time_checked = false;
      break; // при автовыходе ничего не менять
    }
    // задержка нужна для уменьшения скорости смены значений при удержании кнопок "+" и "-"
    delay(100);
  }
}

void digitalClockDisplay(byte number, byte mode)
{
  switch (mode)
  {
  case SHOW_HOUR: // отображение часов
    printDigits(number / 10, 0);
    printDigits(number % 10, 4);
    break;
  case SHOW_MINUTE: // отображение минут
    printDigits(number / 10, 9);
    printDigits(number % 10, 13);
    break;
  case HIDE_HOUR: // убрать часы
    printDigits(10, 0);
    printDigits(10, 4);
    break;
  case HIDE_MINUTE: // убрать минуты
    printDigits(10, 9);
    printDigits(10, 13);
    break;
  }
}

void dpDisplay(boolean disp, boolean dp_blink)
{
  if (disp)
  {
    if (dp_blink)
    {
      lcd.setCursor(7, 0);
      lcd.print(" +");
      lcd.setCursor(7, 1);
      lcd.print("+ ");
    }
    else
    {
      lcd.setCursor(7, 0);
      lcd.print("+ ");
      lcd.setCursor(7, 1);
      lcd.print(" +");
    }
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print("  ");
    lcd.setCursor(7, 1);
    lcd.print("  ");
  }
}

void printDigits(byte digits, byte x)
{
  switch (digits)
  {
  case 0:
    custom0(x);
    break;
  case 1:
    custom1(x);
    break;
  case 2:
    custom2(x);
    break;
  case 3:
    custom3(x);
    break;
  case 4:
    custom4(x);
    break;
  case 5:
    custom5(x);
    break;
  case 6:
    custom6(x);
    break;
  case 7:
    custom7(x);
    break;
  case 8:
    custom8(x);
    break;
  case 9:
    custom9(x);
    break;
  default:
    custom_(x);
    break;
  }
}

void checkNumData(byte &val, bool increment, bool toHour)
{
  if (increment)
  {
    val++;
    if (toHour)
    {
      if (val > 23)
      {
        val = 0;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 0;
      }
    }
  }
  else
  {
    val--;
    if (toHour)
    {
      if (val > 23)
      {
        val = 23;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 59;
      }
    }
  }
}

void setup()
{
  lcd.init();
  lcd.backlight();

  lcd.createChar(0, LT);
  lcd.createChar(1, UB);
  lcd.createChar(2, RT);
  lcd.createChar(3, LL);
  lcd.createChar(4, LB);
  lcd.createChar(5, LR);
  lcd.createChar(6, MB);
  lcd.createChar(7, BM);

  pinMode(BTN_SET, INPUT_PULLUP);
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);

  // Задать данные в часовой модуль в формате:
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  //  myRTC.setDS1302Time(30, 37, 15, 05, 07, 05, 2020);
}

void loop()
{
  // здесь обработка нажатия кнопок, реакция только на кнопку Set, только на смену состояния с LOW на HIGH
  if (checkButtonState(BTN_SET))
  {
    if (!b_set)
    {
      displayMode++;
      b_set = HIGH;
    }
  }
  else
  {
    b_set = LOW;
  }

  // отображение режимов настройки часов, минут, секунд в зависимости от текущего режима отображения
  switch (displayMode)
  {
  case SET_HOUR:
  case SET_MINUTE:
    setHourMode();
    break;
  case SHOW_SECOND:
  case SET_SECOND:
    setSecondMode();
    break;
  }

  // отображение времени в обычном режиме
  myRTC.updateTime();
  digitalClockDisplay(myRTC.hours, SHOW_HOUR);
  digitalClockDisplay(myRTC.minutes, SHOW_MINUTE);
  dpDisplay(true, checkblinkTimer());
}

файл clock_lcd_1602.h

// проверка на отображение символа в режиме блинка - первую половину периода 
// отображается,вторую - нет
bool checkBlinkCount();

// проверка времени отображения настроек при отсутствии активности; 
// как только вернет false, настройки закрываются
bool checkTimeDisplay();

// проверка задержки удержания кнопки перед включением режима ускоренного изменения данных
bool checkButtonDownTimer();

// установка таймеров
void setTimerData(unsigned long& val);

// проверка состояния кнопок с учетом подавления дребезга контактов
bool checkButtonState(byte pin);

// отрисовка цифр в указанной позиции; первая функция - стирание цифры
void custom_(byte x);
void custom0(byte x);
void custom1(byte x);
void custom2(byte x);
void custom3(byte x);
void custom4(byte x);
void custom5(byte x);
void custom6(byte x);
void custom7(byte x);
void custom8(byte x);
void custom9(byte x);

// сохранение измененного времени при выходе из режима настроек
void saveTimeToRTC(byte Hour, byte Minute, byte Second);

// отображение режима настройки часов и минут
void setHourMode();

// отображение режима настройки секунд
void setSecondMode();

// отображение моргающего двоеточия
void dpDisplay(boolean disp, boolean dp_blink);

// вывод числа
void digitalClockDisplay(byte number, byte mode);

// вывод цифры
void printDigits(byte digits, byte x);

// изменение значения в настройках
void checkNumData(byte& val, bool increment, bool toHour);

Схема

b707
Offline
Зарегистрирован: 26.05.2017

замечания по коду нужны или ну его?

v258
Offline
Зарегистрирован: 25.05.2020

Если есть - не помешают )))

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

А где clock_lcd_1602.cpp?

v258
Offline
Зарегистрирован: 25.05.2020

mykaida пишет:

А где clock_lcd_1602.cpp?


А кто его просит?

b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

b707 пишет:

замечания по коду нужны или ну его?

Если есть - не помешают )))

Главное замечание - очень высокая степень дублирования кода. Реально программа могла бы быть раза в три короче. Самый яркий пример - это 10 почти одинаовых процедур custom0() - custom9() Код во всех один и тот же, разница только в шести выводимых символах. Если вынести эти символы в заголовк как параметры - можно вместо десяти процедур обойтись одной.

Ну и остальные процедуры не лучше, только там это не так видно. Две длиннющие процедуры работы с кнопками SetHourMode() SetSecondMode() отличаются буквально в мелочах, думаю их можно было бы обьединить. Процедура CheckNumData сотоит из двух одинаковых частей if (increment) и else. хотя можно обойтись одним.  Подумайте, ведь, например, часы в любом случае не могут быть меньше 0 и больше 23х - так зачем нам знать, в какую сторону мы их меняем - проверяйте разом на оба условия да и все. Кстати, проверьте, в этой процедуре в строчках 730 и 737, похоже, ошибки - тоже результат дублирования кода

Вообще, по коду хорошо прослеживается. что главный принцип программирования для вас - копирование. Что я имею в виду? - Вы пишете процедуру, например, для выдачи на печать числа 1 - а потом для всех остальных чисел размножаете ее копированием.  Уходите от этой практики. Во-первых, это неэффективно - получается очень раздутый код. А главное - при таком методе очень легко насажать ошибок и очень трудно их искать, потому как переменные рассыпаны по куче одинаковых процедур. Вы нашли ошибку в одном месте. исправили - а в других забыли.

v258
Offline
Зарегистрирован: 25.05.2020
b707 пишет:
Главное замечание - очень высокая степень дублирования кода. Реально программа могла бы быть раза в три короче. Самый яркий пример - это 10 почти одинаовых процедур custom0() - custom9() Код во всех один и тот же, разница только в шести выводимых символах. Если вынести эти символы в заголовк как параметры - можно вместо десяти процедур обойтись одной.
Нет, не получится. Одинаковые они только с виду - в каждой пропечатываются шесть пользовательских символов. Но в каждой - разный набор символов. Т.е. объединить в одну процедуру можно, но это будет одна очень длинная процедура. Не знаю, может это что-то съэкономит в памяти МК, но читабельность точно ухудшится.
b707 пишет:
Ну и остальные процедуры не лучше, только там это не так видно. Две длиннющие процедуры работы с кнопками SetHourMode() SetSecondMode() отличаются буквально в мелочах, думаю их можно было бы обьединить.
Опять же - нет. В первой процедуре работа идет с двумя режимами (отображаются две пары цифр), во второй - с одним (отображается одна пара цифр). Изначально они были значительно длинее)), было вынесено в отдельные процедуры прилично повторяющегося кода, хотя, наверное, можно было углубиться сильнее. Т.с. нет предела совершенству ))
b707 пишет:
Процедура CheckNumData сотоит из двух одинаковых частей if (increment) и else. хотя можно обойтись одним.  Подумайте, ведь, например, часы в любом случае не могут быть меньше 0 и больше 23х - так зачем нам знать, в какую сторону мы их меняем - проверяйте разом на оба условия да и все. Кстати, проверьте, в этой процедуре в строчках 730 и 737, похоже, ошибки - тоже результат дублирования кода
Тут идет закольцованный перебор значений, но в разные стороны. Соответственно, при выходе за пределы диапазона значения должны присваиваться разные. Если увеличиваем, то обнулять (22-23-0-1-2...), иначе - присваивать максимум (2-1-0-23-22...). Хошьнехошь, а проверять на инкремент нужно)) Соответственно, в строках 730 и 737 все правильно.
b707 пишет:
Вообще, по коду хорошо прослеживается. что главный принцип программирования для вас - копирование. Что я имею в виду? - Вы пишете процедуру, например, для выдачи на печать числа 1 - а потом для всех остальных чисел размножаете ее копированием.  Уходите от этой практики. Во-первых, это неэффективно - получается очень раздутый код. А главное - при таком методе очень легко насажать ошибок и очень трудно их искать, потому как переменные рассыпаны по куче одинаковых процедур. Вы нашли ошибку в одном месте. исправили - а в других забыли.
Ну, это вы меня совсем уж опустили ))) Это в МК я новичок, а кодерством, хоть и не профессионально и не в С++, но уже лет 15 балуюсь )) Впрочем, к обоснованной критике отношусь крайне положительно.
 
ЗЫ: часть кода, отвечающего за отображение цифири, я использовал готового (уже не вспомню, где брал, это были не часы, а какой-то демопример крупного шрифта на двухстрочном экранчике), но прилично переработал его. Шрифты оттуда тоже слегка переделал. Функции custom переделывать не стал в виду бесперспективности. Ну а уже работу с настройками писал сам полностью.
b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:
Нет, не получится. Одинаковые они только с виду - в каждой пропечатываются шесть пользовательских символов. Но в каждой - разный набор символов. Т.е. объединить в одну процедуру можно, но это будет одна очень длинная процедура.

по мне так процедура не увеличится. То что в каждой - свой набор - я увидел. Ну так что ж - просто передавайте эти символы как параметры.

Сейчас у вас так:

void custom2(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(0);
  lcd.write(7);
  lcd.write(7);
}

void custom3(byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(1);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}
.................. // и так далее

записываем так:

void custom_any(byte x, byte a, byte b, byte c, byte d, byte e, byte f)
{
  lcd.setCursor(x, 0);
  lcd.write(a);
  lcd.write(b);
  lcd.print(c);
  lcd.setCursor(x, 1);
  lcd.write(d);
  lcd.write(e);
  lcd.write(f);
}

в итоге вместо вызова процедур custom2() и custom3() вызываем одну и ту же процедуру

custom_any(x, 1,1,2,0,7,7);
custom_any(x, 1,6,2,4,4,5);

 

С остальными дублями можно поступить точно так же.

v258
Offline
Зарегистрирован: 25.05.2020

Тут вопрос спорный. Кому-то может быть проще при вызове процедуры держать в голове, какой набор символов дать в параметры, чтобы получить нужную цифру, а кому-то проще просто вызвать нужную процедуру, чтобы получить ту же цифру. Имхо, первый вариант может быть оправдан только при условии жесткой нехватки памяти ))

Наверное, было бы лучше сделать массивы для наборов нужных символов для каждой цифры и передавать в процедуру нужный массив

b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

Тут идет закольцованный перебор значений, но в разные стороны. Соответственно, при выходе за пределы диапазона значения должны присваиваться разные. Если увеличиваем, то обнулять (22-23-0-1-2...), иначе - присваивать максимум (2-1-0-23-22...). Хошьнехошь, а проверять на инкремент нужно)) Соответственно, в строках 730 и 737 все правильно.

правильно? Посмотрите внимательно свой код:

else
  {
    val--;
    if (toHour)
    {
      if (val > 23)
      {
        val = 23;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 59;
      }
    }
  }

у вас идет декремент (val--) - , значит значения движутся вниз - как вы сами пишете - (2-1-0-23-22...) - а значит прверять надо на нижнюю границу. А вы что проверяете?

 

b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

Наверное, было бы лучше сделать массивы для наборов нужных символов для каждой цифры и передавать в процедуру нужный массив

несомненно , это лучший вариант :) Но я не хотел вас грузить этим сразу :)

Но тот вариант, что использован сейчас в коде - имхо, худший из трех

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

правильно? 

Угу, работает же правильно)) Это byte, т.е. при val == 0 декремент присвоит не -1, а 255. Т.е. таки больше 23 или 59 ))

 

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

v258 пишет:

Наверное, было бы лучше сделать массивы для наборов нужных символов для каждой цифры и передавать в процедуру нужный массив

несомненно , это лучший вариант :) Но я не хотел вас грузить этим сразу :)

Но тот вариант, что использован сейчас в коде - имхо, худший из трех

Одна голова - хорошо, две - завсегда лучше ))) Будет время, переделаю

b707
Offline
Зарегистрирован: 26.05.2017

v258 пишет:

Угу, работает же правильно)) Это byte, т.е. при val == 0 декремент присвоит не -1, а 255. Т.е. таки больше 23 или 59 ))

 

ну ок, тут подловили :)

 

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

v258 пишет:

Угу, работает же правильно)) Это byte, т.е. при val == 0 декремент присвоит не -1, а 255. Т.е. таки больше 23 или 59 ))

 

ну ок, тут подловили :)

В повседневном кодерстве с этим не сталкивался, там экономить память на типах данных не приходится. Но тут этот момент я, как говорится, прочувствовал на своей шкуре )) Сначала тоже удивился, почему у меня на нуле все останавливалось.

А по первой вообще как-то инициализировал переменную uint8_t минус единицей - и скомпилировалось же, только МК завис на старте. Потом полез в доки и увидел, что uint8_t это byte ))

lean_74
Offline
Зарегистрирован: 22.12.2015

v258 пишет:

Одна голова - хорошо, две - 

Мутант :)

v258
Offline
Зарегистрирован: 25.05.2020

Переписал скетч с учетом замечаний

//  создание пользовательских символов для LCD-дисплея:
//  http://mikeyancey.com/hamcalc/lcd_characters.php

#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <virtuabotixRTC.h>
#include "clock_lcd_1602.h"

#define BTN_SET 2  // кнопка переключения режима настроек
#define BTN_UP 3   // кнопка увеличения значения
#define BTN_DOWN 4 // кнопка уменьшения значения

#define DISPLAY_SETTING_TIMER 10000 // время отображения настроек при отсутствии активности, мс
#define BLINK_TIMER 1000            // период мигания цифр при настройке значений и двоеточия в основном режиме, мс
#define BUTTON_DOWN_TIMER 1000      // задержка удержания нажатой кнопки до включения ускоренной смены значений, мс
#define DEBOUNCE_TIMER 50           // задержка на подавление дребезга контактов кнопок, мс

#define SHOW_TIME 0   // режим отображения времени, основной режим
#define SET_HOUR 1    // режим настройки часов
#define SET_MINUTE 2  // режим настройки минут
#define SHOW_SECOND 3 // режим отображения секунд
#define SET_SECOND 4  // режим настройки секунд

#define SHOW_HOUR 0   // показать часы
#define SHOW_MINUTE 1 // показать минуты
#define HIDE_HOUR 2   // скрыть часы
#define HIDE_MINUTE 3 // скрыть минуты

virtuabotixRTC myRTC(6, 7, 8);      // подключение часового модуля - CLK, DAT, RST
LiquidCrystal_I2C lcd(0x27, 16, 2); // установка LCD с адресом 0x27 для отображения 16 символов в двух строках

unsigned long blinkTimer = 0;          // таймер для работы с мигающими символами
unsigned long displaySettingTimer = 0; // таймер выхода из настроек при отсутствии активности
unsigned long buttonDownTimer;         // таймер удержания кнопки нажатой

byte displayMode = 0;           // текущий режим отображения
bool b_set, b_up, b_down = LOW; // состояние кнопок
bool time_checked = false;      // флаг изменения времени в настройках

// массивы для отрисовки сегментов цифр
byte LT[8] =
    {
        B00111,
        B01111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte UB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000};

byte RT[8] =
    {
        B11100,
        B11110,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte LL[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B01111,
        B00111};

byte LB[8] =
    {
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

byte LR[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11110,
        B11100};

byte MB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111};

byte BM[8] =
    {
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

// массивы сегментов для отрисовки цифр
byte num_[6]{32, 32, 32, 32, 32, 32};
byte num0[6]{0, 1, 2, 3, 4, 5};
byte num1[6]{1, 2, 32, 4, 255, 4};
byte num2[6]{1, 1, 2, 0, 7, 7};
byte num3[6]{1, 6, 2, 4, 4, 5};
byte num4[6]{3, 4, 255, 32, 32, 255};
byte num5[6]{3, 6, 6, 4, 4, 5};
byte num6[6]{0, 6, 6, 3, 4, 5};
byte num7[6]{1, 1, 5, 32, 0, 32};
byte num8[6]{0, 6, 2, 3, 4, 5};
byte num9[6]{0, 1, 2, 7, 7, 5};

void customDraw(byte *arr, byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(arr[0]);
  lcd.write(arr[1]);
  lcd.write(arr[2]);
  lcd.setCursor(x, 1);
  lcd.write(arr[3]);
  lcd.write(arr[4]);
  lcd.write(arr[5]);
}

bool checkblinkTimer()
{
  bool result = true;
  int cur = millis() - blinkTimer;
  if (cur > BLINK_TIMER / 2)
  {
    if (cur <= BLINK_TIMER)
    {
      result = false;
    }
    else
    {
      setTimerData(blinkTimer);
    }
  }
  return (result);
}

void setTimerData(unsigned long &val)
{
  val = millis();
}

bool checkTimeDisplay()
{
  return (millis() - displaySettingTimer < DISPLAY_SETTING_TIMER);
}

bool checkButtonDownTimer()
{
  return (millis() - buttonDownTimer > BUTTON_DOWN_TIMER);
}

bool checkButtonState(byte pin)
{
  bool cur = false;
  switch (pin)
  {
  case BTN_SET:
    cur = !digitalRead(BTN_SET);
    if (cur != b_set)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_SET);
    }
    break;
  case BTN_UP:
    cur = !digitalRead(BTN_UP);
    if (cur != b_up)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_UP);
    }
    break;
  case BTN_DOWN:
    cur = !digitalRead(BTN_DOWN);
    if (cur != b_down)
    {
      delay(DEBOUNCE_TIMER);
      cur = !digitalRead(BTN_DOWN);
    }
    break;
  }
  return (cur);
}

void checkBtnUpDown(byte pin, bool &b_st, byte &val)
{
  if (checkButtonState(pin))
  {
    time_checked = true;
    setTimerData(displaySettingTimer);
    if (displayMode == SHOW_SECOND)
    {
      displayMode = SET_SECOND;
    }
    // значение увеличивать только если кнопка была только что нажата или удерживается свыше интервала задержки
    if (!b_st || (b_st && checkButtonDownTimer()))
    {
      if (!b_st)
      {
        setTimerData(buttonDownTimer);
        b_st = HIGH;
      }
      checkNumData(val, pin == BTN_UP, displayMode == SET_HOUR);
    }
  }
  else
  {
    b_st = LOW;
  }
}

void checkBtnSet(byte h, byte m, byte s)
{
  if (checkButtonState(BTN_SET))
  {
    if (!b_set)
    {
      b_set = HIGH;
      if (displayMode == SHOW_SECOND)
      {
        displayMode = SHOW_TIME;
      }
      else
      {
        // setTimerData(buttonDownTimer);
        setTimerData(displaySettingTimer);
        if (time_checked)
        {
          myRTC.updateTime();
          switch (displayMode)
          {
          case SET_SECOND:
            if (myRTC.seconds != s)
            {
              saveTimeToRTC(myRTC.hours, myRTC.minutes, s);
            }
            displayMode = SHOW_TIME;
            break;
          case SET_HOUR:
          case SET_MINUTE:
            if ((myRTC.hours != h) || (myRTC.minutes != m))
            {
              saveTimeToRTC(h, m, myRTC.seconds);
            }
            break;
          }
        }
        if (displayMode > SHOW_TIME && displayMode <= SET_MINUTE)
        {
          displayMode++;
        }
      }
    }
  }
  else
  {
    b_set = LOW;
  }
}

// Здесь сохранение измененного времени при выходе из режима настройки
void saveTimeToRTC(byte Hour, byte Minute, byte Second)
{
  myRTC.updateTime();
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  myRTC.setDS1302Time(Second, Minute, Hour, myRTC.dayofweek, myRTC.dayofmonth, myRTC.month, myRTC.year);
  time_checked = false;
}

void setHourMode()
{
  byte curHour = 0;
  byte curMinute = 0;
  time_checked = false;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  dpDisplay(false, false);

  for (;;)
  {
    if (!time_checked)
    {
      myRTC.updateTime();
      curHour = myRTC.hours;
      curMinute = myRTC.minutes;
    }
    // здесь отработка нажатия кнопок
    checkBtnSet(curHour, curMinute, 100);
    if (displayMode > SET_MINUTE)
    {
      break;
    }

    switch (displayMode)
    {
    case SET_HOUR:
      checkBtnUpDown(BTN_UP, b_up, curHour);
      checkBtnUpDown(BTN_DOWN, b_down, curHour);
      break;
    case SET_MINUTE:
      checkBtnUpDown(BTN_UP, b_up, curMinute);
      checkBtnUpDown(BTN_DOWN, b_down, curMinute);
      break;
    }

    // отображение данных на экране
    showClockData(curHour, curMinute);

    // автовыход из настроек по таймауту
    if (!checkTimeDisplay())
    {
      if (time_checked)
      {
        myRTC.updateTime();
        // сохранение значений, но только при условии, что они были изменены
        if ((myRTC.hours != curHour) || (myRTC.minutes != curMinute))
        {
          saveTimeToRTC(curHour, curMinute, myRTC.seconds);
        }
      }
      displayMode = SHOW_TIME;
      break;
    }
    // задержка нужна для уменьшения скорости смены значений при удержании кнопок "+" и "-"
    delay(100);
  }
}

void setSecondMode()
{
  byte curSecond = 0;
  time_checked = false;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  digitalClockDisplay(0, HIDE_HOUR);
  dpDisplay(false, false);

  for (;;)
  {
    if (displayMode == SHOW_SECOND)
    {
      myRTC.updateTime();
      curSecond = myRTC.seconds;
    }
    // здесь отработка нажатия кнопок
    checkBtnSet(100, 100, curSecond); // 100 - значения от балды, т.к. в данном случае они не обрабатываются
    if (displayMode == SHOW_TIME)
    {
      break;
    }
    checkBtnUpDown(BTN_UP, b_up, curSecond);
    checkBtnUpDown(BTN_DOWN, b_down, curSecond);

    // отображение данных на экране
    showClockData(100, curSecond);

    // автовыход из настроек по таймауту
    if (!checkTimeDisplay())
    {
      displayMode = SHOW_TIME;
      time_checked = false;
      break; // при автовыходе ничего не менять
    }
    // задержка нужна для уменьшения скорости смены значений при удержании кнопок "+" и "-"
    delay(100);
  }
}

void showClockData(byte h, byte m)
{
  switch (displayMode)
  {
  case SHOW_TIME:
    digitalClockDisplay(h, SHOW_HOUR);
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_HOUR:
    blinkNumber(h);
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_MINUTE:
    digitalClockDisplay(h, SHOW_HOUR);
    blinkNumber(m);
    break;
  case SHOW_SECOND:
    // пока секунды не начали настраивать, они не мигают, и отображается их нормальный ход
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_SECOND:
    // если начали настраивать, начинают мигать и изменяются только по кнопкам
    blinkNumber(m);
    break;
  }
}

void blinkNumber(byte &number)
{
  byte flag = 1;
  if (displayMode == SET_HOUR)
  {
    flag = 0;
  }
  // мигание только при условии не удерживаемых нажатыми кнопках "+" или "-"
  if (b_up || b_down || checkblinkTimer())
  {
    digitalClockDisplay(number, flag);
  }
  else
  {
    digitalClockDisplay(number, flag + 2);
  }
}

void digitalClockDisplay(byte number, byte mode)
{
  switch (mode)
  {
  case SHOW_HOUR: // отображение часов
    printDigits(number / 10, 0);
    printDigits(number % 10, 4);
    break;
  case SHOW_MINUTE: // отображение минут
    printDigits(number / 10, 9);
    printDigits(number % 10, 13);
    break;
  case HIDE_HOUR: // убрать часы
    printDigits(10, 0);
    printDigits(10, 4);
    break;
  case HIDE_MINUTE: // убрать минуты
    printDigits(10, 9);
    printDigits(10, 13);
    break;
  }
}

void dpDisplay(boolean disp, boolean dp_blink)
{
  if (disp)
  {
    if (dp_blink)
    {
      lcd.setCursor(7, 0);
      lcd.print(" +");
      lcd.setCursor(7, 1);
      lcd.print("+ ");
    }
    else
    {
      lcd.setCursor(7, 0);
      lcd.print("+ ");
      lcd.setCursor(7, 1);
      lcd.print(" +");
    }
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print("  ");
    lcd.setCursor(7, 1);
    lcd.print("  ");
  }
}

void printDigits(byte digits, byte x)
{
  switch (digits)
  {
  case 0:
    customDraw(num0, x);
    break;
  case 1:
    customDraw(num1, x);
    break;
  case 2:
    customDraw(num2, x);
    break;
  case 3:
    customDraw(num3, x);
    break;
  case 4:
    customDraw(num4, x);
    break;
  case 5:
    customDraw(num5, x);
    break;
  case 6:
    customDraw(num6, x);
    break;
  case 7:
    customDraw(num7, x);
    break;
  case 8:
    customDraw(num8, x);
    break;
  case 9:
    customDraw(num9, x);
    break;
  default:
    customDraw(num_, x);
    break;
  }
}

void checkNumData(byte &val, bool increment, bool toHour)
{
  if (increment)
  {
    val++;
    if (toHour)
    {
      if (val > 23)
      {
        val = 0;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 0;
      }
    }
  }
  else
  {
    val--;
    if (toHour)
    {
      if (val > 23)
      {
        val = 23;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 59;
      }
    }
  }
}

void setup()
{
  lcd.init();
  lcd.backlight();

  lcd.createChar(0, LT);
  lcd.createChar(1, UB);
  lcd.createChar(2, RT);
  lcd.createChar(3, LL);
  lcd.createChar(4, LB);
  lcd.createChar(5, LR);
  lcd.createChar(6, MB);
  lcd.createChar(7, BM);

  pinMode(BTN_SET, INPUT_PULLUP);
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);

  // Задать данные в часовой модуль в формате:
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  //  myRTC.setDS1302Time(30, 37, 15, 05, 07, 05, 2020);
}

void loop()
{
  // здесь обработка нажатия кнопок, реакция только на кнопку Set, только на смену состояния с LOW на HIGH
  if (checkButtonState(BTN_SET))
  {
    if (!b_set)
    {
      displayMode++;
      b_set = HIGH;
    }
  }
  else
  {
    b_set = LOW;
  }

  // отображение режимов настройки часов, минут, секунд в зависимости от текущего режима отображения
  switch (displayMode)
  {
  case SET_HOUR:
  case SET_MINUTE:
    setHourMode();
    break;
  case SHOW_SECOND:
  case SET_SECOND:
    setSecondMode();
    break;
  }

  // отображение времени в обычном режиме
  myRTC.updateTime();
  showClockData(myRTC.hours, myRTC.minutes); 
  dpDisplay(true, checkblinkTimer());
}

файл clock_lcd_1602.h

// проверка на отображение символа в режиме блинка - первую половину периода 
// отображается,вторую - нет
bool checkBlinkCount();

// проверка времени отображения настроек при отсутствии активности; 
// как только вернет false, настройки закрываются
bool checkTimeDisplay();

// проверка задержки удержания кнопки перед включением режима ускоренного изменения данных
bool checkButtonDownTimer();

// установка таймеров
void setTimerData(unsigned long& val);

// проверка состояния кнопок с учетом подавления дребезга контактов
bool checkButtonState(byte pin);

// отработка нажатия на кнопки +/-
void checkBtnUpDown(byte pin, bool& b_st, byte& val);

// отработка нажатия на кнопку Set
void checkBtnSet(byte h, byte m, byte s);

// отрисовка цифр в указанной позиции
void customDraw(byte* arr, byte x);

// сохранение измененного времени при выходе из режима настроек
void saveTimeToRTC(byte Hour, byte Minute, byte Second);

// отображение режима настройки часов и минут
void setHourMode();

// отображение режима настройки секунд
void setSecondMode();

// мигание часов/минут/секунд
void blinkNumber(byte& number);

// отображение моргающего двоеточия
void dpDisplay(boolean disp, boolean dp_blink);

// отображение данных на экране
void showClockData(byte h, byte m);

// вывод числа
void digitalClockDisplay(byte number, byte mode);

// вывод цифры
void printDigits(byte digits, byte x);

// изменение значения в настройках
void checkNumData(byte& val, bool increment, bool toHour);

Ссылка на архив в сообщении #1 тоже обновлена

b707
Offline
Зарегистрирован: 26.05.2017

ой :)

еще замечания нужны? :)

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

ой :)

еще замечания нужны? :)

Мне еще одни часы делать предстоит, так что принимаю все, что есть))

b707
Offline
Зарегистрирован: 26.05.2017

я кратенько, без обьяснений, а то некогда:

Вот как я бы поменял некоторые места кода.

Было:

// массивы сегментов для отрисовки цифр
byte num_[6]{32, 32, 32, 32, 32, 32};
byte num0[6]{0, 1, 2, 3, 4, 5};
byte num1[6]{1, 2, 32, 4, 255, 4};
byte num2[6]{1, 1, 2, 0, 7, 7};
byte num3[6]{1, 6, 2, 4, 4, 5};
byte num4[6]{3, 4, 255, 32, 32, 255};
byte num5[6]{3, 6, 6, 4, 4, 5};
byte num6[6]{0, 6, 6, 3, 4, 5};
byte num7[6]{1, 1, 5, 32, 0, 32};
byte num8[6]{0, 6, 2, 3, 4, 5};
byte num9[6]{0, 1, 2, 7, 7, 5};

void customDraw(byte *arr, byte x)
{
  lcd.setCursor(x, 0);
  lcd.write(arr[0]);
  lcd.write(arr[1]);
  lcd.write(arr[2]);
  lcd.setCursor(x, 1);
  lcd.write(arr[3]);
  lcd.write(arr[4]);
  lcd.write(arr[5]);
}


void printDigits(byte digits, byte x)
{
  switch (digits)
  {
  case 0:
    customDraw(num0, x);
    break;
  case 1:
    customDraw(num1, x);
    break;
  case 2:
    customDraw(num2, x);
    break;
  case 3:
    customDraw(num3, x);
    break;
  case 4:
    customDraw(num4, x);
    break;
  case 5:
    customDraw(num5, x);
    break;
  case 6:
    customDraw(num6, x);
    break;
  case 7:
    customDraw(num7, x);
    break;
  case 8:
    customDraw(num8, x);
    break;
  case 9:
    customDraw(num9, x);
    break;
  default:
    customDraw(num_, x);
    break;
  }
}

Стало:

// 11 массивов преобразуем в один двумерный, подчеркивание помещаем в конец
// смысл в том, чтобы первый индекс массива соответствовал рисуемой цифре
byte nums[][6] ={ {0, 1, 2, 3, 4, 5},   			// 0
                {1, 2, 32, 4, 255, 4},			// 1
                 	 
                  {0, 1, 2, 7, 7, 5},			// 9
                  {32, 32, 32, 32, 32, 32}		// _
};

//   
void customDraw(byte ind, byte x)
{
  if (ind > 9) ind =10;  // при любом индексе более 9 рисуем пустой символ _
  lcd.setCursor(x, 0);
  lcd.write(nums[ind][0]);
  lcd.write(nums[ind][1]);
  lcd.write(nums[ind][2]);
  lcd.setCursor(x, 1);
  lcd.write(nums[ind][3]);
  lcd.write(nums[ind][4]);
  lcd.write(nums[ind][5]);
}    

// switch / case выродился в одну строчку
void printDigits(byte digits, byte x)
{
  customDraw(digits, x);
}         	  

 

v258
Offline
Зарегистрирован: 25.05.2020

Мелькала такая мысль, поленился ))

Кстати, тогда нужда в customDraw() отпадает, код из нее переезжает в printDigits


void printDigits(byte digits, byte x)
{
  byte ind = digits;
  if (ind > 9)
  {
    ind = 10;
  }
  lcd.setCursor(x, 0);
  lcd.write(nums[ind][0]);
  lcd.write(nums[ind][1]);
  lcd.write(nums[ind][2]);
  lcd.setCursor(x, 1);
  lcd.write(nums[ind][3]);
  lcd.write(nums[ind][4]);
  lcd.write(nums[ind][5]);
}

Итого минус еще 96 байт в памяти ))

Скетч использует 5950 байт (19%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 427 байт (20%) динамической памяти, оставляя 1621 байт для локальных переменных. Максимум: 2048 байт.

 

v258
Offline
Зарегистрирован: 25.05.2020

Переписал скетч с использованием самописной библиотеки для работы с кнопками. Заодно избавился от delay.

Скетч

//  создание пользовательских символов для LCD-дисплея:
//  http://mikeyancey.com/hamcalc/lcd_characters.php

#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <virtuabotixRTC.h>
#include <shButton.h>
#include "clock_lcd_1602.h"

#define B_SET 2  // кнопка переключения режима настроек
#define B_UP 3   // кнопка увеличения значения
#define B_DOWN 4 // кнопка уменьшения значения

#define DISPLAY_SETTING_TIMER 10000 // время отображения настроек при отсутствии активности, мс
#define BLINK_TIMER 1000            // период мигания цифр при настройке значений и двоеточия в основном режиме, мс
#define AUTOADD_TIMER 200           // интервал между изменением значений при удержании кнопки нажатой, мс

#define SHOW_TIME 0   // режим отображения времени, основной режим
#define SET_HOUR 1    // режим настройки часов
#define SET_MINUTE 2  // режим настройки минут
#define SHOW_SECOND 3 // режим отображения секунд
#define SET_SECOND 4  // режим настройки секунд

#define SHOW_HOUR 0   // показать часы
#define SHOW_MINUTE 1 // показать минуты
#define HIDE_HOUR 2   // скрыть часы
#define HIDE_MINUTE 3 // скрыть минуты

virtuabotixRTC myRTC(6, 7, 8);      // подключение часового модуля - CLK, DAT, RST
LiquidCrystal_I2C lcd(0x27, 16, 2); // установка LCD с адресом 0x27 для отображения 16 символов в двух строках
shButton btnSet(B_SET);             // кнопка выбора режима настроек
shButton btnUp(B_UP);               // кнопка увеличения значений
shButton btnDown(B_DOWN);           // кнопка уменьшения значений

unsigned long blinkTimer = 0;          // таймер для работы с мигающими символами
unsigned long displaySettingTimer = 0; // таймер выхода из настроек при отсутствии активности
unsigned long autoAddTimer = 0;        // таймер задержки изменения значений при удержании кнопки

byte displayMode = 0;      // текущий режим отображения
bool time_checked = false; // флаг изменения времени в настройках

// массивы для отрисовки сегментов цифр
byte LT[8] =
    {
        B00111,
        B01111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte UB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000};

byte RT[8] =
    {
        B11100,
        B11110,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111};

byte LL[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B01111,
        B00111};

byte LB[8] =
    {
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

byte LR[8] =
    {
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11110,
        B11100};

byte MB[8] =
    {
        B11111,
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111};

byte BM[8] =
    {
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111,
        B11111};

// массивы сегментов для отрисовки цифр
byte nums[][6]{{0, 1, 2, 3, 4, 5},
               {1, 2, 32, 4, 255, 4},
               {1, 1, 2, 0, 7, 7},
               {1, 6, 2, 4, 4, 5},
               {3, 4, 255, 32, 32, 255},
               {3, 6, 6, 4, 4, 5},
               {0, 6, 6, 3, 4, 5},
               {1, 1, 5, 32, 0, 32},
               {0, 6, 2, 3, 4, 5},
               {0, 1, 2, 7, 7, 5},
               {32, 32, 32, 32, 32, 32}};

void getCurTime(tTime &time)
{
  myRTC.updateTime();
  time.hour = myRTC.hours;
  time.minute = myRTC.minutes;
  time.second = myRTC.seconds;
}

bool checkblinkTimer()
{
  bool result = true;
  int cur = millis() - blinkTimer;
  if (cur > BLINK_TIMER / 2)
  {
    if (cur <= BLINK_TIMER)
    {
      result = false;
    }
    else
    {
      setTimerData(blinkTimer);
    }
  }
  return (result);
}

void setTimerData(unsigned long &val)
{
  val = millis();
}

bool checkTimeDisplay()
{
  return (millis() - displaySettingTimer < DISPLAY_SETTING_TIMER);
}

bool checkAutoAddTimer()
{
  return (millis() - autoAddTimer >= AUTOADD_TIMER);
}

void checkBtnUpDown(shButton &btn, byte &val, bool incr)
{
  btn.getButtonState();
  // значение увеличивать только если кнопка была только что нажата или
  // удерживается свыше интервала задержки; во втором случае учитывается
  // интервал задержки изменения значений
  switch (btn.btnState)
  {
  case BTN_DOWN:
    time_checked = true;
    setTimerData(displaySettingTimer);
    if (displayMode == SHOW_SECOND)
    {
      displayMode = SET_SECOND;
    }
    checkNumData(val, incr, displayMode == SET_HOUR);
    break;
  case BTN_WITHHELD:
    setTimerData(displaySettingTimer);
    if (checkAutoAddTimer())
    {
      checkNumData(val, incr, displayMode == SET_HOUR);
      setTimerData(autoAddTimer);
    }
    break;
  }
}

void checkBtnSet(byte h, byte m, byte s)
{
  btnSet.getButtonState();
  switch (btnSet.btnState)
  {
  case BTN_DOWN:
    if (displayMode == SHOW_SECOND)
    {
      displayMode = SHOW_TIME;
    }
    else
    {
      setTimerData(displaySettingTimer);
      if (time_checked)
      {
        tTime tm;
        getCurTime(tm);
        switch (displayMode)
        {
        case SET_SECOND:
          if (tm.second != s)
          {
            saveTimeToRTC(tm.hour, tm.minute, s);
          }
          displayMode = SHOW_TIME;
          break;
        case SET_HOUR:
        case SET_MINUTE:
          if ((tm.hour != h) || (tm.minute != m))
          {
            saveTimeToRTC(h, m, tm.second);
          }
          break;
        }
      }
      if (displayMode > SHOW_TIME && displayMode <= SET_MINUTE)
      {
        displayMode++;
      }
    }
    break;
  }
}

// Здесь сохранение измененного времени при выходе из режима настройки
void saveTimeToRTC(byte Hour, byte Minute, byte Second)
{
  myRTC.updateTime();
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  myRTC.setDS1302Time(Second, Minute, Hour, myRTC.dayofweek, myRTC.dayofmonth, myRTC.month, myRTC.year);
  time_checked = false;
}

void setHourMode()
{
  byte curHour = 0;
  byte curMinute = 0;
  time_checked = false;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  dpDisplay(false, false);

  for (;;)
  {
    if (!time_checked)
    {
      tTime tm;
      getCurTime(tm);
      curHour = tm.hour;
      curMinute = tm.minute;
    }
    // здесь отработка нажатия кнопок
    checkBtnSet(curHour, curMinute, 100);
    if (displayMode > SET_MINUTE)
    {
      break;
    }

    switch (displayMode)
    {
    case SET_HOUR:
      checkBtnUpDown(btnUp, curHour, true);
      checkBtnUpDown(btnDown, curHour, false);
      break;
    case SET_MINUTE:
      checkBtnUpDown(btnUp, curMinute, true);
      checkBtnUpDown(btnDown, curMinute, false);
      break;
    }

    // отображение данных на экране
    showClockData(curHour, curMinute);

    // автовыход из настроек по таймауту
    if (!checkTimeDisplay())
    {
      if (time_checked)
      {
        tTime tm;
        getCurTime(tm);
        // сохранение значений, но только при условии, что они были изменены
        if ((tm.hour != curHour) || (tm.minute != curMinute))
        {
          saveTimeToRTC(curHour, curMinute, tm.second);
        }
      }
      displayMode = SHOW_TIME;
      break;
    }
  }
}

void setSecondMode()
{
  byte curSecond = 0;
  time_checked = false;
  setTimerData(blinkTimer);
  setTimerData(displaySettingTimer);
  digitalClockDisplay(0, HIDE_HOUR);
  dpDisplay(false, false);

  for (;;)
  {
    if (displayMode == SHOW_SECOND)
    {
      tTime tm;
      getCurTime(tm);
      curSecond = tm.second;
    }
    // здесь отработка нажатия кнопок
    checkBtnSet(100, 100, curSecond); // 100 - значения от балды, т.к. в данном случае они не обрабатываются
    if (displayMode == SHOW_TIME)
    {
      break;
    }
    checkBtnUpDown(btnUp, curSecond, true);
    checkBtnUpDown(btnDown, curSecond, false);

    // отображение данных на экране
    showClockData(100, curSecond);

    // автовыход из настроек по таймауту
    if (!checkTimeDisplay())
    {
      displayMode = SHOW_TIME;
      time_checked = false;
      break; // при автовыходе ничего не менять
    }
  }
}

void showClockData(byte h, byte m)
{
  switch (displayMode)
  {
  case SHOW_TIME:
    digitalClockDisplay(h, SHOW_HOUR);
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_HOUR:
    blinkNumber(h);
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_MINUTE:
    digitalClockDisplay(h, SHOW_HOUR);
    blinkNumber(m);
    break;
  case SHOW_SECOND:
    // пока секунды не начали настраивать, они не мигают, и отображается их нормальный ход
    digitalClockDisplay(m, SHOW_MINUTE);
    break;
  case SET_SECOND:
    // если начали настраивать, начинают мигать и изменяются только по кнопкам
    blinkNumber(m);
    break;
  }
}

void blinkNumber(byte &number)
{
  byte flag = SHOW_MINUTE;
  switch (displayMode)
  {
  case SET_HOUR:
    flag = SHOW_HOUR;
    break;
  }
  // мигание только при условии не нажатых кнопок "+" или "-"
  if ((btnUp.btnState > BTN_UP) || (btnDown.btnState > BTN_UP) || checkblinkTimer())
  {
    digitalClockDisplay(number, flag);
  }
  else
  {
    digitalClockDisplay(number, flag + 2);
  }
}

void digitalClockDisplay(byte number, byte mode)
{
  switch (mode)
  {
  case SHOW_HOUR: // отображение часов
    printDigits(number / 10, 0);
    printDigits(number % 10, 4);
    break;
  case SHOW_MINUTE: // отображение минут
    printDigits(number / 10, 9);
    printDigits(number % 10, 13);
    break;
  case HIDE_HOUR: // убрать часы
    printDigits(10, 0);
    printDigits(10, 4);
    break;
  case HIDE_MINUTE: // убрать минуты
    printDigits(10, 9);
    printDigits(10, 13);
    break;
  }
}

void dpDisplay(boolean disp, boolean dp_blink)
{
  if (disp)
  {
    if (dp_blink)
    {
      lcd.setCursor(7, 0);
      lcd.print(" +");
      lcd.setCursor(7, 1);
      lcd.print("+ ");
    }
    else
    {
      lcd.setCursor(7, 0);
      lcd.print("+ ");
      lcd.setCursor(7, 1);
      lcd.print(" +");
    }
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print("  ");
    lcd.setCursor(7, 1);
    lcd.print("  ");
  }
}

void printDigits(byte digits, byte x)
{
  if (digits > 9)
  {
    digits = 10;
  }
  lcd.setCursor(x, 0);
  lcd.write(nums[digits][0]);
  lcd.write(nums[digits][1]);
  lcd.write(nums[digits][2]);
  lcd.setCursor(x, 1);
  lcd.write(nums[digits][3]);
  lcd.write(nums[digits][4]);
  lcd.write(nums[digits][5]);
}

void checkNumData(byte &val, bool increment, bool toHour)
{
  if (increment)
  {
    val++;
    if (toHour)
    {
      if (val > 23)
      {
        val = 0;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 0;
      }
    }
  }
  else
  {
    val--;
    if (toHour)
    {
      if (val > 23)
      {
        val = 23;
      }
    }
    else
    {
      if (val > 59)
      {
        val = 59;
      }
    }
  }
}

void createSimbols()
{
  lcd.createChar(0, LT);
  lcd.createChar(1, UB);
  lcd.createChar(2, RT);
  lcd.createChar(3, LL);
  lcd.createChar(4, LB);
  lcd.createChar(5, LR);
  lcd.createChar(6, MB);
  lcd.createChar(7, BM);
}

void setup()
{
  lcd.init();
  lcd.backlight();

  createSimbols();

  // Задать данные в часовой модуль в формате:
  // секунды, минуты, часы, день недели, число месяца, месяц, год
  //  myRTC.setDS1302Time(30, 37, 15, 05, 07, 05, 2020);
}

void loop()
{
  // здесь обработка нажатия кнопок, реакция только на кнопку Set, без реакции на удержание кнопки
  btnSet.getButtonState();
  switch (btnSet.btnState)
  {
  case BTN_DOWN:
    displayMode++;
    break;
  }

  // отображение режимов настройки часов, минут, секунд в зависимости от текущего режима отображения
  switch (displayMode)
  {
  case SET_HOUR:
  case SET_MINUTE:
    setHourMode();
    break;
  case SHOW_SECOND:
  case SET_SECOND:
    setSecondMode();
    break;
  }

  // отображение времени в обычном режиме
  tTime tm;
  getCurTime(tm);
  showClockData(tm.hour, tm.minute);
  dpDisplay(true, checkblinkTimer());
}

Файл clock_lcd_1602.h

// структура для хранения времени
struct tTime
{
    byte hour;
    byte minute;
    byte second;
};

// получение текущего времени
void getCurTime(tTime &time);

// проверка на отображение символа в режиме блинка - первую половину периода
// отображается,вторую - нет
bool checkBlinkCount();

// проверка времени отображения настроек при отсутствии активности;
// как только вернет false, настройки закрываются
bool checkTimeDisplay();

// проверка задержки изменения значений при удержании кнопки
bool checkAutoAddTimer();

// установка таймеров
void setTimerData(unsigned long &val);

// отработка нажатия на кнопки +/-
void checkBtnUpDown(shButton &btn, byte &val, bool incr);

// отработка нажатия на кнопку Set
void checkBtnSet(byte h, byte m, byte s);

// сохранение измененного времени при выходе из режима настроек
void saveTimeToRTC(byte Hour, byte Minute, byte Second);

// отображение режима настройки часов и минут
void setHourMode();

// отображение режима настройки секунд
void setSecondMode();

// мигание часов/минут/секунд
void blinkNumber(byte &number);

// отображение моргающего двоеточия
void dpDisplay(boolean disp, boolean dp_blink);

// отображение данных на экране
void showClockData(byte h, byte m);

// вывод числа
void digitalClockDisplay(byte number, byte mode);

// вывод цифры
void printDigits(byte digits, byte x);

// изменение значения в настройках
void checkNumData(byte &val, bool increment, bool toHour);

// создание сегментов цифр
void createSimbols();

Ссылка на архив в сообщении #1 тоже обновлена