LED часы Arduino Nano 3.0, Ds3231,DHT11,MAX7219

может быть лучше использовать секунды (минуты) от rtc

Что-то пропустил, останавливается через некоторое время

можно попробовать

void isr_oneSecond(){
  ShowTime();
  return;
}

Зачем в обработчике экран обновлять? И при этом ещё раз в 500мс обновлять время.Из прерывания надо выходить максимально быстро (исключения бывают конечно, но)
Лучше в обработчике поднимайте флаг обновления секунды, и в loop, уже без миллис, (в самых верхних строчках кода) а по проверке флага обновляйте время и индикацию.

volatile bool NewSec = false;

void isr_oneSecond(){
 NewSec = true;
}

P.S. чуток подправил

Спасибо, сделаю

Так правильно?

#include <LedControl.h>           // Подключение библиотеки   MAX7219  управление 8миразрядными дисплеями 
#include <DHT11.h>                // Подключение библиотеки   DHT11     работас  датчиком влажности/температуры
#include <Wire.h>                 // Подключение библиотеки   I2C         
#include <DS3231.h>               // Подключение библиотеки   RTC       работа с DS3231
#include <shButton.h>             // Подключение библиотеки   shButton  работа с тактовыми кнопками
#define CLINT 2                   // Пин для управления прерываниями
DS3231 myRTC;                     // Обьявления типа RTC - DS3231
LedControl LC = LedControl(12, 11, 10, 3);   // Пины для управления драйвером и количество подключенных модулей
DHT11 dht11(6);                   // Обьявления типа датчика влажности/температуры - DHT11
bool century = false;             // флаг столетия, не используется
bool h12Flag, pmFlag;                   
volatile bool NewSec = false;
byte alarmDay, alarmHour, alarmMinute, alarmSecond, alarmBits; // переменные времени и даты будильника                  
bool alarmDayIsDay, alarmH12, alarmPM;                         // флаги будильника
byte hours, minutes, seconds, DoW, date, month, year;          // переменные времени и даты часов
const int brightSetPin = A0;                                   // пин для подключения датчика света
byte mode = 0;                                                 // переменная режыма настройки
unsigned long buttonTimer = 0;                                 // переменная хранения времени таймера запуска настройки кнопками
unsigned long emptyTimer = 0;                                  // переменная хранения времени таймера вывода пустыг символов на дисплей
int temperature = 0;                                           // переменная хранения температуры
int humidity    = 0;                                           // переменная хранения влажности
shButton setButt(3);                                           // пин кнопки setButt  "Настройка"
shButton upButt(4);                                            // пин кнопки supButt  "Вверх"
shButton downButt(5);                                          // пин кнопки downButt "Вниз"
void ButtonSetControl();                                       // обьявление функции установки времени кнопками
void ShowTime();                                               // обьявление функции вывода времени на дисплей
void CheckTime();                                              // обьявление функции считывания времени с RTC
void ShowEmptyPlace();                                         // обьявление функции вывода пустыг символов на дисплей при настройке времени
void isr_oneSecond();                                          // обьявление функции обработки прерывания
void setup() {                                                  
  Serial.begin(9600);                                          // Запуск вывода в последовательный порт 9600 бод
  Wire.begin();                                                // запуск обмена по I2C
  for (int ledS = 0; ledS < 3; ledS++) {                       // очистка дисплеев
    LC.shutdown(ledS, false);
    LC.clearDisplay(ledS);
  }
  myRTC.setClockMode(false);                  // установка 24-часового формата времени
  upButt.setLongClickMode(LCM_CLICKSERIES);   // включение серии событий при удержании кнопки upButt
  downButt.setLongClickMode(LCM_CLICKSERIES); // включение серии событий при удержании кнопки downButt
  upButt.setTimeoutOfLongClick(1000);         // установка времени удержания кнопки upButt
  downButt.setTimeoutOfLongClick(1000);       // установка времени удержания кнопки downButt
  upButt.setIntervalOfSerial(50);             // настройка интервала между событиями в сериии при удержании кнопки upButt - каждые 50 милисекунд
  downButt.setIntervalOfSerial(50);           // настройка интервала между событиями в сериии при удержании кнопки downButt- каждые 50 милисекунд
  setButt.setVirtualClickOn();                // включение режима виртуального клика setButt
  upButt.setVirtualClickOn();                 // включение режима виртуального клика upButt
  downButt.setVirtualClickOn();               // включение режима виртуального клика downButt
    alarmDay = 0;                             // переменные установки будильника 
    alarmHour = 0;
    alarmMinute = 0;
    alarmSecond = 0;
    alarmBits = 0b00001111;                   // биты установки первого будильника раз в секунду
    alarmDayIsDay = false;
    alarmH12 = false;
    alarmPM = false; 
    myRTC.turnOffAlarm(1);                    // выключение первого будильника
    myRTC.setA1Time(alarmDay, alarmHour, alarmMinute, alarmSecond, alarmBits, alarmDayIsDay, alarmH12, alarmPM);  // установка первого будильника
    myRTC.turnOnAlarm(1);                     // включение прерываний первого будильника
    myRTC.checkIfAlarm(1);                    // сброс флага первого будильника
    alarmMinute = 0xFF;                       // a value that will never match the time
    alarmBits = 0b01100000;                   // Alarm 2 when minutes match, i.e., never
    myRTC.setA2Time(alarmDay, alarmHour, alarmMinute, alarmBits, alarmDayIsDay, alarmH12, alarmPM);  // disable Alarm 2 interrupt
    myRTC.turnOffAlarm(2);                     // clear Alarm 2 flag
    myRTC.checkIfAlarm(2);
pinMode(CLINT, INPUT_PULLUP);                 // настройка пина прерывания
    attachInterrupt(digitalPinToInterrupt(CLINT), isr_oneSecond, FALLING);  // обьявление прерывания на пин
}
void loop() {
  if(NewSec){
    CheckTime();
    ShowTime();
    NewSec = 0;
  }
  if (millis() - buttonTimer > 20) {
    buttonTimer = millis();                
    ButtonSetControl();
  }
  if(seconds == 0 || seconds == 30){
    temperature = dht11.readTemperature();
    humidity = dht11.readHumidity();
  }
  myRTC.checkIfAlarm(1);                      // сброс флага первого будильника
 }
