Помехоустойчивость EEPROM в Arduino Pro Mini

A l e x
Offline
Зарегистрирован: 13.02.2021

С мозгами из китайской Arduino Pro Mini 328p, на базе стандартных библиотек сделал ИК-выключатель с ограничением времени работы для включения/выключения ТВ-бокса и телевизора.

Смысл устройства такой, что при нажатии кнопки (посажена на pin 3) оно запоминает в EEPROM ИК код пульта аэромыши (вкл/выкл - это единственные ИК команды аэромыши, все прочие команды она передает через WiFi-адаптер).

Через оптопару MOC3063M ардуинка управляет симистором, и, через него нагрузкой. На всякий случай добавил таймер выключения на 3,5 часа (нечего полдня телевизор смотреть).

Блок питания - зарядник от телефона Samsung. По питанию стоит электролит и керамический конденсатор на 2,2 мкФ.

Собственно проблема: у нас по сети несколько раз в неделю проходят такие наводки, что комп, подключенный через UPS мгновенно выключается (всякие роутеры в соседней комнате - не реагируют), при этом запомненный в EEPROM ардуинки код пульта стирается и приходится снова его записывать. Может быть кто-то поделится умными мыслями: как с этим можно бороться, куда копать, и, вообще, есть ли способ обуздать стихию?

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

А может не стирается, а просто не записывается? О чем можно рассуждать, не видя кода?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

v258 пишет:

О чем можно рассуждать, не видя кода?

Как о чём? О девочках! Или Вам и для этого код нужен? :-)

A l e x
Offline
Зарегистрирован: 13.02.2021

Код, если любопытно, привожу:

#include <IRLibRecv.h>
#include <IRLibDecodeBase.h>
#include <IRLib_P01_NEC.h>
#include <IRLibCombo.h>
#include <EEPROM.h>
#include <avr/sleep.h>
 
const int ledPin =  10;      // номер выхода, подключенного к оптопаре
const int irPin = 2;         // номер выхода, подключенного к ИК-приемнику
const int butPin =  3;      // номер выхода подключенного к кнопке

// Variables will change:

IRrecv MyReceiver(irPin);        // указываем вывод, к которому подключён приёмник
long previousMillis = 0;        // храним время последнего переключения светодиода
IRdecode MyDecoder;         // код пульта
uint32_t codeValue;         // сохраненный код пульта
int pushButtonState = false;   // нажатие кнопки
 
long interval = 16800000;           // интервал между включение-выключением

 
void setup() {
  pinMode(ledPin, OUTPUT);        // задаем режим выхода для порта, подключенного к светодиоду
  digitalWrite(ledPin, LOW);     // выключаем светодиод
  for (byte i=0; i<ledPin; i++) {    //make all pins inputs with pullups enabled
        pinMode(i, INPUT_PULLUP);
  }
  for (byte i=ledPin+1; i<20; i++) {    //make all pins inputs with pullups enabled
        pinMode(i, INPUT_PULLUP);
  }
  pinMode(13, OUTPUT);        // задаем режим выхода для порта, подключенного к светодиоду
  digitalWrite(13, LOW);     // выключаем светодиод
  //Serial.begin(9600);
  //pinMode(ledPin, OUTPUT);        // задаем режим выхода для порта, подключенного к оптопаре
  //digitalWrite(ledPin, LOW);     // выключаем оптопару
  //pinMode(butPin, INPUT_PULLUP);    // Пин на  прерывание

  pushButtonState = false;
  EEPROM.get(0, codeValue);
  MyReceiver.enableIRIn();         // запускаем приём
  //Serial.println("EEPROM: ");
  //Serial.println(codeValue, HEX);
  EIFR = bit (INTF1);
  attachInterrupt(1, PushInt, CHANGE);  // Разрешаем внешние прерывание при изменении уровня
  pinMode(irPin, INPUT);
  attachInterrupt(0, IRInt, LOW);  // Разрешаем внешние прерывание при изменении уровня
  digitalWrite(ledPin, LOW);     // выключаем оптопару

  //delay(400);
  set_sleep_mode (SLEEP_MODE_STANDBY);  
  sleep_mode();
}
 
void loop()
{
  if (MyReceiver.getResults()) {//wait till it returns true
    MyDecoder.decode();
    
    if(pushButtonState){// если нужно сохранить в EEPROM
      //delay(200);
      detachInterrupt(1);
      codeValue = (uint32_t)MyDecoder.value;
      //Serial.println("To EEPROM: ");
      //Serial.println(codeValue, HEX);
      EEPROM.put(0, codeValue);
      pushButtonState = false;         // Инициализация начального состояния
    EIFR = bit (INTF1);
      attachInterrupt(1, PushInt, CHANGE);  // Разрешаем внешние прерывание при изменении уровня
    }
    else { // если не нужно сохранять сравним с сохраненным
      decodeIR();
    }
    MyReceiver.enableIRIn(); //restart the receiver
  }

  //проверяем вкл или выкл
  if(digitalRead(ledPin)==HIGH){
    unsigned long currentMillis = millis();   // извлекаем текущее время
    //проверяем не прошел ли нужный интервал, если прошел то
    if(currentMillis - previousMillis > interval) {
      digitalWrite(ledPin, LOW);     // выключаем светодиод
      attachInterrupt(0, IRInt, LOW);  // Разрешаем внешние прерывание при изменении уровня
      set_sleep_mode (SLEEP_MODE_STANDBY);
      sleep_mode();
    }
  }
  
}

