Переполнение millis()

Тема в разделе "Arduino & Shields", создана пользователем yden, 25 авг 2016.

  1. yden

    yden Гик

    Здравствуйте.
    Поделитесь опытом, плиз. Кто как обходил проблему переполнения millis()?

    Спасибо
     
  2. DIYMan

    DIYMan Guest

    Нет такой проблемы, поэтому обходить нечего. Главное, правильно писать код:

    Код (C++):
    if(millis() - lastMillis > interval)
    {
    // тут работа по интервалу
    }
    И всё. Ключевые слова - беззнаковая арифметика, в википедии есть статья, ознакомьтесь ;)
     
    Tomasina, 9xA59kK и yden нравится это.
  3. AlexU

    AlexU Гуру

    Проблемы нет до тех пор пока необходимое значение для переменной 'interval' помещается в 32 бита (тип unsigned long), т.е. примерно 49..50 дней. А если нужны интервалы с более длительным сроком?
    То в таком случае пока вижу два решения:
    1. внешний модуль RTC;
    2. реализация функционала RTC с использованием вычислительных мощностей микроконтроллера.
    В первом случае достоинства: не расходуются вычислительные мощности микроконтроллера, микроконтроллер можно отправлять в спячку для экономии эл.энергии. Недостатки: дополнительные финансовые затраты, модуль нужно будет подключать к мк, соответственно потребуется пара выводов мк.
    Во втором случае наоборот: дополнительных финансовых затрат нет, пины все остаются свободными, но мк должен постоянно молотить, соответственно энергосбережение уже сложнее реализовать и тратятся вычислительные ресурсы микроконтроллера (но увеличение затрат ресурсов приемлемое, по крайней мере на скорость работы мк почти не влияет)
     
    VitalyS и yden нравится это.
  4. yden

    yden Гик

    Благодарю. Я такую структуру и использую.
    Но через примерно день-два перестает работать код:
    Код (C++):
    void function_led_button1 (uint16_t time_0, uint16_t time_1)
    {
      static bool state = 0;       // состояние светодиода
      static uint32_t future = 0;  // время будущего перескока на другое состояние
      if (millis() < future) return; // если время не пришло(нужное значение millis() ) то выйти из функции
      state = !state;              // время смены пришло поменяйте состояние
      future += state ? time_0 : time_1 ;
      // определите новое время для перескока если state = 1 то future=future+time_0
      //                                      если state = 0 то future=future+time_1
      digitalWrite(led_button1, state);
    }
    Этим кодом управляется моргание светодиодом.
    Вызов:
    Код (C++):
    //мигание светодиодом кнопки1
      if (flag_button1 == 1)
      {
        function_led_button1(250, 500);
      }

      if (off_light_holl == 1)
      {
        function_led_button1(250, 1000);
      }
      if ((off_light_holl == 0) && (flag_button1 == 0))
      {
        digitalWrite(led_button1, HIGH);
      }
    Должно сработать первое условие, но на деле - третье (светик горит постоянно). Хотя значение переменных отслеживал в мониторе - норма.
     
    Последнее редактирование: 25 авг 2016
  5. qwone

    qwone Гик

    Попробуй этот код.
    Код (C++):
      switch (flag_button1+off_light_holl+off_light_holl) {
        case 0:
          digitalWrite(led_button1, 1);
          break;  
        case 1:
          function_led_button1(250, 500);
          break;
        case 2:
          function_led_button1(250, 1000);
          break;
      }
    Но скорее у вас что-то с кнопками. Переполнения там точно не должно быть
     
  6. DIYMan

    DIYMan Guest

    Предлагаю подумать, будет ли обновляться состояние светодиода, если flag_button1 == 0 ;) Вы один раз вызываете функцию function_led_button1 при зажатой кнопке, а потом благополучно про ней забываете. При этом, если off_light_holl == 0, и flag_button1 == 0 - вы совершенно очевидно зажигаете светодиод. И получаете ровно то поведение, которое запрограммировали - светодиод будет гореть постоянно, с высокой долей вероятности (поскольку он может и мигать, если держать кнопку зажатой очень долго).

    Перепишите логику, и всё заработает как надо ;)
     
  7. yden

    yden Гик

    Не совсем так.
    Код (C++):
    //диммер кладовка
      if (((time.Hours < 0 || time.Hours > 10) && sun_klad_2 == false && off_light_holl == false) || (flag_button1 == true))
      {
        //включаем диммер
        digitalWrite(outPin5, HIGH);
        light_klad = true;
      }
      else
      {
        light_klad = false;
        digitalWrite(outPin5, LOW);
      }
    flag_button1 - своего рода метка, что была нажата кнопка и она будет 1 определенное время или пока повторно не нажата кнопка. Эта метка в числе других включает реле.
    Вот код, устанавливающий значение flag_button1:
    Код (C++):
    //проверка состояния кнопки1 - холл
      if ( bouncer1.update() )
      {
        //если считано значение 1
        if ( bouncer1.read() == HIGH)
        {
          if (light_holl == false)
          {
            previousMillis_button1 = currentMillis_button1;
            off_light_holl = false;
            flag_button1 = true;
          }
          if (light_holl == true)
          {
            if (flag_button1 == true)
              flag_button1 = false;
            else off_light_holl = true;
          }
        }
      }

      if (flag_button1 == true)
      {
        if (currentMillis_button1 - previousMillis_button1 > interval_button1)
        {
          flag_button1 = false;
        }
      }
    Оба фрагмента кода находятся в loop
     
  8. yden

    yden Гик

    Суть этого "безобразия": свет зажигается\гаснет или по времени (часы), или по значению датчика света, или принудительно с помощью кнопки (при этом горит он 30 минут).
     
  9. VitalyS

    VitalyS Нуб

    Давайте немного подробнее. Функция millis() использует timer0 микроконтроллера и возвращает 4х байтовое число без знака, максимальное возвращаемое значение помещается в тип переменной unsigned long (целое число без знака) такое значение может принимать максимальное значение 4 294 967 295 миллисекунд, что соответствует примерно 49 дням и 17 часам, я понимаю, что значение возвращаемое данной функцией обнулится по истечение данного времени непрерывной работы микроконтроллера.
    Чтобы избежать ошибки цикла программ завязанных на данный таймер, предлагаю такое решение:
    Код (C++):
    /****************************************************************************
    Включаем светодиод на 13 пине на одну секунду через заданный интервал времени
    **************************************************************************/

    //Объявляем гобальную переменную для хранения времени последнего срабатывания счетчика:
    unsigned long previousMillis = 0;
    //объявляем куда подключен светодиод:
    #define ledPin 13

    void setup() {
        pinMode(ledPin, OUTPUT);
    }

    void loop() {
      //узнаем текущее время непрерывной работы МК в миллисекундах:
      unsigned long currentMillis = millis();
      /************************************************************************
      Проверяем условие - при переполнении millis() и обнулении значения currentMillis
      условие сработает и мы корректируем значение времени последнего previousMillis:
      ***********************************************************************/

      if(previousMillis > currentMillis){
        previousMillis = currentMillis;
      }
      /***********************************************************************
      Далее включаем светодиод не ранее раз в 360 сек или 6мин,
      условие истинно пока не обнулится значение переменной currentMillis далее без
      корректировки значения previousMillis условие будет ложно
      ***********************************************************************/

      if(currentMillis - previousMillis > 360000){
        // запоминаем время включения светодиода
        previousMillis = currentMillis;
        digitalWrite(ledPinOn,HIGH);
        delay(1000);
        digitalWrite(ledPinOn,LOW);
        }
    }
    По идее таким образом все должно работать без ошибок, даже если непрерывное время работы микроконтроллера превышает 50 дней. Остается одна незначительная проблемка - при обнулении времени пропускается один цикл срабатывания, как это избежать пока не догадался.
     
    Последнее редактирование: 19 ноя 2016
  10. qwone

    qwone Гик

    VitalyS Все это понятно.но зачем вы запулили delay() в программу где идет millis()? ума что ли не хватило убрать и это безобразие.
     
  11. AlexU

    AlexU Гуру

    Этот кусок кода:
    Код (C++):
    /************************************************************************
      Проверяем условие - при переполнении millis() и обнулении значения currentMillis
      условие сработает и мы корректируем значение времени последнего previousMillis:
      ***********************************************************************/

      if(previousMillis > currentMillis){
        previousMillis = currentMillis;
      }
    в некоторых случаях внесёт ошибку в логику работы программы. В Вашем случае:
    если светодиод один раз в 49 дней загорится не через 360 сек, а через, например, через 719 сек, то это не нарушит логики "не ранее 360 сек". Но в некоторых случаях нужно точно соблюдать период, и в таких случаях Ваше предложение не применимо. Это первое.
    Второе -- в данном случае имеем дело с беззнаковыми числами. И, если 'previousMillis' станет больше 'currentMillis', это не нарушит логику программы -- разница этих значений будет такой какой нужно. Поэтому проверять, стало ли 'previousMillis' больше, чем 'currentMillis', не нужно -- это лишнее.
    Третье -- если нужно считать время более, чем 49 дней, то в стандартной библиотеке avr-libc (во второй версии точно) есть реализация RTC (заголовочный файл time.h). После небольшой правки обработчика прерывания Timer0 AVR-ка будет подсчитывать время хоть до "второго потопа" (хотя надо посмотреть нет ли каких ограничений, например, в виде десятка веков).
     
  12. VitalyS

    VitalyS Нуб

    Можно и убрать, но в данном коде это приемлемо т.к. никаких других задач, кроме моргания светодиодом нет и на таймер это, по-моему, никак не влияет.
     
  13. VitalyS

    VitalyS Нуб

    Можно подробнее?
    По первому и третьему случаю согласен, а вот со вторым - нет.
    Мне кажется нарушит логику, т.к в случае обнуления previousMillis = 4 294 967 295 а currentMillis = 0 так ведь? если приводим к unsigned long значение выражения (0-4294967295) Условие все равно ложно и срабатывание светодиода, на мой взгляд не произойдет.
     
    Последнее редактирование: 19 ноя 2016
  14. qwone

    qwone Гик

    вот скетч.который имитирует millis()
    Код (C++):
    uint32_t mil = 0xFFFFFFF2;
    uint32_t old_mil ;
    uint8_t interval = 10;
    void setup() {
      Serial.begin(9600);
      old_mil = mil;
      for (int i = 0; i < 40; i++) {
        if (mil - old_mil > interval) old_mil = mil;
        Serial.print( mil, HEX);
        Serial.print(" ");
        Serial.print(old_mil, HEX);
        Serial.print(" ");
        mil++;
        Serial.print((mil - old_mil > interval));
        Serial.println(" ");
      }
    }

    void loop() {
    }
    а по поводу
    Можно и убрать, но в данном коде это приемлемо т.к. никаких других задач, кроме моргания светодиодом нет и на таймер это, по-моему, никак не влияет.
    влияет и очень сильно на ваши ,прости госпади,мозги. Получается одна нога у вас на лыже, а другая на коньках и в результате херня. delay() нужен для упрощения демонтрационого кода. А millis() освобождения процессора от ненужного стояния и выполнения другой работы.
     
  15. AlexU

    AlexU Гуру

    С точки зрения беззнаковых чисел 0-4294967295 = 1 (или 0x00000000 - 0xFFFFFFFF = 0x00000001). Что соответствует действительности -- после последнего запоминания 'previousMillis' прошла всего 1 мсек. И условие не сработает потому, что должно пройти 360 сек, а не 1 мсек. Условие сработает при 360000 - 4294967295 = 360001.
     
  16. VitalyS

    VitalyS Нуб

    Я почему-то думаю, что "0" это целое число и unsigned long a = 0-4294967295; a будет равно нулю. Разве не так? Пойду проверю на скетче....
    Проверил...
    unsigned long a = 0-4294967295;
    результат а = 1.
    согласен, оч. интересно.
    Получается, что описанной проблемы с millis() вообще нет?
     
    Последнее редактирование: 19 ноя 2016
  17. qwone

    qwone Гик

    а вы составте тестовый тест и испытайте. Программирование это практическая работа. Результат всег можно увидеть в железе.
    А вот альтернатива вашему коду но без delay()
    Код (C++):
    /*
    светодиод -> 13 (Led_pin) 1 горит/ 0 нет
           GND ->  GND
    */

    const int Led_pin = 13 ; // нога светодиода
    uint8_t Led  ;
    void setup() {
      pinMode(Led_pin, OUTPUT);
      digitalWrite(Led_pin, Led = 0);
    }
    void loop() {
      //#1
      static uint32_t past_1 = 0 ;
      static uint32_t past1_1 = 0 ;
      if (Led&&(millis() - past1_1 >= 1000)) { // если светодиод горит 1 сек
        digitalWrite(Led_pin, Led = 0);//то выключить
      }
      if (millis() - past_1 >= 360000) { // если прошло 360 сек
        past_1 = millis() ;
        digitalWrite(Led_pin, Led = 1); // включить светодиод
        past1_1 = millis() ;
      }
    }
     
    VitalyS нравится это.
  18. AlexU

    AlexU Гуру

    Есть, если время "ожидания" больше, чем 49 "с копейками" суток, т.е. больше 4294967295 (или 0xFFFFFFFF). Но я с такой ситуацией не сталкивался.
     
  19. VitalyS

    VitalyS Нуб

    Вот только разобрали все выше в форуме - получается, что нет проблемы ни какой, или объясните какая есть? На мой взгляд проблема только одна - невозможно задать цикличность события больше 49 с копейками дней.
     
    Последнее редактирование: 19 ноя 2016
  20. AlexU

    AlexU Гуру

    Да, проблема одна. Именно о ней и сообщил ранее.