void CheckTime() {
  hours = myRTC.getHour(h12Flag, pmFlag);     // считывание и запись в переменные значения времени и даты с RTC
  minutes = myRTC.getMinute();
  seconds = myRTC.getSecond();
  DoW = myRTC.getDoW();
  date = myRTC.getDate();
  month = myRTC.getMonth(century);
  year = myRTC.getYear();
 }
void ShowTime() {
  int brightSetVol = analogRead(brightSetPin) / 50;
  if (brightSetVol < 0) { brightSetVol = 2; }
  for (int ledS = 0; ledS < 3; ledS++) {
    LC.setIntensity(ledS, brightSetVol);
  }
  LC.setDigit(0, 0, hours / 10, false);
  LC.setDigit(0, 1, hours % 10, true);
  LC.setDigit(0, 2, minutes / 10, false);
  LC.setDigit(0, 3, minutes % 10, true);
  LC.setDigit(0, 4, seconds / 10, false);
  LC.setDigit(0, 5, seconds % 10, false);
  switch (DoW) {
    case 1:  //  ПН
      LC.setRow(0, 6, 0b01110110);
      LC.setRow(0, 7, 0b00110111);
      break;
    case 2:  //  ВТ
      LC.setRow(0, 6, 0b01111111);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 3:  //  СР
      LC.setRow(0, 6, 0b01001110);
      LC.setRow(0, 7, 0b01100111);
      break;
    case 4:  //  ЧТ
      LC.setRow(0, 6, 0b00110011);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 5:  //  ПТ
      LC.setRow(0, 6, 0b01110110);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 6:  //  СБ
      LC.setRow(0, 6, 0b01001110);
      LC.setRow(0, 7, 0b00011111);
      break;
    case 7:  //  НД
      LC.setRow(0, 6, 0b00110111);
      LC.setRow(0, 7, 0b00111101);
      break;
  }
  LC.setDigit(1, 0, date / 10, false);
  LC.setDigit(1, 1, date % 10, true);
  LC.setDigit(1, 2, month / 10, false);
  LC.setDigit(1, 3, month % 10, true);
  LC.setDigit(1, 4, 2, false);
  LC.setDigit(1, 5, 0, false);
  LC.setDigit(1, 6, year / 10, false);
  LC.setDigit(1, 7, year % 10, false);
  if (temperature < 0) {
    LC.setRow(2, 0, 0b0000001);
  }
  LC.setDigit(2, 1, temperature / 10, false);
  LC.setDigit(2, 2, temperature % 10, false);
  LC.setRow(2, 3, 0b01100011);
  LC.setRow(2, 4, 0b01001110);
  LC.setDigit(2, 5, humidity / 10, false);
  LC.setDigit(2, 6, humidity % 10, false);
  LC.setRow(2, 7, 0b00110111);
}
void ButtonSetControl() {
 switch (setButt.getButtonState()){
    case BTN_ONECLICK:
    mode++;
      if (mode >= 8){
        mode = 0;
      }
      break;
  }
   
if (mode) {
  switch (upButt.getButtonState()){
    case BTN_ONECLICK:
    case BTN_LONGCLICK:
    switch (mode) {
        /* сек */ case 1:
          myRTC.setSecond(0);
        break;
        /* мин */ case 2:
          myRTC.setMinute(minutes == 59 ? 0 : minutes +1);
          break;
        /* час */ case 3:
          myRTC.setHour(hours == 23 ? 0 : hours + 1);
          break;
        /* дни */ case 4:
          myRTC.setDate(date == 31 ? 1 : date +1);
          break;
        /* мес */ case 5:
          myRTC.setMonth(month == 12 ? 1 : month +1);
          break;
        /* год */ case 6:
          myRTC.setYear(year == 99 ? 0 : year + 1);
         break;
        /* д.н.*/ case 7:
          myRTC.setDoW(DoW == 7 ? 1 : DoW + 1);
          break;
    }
    break;
  }
  switch (downButt.getButtonState()) {
    case BTN_ONECLICK:
    case BTN_LONGCLICK:
       switch (mode) {
      /* сек */ case 1:
        myRTC.setSecond(0);
        break;
      /* мин */ case 2:
        myRTC.setMinute(minutes == 0 ? 59 : minutes - 1);
        break;
      /* час */ case 3:
        myRTC.setHour(hours == 0 ? 23 : hours - 1);
        break;
      /* дни */ case 4:
        myRTC.setDate(date == 1 ? 31 : hours - 1);
        break;
      /* мес */ case 5:
        myRTC.setMonth(month == 1 ? 12 : month - 1);
        break;
      /* год */ case 6:
        myRTC.setYear(year == 1 ? 99 : year - 1);
        break;
      /* д.н.*/ case 7:
        myRTC.setDoW(DoW == 1 ? 7 : DoW - 1);
        break;
    }
    break;
  }
}
ShowEmptyPlace();
}
void ShowEmptyPlace() {
  if (mode) {
    if (millis() - emptyTimer > 1000) {
      emptyTimer = millis();
      switch (mode) {
        case 1:
          LC.setChar(0, 5, 0b0100000, false);
          LC.setChar(0, 4, 0b0100000, false);
          break;
        case 2:
          LC.setChar(0, 2, 0b0100000, false);
          LC.setChar(0, 3, 0b0100000, true);
          break;
        case 3:
          LC.setChar(0, 0, 0b0100000, false);
          LC.setChar(0, 1, 0b0100000, true);
          break;
        case 4:
          LC.setChar(1, 0, 0b0100000, false);
          LC.setChar(1, 1, 0b0100000, true);
          break;
        case 5:
          LC.setChar(1, 2, 0b0100000, false);
          LC.setChar(1, 3, 0b0100000, true);
          break;
        case 6:
          LC.setChar(1, 6, 0b0100000, false);
          LC.setChar(1, 7, 0b0100000, false);
          break;
        case 7:
          LC.setChar(0, 6, 0b0100000, false);
          LC.setChar(0, 7, 0b0100000, false);
          break;
      }
    }
  }
}
void isr_oneSecond(){
 NewSec = true;
  return;
}