// определяем значение нажатой клавиши
void decodeIR()
{
  if((uint32_t)MyDecoder.value == codeValue){
    //Serial.println("Read ");
    //Serial.println((uint32_t)MyDecoder.value, HEX);
    //проверяем вкл или выкл
    if(digitalRead(ledPin)==LOW){
      digitalWrite(ledPin, HIGH);       // зажигаем оптопару

      previousMillis = millis();   // сохраняем время включения
    }
    else {
      digitalWrite(ledPin, LOW);       // гасим оптопару

      attachInterrupt(0, IRInt, LOW);  // Разрешаем внешние прерывание при изменении уровня
      set_sleep_mode (SLEEP_MODE_STANDBY);
      sleep_mode();
    }
  }
  else {
    if(digitalRead(ledPin)==LOW){
      attachInterrupt(0, IRInt, LOW);  // Разрешаем внешние прерывание при изменении уровня
      set_sleep_mode (SLEEP_MODE_STANDBY);
      sleep_mode();
    }
  }
}

void PushInt(){
  // Обработчик прерывания
  pushButtonState = true;
}
void IRInt(){
  // Обработчик прерывания
  detachInterrupt(0);
}

Но, еще раз, код запоминается в EEPROM, при нажатии кнопки. Это очень легко проверить: далее отключение/включение питания никак не сказывается на работоспособности устройства. Сбои происходят только при неведомых помехах.

Может быть неясно написал: при помехе одновременно выключается комп, находящийся в той же комнате, кратковременно включает и тут же выключает телевизор, описываемое устройство, и - все, в памяти ардуинки нет запомненных кодов ИК! Мой компьютер, в данном случае - индикатор прихода помехи.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

A l e x, как минимум запитать арду от заведомо надёжного БП (лучше олд-скульного трансформаторного) и проверить, что установлен fuse (brown-out detectin level).Но лучше найти источник этих помех, я б бросил все силы именно на поиск помех.

A l e x
Offline
Зарегистрирован: 13.02.2021

В жилом доме, даже если я дядю Петю, который втихаря сваркой пользуется найду, устранить не смогу.

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

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

Помеха критична не для некоторых устройств, а для устройств на некоторых блоках питания. Если не можете исключить саму помеху, исключайте ее влияние. Подбирайте БП и/или фильтры.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

A l e x, вызовите хорошего электрика. Вероятность что проблема именно в вашей квартире/проводке/щитке -очень высока. У меня однажды были похожие симптомы - выяснилось , что старая алюминиевая проводка медленно прогорала, причём прямо в штукатурке, в стене. 

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

A l e x, переменные, которые изменяются не только в прерываниях, нужно объявлять как volatile. В вашем случае это 

int pushButtonState = false;   // нажатие кнопки

 

A l e x
Offline
Зарегистрирован: 13.02.2021

To dimax:

Вы заставили меня задуматься. У нас, именно, старая алюминиевая проводка и уже дважды находил "сюрпризы" - скрутки от предыдущих жильцов в квартире. Второй раз пришлось идти по проводу - искать скрутку под слоем штукатурки.

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

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

v258 пишет:

A l e x, переменные, которые изменяются не только в прерываниях, нужно объявлять как volatile. В вашем случае это 

int pushButtonState = false;   // нажатие кнопки

Покажите мне идиота который поставил минус за это сообщение !

Что блин за люди, мало того, что не понимают, что не соблюдают атомарность, так еще и вертят как хотят переменными в прерывании. Нихрена не понимают что это и зачем, а минусы ставят....

Тут нужно не только volatile, но еще и объявить ее как bool или byte.....

Green
Offline
Зарегистрирован: 01.10.2015

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

A l e x
Offline
Зарегистрирован: 13.02.2021

To brokly. Я минус не ставил. С точки зрения правильности - поддерживаю, посыпаю голову пеплом. Спасибо!

Ресурсов микроконтроллера хватает с избытком, компилятор эту штуку понимает (еще раз, по сути, Вы на 100% правы), в данном случае- сойдет и так.

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

Green пишет:

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

Вряд ли это поможет. Сдается мне, что там ничего не стирается, а в момент прохождения помехи  срабатывает прерывание, и МК с чистой совестью записывает нуль. 

Как вариант, я бы вообще в данном случае от прерываний отказался. Имхо, в этом коде они лишние

A l e x
Offline
Зарегистрирован: 13.02.2021

To v258. Надо бы проверить. Но: не знаю как внутри библиотеки все организовано, но нужно, чтобы код пришел. Пока не вернется считанное значение программа будет ждать.

