millis() зависает

knik777
Offline
Зарегистрирован: 26.09.2015

Добрый день!

Подскажите, пожалуйста, в какую сторону копать!

Вот этот кусок кода отлично работает, если поставить 10, 20, 30 секунд в условии. Но если ставим 40 секунд или больше, то во первых условие не срабатывает, а во вторых программа как бы подвешивается. И чтобы она заработала снова, нужно пару раз похлопать в ладоши, активизируя соответствующее условие двойного хлопка.

   if ((millis()-globaltimeout)>1000*40) //принудительное выключение через 1 час бездействия
   {
     ledstate=0;
   }

ниже привожу весь полный код моей программулины )


int ledpin=13;
int micPin=A0; // датчик шума подключен через аналоговый вход
int sensorPin=2; // датчик движения
int relePin=3; // реле включает свет
int analogData=0; //тут будут храниться данные с АЦП пин 0
int ledstate=0; //переменная состояния вкл-выкл при хлопках
int ledstate2=0; // переменная состояния вкл-выкл для датчика движения
int ledstate3=0;
int maxvalue=250; // настройки моего конкретного микрофона, уровень громкости
unsigned long timeoff;   // отслеживается 15 секунд без движения и выключает свет
unsigned long globaltimeout; // отслеживает 1 час работы и выключает свет

void setup() {
  pinMode(ledpin, OUTPUT);
  pinMode(micPin, INPUT);
  pinMode(sensorPin, INPUT);
  pinMode(relePin, OUTPUT);
  digitalWrite(13,0);
  digitalWrite(relePin,1); //реле свет выключен с самого начала
  globaltimeout=millis();
}
   
  void loop()
{
  if (digitalRead(sensorPin)==1) // если сработал датчик движения
  {
    timeoff=millis(); // засекаем время когда он сработал
    ledstate2=1; // свет включить
  }
  else
  {
    if (millis()-timeoff>1000)  // если с момента включения по датчику движения прошло более 15 секунд
    {
      ledstate2=0; // свет выключить
    }    
  }      
  analogData=analogRead(micPin); //читаем значение
  if(analogData>maxvalue)
  {
    delay(200); //ожидаем 200милисекунд для повторного хлопка - можно не делать но в моём случае это добавило удобства
    for(int t=0;t<=500;t++) //цикл на проверку второго хлопка по идее на пол секунды но ардуино делает его дольше
    {
      delay(1); //для цикла
      analogData=analogRead(micPin);//считываем данные
      if(analogData>maxvalue)
      {  //сверяем данные для второго хлопка, если он был то меняем состояние лампы с вкл на выкл и наоборот
        ledstate=!ledstate; //меняем состояние
        analogData=0;//обнуляем данные
        globaltimeout=millis(); // запоминаем время срабатывания по двойному хлопку
        break;//выходим из цикла после второго хлопка
        delay(200);//для удобства
        }
     }
     analogData=0;
   }
   if ((millis()-globaltimeout)>1000*40) //принудительное выключение через 1 час бездействия
   {
     ledstate=0;
   }
  ledstate3=(ledstate || ledstate2);
  digitalWrite(13,ledstate3); //включаем выключаем светодиод на 13м пине
  digitalWrite(relePin,!ledstate3); // включаем выключаем свет через реле
}

 

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

Комментарии писались для введения противника в заблуждение? Похоже на то.

Тут у Вас много чего намешано и кроме того условия. Давайте с начала.

Итак. самое начало времён. датчик движения пока ещё не сработал. Проверка в строке 28 даёт ложь (кстати, почему 1, а не HIGH?) и управление получает else-участок - строки 35-38. Здесь протсходит сравнение

if (millis()-timeoff>1000)

Но ведь timeoff пока ещё не получила НИКАКОГО значения! Результат операции непредсказуем. Можно, конечно, предположить, что после перезагрузки МК там 0, но ... так не делается.