Да. Я об этом.
А правильно это или нет - покажет проверка на железе.))

пока работает, оставлю на ночь, утром будет видно

Быть может я не прав, используемую библиотеку DS3231.* не смотрел. Но не может возникнуть ситуация когда CheckTime() вернёт “неверные” данные? Нет вероятности получить часть данных из одного “временного тика”, а часть из следующего, в момент перехода? Например при Unixtime 1706399999 → 1706400000 (Sat, 27 Jan 2024 23:59:59 GMT → Sun, 28 Jan 2024 00:00:00 GMT).

Даже про такое не задумывался…

getSecond()

/*
 * returns: byte = 0 to 59
 * parameters: none
 * asserts: none
 * side effects: none
 * DS3231 register addressed: 0x00
 */

byte theSecond = myRTC.getSecond();

getMinute()

/*
 * returns: byte = 0 to 59
 * parameters: none
 * asserts: none
 * side effects: none
 * DS3231 register addressed: 0x01
 */

byte theMinute = myRTC.getMinute();

getHour()

/*
 * returns: byte, value depending on mode settings in the DS3231
 *   either 1 to 12 (12-hour mode)
 *   or 0 to 23 (24-hour mode) 
 * parameters: two boolean variables, passed by reference
 *   parameter #1: 12/24-hour flag
 *   parameter #2: AM/PM flag
 *   Note: must provide the variable names, not constants
 * asserts: none
 *
 * side effects: 
 *   the two boolean parameters are set or cleared 
 *   according to the state of the corresponding flags 
 *   in the DS3231 hardware register
 *     12/24 hour flag = true if DS3231 is in 12-hour mode
 *     AM/PM flag = false if AM, true if PM, in 12-hour mode
 *
 * DS3231 register addressed: 0x02
 */