Прерывания используются для минимизации потребления в ждущем режиме (ток уменьшается примерно на 10 мА).

A l e x
Offline
Зарегистрирован: 13.02.2021

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

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

A l e x пишет:

To brokly. Я минус не ставил. С точки зрения правильности - поддерживаю, посыпаю голову пеплом. Спасибо!

Ресурсов микроконтроллера хватает с избытком, компилятор эту штуку понимает (еще раз, по сути, Вы на 100% правы), в данном случае- сойдет и так.

Беда в том, что не сойдет. Ни в каком случае не сойдет. Обязательно глюком вылезет. Может быть это и не причина вашей текущей проблемы, но тогда причина будущей проблемы. 

Upper
Offline
Зарегистрирован: 23.06.2020

brokly пишет:

Беда в том, что не сойдет. Ни в каком случае не сойдет. Обязательно глюком вылезет. Может быть это и не причина вашей текущей проблемы, но тогда причина будущей проблемы. 

Где вы думаете в ДАННОМ КОНКРЕТНОМ случае может произойти глюк?

Для переменной int  pushButtonState  которая используется для TRUE или FALSE атомарность не важна, 

Отсутствие volatile в ДАННОМ КОНКРЕТНОМ случае тоже не влияет на работу.

Или я где то заблуждаюсь в ДАННОМ КОНКРЕТНОМ случае?

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

A l e x пишет:

To v258. Надо бы проверить. Но: не знаю как внутри библиотеки все организовано, но нужно, чтобы код пришел. Пока не вернется считанное значение программа будет ждать.

Тут могут быть два возражения: а) а вы уверены, что не может вернуться нулевое значение, например, по истечении таймаута? и б) а вы уверены, что значение в EEPROM именно обнуляется, а не записывается левым значением?

ЗЫ: кстати, можно попробовать после настройки, когда в EEPROM записано нужное значение, закомментировать в коде строку 66 и перепрошить плату. И посмотреть, останется ли проблема. Вы же не каждый день этой кнопкой пользуетесь? ))

A l e x пишет:

Прерывания используются для минимизации потребления в ждущем режиме (ток уменьшается примерно на 10 мА).

А проблема возникает, когда схема работает или находится в ждущем режиме?

ЗЫ: если схема питается от сетевого источника, то какой смысл в экономии 10мА?

vladsf
Offline
Зарегистрирован: 30.03.2021

А может все дело во фьюзах. У Вас какое питание и какие установлены фьюзы (bodlevel0,1,2)

vladsf
Offline
Зарегистрирован: 30.03.2021

А может все дело во фьюзах. У Вас какое питание и какие установлены фьюзы (bodlevel0,1,2)

A l e x
Offline
Зарегистрирован: 13.02.2021

To v258. Начну, по обыкновению, с конца. Смысл в 10 мА - малюсенькая экономия электроэнергии: если есть возможность, то почему бы и не сэкономить. Тем более, когда подобных устройств, как у меня, десяток - это уже 0,1 А.

Что записывается в EEPROM я вообще не знаю.  Можно при небольшой модификации программы в консольке посмотреть. Но что мне это даст? Ну ноль, ну случайная величина. Тем более, что сбои могут пару раз в день быть, с могут и через пару дней.
Внутреннее сопротивление PULL-UP, ЕМПНИП, 20-50 КОм. В принципе на нем 1,5-2 В могут навестись, и, что же теперь вообще от прерываний в устройствах отказаться?

Надо как-то помехоустойчивость повышать. 

A l e x
Offline
Зарегистрирован: 13.02.2021

To vladsf. Прошивал через USB-TTL преобразователь, соответственно, состояние фьюзов не знаю, что китайцы по умолчанию выставили, то и осталось. Можно посмотреть ISP-ом. Но, опять же, контроллер не просто сбрасывается по питанию, а или, как предположил v258 (спасибо!) вызывается прерывание для записи в память, или что-то ещё память сбрасывает.

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

A l e x пишет:

To v258. Начну, по обыкновению, с конца. Смысл в 10 мА - малюсенькая экономия электроэнергии: если есть возможность, то почему бы и не сэкономить. Тем более, когда подобных устройств, как у меня, десяток - это уже 0,1 А.

0,1А * 5В = 0,5Вт * 24 = 12Вт/ч * 30 = 0,36кВт/ч в месяц. Потребление на уровне погрешности. Я бы не заморачивался ))

A l e x пишет:

Что записывается в EEPROM я вообще не знаю.  Можно при небольшой модификации программы в консольке посмотреть. Но что мне это даст? Ну ноль, ну случайная величина. Тем более, что сбои могут пару раз в день быть, с могут и через пару дней.

Это даст понимание, что происходит. И главное, попробуйте таки закомментировать строку 66 после настройки и перепрошить плату. Чтобы у МК вообще не было возможности поменять значение в EEPROM. Собственно, если вы не планируете менять аэромышь, то смысла в постоянной возможности настройки кодов нет в принципе - раз настроил и забыл