Arduino зависает (вероятно, из-за I2C). Нужна помощь в написании надежного сценария.

Arduino Pro mini 3,3 В зависает через несколько часов после запуска. После повторного включения он начинает зависать через несколько секунд. Если я подожду достаточно долго, прежде чем включить его, он зависнет немного позже, например, теперь это длилось 45 секунд, прежде чем зависло. Всякий раз, когда он застревает, на выводе SCL высокий уровень, а на выводе SDA низкий уровень, поэтому я подозреваю, что что-то не так с I2C. При запуске происходит обратный отсчет в 60 секунд, и там обычно происходит зависание. Вы увидите, что это чрезвычайно простой цикл for. В проекте есть датчик SHT31, OLED-экран с управлением SH1106 128x64, модуль часов DS3231, датчик hc-sr501PIR, LDR, кодировщик и кнопка. Только первые три работают с I2C, и код для этих устройств очень короткий, они находятся в "start(), updatescreen(), getTime(), getDew()". функции в длинном сценарии ниже, поэтому я надеюсь, что диагностировать проблему не составит труда. Я провел несколько экспериментов. Я изолировал SHT31 и OLED, оба вызвали зависание, хотя все остальные устройства были отключены (я добавил код мигания в их пример тестового скетча, и мигание прекратилось через несколько секунд, что означает зависание). OLED-экран работает, когда он подключен с помощью перемычек за пределами печатной платы, поэтому я даже подозревал, что это печатная плата, но он настолько прост, что не должен вызывать проблем. Самое странное, что это устройство прекрасно проработало 2 дня, прежде чем впервые сломалось.

Схема: schematic

Печатная плата (некоторые ошибки были исправлены позже):

Реальные изображения:

Тестовый скрипт (возникает та же проблема с зависанием, но его легче читать):

#include <U8g2lib.h>
#include <Wire.h>

U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
void setup() {
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB14_tr);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, 20);
    u8g2.print(F("Hello World!"));
  } while (u8g2.nextPage());
  delay(1000);
  digitalWrite(LED_BUILTIN, HIGH);  // включаем светодиод (HIGH - уровень напряжения)
  delay(1000);                      // подождем секунду
  digitalWrite(LED_BUILTIN, LOW);
}

Тестовый сценарий 2 (на этот раз для sht31 возникает та же проблема с зависанием, но его легче читать):

#include <Wire.h>
#include "ClosedCube_SHT31D.h"

