"Умерло" два Digispark в автополиве. Ничего не помогает

Здравствуйте.
Собирал немного изменённый многоканальный авто полив растений от Гайвера.ULN2003
Использовал Digispark, Pcf8574, CD74HC4067 и 5 ULN2003.
Использовал отдельное питание для ULN2003 с помпами.
При включении, через некоторое время, микроконтроллер по неизвестной причине зависал, оставляя последнюю запущенную помпу включённой. Других проблем на от момент не было. В какой-то момент Digispark перестал загружаться по USB . Попытки восстановить загрузчик не получились , но плата прошивалась. Через еще десяток попыток решить проблему зависания Digispark вообще перестал прошиваться.
инструкции с сайтов:
Про Ардуино и не только: Прошиваем загрузчик micronucleus в ATtiny85”,
и снятие фьюзов “Снятие проклятия с фьюзов ATtiny85 / Хабр” не дали результат. результатом записи и чтения стали FF (использовал мосфет IRF540N)
При использовании новой платы Digispark всё повторилось .
Подскажите, как исправить повторения перезагрузок и окирпичивания Digispark?

#define LCD_BACKL 1     // автоотключение подсветки дисплея (1 - разрешить)
#define BACKL_TOUT 30   // таймаут отключения дисплея, секунды
#define ENCODER_TYPE 1  // тип энкодера (0 или 1). Если энкодер работает некорректно (пропуск шагов), смените тип
#define ENC_REVERSE 1   // 1 - инвертировать энкодер, 0 - нет
//#define DRIVER_VERSION 0    // 0 - маркировка драйвера дисплея кончается на 4АТ, 1 - на 4Т
#define PUPM_AMOUNT 16  // количество помп, подключенных через реле/мосфет
#define START_PIN 0     // подключены начиная с пина
#define PUMP_PIN 1      // это реле, ведущее на общую помпу
#define SWITCH_LEVEL 1  // реле: 1 - высокого уровня (или мосфет), 0 - низкого
#define PARALLEL 0      // 1 - параллельный полив, 0 - полив в порядке очереди
#define TIMER_START 1   // 1 - отсчёт периода с момента ВЫКЛЮЧЕНИЯ помпы, 0 - с момента ВКЛЮЧЕНИЯ помпы
#define VOLDER_PIN 13   //СИГНАЛЬНЫЙ ПИН  SIG ДЛЯ УПРАВЛЕНИЯ УРОВНЕМ ПОМПЫ ВКЛ ВЫКЛ

#define PPIN pcf, 7   // на пин EN выключение мультиплексора
bool setmrn = false;  //  замена значений в меню (старт стоп и ресет)


#include <EEPROMex.h>
#include <EEPROMVar.h>

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);



#include <pcf8574.h>

PCF8574 pcf(0x20);

//#include <CD74HC4067.h>

//#define muxSIG A0
#define muxS0 pcf, 3
#define muxS1 pcf, 2
#define muxS2 pcf, 1
#define muxS3 pcf, 0
//CD74HC4067 my_mux((s0), (s1), (s2), (s3));

#define CLK pcf, 4
#define DT pcf, 5
#define SW pcf, 6





#include "GyverEncoder.h"
//Encoder enc1(CLK, DT, SW);
Encoder enc1;


//#include "LCD_1602_RUS.h"

// -------- АВТОВЫБОР ОПРЕДЕЛЕНИЯ ДИСПЛЕЯ-------------
// Если кончается на 4Т - это 0х27. Если на 4АТ - 0х3f
/*#if (DRIVER_VERSION)
LiquidCrystal_I2C lcd(0x27, 16, 2);
#else
LiquidCrystal_I2C lcd(0x3f, 16, 2);
#endif*/
// -------- АВТОВЫБОР ОПРЕДЕЛЕНИЯ ДИСПЛЕЯ-------------

uint32_t pump_timers[PUPM_AMOUNT];
uint32_t pumping_time[PUPM_AMOUNT];
uint32_t period_time[PUPM_AMOUNT];
boolean pump_state[PUPM_AMOUNT];
byte pump_pins[PUPM_AMOUNT];

int8_t current_set;
int8_t current_pump;
boolean now_pumping;

int8_t thisH, thisM, thisS;
long thisPeriod;
boolean startFlag = true;

//bool setmrn = true;


boolean backlState = true;
uint32_t backlTimer;



int SetMuxChannel(byte channel) {
  for (byte i = 0, r = 3; i < 4; i++, r--) {
    digitalWrite(pcf, r, bitRead(channel, i));
    /*digitalWrite(pcf, 3, bitRead(channel, 0));
    digitalWrite(pcf, 2, bitRead(channel, 1));
    digitalWrite(pcf, 1, bitRead(channel, 2));
    digitalWrite(pcf, 0, bitRead(channel, 3));*/
  }
}