Теперь про Ваше "зависание".

Переменная globaltimeout получает значение в setup, а потом только при двойном хлопке. Это означает, что если условие в строке 59 единожды сработало, оно будет продолжать срабатывать в каждом такте пока не появится двойной хлопок, т.е. пока не поменяется переменная globaltimeout. Отсюда странное поведение. Думаю, что она должна как-то меняться внутри условия (при срабатывании), но это Вам виднее т.к. я не знаю чего Вы хотите достичь.

 

knik777
Offline
Зарегистрирован: 26.09.2015

Евгений, спасибо за подсказки!

Теперь присваиваю значение переменной timeoff в сетапе.

1 и 0 поменял а HIGH и LOW (думал что это одно и то же)...

globaltimeout именно должна меняться вне условия :) Тут все верно. Переменная отслеживает, что если прошло больше часа с момента последнего хлопка, то свет выключаем.

Насчет глюка - похоже, дело оказалось в преобразовании типов переменных, про которые я прочитал у автора LESHAK. Спасибо ему! Он предлагает определять значения с явным указанием типа. По крайней мере на 60 секундах мне это помогло! Сейчас проверяю на 1 часе.

Вот ссылка на тему Leshaka:

http://arduino.ru/forum/programmirovanie/eshche-raz-migaem-svetodiodom-b...

knik777
Offline
Зарегистрирован: 26.09.2015
int ledpin=13;
int micPin=A0; // датчик шума подключен через аналоговый вход
int sensorPin=2; // датчик движения
int relePin=3; // реле включает свет
int analogData=0; //тут будут храниться данные с АЦП пин 0
int ledstate=LOW; //переменная состояния вкл-выкл при хлопках
int ledstate2=LOW; // переменная состояния вкл-выкл для датчика движения
int ledstate3=LOW;
int maxvalue=250; // настройки моего конкретного микрофона, уровень громкости
unsigned long timeoff;   // отслеживается 15 секунд без движения и выключает свет
unsigned long globaltimeout; // отслеживает 1 час работы и выключает свет
#define  INTERVAL  60*60*1000UL           // интервал между включение/выключением светодиода (1 секунда)
#define  INTERVAL2  20*1000UL           // интервал между включение/выключением светодиода (1 секунда)

void setup() {
  pinMode(ledpin, OUTPUT);
  pinMode(micPin, INPUT);
  pinMode(sensorPin, INPUT);
  pinMode(relePin, OUTPUT);
  digitalWrite(13,LOW);
  digitalWrite(relePin,HIGH); //реле свет выключен с самого начала
  globaltimeout=millis();
  timeoff=millis();
}
   
  void loop() 
{
  if (digitalRead(sensorPin)==1) // если сработал датчик движения
  {
    timeoff=millis(); // засекаем время когда он сработал
    ledstate2=HIGH; // свет включить
  }
  else
  {
    if (millis()-timeoff>INTERVAL2)  // если с момента включения по датчику движения прошло более 20 секунд
    {
      ledstate2=LOW; // свет выключить
    }    
  }      
  analogData=analogRead(micPin); //читаем значение
  if(analogData>maxvalue)
  {
    delay(200); //ожидаем 200милисекунд для повторного хлопка - можно не делать но в моём случае это добавило удобства
    for(int t=0;t<=500;t++) //цикл на проверку второго хлопка по идее на пол секунды но ардуино делает его дольше
    {
      delay(1); //для цикла
      analogData=analogRead(micPin);//считываем данные
      if(analogData>maxvalue)
      {  //сверяем данные для второго хлопка, если он был то меняем состояние лампы с вкл на выкл и наоборот
        ledstate=!ledstate; //меняем состояние
        analogData=0;//обнуляем данные
        globaltimeout=millis(); // запоминаем время срабатывания по двойному хлопку
        break;//выходим из цикла после второго хлопка
        delay(200);//для удобства
        }
     }
     analogData=0;
   }
   if ((millis()-globaltimeout)>INTERVAL) //принудительное выключение через 1 час бездействия
   {
     ledstate=LOW;
   }
  ledstate3=(ledstate || ledstate2); 
  digitalWrite(13,ledstate3); //включаем выключаем светодиод на 13м пине
  digitalWrite(relePin,!ledstate3); // включаем выключаем свет через реле
}

 

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