ClosedCube_SHT31D sht3xd;
void setup() {
  Wire.begin();
  sht3xd.begin(0x44);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

Полный сценарий:

#include "Wire.h"
#include "ClosedCube_SHT31D.h"
#include <DS3231.h>
#include "LowPower.h"
#include <EEPROM.h>
#include <U8g2lib.h>

#define PIR 2
#define ENCODER_BUTTON 3
#define ENC_A 4
#define ENC_B 5
#define FAN_MAIN_REL 8
#define FAN_SECOND_REL 9
#define BUZZER 10
#define LIGHT_REL 11
#define LDR_PWR 12
#define LDR_READ A0
#define FREE_RUN_BUTTON A2
#define CLOCK_INT A3

volatile bool PIR_flag, TIM1_flag, But_flag, FreeRun_flag, Settings_flag;
uint8_t Weather_flag;  // Weather_flag является особенным, он устанавливается/сбрасывается обычной функцией, 0: роса ниже порогового значения, 1: роса превышает DewThres, но меньше, чем DewThresExtreme, 2: роса превышает DewThresExtreme
volatile int16_t encVal;

// Переменные:
float Temp, Hum, Dew;
bool DewTrigEnabled;
uint8_t FanMotTrigEnabled;  // для реле вентилятора
bool LightRelEnabled;       // для реле освещения
uint16_t waitDur;           // Настройка 1
uint16_t Runtime;           // Настройка 2
float DewThres;             // Настройка 3
float DewThresExtreme;
uint16_t FreeRunDur;  // Настройка 5
bool FreeRunStatus;
bool DNDenabled;
uint8_t DND_SH;  // Час начала режима «Не беспокоить»
uint8_t DND_SM;  // Минута начала режима «Не беспокоить»
uint8_t DND_FH;  // Час завершения DND
uint8_t DND_FM;  // Минута окончания режима «Не беспокоить»
uint16_t counter;
uint8_t hour, minute;
bool LDRenabled;
uint16_t LDRThres;
bool screen_awake = true;  // экран запускается "вкл"
uint8_t FanRelayStatus;
const uint8_t screenTime = 60;  // Время включения экрана без движения (секунды)
const float DewUSratio = 0.98;  // Определяет точку росы, которую выключает ВЕНТИЛЯТОР, если она изначально была вызвана росой
const uint8_t phaseLim = 120;   // Определяет скорость проверки LDR, если свет выключен, но LDR включен, каждая фаза соответствует 5 секундам.

ClosedCube_SHT31D sht3xd;
RTClib myRTC_1;
DS3231 myRTC_2;
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

void swCounter(bool turn_on, uint32_t cnt = 0) {
  if (turn_on) {
    counter = cnt;
    TCNT1 = 0;                            // значение счетчика TIM1
    TCCR1B |= (1 << CS12) | (1 << CS10);  // Запускаем таймер, установив для прескалера значение 1024
  } else
    TCCR1B &= ~(1 << CS12) | ~(1 << CS10);  // Остановить ТИМ1
}

void setup() {
  LoadPinsAndEEPROM();

  Wire.begin();

  // Датчик температуры и влажности:
  sht3xd.begin(0x44);

  // Модуль часов:
  myRTC_2.setA2Time(
    0, 0, 0,
    0b01110000, false, false, false);
  myRTC_2.turnOnAlarm(2);

  myRTC_2.setA1Time(
    0, 0, 0xFF, 0,
    0b00001110, false, false, false);
  myRTC_2.turnOffAlarm(1);
  myRTC_2.checkIfAlarm(1);  // очищаем флаг тревоги 1

  sht3xd.heaterEnable();
  // OLED-экран:
  u8g2.begin();
  u8g2.setFont(u8g2_font_lubB12_tr);
  for (int i = 60; i > 0; i--) {
    u8g2.firstPage();
    do {
      u8g2.drawStr(6, 25, "by Prince");
      u8g2.setCursor(58, 55);
      u8g2.print(i);
      if (i == 30) sht3xd.heaterDisable();
    } while (u8g2.nextPage());
    delay(1000);
  }
  u8g2.setDrawColor(2);

  //Конфигурация TIM1:
  TCCR1A = 0;
  TCCR1B = 0;
  TIMSK1 |= (1 << OCIE1A);  // Сравнение выходных данных и разрешение прерывания по совпадению
  OCR1A = 39061;            // Сравнение выходных данных A
  TCCR1B |= (1 << WGM12);   // CRC (таймер автоматического сброса при достижении OCR1A)
  TCNT1 = 0;                // значение счетчика TIM1

  // Присоединяем прерывания (PinChanges и INTpin)
  attachInterrupt(digitalPinToInterrupt(PIR), ISR_PIR, RISING);
  attachInterrupt(digitalPinToInterrupt(ENCODER_BUTTON), ISR_ENCODER_BUTTON, FALLING);
  PCICR |= (1 << PCIE1);                      // Группа A0-A6 может создавать прерывания
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // Выводы A2 и A3 могут создавать прерывания
  PCMSK2 |= (1 << PCINT20) | (1 << PCINT21);  // Выводы D4 и D5 могут создавать прерывания

  Beep(1, 100, 0);
  PIR_flag = false;
  But_flag = false;
}

void loop() {
  checkButtons();  // После выполнения он вернется.
  if (!getDNDstatus()) {
    getDew();  // соответственно устанавливаем флаг

    if (PIR_flag) {
      if (FanMotTrigEnabled == 1) {
        TIM1_flag = true;  // Первоначально обновляем экран
        screenPwr(1);

        unsigned long lastMotion;
        uint8_t step = 1;
        if (waitDur <= 5) step = 2;  // PIR_flag уже имеет значение true в этот момент
        else swCounter(1, waitDur);

        for (step = step; step <= 2; step++) {  // 1: ожидание, 2: взято на охрану, 3: запуск
          if (step == 2) {
            (waitDur >= 90) ? counter = 60 : counter = 30;  // Решаем, как долго будет проверяться движение
            swCounter(1, counter);
            PIR_flag = false;
            updateScreen(step);
          }
          while (counter > 0) {  // Цикл счетчика
            if (checkButtons()) return;
            if (PIR_flag) {
              PIR_flag = false;
              lastMotion = millis();
              if (step >= 2) {    // если он поставлен на охрану или работает и обнаружено движение
                if (step == 2) {  // если он поставлен на охрану и обнаружено движение
                  if (FanRelayStatus != 2) swFanRelays(1);
                  step = 3;  // Движение..
                  updateScreen(step);
                }
                swCounter(1, Runtime);  // Интервал перезапуска
              }
            }
            if (TIM1_flag) {
              TIM1_flag = false;
              if (getDNDstatus()) return;
              if (step == 1 && (millis() - lastMotion) / 1000 >= screenTime) return;
              getDew();
              DewControl(step != 3);  // в случае, если роса увеличивается во время ожидания
              updateScreen(step);
            }
          }
        }
      } else if (FanMotTrigEnabled != 1) {  // Триггер движения отключен, но движение есть
        PIR_flag = false;
        if (FanMotTrigEnabled == 2) swFanRelays(0);
        RunScreen(false);
      }
    }

    DewControl(true);
    Sleep(true);

  } else {  // если сейчас время «Не беспокоить»:
    if (PIR_flag) {
      PIR_flag = false;
      RunScreen(true);
    }
    if (getDNDstatus()) Sleep(false);  // Статус DnD проверяется повторно, поскольку он мог застрять в RunScreen пользователем после завершения режима DND.
  }
}

void DewControl(bool cmd1) {  // 0: невозможно выключить вентилятор; но только "вкл" 1: оба могут включить "вкл" и «выкл.»
  if (Weather_flag == 0 && cmd1) swFanRelays(0);
  else if (Weather_flag == 1) swFanRelays(1);
  else if (Weather_flag == 2) swFanRelays(2);
}

void RunScreen(bool cmd1) {  // 0: не выполнять возврат в зависимости от режима «Не беспокоить», 1: возвращать, если больше не в режиме «Не беспокоить» (предотвращает зависание в режиме «Не беспокоить», если экран включен)
  TIM1_flag = true;          // Первоначально обновляем экран
  screenPwr(1);
  swCounter(1, screenTime);  // Исправлено время экрана запуска
  while (counter > 0) {
    if (checkButtons()) return;
    if (TIM1_flag) {
      if (getDNDstatus()) {
        if (FanRelayStatus) return;
      } else {
        if (cmd1) return;
        DewControl(true);
      }
      getDew();
      updateScreen(0);  // Простой или роса

      if (PIR_flag) {
        counter = screenTime;
        PIR_flag = false;
      }
      TIM1_flag = false;
    }
  }
  LightRel_IfNeeded(0);
}

void getDew() {
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);  // раньше это была статическая переменная
  Temp = result.t;
  Hum = result.rh;

  float a_func = log(Hum / 100) + 17.625 * Temp / (243.04 + Temp);
  Dew = (243.04 * a_func) / (17.625 - a_func);

  if (!DewTrigEnabled || (Dew <= DewThres * DewUSratio) || (Dew < DewThres && FanRelayStatus == 0)) Weather_flag = 0;
  else if (Dew < DewThresExtreme) Weather_flag = 1;
  else Weather_flag = 2;
}