void setup() {

  //pinMode(muxSIG, OUTPUT);
  for (byte i = 0; i < 4; i++) {
    pinMode(pcf, i, OUTPUT);
  }

  /*pinMode(muxS0, OUTPUT);
  pinMode(muxS1, OUTPUT);
  pinMode(muxS2, OUTPUT);
  pinMode(muxS3, OUTPUT);*/



  //pcf.begin(0x20);
  /*for (byte i = 0; i < 5; i++) {
    pinMode(pcf, i, OUTPUT);
    digitalWrite(pcf, i, LOW);
  }*/
  //pinMode(s1, OUTPUT);  //OUTPUT  INPUT_PULLUP
  //pinMode(s2, OUTPUT);
  //pinMode(s3, OUTPUT);
  for (byte i = 5; i < 7; i++) {
    pinMode(pcf, i, INPUT_PULLUP);
    //pinMode(DT, INPUT_PULLUP);
    //pinMode(SW, INPUT_PULLUP);
  }
  //pinMode(pcf, 7, OUTPUT);

  // --------------------- КОНФИГУРИРУЕМ ПИНЫ ---------------------
  pinMode(PUMP_PIN, OUTPUT);
  digitalWrite(PUMP_PIN, !SWITCH_LEVEL);  // выключаем от греха
  digitalWrite(PPIN, SWITCH_LEVEL);
  for (byte i = 0; i < PUPM_AMOUNT; i++) {  // пробегаем по всем помпам
    pump_pins[i] = START_PIN + i;           // настраиваем массив пинов
    // pinMode(START_PIN + i, OUTPUT);              // настраиваем пины
    //digitalWrite(VOLDER_PIN, !SWITCH_LEVEL);  // выключаем от греха
  }
  digitalWrite(VOLDER_PIN, !SWITCH_LEVEL);
  // --------------------- ИНИЦИАЛИЗИРУЕМ ЖЕЛЕЗО ---------------------
  //Serial.begin(9600);

  lcd.init();
  lcd.backlight();
  lcd.clear();
  //enc1.setStepNorm(1);
  //attachInterrupt(0, encISR, CHANGE);
  enc1.setType(ENCODER_TYPE);
  if (ENC_REVERSE) enc1.setDirection(REVERSE);

  // --------------------- СБРОС НАСТРОЕК ---------------------
  if (!digitalRead(SW)) {  // если нажат энкодер, сбросить настройки до 1
    lcd.setCursor(0, 0);
    lcd.print(setmrn);  // "reset"
    for (byte i = 0; i < 500; i++) {
      EEPROM.writeLong(i, 0);
    }
  }
  while (!digitalRead(SW))
    ;           // ждём отпускания кнопки"
  lcd.clear();  // очищаем дисплей, продолжаем работу

  // --------------------------- НАСТРОЙКИ ---------------------------
  // в ячейке 1023 должен быть записан флажок, если его нет - делаем (ПЕРВЫЙ ЗАПУСК)
  if (EEPROM.read(511) != 5) {  //1023
    EEPROM.writeByte(511, 5);

    // для порядку сделаем 1 ячейки с 0 по 500
    for (byte i = 0; i < 500; i += 4) {
      EEPROM.writeLong(i, 0);
    }
  }

  for (byte i = 0; i < PUPM_AMOUNT; i++) {         // пробегаем по всем помпам
    period_time[i] = EEPROM.readLong(8 * i);       // читаем данные из памяти. На чётных - период (ч)
    pumping_time[i] = EEPROM.readLong(8 * i + 4);  // на нечётных - полив (с)

    if (SWITCH_LEVEL)  // вырубить все помпы
      pump_state[i] = 0;
    else
      pump_state[i] = 1;
  }

  // ---------------------- ВЫВОД НА ДИСПЛЕЙ ------------------------
  drawLabels();
  changeSet();
}

void loop() {






  encoderTick();
  periodTick();
  flowTick();
  backlTick();
}

void backlTick() {
  if (LCD_BACKL && backlState && millis() - backlTimer >= BACKL_TOUT * 1000) {
    backlState = false;
    lcd.noBacklight();
  }
}
void backlOn() {
  backlState = true;
  backlTimer = millis();
  lcd.backlight();
}