чой-то я не пойму как это должно работать:

ledstate3=(ledstate || ledstate2);

 

knik777
Offline
Зарегистрирован: 26.09.2015

у меня два датчика: датчик шума и датчик движения.

если хотя бы один из них сработал, то свет включен. если оба не работают. то свет выключен.

|| - это логическое ИЛИ

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Есть такая бяда у этой функции, но вряд ли вы именно на этот случай напоролись. :)

Дело в том, что она считает .. "скачками", а именно: каждые 42 миллисекунды добавляется не 1, а сразу 2 миллисекунды за счет "накопления". Поэтому у неё не бывает 42 .. 84 и т.д. миллисекунд (или около того, инкремент остатка по 24 микросекунды..), далее со сдвигом в 8 микросекунд каждое накопление. Ваще-то. 40-ю - она отсчитает вполне сносно. Ещё это приводит к "плаванию" миллисекунд +- 2мсек с периодом в 41 миллисекунду от реального времени. И только.

Проверяйте millis() на вариант >= ... , а не просто == или на % (остаток от деления).

Скетч не смотрел, но думаю что это, все-таки не ваш случай.. смотрите что ещё может быть не так.

Nurbol
Offline
Зарегистрирован: 15.07.2013

попробуйте избавиться с delay(), так как delay(200) в пять раз в цикле уже отставания на 1сек.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Действительно если вы работаете с переменной которую сами изменяете
то она обязательно станет равна (==) чему-то.
Она меняется " монотонно" в нужную вам строну

А вот с millis() лучше использовать >=  или <=
так как для вас в момент считывания ( в почти случайное время) она меняется "скачками"
и как правильно заметил Arhat109-2 ещё и "скачками" разной величины.
 

knik777
Offline
Зарегистрирован: 26.09.2015

Всем спасибо за советы!

Проблема решилась. Дело было действительно в неверном преобразовании типов данных, как я и писал выше. См. пояснения от Leshak по ссылке в моем посте выше.

Вот конечный работающий код, если кому интересно:

 

#define  INTERVAL  60*60*1000UL           // интервал принудительного отключения света при бездействии 1 час
#define  INTERVAL2  20*1000UL           // интервал таймаута по датчику движения 20 секунд
#define LEDPIN 13
#define MICPIN A0 // датчик шума подключен через аналоговый вход
#define SENSORPIN 2 // датчик движения
#define RELEPIN 3 // реле включает свет
#define MAXVALUE 250 // настройки моего конкретного микрофона, уровень громкости

int analogData=0; //тут будут храниться данные с АЦП пин 0
boolean ledstate=LOW; //переменная состояния вкл-выкл при хлопках
boolean ledstate2=LOW; // переменная состояния вкл-выкл для датчика движения
boolean ledstate3=LOW;