bool getDNDstatus() {  // проверяем, находятся ли часы и минуты в интервале
  if (!DNDenabled) return false;

  updateTime();
  uint16_t currentmin = hour * 60 + minute;

  uint16_t localDNDstart = DND_SH * 60 + DND_SM;
  uint16_t localDNDfinish = DND_FH * 60 + DND_FM;

  if (localDNDstart < localDNDfinish) return (currentmin >= localDNDstart && currentmin < localDNDfinish);
  else return (currentmin >= localDNDstart || currentmin < localDNDfinish);
}

void updateTime() {
  if (myRTC_2.checkIfAlarm(2)) {
    DateTime now;  // раньше это была статическая переменная
    now = myRTC_1.now();
    hour = now.hour();
    minute = now.minute();
  }
}

void swFanRelays(uint8_t cmd1) {  // 0: выключить, 1: включить
  if (FanRelayStatus == cmd1) return;
  bool PIR_flag_original = PIR_flag;
  if (cmd1 == 0) {
    digitalWrite(FAN_MAIN_REL, LOW);
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 0;
  } else if (cmd1 == 1) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    if (FanRelayStatus != 2) {
      digitalWrite(FAN_SECOND_REL, HIGH);
      delay(500);
    }
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 1;
  } else if (cmd1 == 2) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    digitalWrite(FAN_SECOND_REL, HIGH);
    FanRelayStatus = 2;
  }
  delay(100);
  if (!PIR_flag_original) PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;
}