void periodTick() {
  for (byte i = 0; i < PUPM_AMOUNT; i++) {  // пробегаем по всем помпам
    if (startFlag || (period_time[i] > 0 && millis() - pump_timers[i] >= period_time[i] * 1000 && (pump_state[i] != SWITCH_LEVEL) && !(now_pumping * !PARALLEL))) {
      pump_state[i] = SWITCH_LEVEL;
      SetMuxChannel(i);  // открыть КЛАПАН , SWITCH_LEVEL
      digitalWrite(VOLDER_PIN, SWITCH_LEVEL);
      pump_timers[i] = millis();
      now_pumping = true;
      digitalWrite(PUMP_PIN, SWITCH_LEVEL);  // включить общую ПОМПУ
      digitalWrite(PPIN, !SWITCH_LEVEL);
      //Serial.println("Pump #" + String(i) + " ON");
    }
  }
  startFlag = false;
}

void flowTick() {
  for (byte i = 0; i < PUPM_AMOUNT; i++) {  // пробегаем по всем помпам
    if (pumping_time[i] > 0
        && millis() - pump_timers[i] >= pumping_time[i] * 1000
        && (pump_state[i] == SWITCH_LEVEL)) {
      pump_state[i] = !SWITCH_LEVEL;
      //my_mux.channel(i);
      digitalWrite(VOLDER_PIN, !SWITCH_LEVEL);  // закрыть КЛАПАН
      if (TIMER_START) pump_timers[i] = millis();
      now_pumping = false;
      digitalWrite(PUMP_PIN, !SWITCH_LEVEL);  // выключить общую ПОМПУ
      digitalWrite(PPIN, SWITCH_LEVEL);
      //Serial.println("Pump #" + String(i) + " OFF");
    }
  }
}

/*
  void encISR() {
  enc1.tick();  // отработка энкодера
  }
*/

void encoderTick() {
  //bool stateCLK = digitalRead(CLK);
  // bool stateDT = digitalRead(DT);
  //bool stateSW = digitalRead(SW);

  enc1.tick(digitalRead(CLK), digitalRead(DT), digitalRead(SW));
  //enc1.tick();  // отработка энкодера

  if (enc1.isTurn()) {  // если был совершён поворот
    if (backlState) {
      backlTimer = millis();  // сбросить таймаут дисплея
      if (enc1.isRight()) {
        if (++current_set >= 7) current_set = 6;
      } else if (enc1.isLeft()) {
        if (--current_set < 0) current_set = 0;
      }

      if (enc1.isRightH())
        changeSettings(1);
      else if (enc1.isLeftH())
        changeSettings(-1);

      changeSet();
    } else {
      backlOn();  // включить дисплей
    }
  }
}

// тут меняем номер помпы и настройки
void changeSettings(int increment) {
  if (current_set == 0) {
    current_pump += increment;
    if (current_pump > PUPM_AMOUNT - 1) current_pump = PUPM_AMOUNT - 1;
    if (current_pump < 0) current_pump = 0;
    s_to_hms(period_time[current_pump]);
    drawLabels();
  } else {
    if (current_set == 1 || current_set == 4) {
      thisH += increment;
    } else if (current_set == 2 || current_set == 5) {
      thisM += increment;
    } else if (current_set == 3 || current_set == 6) {
      thisS += increment;
    }
    if (thisS > 59) {
      thisS = 0;
      thisM++;
      if (thisM > 59) {
        thisM = 0;
        thisH++;
      }
    }
    if (thisS < 0) {
      thisS = 59;
      thisM--;
      if (thisM < 0) {
        thisM = 59;
        thisH--;
        if (thisH < 0) thisH = 0;
      }
    }
    if (current_set < 4) period_time[current_pump] = hms_to_s();
    else pumping_time[current_pump] = hms_to_s();
  }
}

// вывести название реле
void drawLabels() {
  lcd.setCursor(1, 0);
  lcd.print("                ");
  lcd.setCursor(1, 0);
  lcd.print(current_pump);
}

// изменение позиции стрелки и вывод данных
void changeSet() {
  switch (current_set) {
    case 0:
      drawArrow(0, 0);
      update_EEPROM();
      break;
    case 1:
      drawArrow(7, 1);
      break;
    case 2:
      drawArrow(10, 1);
      break;
    case 3:
      drawArrow(13, 1);
      break;
    case 4:
      drawArrow(7, 1);
      break;
    case 5:
      drawArrow(10, 1);
      break;
    case 6:
      drawArrow(13, 1);
      break;
  }
  lcd.setCursor(0, 1);
  if (current_set < 4) {
    lcd.print(setmrn);
    s_to_hms(period_time[current_pump]);
  } else {
    lcd.print(!setmrn);
    s_to_hms(pumping_time[current_pump]);
  }
  lcd.setCursor(8, 1);
  if (thisH < 10) lcd.print(0);
  lcd.print(thisH);
  lcd.setCursor(11, 1);
  if (thisM < 10) lcd.print(0);
  lcd.print(thisM);
  lcd.setCursor(14, 1);
  if (thisS < 10) lcd.print(0);
  lcd.print(thisS);
}