void setup() {
  pinMode(LEDPIN, OUTPUT);
  pinMode(MICPIN, INPUT);
  pinMode(SENSORPIN, INPUT);
  pinMode(RELEPIN, OUTPUT);
  digitalWrite(LEDPIN,LOW);
  digitalWrite(RELEPIN,HIGH); //реле свет выключен с самого начала
}
   
  void loop() 
{
static unsigned long timeoff=millis();   // отслеживается 15 секунд без движения и выключает свет
static unsigned long globaltimeout=millis(); // отслеживает 1 час работы и выключает свет

  if (digitalRead(SENSORPIN)==1) // если сработал датчик движения
  {
    timeoff=millis(); // засекаем время когда он сработал
    ledstate2=HIGH; // свет включить
  }
  else
  {
    if (millis()-timeoff>INTERVAL2)  // если с момента включения по датчику движения прошло более 20 секунд
    {
      ledstate2=LOW; // свет выключить
    }    
  }      
  analogData=analogRead(MICPIN); //читаем значение
  if(analogData>MAXVALUE)
  {
    delay(200); //ожидаем 200милисекунд для повторного хлопка - можно не делать но в моём случае это добавило удобства
    for(int t=0;t<=500;t++) //цикл на проверку второго хлопка по идее на пол секунды но ардуино делает его дольше
    {
      delay(1); //для цикла
      analogData=analogRead(MICPIN);//считываем данные
      if(analogData>MAXVALUE)
      {  //сверяем данные для второго хлопка, если он был то меняем состояние лампы с вкл на выкл и наоборот
        ledstate=!ledstate; //меняем состояние
        analogData=0;//обнуляем данные
        globaltimeout=millis(); // запоминаем время срабатывания по двойному хлопку
        break;//выходим из цикла после второго хлопка
        delay(200);//для удобства
        }
     }
     analogData=0;
   }
   if ((millis()-globaltimeout)>INTERVAL) //принудительное выключение через 1 час бездействия
   {
     ledstate=LOW;
   }
  ledstate3=(ledstate || ledstate2); 
  digitalWrite(LEDPIN,ledstate3); //включаем выключаем светодиод на 13м пине
  digitalWrite(RELEPIN,!ledstate3); // включаем выключаем свет через реле
}

 

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

knik777, да действительно, там 40 тысяч - т.е. переход через границу int, я этого не заметил. Впрочем, и хорошо , что не заметил. От того, что Вы нашли сами пользы много больше, чем если бы я сказал.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

А где там граница int ?
Я тоже смотрел на эти 40*1000, но вроде это не переменная, поэтому тип  и разрядность описывать негде.

Разве что  400000UL или 40*1000UL

knik777
Offline
Зарегистрирован: 26.09.2015

Евгений, а я про 40 тыщ понятия не имел. просто взял один-в-один и применил, как советовал Лешак. Оно заработало. И это для меня главное. Принцип понял, что надо следить за соответствием типов в будущем. а дальше лень разбираться :)

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

32767 - int больше не бывает.

knik777
Offline
Зарегистрирован: 26.09.2015

только сейчас дошло!!!  :)

int это диапазон от -32768 до 32767.

поэтому ставлю 10000-30000 мс, все работает. А на 40000 мс реально сваливалось.

PS 40000 мс есть только в первом листинге. Это было тестовое значение. Сейчас оно заменено на 1 час и приведено к unsigned long (то есть 60*60*1000UL)

Действительно, какой изящный оказался глюк )

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

knik777 пишет:

Евгений, а я про 40 тыщ понятия не имел. просто взял один-в-один и применил, как советовал Лешак. Оно заработало. И это для меня главное. Принцип понял, что надо следить за соответствием типов в будущем. а дальше лень разбираться :)

Вам лучше это понять.

Если писать просто числа без специальных суффиксов, то считается, что у них тип int. Но числа этого типа не бывают больше, чем 32767. Поэтому 40*1000 получится вовсе не 40000 как можно было бы ожидать. Другое дело, если написать 40L*1000L (собственно достаточно L в одном месте).

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

trembo пишет:

А где там граница int ?
Я тоже смотрел на эти 40*1000, но вроде это не переменная, поэтому тип  и разрядность описывать негде.

Разве что  400000UL или 40*1000UL

Примерно так и надо. Можно просто L - достаточно.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Вообще-то millis() имеет тип unsigned(!) long. Соответсвенно проверки интервалов, даже в int желательно делать в unsigned .. и диапазон побольше 0..65365 и сравнение проще и вернее, и от millis() кумпилятыр отожрет что ему надо без вопросов, пардон "накладных расходов" на преобразование. :)