void LightRel_IfNeeded(bool cmd1) {  // 0: выключить реле освещения, 1: включить реле освещения
  static bool LightRelStatus;
  bool LDRresult;
  static uint8_t phase;  // регулярно проверяйте уровень освещенности, если яркость в помещении со временем меняется.

  switch (cmd1) {
    case 0:
      if (!LightRelStatus) return;
      LightRelStatus = false;
      phase = 0;
      digitalWrite(LIGHT_REL, LOW);
      delay(100);
      PIR_flag = false;
      But_flag = false;
      FreeRun_flag = false;
      break;

    case 1:
      if (!LightRelStatus && phase == 0) {
        if (LDRenabled) {
          digitalWrite(LDR_PWR, HIGH);
          delay(10);
          LDRresult = (analogRead(LDR_READ) > LDRThres);  // Тьма --> Высокое сопротивление --> Высокое напряжение на LDR --> ИСТИНА Горит --> Низкое сопротивление --> Низкое напряжение на LDR --> ЛОЖЬ
          digitalWrite(LDR_PWR, LOW);
        } else LDRresult = true;

        if (LDRresult) {
          LightRelStatus = true;
          digitalWrite(LIGHT_REL, HIGH);
          delay(100);
          PIR_flag = false;
          But_flag = false;
          FreeRun_flag = false;
        }
      }
      phase++;
      if (phase > phaseLim) phase = 0;  // значение по умолчанию — 120, что означает 10 минут
      break;
  }
}

void Sleep(bool cmd1) {  // 0: нормальный сон 1: сон без выключения вентилятора
  if (cmd1 == 0) swFanRelays(0);
  LightRel_IfNeeded(0);
  screenPwr(0);
  updateTime();
  PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;

  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}

void updateScreen(uint8_t cmd1) {  // 0: триггер простоя или росы, 1: обнаружен, 2: ожидание, 3: поставлен на охрану, 4: триггер двигателя, 5: продолжительность свободного хода
  String info;
  LightRel_IfNeeded(1);  // Вызывается регулярно, поскольку информация о яркости комнаты обновляется каждую фазу*5/60 минут.
  updateTime();

  switch (cmd1) {
    case 0:
      if (FanRelayStatus == 0) info = "Idle";
      else if (FanRelayStatus == 1) info = "Dew";
      else info = "OverDew";
      break;
    case 1:
      info = "Waiting";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 2:
      info = "Armed";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 3:
      info = "Motion";
      if (Weather_flag == 1) info += "&Dew";
      else if (Weather_flag == 2) info += "&OverDew";
      break;
    case 4:
      info = "<";
      info += String(counter / 60 + 1);
      info.remove(info.indexOf('.'));
      info += " mins";
      break;
  }

  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_6x10_mr);  // u8g2_font_haxrcorp4089_tn
    u8g2.setCursor(0, 7);
    u8g2.print(info);

    u8g2.setCursor(99, 7);
    if (hour < 10) u8g2.print("0");
    u8g2.print(hour);
    u8g2.print(":");
    if (minute < 10) u8g2.print("0");
    u8g2.print(minute);

    u8g2.drawLine(0, 10, 128, 10);

    u8g2.setFont(u8g2_font_fub20_tn);
    u8g2.setCursor(0, 38);
    if (Temp < 10 && Temp >= 0) u8g2.print("0");
    u8g2.print(Temp, 1);
    u8g2.setCursor(0, 64);
    u8g2.print(Hum, 1);
    u8g2.setCursor(71, 61);
    if (Dew < 10) u8g2.print("0");
    u8g2.print(Dew, 1);
    if (Dew < DewThres) u8g2.drawFrame(70, 38, 58, 26);
    else u8g2.drawBox(71, 39, 56, 24);


    u8g2.setFont(u8g2_font_lubB12_tr);
    u8g2.drawStr(55, 30, "*C");
    u8g2.drawStr(55, 64, "%");

    if (FreeRunStatus == true) u8g2.drawStr(81, 30, "FREE");
    else if (getDNDstatus()) u8g2.drawStr(82, 30, "DND");

  } while (u8g2.nextPage());
}

