Задался вопросом о delay()

Тема в разделе "Arduino & Shields", создана пользователем РусНекромант, 8 авг 2016.

  1. Тут есть люди которые не любят delay() я в принципе понимаю их когда delay вставляют по несколько штук подряд в коде. Я не представляю как при таком подходе можно написать что то более менее удобоваримое. Но считаю что не так страшен чёрт как его малюют. Но перейдём к примерам,

    Например нам нужно что то делать раз в 5 секунд. И нам не нужно делать что то раз в 5 секунд и раз в 1секунду
    Код (C++):
    unsigned long lastMillis=0;
    void loop()
    {
        unsigned long curMillis = millis();
        uint16_t dt = curMillis - lastMillis;
        lastMillis = curMillis;

        if (dt  >= 5000){
             //тут основная программа
        }
    }
    не проще ли написать вот так?

    Код (C++):
    void loop()
    {
             //тут основная программа
        delay(5000)
    }
    если всё же хотил что то делать раз в секунду а что то раз в 5 секунд то пишем так

    Код (C++):
    byte i=0;
    void loop()
    {
        //что то делаем раз в секунду
        if (i>4){
             //что то раз в 5-ть секунд
            i=0;
        }
         delay(1000);
    }
    Я о том что можно таким образом понизить частоту вызова кода. А так же энергопотребление так как пустой цикл всяко меньше чем вычисления сколько прошло времени, экономим на спичках конечно но в долгосрочной перспективе вполне вероятно прилично набежит mA.

    Плюс я немного не в курсе что будет когда millis() достигнет предела (из первого примера).
    Вероятно что начнётся отсчёт с нуля и в этом случае
    1. arduino переинициализируется и сбрасывает значения всех переменных? Переинициализация в некоторых проектах плачевно может сказаться, особенно если человек на это не рассчитывает(наверное в доках об этом написано я не читал и уверен больше половины не читали). В принципе это и на второй пример повлияет.
    2. Не сбрасывает и в итоге dt будет отрицательным? Тогда вообще мрак в алгоритме.
    Поясните кто в теме. Я не прав?

    P.S. первый пример взял(и творчески переработал) из скетча пользователя DIYMan
    P.S.S. unsigned long это около 49 дней не так уж и много для контроллера например управляющим день ото дня температурой в комнате.
     
    Последнее редактирование: 8 авг 2016
  2. DIYMan

    DIYMan Guest

    Если правильно проверять интервалы, то ничего страшного не будет - просто прочитайте про беззнаковую арифметику ;) Правильная проверка интервала - это такая:
    Код (C++):
    if(curMillis - lastMillis > interval)
    {
        doTheJob();
    }
    И ей пофиг на переполнение беззнаковых переменных, даже если вы отнимете от меньшего больший (тот случай, когда через 49 дней millis сбросится в 0) - результат будет корректный.

    По-моему, это самый часто обсасываемый вопрос по ардуино. И всё из-за того, что недосуг почитать теорию компьютерной арифметики ;)
     
  3. DIYMan

    DIYMan Guest

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

    Это я к тому, что всё зависит от конкретного проекта. И если вы бьётесь за реальное энергосбережение, то готовая ардуина - это не совсем то, что вам его обеспечит, имхо.
     
  4. Эта часть кода скопирована из вашего примера. Так то да проверка будет верной если не присваивать переменной значение.


    Ну это не реализовано я так понимаю на самой плате, мы не рассматриваем в данном случае какие то надстройки. Тем более не всегда приемлемо "засыпать".
    Мне попадалось несколько примеров(из большой разработки программного обеспечения, так сказать) когда писали код в расчёте что типа "я так умею и так работает, красиво", а на практике при изменении условий всё начинало тупить через пару лет.

    Я не хочу сказать что код из первого примера плох(после доработки), я хочу сказать если применять бездумно один подход то можно вляпаться. А бездумно его применять будут как раз те кто не знает альтернатив. Например я вот недавно выжрал оперативку на UNO, у меня много чего там понатыкано и даже уже не хватает выходов, пришлось оптимизировать. Это повлияло на удобочитаемость кода, но зато стало полегче. Переменная lastMillis конечно капля в 2кБ, но так по капле съедается и не по 1Гб(в других сферах конечно).

    Я лишь хочу найти приемлемую альтернативу, возможно кто то знает по лучше вариант чем delay или lastMillis. Возможно что то недокументированное.
     
  5. DIYMan

    DIYMan Guest

    Она одна на весь проект, 4 байта - куда ещё экономить? Экономить надо в других местах ;)

    Таймеры, вполне себе документировано.
     
  6. AlexU

    AlexU Гуру

    Всё зависит от разрабатываемого функционала. Если функционал однопоточный, то в применении delay() нет ничего страшного, даже если вызов этой функции будет в каждой второй строчке кода. А вот при многопоточном функционале применение функции delay() усложнит реализацию или вообще сделает невозможной. Пример однопоточного функционала -- есть некий датчик, который способен выдавать результат не чаще, чем раз в секунду, и показания этого датчика нужно выдавать на дисплей; вывод на дисплей занимает микрсекунду; соостветственно нужно обеспечить задержку, которую проще реализовать через delay(). Пример многопоточного функционала -- есть несколько датчиков (каждый со своей частотой выдачи результатов), есть клавиатура для управления устройством, есть дисплей для вывода информации; на каждый датчик нужен свой поток, на клавиатуру нужен свой поток, на дисплей - поток. При реализации такого функционала delay() будет только мешать.

    Применение delay() ни как не повлияет на частоту вызова кода и на энергосбережение -- mA-ы как бежали так и будут бежать (это в контексте ATmega и ATtiny).
    Энергосбережение достигается другими подходами, но это за рамками данной темы.
     
    Airbus нравится это.
  7. qwone

    qwone Гик

    Если не нравится delay() воспользуйтесь antidelay()
    Код (C++):
    void antidelay(uint32_t timing){
    timing += millis();
    while(millis()<=timing);
    }
    void setup() {
    pinMode(13, OUTPUT);
    }
    void loop() {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
      antidelay(1000);              // wait for a second
      digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
      antidelay(1000);
    }
    А вообще не надо разводить фобии. Программист прежде всего должен уметь тестировать свой код, а потом уже писать. Да и принцип модульности никто не отменял, Хотя многие на модульность забили.
     
  8. А разве не будет переполнения через 49 дней в последнюю секунду? Конечно такое стечение обстоятельств стремится к нулю и при тестировании врятли случится. Но ревью кода это вполне исправляет :)

    Но в целом да, мысль ясна.
     
  9. многопоточность на arduino это в принципе понятие относительное. Можно эмулировать несколько потоков, но никак не сделать полноценную многопоточность (поправте если я не прав). Я сейчас про Arduino uno а конкретно ATmega328p. И в связи с этим придётся для каждого потока ввести свои переменные счётчики, если нужно выполнять каждый поток со своей периодичностью, delay это будет или анализ curMillis - lastMillis особо разницы не вижу. Возможно у меня не хватает опыта.

    P.S. почему кнопка "сбросить форматирование" не убирает выделение цветом?
     
  10. Onkel

    Onkel Гуру

    многопоточность она всегда с разделением времени или ресурса. У DHalt в easyelectronics есть тема "операционка для мк", у него почитайте, там хорошо про поточность и пример, содранный (творчески) с мотороловского.
     
  11. qwone

    qwone Гик

    А еще проще ,вот код обеспечивающий многопоточность на достаточно простом уровне.
    3 потока, которые позволят свободно общатся с человеком и делать кое-какую работу.
    Код (C++):
    uint8_t non_stop_program1(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program2(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program3(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    void setup() {
      Serial.begin(9600);
    }
    void loop() {
      if (non_stop_program1(500))  {
    // Здесь вы организовываете вывод на экран. Вызывается раз в 0.5 сек
        Serial.println("knok");
     }
      if (non_stop_program2(200))  {
    // Здесь вы организовываете ввод с клавиатуры. Вызывается раз в 0.2 сек
      }
      if (non_stop_program3(100))  {
    // Здесь сама работа устройства не зависимая от вашего ввода и показа на экран. Вызывается раз в 0.1 сек(может быть по желанию другим)
       }
    }
     
     
    Последнее редактирование: 17 авг 2016
  12. AlexU

    AlexU Гуру

    Такое же относительное как, например, на одноядерных Pentium'ах или на любом одном ядре современных многоядерных процессоров. Основная проблема -- это даже не частота микроконтроллера (те же 80386/80486, предшественники Pentium'ов, работали на аналогичных частотах, что не мешало на этих процессорах запускать многопоточный Linux), а объём памяти, особенно оперативной. Но тем не менее есть реализации честной многопоточности для ATmega, например, https://bitbucket.org/dferreyra/avr-threads.
     
    РусНекромант нравится это.
  13. проблема этого кода в том что любой так сказать поток будет грести на себя одеяло и например получить температуру с датчиков в 12 битном разрешении занимает около секунды, так вот эту секунду будет работать только это. И стройный ряд 0,5 0,2 0,1 секунда рушится.

    Понятно что "многопоточность она такая на одном ядре", но всё же не нужно говорить что это "как два пальца...". Работать будет, но только приблизительно верно.
     
    Vetrinus нравится это.
  14. Onkel

    Onkel Гуру

    так не надо ждать эту одну секунду. Опрос ds18b20 отлично бьется на ряд запросов, занимающих миллисекунды. И суммарно получится несколько миллисекунд.
     
    Vetrinus нравится это.
  15. вы сейчас ударяятесь в частности. Я говорю что пока операция в блоке if не завершится код не пойдет дальше хоть целая минута пройдёт. То есть если блоки у вас небольшие то будет всё нормально. Как только блоки/расчёты вырастут начнутся проблемы. Причём проблема будет "плавающая" и не будет лежать на поверхности, отлаживая такой код можно посадить печень :).
     
    Vetrinus нравится это.
  16. Onkel

    Onkel Гуру

    Вы привели конкретный пример- я про этот пример и написал. Пишите нормальный код, с ифами не занимающими целые минуты, и все у вас будет работать. У меня работает, стандартная заготовка (шаблон) на 8 функций разного уровня для 8 биток и на 32 функции для arm кортексов. Любой сложный рассчет, вроде бпф, эйлеровых углов или кватернионов можно бить на блоки и считать поблочно так, что каждый блок будет занимать не более 1 мс. У меня всегда получалось. У кого-то , возможно, но получается, но это не основание винить в этом метод.
     
    Vetrinus нравится это.
  17. Рабочий метод != хороший метод.

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

    Всё таки это не та же самая многопоточность что и в одноядерных процессорах. Хотя после допила напильником можно примерно такое организовать.
     
    Vetrinus нравится это.
  18. Onkel

    Onkel Гуру

    как раз наоборот - сложные задачи решаются многопоточным методом. Ознакомтесь плиз
    http://easyelectronics.ru/avr-uchebnyj-kurs-operacionnaya-sistema-vvedenie.html

    имхо это не просто хороший, а лучший метод, разделяй и властвуй.
     
    Vetrinus нравится это.
  19. Vetrinus

    Vetrinus Гик

    @Onkel, спасибо за ссылку
     
  20. qwone

    qwone Гик

    мне понравилась цитата оттуда
    А вот реализация и не не надо лезть в ассемблер.
    Код (C++):
    // ------------светодиод 1-------------------
    const int VD1_pin = 13;// нога светодиода VD1
    void ini_VD1(){
      pinMode(VD1_pin, OUTPUT);
    }
    void work_VD1(){
       static uint8_t VD1 = 0;
       VD1 = ! VD1;
       digitalWrite(VD1_pin, VD1);
    }
    // -------------светодиод 2----------------
    const int VD2_pin = 12;// нога светодиода VD1
    void ini_VD2(){
      pinMode(VD2_pin, OUTPUT);
    }
    void work_VD2(){
       static uint8_t VD2 = 0;
       VD2 = ! VD2;
       digitalWrite(VD2_pin, VD2);
    }
    // -------------светодиод 3-------------
    const int VD3_pin = 11;// нога светодиода VD1
    void ini_VD3(){
      pinMode(VD3_pin, OUTPUT);
    }
    void work_VD3(){
       static uint8_t VD3 = 0;
       VD3 = ! VD3;
       digitalWrite(VD3_pin, VD3);
    }
    //============= ядро системы ===================
    uint8_t non_stop_program1(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program2(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program3(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }


    void setup() {
      ini_VD1();
      ini_VD2();
      ini_VD3();
    }
    void loop() {
      if (non_stop_program1(1))  {
        // Здесь первый мигал с частотой в 1кГц
        work_VD1();
        }
      if (non_stop_program2(2))  {
        // Здесь второй в 500Гц
        work_VD2();
        }
      if (non_stop_program3(5))  {
        // Здесь третий 200Гц.
        work_VD3();
         }
    }
     
    Последнее редактирование: 18 авг 2016