Счетчик millis()

Тема в разделе "Arduino & Shields", создана пользователем FlameWind, 19 июл 2016.

Метки:
  1. FlameWind

    FlameWind Нерд

    Приветствую всех. Помогите разобраться.
    Есть один скетч вывода на TFT различных показаний, в нем использовал отсчет через millis. Обновление данных каждую секунду:
    Код (C++):

    в Setup:
      currentTime = millis();       // считываем время, прошедшее с момента запуска программы
      MainTime = currentTime;
    в Loop:
    currentTime = millis();

    if (currentTime >= (MainTime + 1000) or Redraw) {
      MainTime = currentTime;
      ...Код...
    Все прекрасно отрабатывается ровно через секунду.
    Потом написал другой скетч. В Setup все тоже самое, в Loop:
    Код (C++):

    currentTime = millis();

    if (currentTime >= (RegTime + 10000) and Done) {
       RegTime = currentTime;
         Randomizer(Min, Max);
       }
     
    Срабатывает раньше чем через 10 секунд.
    Так же пробовал и с таким вариантом:
    Код (C++):

    if (Done) {
       digitalWrite(13, HIGH);
       if (currentTime >= (RegTime + 10000)) {
       RegTime = currentTime;
        digitalWrite(13, LOW);
        Randomizer(Min, Max);
       }
      }
    Судя по светодиоду, пролетает то за 5 секунд, то за 3.
    Вобщем мне нужно чтобы таймер запускался только после того как будет выполнено условие Done.

    PS В каком месте правильнее ставить Time = currentTime, сразу после if, или в конце на выходе из if?
    PPS Можно ли использовать в if - and, or, или только &&, ||? Компилятор не ругается.
     
  2. DIYMan

    DIYMan Guest

    Можно, это стандартные альясы операторов && и || в С++.

    По поводу работы с интервалами: надеюсь, currentTime и MainTime у вас имеют тип unsigned long, это, что называется, раз. Два - так работать с беззнаковыми числами, как работаете вы, замеряя интервал - неправильно. Вкратце, не вдаваясь в особенности беззнаковой арифметики, правильный вариант замера интервала между двумя беззнаковыми числами таков:

    Код (C++):
    unsigned long lastTime = millis();

    ......
    unsigned long curTime = millis();
    if(curTime - lastTime >= interval)
    {
      lastTime = curTime;
      ....
    }
     
  3. FlameWind

    FlameWind Нерд

    У меня они объявлены как uint32_t. Правильно ли я понял из вашего ответа, что назначение lastTime нужно делать сразу после входа в if?
    lastTime в Setup объявляется, или в начале Loop?
    curTime назначать один раз в начале Loop, или перед каждым if где проверяется таймер?
     
  4. ostrov

    ostrov Гуру

    Я предпочитаю так:
    Код (C++):
    unsigned long curTime = millis() + interval;
    if(curTime < millis())
    {
    ...
    curTime  = millis() + interval;;
    }
    Мелочи, но на один оператор в сравнении меньше, стало быть теоретически быстрее и короче.
     
  5. DIYMan

    DIYMan Guest

    Неправильно предпочитаете ;) Смотрите: millis отдала значение, близкое к максимальному для unsigned long (скажем, на 1 меньше). Вы прибавили interval, и в curTime у вас стало, из-за переполнения, минимальное значение. Поэтому ваш код отработает правильно в этом случае, но - неправильно в других, когда millis() + interval ещё не достигли максимального значения для unsigned long.

    Я настаиваю, что единственно расово верный вариант проверки интервала при работе с беззнаковой арифметикой - следующий:

    Код (C++):
    if(millis() - lastTime > interval)
    {
      lastTime = millis();
    ....
    }
    Какие операторы сравнения юзать - неважно, можно "больше", можно "больше либо равно" - на быстродействие это не повлияет никак. А вот интервал всегда будет подсчитан верно, вне зависимости от значения millis(), т.е. даже в том случае, когда millis() между вызовами loop начнёт маслать с самого начала ;)
     
    Securbond нравится это.
  6. ostrov

    ostrov Гуру

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

    FlameWind Нерд

    А на мои вопросы никто так и не ответил =)
     
  8. DIYMan

    DIYMan Guest

    uint32_t - это то же самое, что unsigned long. Примеры правильной работы с интервалами я вам привёл. Какие именно вопросы вас интересуют?
     
  9. FlameWind

    FlameWind Нерд

    Это я знаю.
    И как быть если нужно несколько разных таймеров с разными интервалами?
     
  10. DIYMan

    DIYMan Guest

    Несколько переменных - это первый вариант. Простейший класс написать - это второй вариант. Какой именно для вас предпочтительней - тот и реализуйте. Я делаю несколько по-другому: в loop высчитываю дельту между предыдущим и текущим значением millis. Эту дельту скармливаю классам, которые внутри себя уже сами разбираются, что с ней делать. По итогу - для интервалов меньше чем 65535 миллисекунд нет надобности в каждом классе держать uint32_t, достаточно - uint16_t, экономия :)

    Выглядит это добро примерно так:

    Код (C++):

    unsigned long lastMillis;
    IntervalWorker worker(1000);
    void setup()
    {
    lastMillis = millis();
    }

    void loop()
    {
    unsigned long curMillis = millis();
    unsigned int delta = curMillis - lastMillis;
    lastMillis = curMillis;

    if(worker.Update(delta))
      {
        // интервал настал
      }
    }
     
    где класс для работы по интервалу может выглядеть примерно так:
    Код (C++):

    class IntervalWorker
    {
    public:
      IntervalWorker(uint16_t interval) : _interval(interval), _delta(0) {}
     
      bool Update(uint16_t dt)
      {
        _delta += dt;
       if(_delta >= _interval)
       {
         _delta -= _interval;
         return true;
       }
        return false;
      }

    private:
      uint16_t _interval;
      uint16_t _delta;
    };
     
    Более того - сделав абстрактный класс и определив виртуальную функцию, которая будет вызываться по достижению интервала - можно наплодить наследников класса, реализовав внутри каждого специфичный функционал. Т.е. подходов, как видите - over9000.

    З.Ы. Приведённый пример - тестовый и никакого отношения к реальной жизни не имеет, а призван лишь показать вариант подхода к решению проблемы.
     
    FlameWind нравится это.
  11. FlameWind

    FlameWind Нерд

    Все таймеры переписал по канону, но не выходит каменный цветок. Пролетает таймер...
    Опишу чего я хочу сделать: RGB лампа настроения. Функция выдает 3 рандомных значения RGB. Через таймеры со случайными задержками каждый цвет доползает до заданных значений, и сообщает true.
    И я хочу чтобы таймер на 5 секунд начинал отсчет только после того как все цвета выдали true. Прошло 5 секунд, и новый рандом RGB.
    Кусок кода с таймером на fade:
    Код (C++):

    // Green Fader
      curGreenTime = millis();
      if (curGreenTime - prevGreenTime >= FadeDelayG) {
        prevGreenTime = curGreenTime;
        if (G == CurG) { GDone = true; }
        else {
          if (G > CurG) {
            analogWrite(LG, 255-CurG);
            CurG++; }
          else {
            analogWrite(LG, 255-CurG);
            CurG--; }
        }
      }
     
  12. DIYMan

    DIYMan Guest

    Тогда вам надо включать таймер на 5 секунд только после того, как все цвета выдали true, очевидно же. Т.е. ввести флаг для последнего таймера, и включать его только тогда, когда остальные таймеры уже отработали. Вот доработанный класс:
    Код (C++):
    class IntervalWorker
    {
    public:
      IntervalWorker(uint16_t interval) : _interval(interval), _delta(0), _enabled(false), _continuousMode(true) {}
      bool Update(uint16_t dt)
      {
       if(!_enabled)
         return;

        _delta += dt;
       if(_delta >= _interval)
       {
         _delta -= _interval;
         setEnabled(!_continuousMode);
         return true;
       }
        return false;
      }

      void setEnabled(bool en) { _enabled = en; }
      void  setContinuousMode(bool cm) { _continuousMode = cm; }

    private:
      uint16_t _interval;
      uint16_t _delta;
      bool _enabled;
      bool _continuousMode;
    };
    Позволяет сделать однократное срабатывание таймера ( setContinuousMode(false) ), позволяет вкл/выкл таймер ( setEnabled(true/false) ).
     
  13. FlameWind

    FlameWind Нерд

    Так я вроде и включаю только после входа в if. Где косяк? Пробовал ставить curRegTime = millis(); и до входа в if, и после, пролетает всеравно.
    Код (C++):

    if (AllDone) {
       curRegTime = millis();
       digitalWrite(LL, HIGH);
       if (curRegTime - prevRegTime >= 5000) {
        prevRegTime = curRegTime;
        digitalWrite(LL, LOW);
        Randomizer(Min, Max);
       }
      }
    Я классы еще не вкурил. Только разбираюсь. Мне бы пока в таком варианте сделать чтобы работало нормально =)
     
  14. DIYMan

    DIYMan Guest

    Вы ими уже пользуетесь - всякие Serial и т.п. Что мешает поюзать ещё один?

    По теме: дайте полный код, плз.
     
  15. FlameWind

    FlameWind Нерд

    Пользоваться пользуюсь, но суть еще не познал. Что именно в них нужно ставить, что им передавать, и что забирать. Вообщем для чего их лучше всего использовать.

    Чего я пытаюсь получить. Яркость диода ползет до нужных значений, если это Regular цвет, то ждем 3 секунды, и рандомим новый. Если это Solid, то ждем 7 секунд, чтобы насладится цветом. Ну и новый рандом. LED13 использую для контроля входа и выхода из if.
    Код (C++):

    #define LR 3  // Red LED
    #define LG 5  // Green LED
    #define LB 6  // BLue LED
    #define LL 13 // L-LED

    // Byte Vars
    byte R, G, B;
    byte CurR = 0; // Текущая яркость
    byte CurG = 0;
    byte CurB = 0;
    byte Min = 0; // Пределы яркости
    byte Max = 255; //
    byte minlim = 37; // Лимиты для проверки
    byte AverLim = 127;
    byte MAXLIM = 207; // ---
    // Int Vars
    uint16_t FadeDelayR = 37;
    uint16_t FadeDelayG = 37;
    uint16_t FadeDelayB = 37;
    uint16_t RegDelay   = 3000; // Интервалы
    uint16_t SolidDelay = 7000;
    uint16_t curSolidTime, curRegTime, curRedTime, curGreenTime, curBlueTime; // Текущие таймеры
    uint16_t prevSolidTime, prevRegTime, prevRedTime, prevGreenTime, prevBlueTime;
    // Boolean Vars
    boolean Solid = false;
    boolean RDone, GDone, BDone, AllDone;
    boolean Debug = false;

    void setup() {
    // Setup Begin
      randomSeed(analogRead(0));
      pinMode(LR, OUTPUT);
      pinMode(LG, OUTPUT);
      pinMode(LB, OUTPUT);
      pinMode(LL, OUTPUT);
      Randomizer(Min, Max);  
      prevSolidTime = millis();
      prevRegTime = millis();
      prevRedTime = millis();
      prevGreenTime = millis();
      prevBlueTime = millis();
    // End of Setup
    }

    void loop() {
    // Loop Begin
      // Red Fader
      curRedTime = millis();
      if (curRedTime - prevRedTime >= FadeDelayR) {
        prevRedTime = curRedTime;
        if (R == CurR) { RDone = true; }
        else {
          if (R > CurR) {
            analogWrite(LR, 255-CurR);
            CurR++; }
          else {
            analogWrite(LR, 255-CurR);
            CurR--; }
        }
      }
      // Green Fader
      curGreenTime = millis();
      if (curGreenTime - prevGreenTime >= FadeDelayG) {
        prevGreenTime = curGreenTime;
        if (G == CurG) { GDone = true; }
        else {
          if (G > CurG) {
            analogWrite(LG, 255-CurG);
            CurG++; }
          else {
            analogWrite(LG, 255-CurG);
            CurG--; }
        }
      }
      // Blue Fader
      curBlueTime = millis();
      if (curBlueTime - prevBlueTime >= FadeDelayB) {
        prevBlueTime = curBlueTime;
        if (B == CurB) { BDone = true; }
        else {
          if (B > CurB) {
            analogWrite(LB, 255-CurB);
            CurB++; }
          else {
            analogWrite(LB, 255-CurB);
            CurB--; }
        }
      }

      if (RDone && GDone && BDone) { // Сверяем что все цвета вышли на заданные значения
        AllDone = true;
      }

      if (AllDone and !Solid) {  // Если все готовы, и это не сплошной цвет
       curRegTime = millis();
       digitalWrite(LL, HIGH);
       if (curRegTime - prevRegTime >= RegDelay) {
        prevRegTime = curRegTime;
        Randomizer(Min, Max);
        digitalWrite(LL, LOW);
       }
      }

      if (AllDone && Solid) { // Если все готовы и сплошной цвет
       curSolidTime = millis();
       digitalWrite(LL, HIGH);
       if (curSolidTime - prevSolidTime >= SolidDelay) {
        SolidTime = currentTime;
        Solid = false;
        Randomizer(Min, Max);
        digitalWrite(LL, LOW);
       }
      }

    // End of loop
    }

    void Randomizer(byte Min, byte Max) {  // Рандомайзер цвета с ограничителями, чтобы не выдавать цвета близкие к белому  и тд
    NewRand:
      R = random(Min, Max);
      G = random(Min, Max);
      B = random(Min, Max);
      if (R < minlim && G < minlim && B < minlim) goto NewRand;
      if (R > MAXLIM && G > MAXLIM && B > MAXLIM) goto NewRand;
      if (R < AverLim && G < AverLim && B < AverLim) goto NewRand;  // <
      if (R > AverLim && G > AverLim && B > AverLim) goto NewRand;  // >
      RandDelay(7, 37);
      if (R > MAXLIM && G < minlim && B < minlim) { RandDelay(37, 73); Solid = true; goto Done; }  // Red
      if (R < minlim && G > MAXLIM && B < minlim) { RandDelay(37, 73); Solid = true; goto Done; }  // Green
      if (R < minlim && G < minlim && B > MAXLIM) { RandDelay(37, 73); Solid = true; goto Done; }  // Blue
      if (R > MAXLIM && G > MAXLIM && B < minlim) { RandDelay(37, 73); Solid = true; goto Done; }  // Yellow
      if (R < minlim && G > MAXLIM && B > MAXLIM) { RandDelay(37, 73); Solid = true; goto Done; }  // Cyan
      if (R > MAXLIM && G < minlim && B > MAXLIM) { RandDelay(37, 73); Solid = true; goto Done; }  // Purple
    Done:
      RDone = false;
      GDone = false;
      BDone = false;
      AllDone = false;
    }

    void RandDelay(byte Min, byte Max) { // Рандомайзер фэйдеров
      FadeDelayR = random(Min, Max);
      FadeDelayG = random(Min, Max);
      FadeDelayB = random(Min, Max);
    }
     
     
    Последнее редактирование: 20 июл 2016
  16. DIYMan

    DIYMan Guest

    Сходу не разберёшься. Такой код, как у вас - очень трудно сопровождать, т.к. комментариев - ноль, декомпозиции - ноль, имена переменных - маловразумительные. Возможно, как у мну будет настроение - я покопаюсь, но сейчас - увы, сходу ничего подсказать не могу.
     
  17. FlameWind

    FlameWind Нерд

    Добавил комментарии. Все переменные в составе которых есть R, G, B, относятся к цвету. В любом случае, спасибо за помощь.
     
  18. DIYMan

    DIYMan Guest

    Так, давайте по-другому: ещё раз напишите весь алгоритм словами, я постараюсь накидать код, и вы сравните, что отличается в двух вариантах - вашем и моём, ок?
     
  19. FlameWind

    FlameWind Нерд

    1. Рандомим RGB. Цвета могут быть промежуточными, и сплошными (красный, фиолетовый и тд, флаг Solid)
    2. Плавно делаем fade яркость светодиода до этих значений, и говорим - готово
    3. Таймер один - если все цвета готовы, ждем мало (1-3 сек), и рандомим новые
    4. Таймер два - если цвета готовы, и у нас есть флаг Solid, ждем дольше (5-7 сек), и все сначала.
    У меня весь затык в таймерах. С delay все прекрасно работает, но не хочу использовать, так как буду добавлять еще другой код. Да и delay не комильфо =)
     
  20. DIYMan

    DIYMan Guest

    1. Навскидку, флаг Solid не нужен.
    2. По остальному - попробую набросать и отпишусь.

    Ещё вопрос: fade делать только вверх, вниз не надо?