void screenPwr(bool cmd1) {
  if (cmd1 == screen_awake) return;

  switch (cmd1) {
    case 0:
      u8g2.setPowerSave(1);
      u8g2.clear();
      screen_awake = false;
      break;

    case 1:
      u8g2.setPowerSave(0);
      swCounter(0);
      screen_awake = true;
      break;
  }
}

void FreeRun() {
  Beep(1, 500, 0);
  FreeRun_flag = false;
  FreeRunStatus = true;
  getDew();
  swFanRelays(1);
  swCounter(1, FreeRunDur);
  updateScreen(4);
  screenPwr(1);
  while (counter > 0) {
    if (But_flag) {
      FreeRunStatus = false;
      Settings();
      return;
    }
    if (FreeRun_flag) {
      Beep(1, 100, 0);
      delay(1000);
      if (!digitalRead(FREE_RUN_BUTTON)) {  // если пользователь удерживает кнопку нажатой
        swFanRelays(0);
        Beep(2, 100, 100);
        while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // Чтобы пользователь удалил руку
        delay(500);                                        // предотвращаем дребезг
        FreeRun_flag = false;
        break;
      } else {
        FreeRun_flag = false;
        if (FanRelayStatus == 1) swFanRelays(2);
        else if (FanRelayStatus == 2) swFanRelays(1);
      }
    }
    if (TIM1_flag) {
      TIM1_flag = false;
      if (getDNDstatus()) break;
      getDew();
      updateScreen(4);
    }
  }
  DewControl(true);
  FreeRunStatus = false;
  PIR_flag = false;  // Игнорировать движение в режиме "Free Run" режим
}

bool checkButtons() {
  if (FreeRun_flag) {
    if (!getDNDstatus()) {
      FreeRun();
      return 1;
    } else {  // если во время режима «Не беспокоить» нажата кнопка «Свободный запуск», запретить свободный запуск
      Beep(3, 50, 50);
      FreeRun_flag = false;
    }
  } else if (But_flag) {
    Beep(1, 100, 0);
    delay(1000);  // Если кнопка удерживается нажатой
    if (!digitalRead(ENCODER_BUTTON)) {
      Settings();
      return 1;
    }
    But_flag = false;
  }
  return 0;
}

void Beep(uint8_t rep, uint8_t dur_act, uint8_t dur_pass) {  // повторение, длительность звукового сигнала, длительность пауз
  for (int i = 0; i < rep; i++) {
    digitalWrite(BUZZER, HIGH);
    delay(dur_act);
    digitalWrite(BUZZER, LOW);
    if (i < (rep - 1)) delay(dur_pass);
  }
}