// перевод секунд в ЧЧ:ММ:СС
void s_to_hms(uint32_t period) {
  thisH = floor((long)period / 3600);  // секунды в часы
  thisM = floor((period - (long)thisH * 3600) / 60);
  thisS = period - (long)thisH * 3600 - thisM * 60;
}

// перевод ЧЧ:ММ:СС в секунды
uint32_t hms_to_s() {
  return ((long)thisH * 3600 + thisM * 60 + thisS);
}

// отрисовка стрелки и двоеточий
void drawArrow(byte col, byte row) {
  lcd.setCursor(0, 0);
  lcd.print(" ");
  lcd.setCursor(7, 1);
  lcd.print(" ");
  lcd.setCursor(10, 1);
  lcd.print(":");
  lcd.setCursor(13, 1);
  lcd.print(":");
  lcd.setCursor(col, row);
  lcd.write(126);
}

// обновляем данные в памяти
void update_EEPROM() {
  EEPROM.updateLong(8 * current_pump, period_time[current_pump]);
  EEPROM.updateLong(8 * current_pump + 4, pumping_time[current_pump]);
}


![2024-12-25 13.07.59  f539f8394ff8|683x500](upload://vxB5yaPBQvHueNJC9Z1zBTjF3KD.jpeg)

Есть ссылки на библиотеки?
и почему их две?

Так было в оригинальном коде Гайвера. Так как я не понимаю как работать с EEPROM , я не менял части кода связанные с этим , кроме части где ставится флаг на последней ячейке ( с 1023 поменял на 511)

А библиотеки откуда брали? тоже у Гайвера?

Да , были в папке со скетчем.
Не смог прикрепить библиотеку архивом.

Вроде там про дигиспарк речи не идет

1 лайк

Не могу сказать ничего определенного, но в коде и в схеме я по первому взгляду не нашел ничего, что могло бы привести к такому постепенному умиранию Тиньки.
Единственное у меня подозрение на ЕЕПРОМ.

Библиотека старая и давно не обновляющаяся. отзывов про нее мало. Почему именно ее выбрал Гайвер - не ясно.
И, кстати. поддержки Тиньки в ней не заявлено.

Чисто профилактически я бы переписал код на стандартную АВР-овскую либу EEPROM.h, тем более что у вас вкоде она и вызывается то всего пару раз.

Хотя уверенности, что дело в этом, конечно же нет.

Я грешу на обратный ток помп.
До этого я делал автополив на релейной сборке через PCF8574 и добавил обратные диоды. там по линии питания моторов и реле напряжение странно себя вело . Мультиметр показывал то -5 то +10 вольт.

Аптамуш, каденсаторы надо побольше по питанию ставить, тогда провалы нивелируются. Там, где есть моторчики и электромагниты, 4700мкФ не выглядят излишними, особенно, если БП китайский с 50-100мкФ на конце.

1 лайк

да, возможно Вы правы…

Посмотрел либу ЕПРОМ внимательнее - она вряд ли что-то портит, так как сама в память не лезет, работает через стандартные функции <avr/eeprom.h>.

1 лайк

Емнип, у uln2003 нужно подключать вывод GND как раз чтобы задействовать встроенные диоды. Типа так

У вас эти выводы висят в воздухе судя по схеме

3 лайка

Тогда пожалуйста подскажите как защитить микроконтроллер от наводок со стороны помп?
Помпы имеют сопротивление 10ом , работают на 5 в.
Я не знаю куда кроме по входу на плату ещё конденсаторы ставить.
в 2003 уже есть диоды так что думаю с ними проблем нет. На сами помпы я не могу поставить конденсаторы (разве что у выхода с 2003-их.

Во время работы просадок напряжения не наблюдались.

См. мой пост выше

Параллельно самим помпам 0,1u

1 лайк

Извините не добавил на схему . Они подключены к земле.

Вообще дигиспарки нежные, у меня почему-то каждый раз, как на них что-то делаю, хотя бы одна плата умирает (или уходит в астрал - последнюю удалось оживить), причем буквально от неосторожного чиха ))

а по питанию увеличить ? 470 максимум что сейчас имею. Может есть способы полностью разделить логику (хотя бы тиньку) и помпы?

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

Гальваническая развязка на оптопарах с отдельными блоками питания