Глупый блинкер робко прячет ... наш delay в пещере тёмной

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

  1. DIYMan

    DIYMan Guest

    Корочее! Здравствуйтееее!

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

    Все, наверное, видели пример Blink without delay, живёт он здесь: https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay . Пример описывает подход, как мигать светодиодом, не прибегая к тупейшим программным задержкам. Всё бы хорошо, но, во-первых, данный пример даёт только самые основы понимания, это раз, два - он не объясняет нам, как быть, если интервалы нужны для каждого светодиода разные и, три - совершенно немасштабируемый, т.е. поле n-ного светодиода ваш код превратится в тыкву, и даже полуночи ждать не надо.

    Чего, собственно я тут распинаюсь? Приведённый ниже код - один из возможных подходов для руления миганием сколь угодно большого разумного кол-ва светодиодов, пришпиленных к пинам дуни. Более того - его можно рассматривать как стартовый, если надо будет рулить светодиодами через сдвиговый регистр, не занимая пины. Он в меру масштабируемый, и позволяет (опять же, в меру) сохранить читабельность скетча. Плюс - совершенно бесплатно, из коробки, позволяет моргать светодиодами с разными интервалами, независимо друг от друга. Можно хоть азбуку Морзе забабахать, никак не влияя на работу остальных частей кода.

    Из недостатков: это - не законченное решение, а лишь демонстрация подхода к вопросу. Хотя - в большинстве несложных проектов оно вполне применимо.

    Как использовать, на примере трёх светодиодов, при этом второй из них мигает с хаотичными интервалами. Код для кнопки - просто псевдокодом, для понимания - как можно управлять морганием, если надо:

    Код (C++):

    #define NUM_BLINKERS 3
    DiodeBlink blinkers[NUM_BLINKERS] =
    {
        {6,1000} // на пине 6, 1000 между сменой состояния по умолчанию
      , {7,500} // на пине 7, 500 между сменой состояния по умолчанию
      , {8,200} // на пине 8, 200 между сменой состояния по умолчанию

    };
    unsigned long lastMillis;
    bool isBlinkOn; // флаг разрешения мигания
    void setup()
    {
        lastMillis = millis();
        isBlinkOn = true; // разрешили мигание
    }
    void loop()
    {
        unsigned long curMillis = millis();
        uint16_t dt = curMillis - lastMillis;
        lastMillis = curMillis;
     
        if(isBlinkOn)
        {
            for(byte i=0;i<NUM_BLINKERS;i++)
            {
                if(blinkers[i].update(dt))
                {
                    // светодиод сменил состояние, тут можно назначить ему новый интервал
                    if(i == 1)
                    {
                        // только для второго светодиода меняем интервал на рандомный
                        blinkers[i].setInterval(random(200,2000));
                    }
                }
            }
        } // isBlinkOn
     
        if(buttonPressed) // если нажата какая-то кнопка, то включаем/выключаем моргание
        {
            isBlinkOn = !isBlinkOn;
         
            if(!isBlinkOn)
            {
                // выключаем все светодиоды, если запретили мигать
                for(byte i=0;i<NUM_BLINKERS;i++)
                    blinkers[i].off();
            }
        } // buttonPressed
     
    }
     
    Если вынести весь этот навал в отдельные функции, то в loop всё будет красиво и аккуратно (говорю же - это только демонстрационный подход к решению проблемы ;)). Сами исходники:

    Classes.h-файл:
    Код (C++):
    #ifndef _CLASSES_H
    #define _CLASSES_H
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    #include <Arduino.h>
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    // класс моргания диодом
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    class DiodeBlink
    {
        public:
            DiodeBlink(byte pinNumber, uint16_t intrval);
         
            // меняем состояние вывода на противоположное
            void toggle();
         
            // включаем светодиод
            void on();
             
            // выключаем светодиод
            void off();
         
            // устанавливаем новый интервал
            void setInterval(uint16_t intrval);
         
            // обновляем внутреннее состояние
            bool update(uint16_t dt);

        // возвращаем номер пина, с которым работаем
            byte getPin();
     
        private:

            uint16_t interval, interval_check;
            byte pin;
            bool state;
    };
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    #endif
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    Classes.cpp - файл:
    Код (C++):
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    #include "Classes.h"
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    DiodeBlink::DiodeBlink(byte pinNumber, uint16_t intrval)
    {
      pin = pinNumber;
      state = false;
      setInterval(intrval);
      interval_check = 0;

      pinMode(pin,OUTPUT);
      off();
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    bool DiodeBlink::update(uint16_t dt)
    {
      interval_check += dt; // накапливаем интервал

      if(interval_check < interval) // интервал ещё не вышел, не надо менять состояние
        return false;

      toggle(); // меняем состояние порта на противоположное

      interval_check = 0; // сбрасываем интервал
      return true;
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    void DiodeBlink::toggle()
    {
      if(!state)
        on();
      else
        off();
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    void DiodeBlink::on()
    {
      if(state)
        return;
     
      state = true;
      digitalWrite(pin,HIGH);
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    void DiodeBlink::off()
    {
      if(!state)
        return;

      state = false;
      digitalWrite(pin,LOW);
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    void DiodeBlink::setInterval(uint16_t intrval)
    {
      interval = intrval;
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    byte DiodeBlink::getPin()
    {
      return pin;
    }
    //------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     
    Как видите - всё просто как три копейки, заранее извиняюсь перед теми, кому всё это очевидно. Тем не менее, надеюсь, что новичкам данный пост будет хоть в чём-то полезен.

    З.Ы. И да, помните: критикуя - предлагай. Поэтому вместо того, чтобы разбирать тут по косточкам, как я наговнокодил - выкладывайте свои решения в эту тему, сделаем мир чуть более наполненным всяким кодом :D
     
    Alex19, 9xA59kK, alex-prog и 2 другим нравится это.
  2. AlexVS

    AlexVS Гик

    Не критикую, просто размышляю......
    Есть ли смысл публиковать подобные материалы на форуме? Может правильнее блог соответствующий вести?
     
  3. alex-prog

    alex-prog Гик

    AlexVS, блог завести не проблема, но его раскручивать ещё нужно... К тому же, DIYMan, не просто опубликовал код, но и хочет получить конструктивную критику, поэтому, для таких целей, форум, на мой взгляд, лучше... Главное, чтобы на форуме были люди, которые стараются улучшать свои знания, а не перекладывают из одной темы в другую; тогда и общаться будет с кем ;)

    DIYMan, у меня вопрос есть, он не совсем отражает данную тему, но является её продолжением, так сказать. Мигание светодиодом можно применить для таких светодиодов, чтобы получить промежуточный цвет, для красно-синего например, это будет фиолетовый. Не подскажите, с какой частотой лучше (более грамотно) делать переключения между включениями светодиодов, то есть, какой интервал паузы выдерживать для включенного светодиода? Пробовал получать фиолетовый, достигается от 250 мс задержки, но можно и меньше паузу выставлять, хотя, есть ли какой-то смысл в уменьшении паузы, я понять не смог...
     
  4. DIYMan

    DIYMan Guest

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

    Далее по теории: если получить данные о кол-ве инерционности, то можно вычислить, какие примерно цвета могут получиться. Вы не пробовали зажигать один цвет в два раза дольше, чем второй, например?

    Блин, теория это хорошо, вот иметь бы такой светик под рукой, поёрзал бы его немного. А так - вода, а не помощь :)
     
  5. alex-prog

    alex-prog Гик

    DIYMan, пробовал, получается соответственно более красный / синий оттенок. Пробовал даже микросекундные задержки, разницы для глаза, увы, не обнаружил.
     
  6. AlexVS

    AlexVS Гик

    Блин, чего то туплю, а как этот парный светодиод подключить, чтоб не спалить контроллер :rolleyes:
    Они же включены встречно-параллельно?
     
  7. alex-prog

    alex-prog Гик

    H-мост?
     
  8. AlexVS

    AlexVS Гик

    Что, через драйвер что ли?
     
  9. alex-prog

    alex-prog Гик

    Схема мигалки на транзисторе с конденсатором, точнее две наверно даже, нужно будет собрать.
     
  10. petypen

    petypen Нерд

    Спасибо за пример. Очень показательно.
     
  11. ANV

    ANV Гуру

    Через резистор к двум пинам.
    Если на них low и high, то один цвет. Если high и low, то второй.
     
    AlexVS нравится это.
  12. alex-prog

    alex-prog Гик

    Для одного светодиода можно, для нескольких - H-мост на транзисторах.
     
  13. acos

    acos Официальный гик Администратор

  14. Benny_Ray

    Benny_Ray Нерд

    а если сделать проще?

    Код (C++):
    void Setup() {
        for (i=0; i<=13; i++) { // i = количеству пинов
            long previousMillis[i] = 0;
        }
    }

    void Blink(int ledPin, long Timer) {
        int ledState = LOW;
        previousMillis[ledPin] = 0;
        unsigned long currentMillis = millis();

        if (currentMillis - previousMillis > Timer) {
            previousMillis[ledPin] = currentMillis;
            if (ledState == LOW)
                ledState = HIGH;
            else
                ledState == LOW;

            digitalWrite(ledPin, ledState);
        }
    }

    void loop() {
        Blink(13, 1000); // Ну или в любой вариации (пин, задержка)
    }
     
    Последнее редактирование: 2 авг 2016
  15. DIYMan

    DIYMan Guest

    Где проще-то? Как различные интервалы пинам назначить? Как их динамически менять независимо от пинов? Как независимо вкл/выкл пины?

    В том-то и дело, что я предложил наиболее простой, с точки зрения наличия функционала, подход. Впрочем, на единственно верном решении не настаиваю.
     
  16. Benny_Ray

    Benny_Ray Нерд

    Например так
    Код (C++):
    void loop() {
        Blink(13, 1000);
        Blink(10, 500);
        Blink(9, 200);
    }
    Например так
    Код (C++):
    void loop() {
        int button;
        long timer;

    //код нажатия кнопки (лень писать)

        if (button == 1)
            timer = 1000;
        else if (buttton == 2)
            timer = 2000;
        else if (buttton == 3)
            timer = 1500;
        else if (button >= 4)
            button = 0;

        Blink(13, timer);

    }
    итого получим что при нажатии кнопки 1 раз 13 светодиод будет мигать с частотой 1 сек,
    при нажатии кнопки 2 раз частота изменится на 2 сек, при нажатии кнопки 3 раз частота изменится на 1,5 секунды.

    Точно так же условием можно передать не значение timer а значение ledPin

    Код (C++):
    void loop() {
        int button;
        long timer = 1000;

    //код нажатия кнопки (лень писать)

        if (button == 1) {
            ledPin = 13;
        else if (buttton == 2)
            ledPin = 12;
        else if (buttton == 3)
            ledPin = 11;
        else if (button >= 4)
            button = 0;

        Blink(ledPin, timer);

    }
    тоже самое только при 1 нажатии загорится 13 пин, при 2- 12 пин, при 3 - 11 пин.

    ну тут как вариант

    Код (C++):
    void setup() {
        for (i = 10; i <=13; i++){
            pinMode(i, OUTPUT);
        }
    }
    или если хотите прям динамически даже вызвав ее из кода то можно в саму функцию вставить условие
    Код (C++):

    pinMode(ledPin, OUTPUT);
     
    т.е. всеравно как и откуда вы будете вызывать функцию
    Код (C++):

    Blink(ledPin, timer);
     
    передав ей значение пина любым способом и передав ей значение таймера любым способом она будет каждый пин отрабатывать со своим таймером.
     
  17. DIYMan

    DIYMan Guest

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

    Просто я привык мыслить архитектурно, и отсюда все пляски. Достаточно ввести понятие диспетчера в мой код - и он станет ещё более читаемым и внятным. Если ввести механизм стратегий - стройность не пострадает. Прошу понять меня правильно - меня всегда убивают вот такие вещи:
    Код (C++):
     if (button == 1) {
            ledPin = 13;
        else if (buttton == 2)
            ledPin = 12;
        else if (buttton == 3)
            ledPin = 11;
        else if (button >= 4)
            button = 0;
    Непереносимо, трудно в отладке, некрасиво, в конце концов. Но, конечно, стоит не забывать и старого анекдота про "шашечки или ехать". Так что, подчеркну ещё раз - каждый подход имеет право на жизнь.
     
  18. Benny_Ray

    Benny_Ray Нерд

    ну тут я нуб но как бы я не вижу другого способа считать нажатия кнопки, если привязаться к 1 раз нажали замигало, 2 раз нажали отключилось то понятно, а если 1 кнопка и на каждое ее нажатие вызывает срабатывание разных пинов, буду признателен если подскажите как в этом случае написать более оптимально.
     
  19. alex-prog

    alex-prog Гик


    В строке: currentMillis - previousMillis > Timer, previousMillis ранее где-то было объявлено?

    Тут я логику потерял вообще, разве в данном коде не получится переопределение времени задержки?
     
  20. Benny_Ray

    Benny_Ray Нерд

    Код (C++):
    void Setup() {
        for (i=0; i<=13; i++) { // i = количеству пинов
            long previousMillis[i] = 0;
        }
    }
    создается массив из в данном случае 14 элементов с 0 по 13 и заполняется значением 0. в функции косяк увидел, лишнее вот это, его убрать надо

    Код (C++):

    previousMillis[ledPin]=0;
     
    а вот тут надо вот так написать

    Код (C++):

    if(currentMillis - previousMillis[ledPin] > Timer){
            previousMillis[ledPin]= currentMillis;
           if(ledState == LOW)
                ledState = HIGH;
           else
                ledState == LOW;

            digitalWrite(ledPin, ledState);
       }
     
    т.е. у нас есть массив с 14 элементами, и мы передав значение пина, меняем аналогичное значение в массиве, этим достигается применение различных таймеров для каждого из пинов,
    первоначально код писал на работе одним глазом, поэтому мелкие недочеты есть.