void LoadPinsAndEEPROM() {

#define FanMotTrigEnabled_Address 0
#define waitDur_Address 1
#define Runtime_Address 3
#define DewTrigEnabled_Address 5
#define DewThres_Address 6
#define DewThresExtreme_Address 10
#define LightRelEnabled_Address 14
#define LDRenabled_Address 15
#define LDRThres_Address 16
#define DND_Enabled_Address 18  // Режим «Не беспокоить» включен
#define DND_SH_Address 19       // Час начала режима «Не беспокоить»
#define DND_SM_Address 20       // Минута начала режима DND
#define DND_FH_Address 21       // Час окончания режима DND
#define DND_FM_Address 22       // Минута завершения режима «Не беспокоить»
#define FreeRunDur_Address 23

  EEPROM.get(FanMotTrigEnabled_Address, FanMotTrigEnabled);
  EEPROM.get(waitDur_Address, waitDur);
  EEPROM.get(Runtime_Address, Runtime);
  EEPROM.get(DewTrigEnabled_Address, DewTrigEnabled);
  EEPROM.get(DewThres_Address, DewThres);
  EEPROM.get(DewThresExtreme_Address, DewThresExtreme);
  EEPROM.get(LightRelEnabled_Address, LightRelEnabled);
  EEPROM.get(LDRenabled_Address, LDRenabled);
  EEPROM.get(LDRThres_Address, LDRThres);
  EEPROM.get(DND_Enabled_Address, DNDenabled);
  EEPROM.get(DND_SH_Address, DND_SH);
  EEPROM.get(DND_SM_Address, DND_SM);
  EEPROM.get(DND_FH_Address, DND_FH);
  EEPROM.get(DND_FM_Address, DND_FM);
  EEPROM.get(FreeRunDur_Address, FreeRunDur);

  // ОТЛАДКА:
  /*
  FanMotTrigEnabled = 1;
  waitDur = 10;
  Runtime = 10;
  DewTrigEnabled = true;
  DewThres = 18.0;
  DewThresExtreme = 19.0;
  LightRelEnabled = true;
  LDRenabled = true;
  LDRThres = 512;
  DNDenabled = false;
  DND_SH = 23;
  DND_SM = 45;
  DND_FH = 0;
  DND_FM = 2;
  FreeRunDur = 30;
*/
  pinMode(PIR, INPUT);
  pinMode(LDR_READ, INPUT);
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);

  pinMode(CLOCK_INT, INPUT_PULLUP);  // В таблице данных указано, что необходим подтягивающий резистор
  pinMode(FREE_RUN_BUTTON, INPUT_PULLUP);
  pinMode(ENCODER_BUTTON, INPUT_PULLUP);

  pinMode(FAN_MAIN_REL, OUTPUT);
  pinMode(FAN_SECOND_REL, OUTPUT);
  pinMode(LIGHT_REL, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(LDR_PWR, OUTPUT);
}

void ISR_PIR() {
  PIR_flag = true;
}

void ISR_ENCODER_BUTTON() {
  But_flag = true;
}

ISR(PCINT1_vect) {
  if (!digitalRead(FREE_RUN_BUTTON)) FreeRun_flag = true;  // Кнопка имеет подтягивающий резистор, поэтому "!" используется.
}

ISR(TIMER1_COMPA_vect) {
  TIM1_flag = true;
  counter -= 5;
  if (counter <= 0) swCounter(0);
}


// Ужасные коды настроек xd:
ISR(PCINT2_vect) {  // Прерывание поворотного энкодера
  static bool aLastState;
  bool aState = digitalRead(ENC_A);
  bool bState = digitalRead(ENC_B);

  if (aState != aLastState) {
    if (bState != aState) {
      encVal++;
    } else {
      encVal--;
    }
    aLastState = aState;
  }
}

void ButDebounce(bool cmd1) {  // 0: Но кодер, 1: Свободный запуск
  if (cmd1) {
    while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // Подождем, пока кнопка не будет отпущена
    delay(200);
    FreeRun_flag = false;
  } else {
    while (!digitalRead(ENCODER_BUTTON)) delay(100);  // Подождем, пока кнопка не будет отпущена
    delay(200);
    But_flag = false;
  }
}

String formatTime(int input, bool type = true) {  // type = true означает, что ввод равен секундам, если false, это означает, что ввод равен минутам
  int hours = input / 3600;                       // Получаем количество часов
  int minutes = (input % 3600) / 60;              // Получаем количество минут
  int remainingSeconds = input % 60;              // Получаем количество секунд

  if (!type) {
    hours = minutes;
    minutes = remainingSeconds;
    remainingSeconds = 0;
  }

  // Создаем отформатированную строку в формате "час:минута:секунда"
  String formattedTime = "";

  if (hours < 10) {
    formattedTime += "0";  // Добавляем начальный ноль для часов
  }
  formattedTime += String(hours) + ":";

  if (minutes < 10) {
    formattedTime += "0";  // Добавляем начальный ноль для минут
  }
  formattedTime += String(minutes) + ":";

  if (remainingSeconds < 10) {
    formattedTime += "0";  // Добавляем начальный ноль для секунд
  }
  formattedTime += String(remainingSeconds);

  return formattedTime;
}

uint8_t setEncVal(uint16_t target, uint8_t inc1, uint8_t inc2, uint16_t inc3, uint8_t lim1, uint8_t lim2) {
  uint16_t a = 0;
  uint8_t b = 0;
  while (a < target) {
    if (b < lim1) a += inc1;
    else if (b < lim2) a += inc2;
    else a += inc3;
    b++;
  }
  return b;
}