// declare global variables to be passed into the function
bool h12;
bool hPM;

byte theHour = myRTC.getHour(h12, hPM);

// example of printing the hour to the Serial monitor
Serial.print("The hour is ");
Serial.print( theHour ); // the value returned by the function

// test the values altered by side-effects of the function
if (h12 == true) { // 12-hour mode
  if (hPM == true) {
    Serial.println(" PM.");
  } else {
    Serial.println(" AM.");
  }
} else { // 24-hour mode
  Serial.println(" in 24-hour mode.");
}

Note that supplying boolean constants as parameters will halt program compilation with an error. The parameters must be the names of boolean variables defined in the program code.

getDoW()

/*
 * returns: byte = 1 to 7
 * parameters: none
 * asserts: none
 * side effects: none
 * DS3231 register addressed: 0x03
 */

byte theWeekday = myRTC.getDoW();

Note that the meaning of the day-of-week value is determined by the user when the time is set on the DS3231. See the documentation for setDoW(). In other words, “1” can signify any day of the week that the code writer chooses it to mean when setting the time. The values “2” through “7” then refer to the succeeding days, in their usual order.

getDate()

/*
 * returns: byte = 1 to 28, 29, 30 or 31, depending on the month and year
 * parameters: none
 * asserts: none
 * side effects: none
 * DS3231 register addressed: 0x04
 */

byte theDate = myRTC.getDate();

getMonth()

/*
 * returns: byte = 1 to 12
 * parameters: one boolean variable, passed by reference
 * asserts: none
 * side effects: 
 *   the boolean parameter is set or cleared
 *   according to the value of the "Century" flag
 *   in the hardware register of the DS3231
 * DS3231 register addressed: 0x05
 */

// declare a variable to receive the Century bit
bool CenturyBit;

byte theDate = myRTC.getMonth(CenturyBit);

Note: according to the datasheet, “The century bit (bit 7 of the month register) is toggled when the years register overflows from 99 to 00.”

Note also that supplying a boolean constant value of true or false as the parameter will halt program compilation with an error. The parameter must be the name of a boolean variable defined in the program code.

The “Contemplations”, below, further discuss the Century Bit.

getYear()

/*
 * returns: byte = 00 to 99
 * parameters: none
 * asserts: none
 * side effects: none
 * DS3231 register addressed: 0x06
 */

byte theDate = myRTC.getYear();

Добавил отображение тире на месте вывода температуры и влажности, когда программа только запустилась и показания с датчика еще не переданы. " - - - ° С - - Н "