void Settings() {} // удалено из-за ограничения на количество символов, но я все равно не запускаю эту функцию, пока она не зависла

, 👍5

Обсуждение

Пробовали ли вы добавить специальные подтягивающие резисторы к SDA и SCL? Вроде 4,7 кОм. Это может помочь в данном случае, @chrisl

Нет, но у модулей есть подтяжки и это по 2,2к каждый, когда я замерял, @CaveScientist

первые три предложения описывают возможную проблему перегрева, @jsotola

В прошлый раз у меня были очень странные зависания I2C, это было вызвано сменой контакта ISR без его обработчика., @KIIV

У меня есть прерывание смены контакта, которое я использую, чтобы каждую минуту выводить Arduino из спящего режима с помощью Ds3231, и оно подключено к A3, а у меня есть кнопка, которая подключена к A2. Мне не нужен был обработчик для модуля часов, но поскольку они оба являются аналоговыми выводами, у меня есть «ISR(PCINT1_vect)» для кнопки, так что, думаю, я могу сказать, что у меня нет необработанных прерываний, даже несмотря на то, что A3 по выводам ничего не делает, кроме пробуждения., @CaveScientist

jsotola, нет нагрева ни регулятора, ни чипа. Вы говорите о МОП-транзисторах I2C внутри Arduino? Наверное, я не чувствую их пальцами. Думаю, для надежного теста мне нужно заменить Arduino, потому что МОП-транзисторы уже повреждены. Есть ли вероятность, что модуль температуры, или экран, или Ds3231 тоже сломан? Потому что, как я полагаю, они управляют линией SDA с помощью своих МОП-транзисторов., @CaveScientist

ОТ: Отсутствие разделения между сетью и низкоуровневой электроникой кажется мне опасным. Зазоры в К1 и К2 кажутся слишком маленькими. Соблюдали ли вы правила защиты людей?, @the busybee

Я не следовал никаким правилам, признаю это недостаток моей конструкции., @CaveScientist

Возможно, вы захотите изменить это перед вводом в эксплуатацию, чтобы не причинить вреда и не убить кого-либо, включая себя., @the busybee


2 ответа


1

Это похоже на слишком амбициозные подтягивающие резисторы. Их должно быть около 3 тыс. на каждый автобус. Я возьму SWAG и скажу, что драйвер I2C потребляет большой ток и нагревается. Когда это происходит, его сопротивление увеличивается, тем самым обеспечивая меньший ток. Подберите правильные резисторы, и я уверен, что все будет работать нормально.

Если у вас есть замораживающий спрей, охладите их, и они, вероятно, начнут работать, пока не нагреются.

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

,

Это первый ответ, который показался мне разумным. На другом форуме мне сказали, что у меня проблемы с холодной пайкой или подключением, но это звучало нереально. Возможно, это объясняет, почему устройство работает дольше, когда я долго жду перед повторным включением, потому что полевые транзисторы остывают. Также это объясняет, почему в первый раз ему удалось проработать 2 дня, потому что полевые транзисторы I2C были исправны, но теперь они наполовину сломаны, поэтому это длится всего несколько часов. Это длится несколько секунд, когда я перезаряжаюсь, не дожидаясь. Я просто не могу поверить, что сопротивление в килоомах все еще может повредить полевые транзисторы. Я удалю некоторые резисторы из модулей., @CaveScientist

Я удалил DS3231, потому что на его плате было 4,7 кОм подтягиваний, поэтому общее сопротивление стало 5 кОм (по 10 кОм на SHT31 и OLED). К сожалению, через несколько минут устройство зависло., @CaveScientist


1

Вы уверены, что используете последнюю версию библиотеки I2C? Вплоть до середины 2020 года в «официальной» библиотеке I2C был известный режим сбоя, который приводил к зависанию шины I2C. См. мой пост «Дворец художников» на эту тему. тема для получения дополнительной информации.

Фрэнк

,

Есть комментарий: «Изменено Грейсоном Христофоро ([email protected]) в 2020 году для реализации тайм-аутов». Думаю, тайм-аут по умолчанию уже существует. Также я установил Arduino IDE 2 в апреле 2023 года. Что-то мне не хватает? Спасибо., @CaveScientist