void ShowTime() {
  int brightSetVol = analogRead(brightSetPin) / 50;
  if (brightSetVol < 0) { brightSetVol = 2; }
  for (int ledS = 0; ledS < 3; ledS++) {
    LC.setIntensity(ledS, brightSetVol);
  }
  LC.setDigit(0, 0, hours / 10, false);
  LC.setDigit(0, 1, hours % 10, true);
  LC.setDigit(0, 2, minutes / 10, false);
  LC.setDigit(0, 3, minutes % 10, true);
  LC.setDigit(0, 4, seconds / 10, false);
  LC.setDigit(0, 5, seconds % 10, false);
  switch (DoW) {
    case 1:  //  ПН
      LC.setRow(0, 6, 0b01110110);
      LC.setRow(0, 7, 0b00110111);
      break;
    case 2:  //  ВТ
      LC.setRow(0, 6, 0b01111111);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 3:  //  СР
      LC.setRow(0, 6, 0b01001110);
      LC.setRow(0, 7, 0b01100111);
      break;
    case 4:  //  ЧТ
      LC.setRow(0, 6, 0b00110011);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 5:  //  ПТ
      LC.setRow(0, 6, 0b01110110);
      LC.setRow(0, 7, 0b00001111);
      break;
    case 6:  //  СБ
      LC.setRow(0, 6, 0b01001110);
      LC.setRow(0, 7, 0b00011111);
      break;
    case 7:  //  НД
      LC.setRow(0, 6, 0b00110111);
      LC.setRow(0, 7, 0b00111101);
      break;
  }
  LC.setDigit(1, 0, date / 10, false);
  LC.setDigit(1, 1, date % 10, true);
  LC.setDigit(1, 2, month / 10, false);
  LC.setDigit(1, 3, month % 10, true);
  LC.setDigit(1, 4, 2, false);
  LC.setDigit(1, 5, 0, false);
  LC.setDigit(1, 6, year / 10, false);
  LC.setDigit(1, 7, year % 10, false);
  if(temperature == 0 && humidity == 0){
      LC.setRow(2, 0, 0b0000001);
      LC.setRow(2, 1, 0b0000001);
      LC.setRow(2, 2, 0b0000001);
      LC.setRow(2, 5, 0b0000001);
      LC.setRow(2, 6, 0b0000001);
    }
    else{
      LC.setDigit(2, 1, temperature / 10, false);
      LC.setDigit(2, 2, temperature % 10, false);
      LC.setDigit(2, 5, humidity / 10, false);
      LC.setDigit(2, 6, humidity % 10, false);
      if (temperature < 0) {
    LC.setRow(2, 0, 0b0000001);
  }
      else{
    LC.setChar(2, 0, 0b0100000, false);  
      }
   }
  LC.setRow(2, 3, 0b01100011);
  LC.setRow(2, 4, 0b01001110);
 
  LC.setRow(2, 7, 0b00110111);
}

Это может получиться, только если время выполнения цикла loop длится долго, близко к секунде.

P.S.
Можно, ради интереса измерить это время, выводя в монитор (а ещё лучше записать в массив, а потом вывести) разницу между новым и старым значением миллис в начале цикла loop
Как по мне, для такого кода 200 - 300мс за глаза должно хватить, не смотрел, правда все библиотеки. Если, скажем DHT11 код блокирующий, то да, может быть и больше.

Чисто теоретически может. Поэтому со временем там нужно работать немного по другому - нужно получать сразу все, а потом спокойно разбирать на составляющие. Как это делается, расписано в примере к библиотеке - examples/now/now.ino

RTClib myRTC; // объявляеем экземпляр RTC

void loop() {
.....................
  DateTime now = myRTC.now(); // получаем данные по времени/дате

  // разбираем данные на часы/минуты/секунды
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
...................
1 лайк

Принял, переделаю. Я про такие нюансы не в курсе, есть еще какие-то важные нюансы по поводу работы с RTC?

Здесь не именно про RTC, это общее. Речь о том, что, запрашивая часы, минуты и секунды по очереди, можно попасть на момент смены секунды. Например, текущее время - 12:59:59. Получаете часы - 12, получаете минуты - 59 и в этот момент меняются секунды, текущее время теперь 13:00:00. Вы получаете секунды - 00 и выводите получившееся время - 12:59:00. Совсем не то выходит ))

однозначно попадаем, это только вопрос времени

Согласен, спасибо за объяснение, теперь понятно о чем речь.

Если цикл loop длится, скажем ~200мс, как такое может быть?

Что такое Unix время? Эра Unix (Unix epoch) началась в ночь с 31 декабря 1969 года на 1 января 1970 года. Именно эту дату взяли за точку отсчета “компьютерного” времени, которое исчисляется в секундах и занимает очень мало места на диске – всего 4 или 8 байт.