Умное мигание светодиодом в Ардуино



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

    Бывает при программирование какого-нибудь устройства не хватает портов ввода-вывода микроконтроллера. Или из экономических соображений, а может нехватки места в корпусе, не хочется устанавливать дисплей, а как то сигнализировать о режимах работы устройства очень хотелось бы. Часто достаточно сигнализировать о этих режимах горением или миганием светодиода. А если режимов много?

    На мысль меня навела автомобильная сигнализация, в которой я как то программировал режим автозапуска. Там, чтобы установить, например, 14-й бит определенного регистра нужно было после входа в режим программирования этого регистра 14 раз нажать на определенную кнопку брелка, а потом дождаться 14-ти коротких сигналов (или мигания поворотников). Затем нажать кнопку в подтверждения и услышать длинный сигнал. Гениально! Никаких дисплеев и экранных меню. Правда, одновременно, и жутко неудобно.

    Но если внутренних режимов немного, то использовать количество морганий светодиодом вполне функционально.

    Начнем с простого.

    Пример мигания светодиодом для Ардуино



    Это первая программа которую осваивают при изучении Ардуино. Во многих контроллерах, которые мне попадались в последнее время, эта программа зашита на заводе, видимо для тех кто не осилил и это.

    Простейший пример мигания светодиодом
    void setup() {
      pinMode(13, OUTPUT);
      digitalWrite(13, LOW); 
    }
    
    void loop() {
      digitalWrite(13,HIGH);
      delay(500);
      digitalWrite(13,LOW);
      delay(500);
    }
    



    Казалась бы задавай различные интервалы между высокими и низкими уровнями порта и будет нужное. Но при этом контроллер больше ничего не делает (ну почти ничего, прерывания он все таки обрабатывает). Делать что-то еще он конечно может, но не в основном цикле loop().

    Поэтому отказываемся от delay() и переходим на события с использованием millis()

    Использование событий с использованием millis()



    Код мигания светодиодом с использованием millis()
    void setup() {
      pinMode(13, OUTPUT);
      digitalWrite(13, LOW); 
    }
    
    uint32_t ms, ms1 = 0;
    bool led_stat    = true;
    
    void loop() {
       ms = millis();
    // Событие срабатывающее каждые 500 мс   
       if( ( ms - ms1 ) > 500 || ms < ms1 ){
           ms1 = ms;
    // Инвертируем светодиод       
           digitalWrite(13, led_stat); 
           led_stat = !led_stat;
       }
    }
    



    Ну вот. Цель достигнута. Светодиод мигает, а процессорное время в цикле loop() практически полностью доступно для других функций. Правда в таком коде использование требуемых режимов мигания реализуется довольно сложно — несколько событий с разными интервалами, много условий по необходимому режиму и предыдущему состоянию. Слишком сложно.

    Обработка битовой матрицы состояния светодиода



    Уменьшаем время срабатывания события до 1/8 секунды и в 1 байте кодируем 8 бит состояний, отображаемых последовательно.
    Код мигания светодиода с битовой матрицей состояний
    // Массив режимов работы светодиода
    byte modes[] = {
       0B00000000, //Светодиод выключен
       0B11111111, //Горит постоянно
       0B00001111, //Мигание по 0.5 сек
       0B00000001, //Короткая вспышка раз в секунду
       0B00000101, //Две короткие вспышки раз в секунду
       0B00010101, //Три короткие вспышки раз в секунду
       0B01010101  //Частые короткие вспышки (4 раза в секунду)
    };
    
    uint32_t ms, ms1 = 0, ms2 = 0;
    uint8_t  blink_loop = 0;
    uint8_t  blink_mode = 0;
    uint8_t  modes_count = 0; 
    
    void setup() {
      pinMode(13, OUTPUT);
      digitalWrite(13, LOW); 
      modes_count = 1;
      blink_mode = modes[modes_count];
    }
    
    void loop() {
       ms = millis();
    // Событие срабатывающее каждые 125 мс   
       if( ( ms - ms1 ) > 125|| ms < ms1 ){
           ms1 = ms;
    // Режим светодиода ищем по битовой маске       
           if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH); 
           else  digitalWrite(13, LOW);
           blink_loop++;    
        }
        
    // Этот код служит для демонстрации переключения режимов    
    // Один раз в 5 секунд меняем эффект   
       if( ( ms - ms2 ) > 5000|| ms < ms2 ){
           ms2 = ms;
           blink_mode = modes[modes_count++];
           if( modes_count >= 7 )modes_count = 1;
       }
    }
    



    Первые три режима работы светодиода простые. А вот остальные уже можно использовать для демонстрации режима микроконтроллера:

    Короткая вспышка 1 раз в секунду

    Две вспышки в секунду

    Три вспышки

    И постоянные вспышки четыре раза в секунду


    В принципе, на этом можно было и остановиться, так как для большинства проектов этого бы хватило. Но если этого мало и вам нужно будет разрабатывать программирование автосигнализации )))

    Что если 8 бит состояний светодиодов мало?

    Использование 4-х байт для определения состояния светодиода



    Код сигнала SOS азбукой Морзе
    byte bytes[] = {0B00010101,0B00110011,0B10100011,0B00000010}; 
    
    uint32_t ms, ms1 = 0;
    uint8_t  blink_loop = 0;
    
    void setup() {
      pinMode(13, OUTPUT);
      digitalWrite(13, LOW); 
    }
    
    void loop() {
       ms = millis();
    // Событие срабатывающее каждые 125 мс   
       if( ( ms - ms1 ) > 125|| ms < ms1 ){
           ms1 = ms;
    // Выделяем сдвиг светодиода (3 бита)   
           uint8_t n_shift = blink_loop&0x07;
    // Выделяем номер байта в массиве (2 байта со здвигом 3 )      
           uint8_t b_count = (blink_loop>>3)&0x3;
           if(  bytes[b_count] & 1<< n_shift )digitalWrite(13, HIGH);
           else  digitalWrite(13, LOW);
           blink_loop++;    
        }
    }
    



    Получаем циклический сигнал SOS — три коротких, три длинных и снова три коротких сигнала светодиодом, повторяемый каждые 4 секунды


    Очень много людей критиковали Ардуино за ужасный стиль программирования микроконтроллеров без использования прерываний

    Только хардкор. Только прерывания!



    Берем 16-ти битный Таймер 1. Устанавливаем прерывание на переполнение за 125мс

    Код многорежимного мигания светодиода с использованием прерываний по таймеру
    uint8_t  blink_loop  = 0;
    uint8_t  blink_mode  = 0;
    uint8_t  modes_count = 0; 
    // Начальное значение таймера
    uint16_t n = 63583;
    
    // Обработчик прерывания по переполнению таймера
    ISR( TIMER1_OVF_vect )
    {
       if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH); 
       else  digitalWrite(13, LOW);
       blink_loop++;    
       TCNT1 = n; //выставляем начальное значение TCNT1
    }
    
    void setup() {
      pinMode(13,OUTPUT);
      blink_mode = 0B00000000;
    // А вот и хардкор - установка регистров таймера
      TCCR1A = 0;
    // Устанавливаем делитель 1024 к тактовой частоте 16МГц
      TCCR1B = 1<<CS22 | 0<<CS21 | 1<<CS20;
    //Подключаем прерывание по переполнению Timer1
      TIMSK1 = 1<<TOIE1;
    //Загружаем начальное значение таймера для первого цикла
      TCNT1 = n; 
      sei();    // выставляем бит общего разрешения прерываний}
    }
    
    void loop() {
       blink_mode = 0B00001111; //Мигание по 0.5 сек
       delay(5000);
       blink_mode = 0B00000001; //Короткая вспышка раз в секунду
       delay(5000);
       blink_mode = 0B00000101; //Две короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B00010101; //Три короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B01010101;  //Частые короткие вспышки (4 раза в секунду)
       delay(5000);
    }
    



    Подробно по программированию таймера можно почитать здесь. При этом delay() на 5 секунд в Loop() совершенно не мешают управлению нашим светодиодом.

    Недостаток такого метода в том, что не будут работать некоторые функции и библиотеки, использующие таймер 1. Например, ШИМ.

    Если с программированием регистров таймера сложно, а прерывание по таймеру использовать интересно —

    Прерывание по таймеру с «человеческим лицом»



    Добрые люди написали программный интерфейс к таймеру в виде библиотеки TimerOne

    Код многорежимного мигания светодиодом с использованием TimerOne
    #include "TimerOne.h"
    
    uint8_t  blink_loop = 0;
    uint8_t  blink_mode = 0;
    uint8_t  modes_count = 0; 
    
    // Callback функция по таймеру
    void timerIsr()
    {
       if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH); 
       else  digitalWrite(13, LOW);
       blink_loop++;    
    }
    
    void setup() {
      pinMode(13,OUTPUT);
      blink_mode = 0B00000000;
      Timer1.initialize(125000);  
      Timer1.attachInterrupt( timerIsr ); 
    }
      
    void loop() {
       blink_mode = 0B00001111; //Мигание по 0.5 сек
       delay(5000);
       blink_mode = 0B00000001; //Короткая вспышка раз в секунду
       delay(5000);
       blink_mode = 0B00000101; //Две короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B00010101; //Три короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B01010101;  //Частые короткие вспышки (4 раза в секунду)
       delay(5000);
    }
    



    Библиотеку с таймером TimerOne можно скачать здесь

    Ну, и напоследок, код для тех, кто как и я «грызет» программирование WiFi модулей ESP8266 в среде Arduino IDE.

    Прерывание по таймеру в ESP8266



    Там другие добрые люди прямо в ядро ESP для Arduino встроили библиотеку Ticker
    Код многорежимного мигания светодиода по таймеру в ESP8266
    #include <Ticker.h>
     
    uint8_t  blink_loop = 0;
    uint8_t  blink_mode = 0;
    uint8_t  modes_count = 0; 
     
    Ticker blinker;
     
    void timerIsr()
    {
       if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH); 
       else  digitalWrite(13, LOW);
       blink_loop++;    
    }
     
    void setup() {
      pinMode(13,OUTPUT);
      blink_mode = 0B00000000;
      blinker.attach(0.125, timerIsr);
    }
      
    void loop() {
       blink_mode = 0B00001111; //Мигание по 0.5 сек
       delay(5000);
       blink_mode = 0B00000001; //Короткая вспышка раз в секунду
       delay(5000);
       blink_mode = 0B00000101; //Две короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B00010101; //Три короткие вспышки раз в секунду
       delay(5000);
       blink_mode = 0B01010101;  //Частые короткие вспышки (4 раза в секунду)
       delay(5000);
    }
    



    Использовать прерывания в ESP следует осторожно, так как очень часто это вызывает срабатывание злобного сторожевого таймера WDT, который считает, что на обработку встроенных WiFi функций выделяется слишком мало времени.

    Надеюсь, эта статья будет немного полезной для всех любителей мигать светодиодами в Ардуино и не только им.

    О всех моих экспериментах с микроконтроллерами и умным домом читайте в мое блоге
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 28

      +1
      Спасибо, очень интересно. Надо будет где-нибудь применить такой метод индикации. Только таблицу с расшифровкой морганий придется вешать на готовом устройстве, а то через неделю забудется, что там чему соответствует.
        0
        Или азбуку Морзе учить и первые буквы режимов траслировать )))
          0
          в последнее время перешёл на вот такую конструкцию

          pause = millis() % 200; // pause will loop from 0 to 199
          if(pause == 0)
          {
          digitalWrite(13,!digitalRead(13));
          }
            0
            Может тогда так для классики жанра?
            if( !( millis() % 200 ) )digitalWrite( 13, !digitalRead( 13 ) );
            
              0
              как вариант. только в последней версии трындуино айди касячок обнаружил, что в одну строчку работать так не будет. нужно соблюдать эту дьявольскую конструкцию с фигурными скобками.
                0
                Последняя 1.6.7?
                Она еще и с ESP8266 core плохо дружит. Уже сколько народу присылало претензии, что код не хочет компилироваться. При откате на 1.6.5 все собиралось как часы
                Кстати, кто мешает фигурные скобки в одну строчку использовать?
                if( !( millis() % 200 ) ){ digitalWrite( 13, !digitalRead( 13 ) ); }
                

                Это Си, великий и могучий!
                  0
                  трындуино айди мешает :))
                  точнее скомпилируется то нормально и зальётся на ура. вот только в цикле эта строчка работать не будет и светодиод естественно не будет моргать. такую фигню заметил в последней версии трындуино айди когда размашистый код марафетил, что бы читать одной строчкой. за более ранние версии ничего сказать не могу. но на поиск касячка потратил время. потому и сказал, что надо придерживаться дьявольской конструкции в предыдущем комменте.
                    0
                    последняя это 1.6.7
          0
          Использовать прерывания в ESP следует осторожно, так как очень часто это вызывает срабатывание злобного сторожевого таймера WDT, который считает, что на обработку встроенных WiFi функций выделяется слишком мало времени.
          В NodeMCU это решается вызовом в длинном цикле tmr.wdclr(), который сбрасывает watchdog- счетчик. Но тут нужно быть уверенным, что цикл не станет бесконечным ни при каких условиях.
            0
            Даже если бы сброс WDT работал без глюков, это не всегда спасает.
            Например, делаю выгрузку LOG-фала с SD-карточки через WEB-сервер.
            При размере файла 100-200к WDT гарантированно срабатывает.
              0
              Ва подключили SD-карточку непосредстенно к 8266?
              Было бы очень интересно увидеть статью на эту тему!
              0
              А что там подключать?
              Пример есть в ESP8266 Core для Arduino IDE
              Внешний SPI там работает:
              pinMode(SCK, SPECIAL); ///< GPIO14
              pinMode(MISO, SPECIAL); ///< GPIO12
              pinMode(MOSI, SPECIAL); ///< GPIO13
              ну а CS к любому GPIO

              Там даже есть WEB-сервер с файлами на карточке!

              Что не попробовал еще, это зарузку программы с карты памяти
                0
                Передача данных с использованием манчестерского кода давно уже не новость в использовании светодиодов.
                  +1
                  Реализация манчестерского кода несколько сложнее, чем описано в данной статье. В ту же тиньку 13 ее еще нужно умудриться запихнуть.
                    0
                    Это правда. Зато появляется возможность общаться на «понятном языке», как это делает Intel и многие другие известные бренды. Азбука Морзе в данном случае видится не лучшим выходом для коммуникации. Хотя… Если будет вменяемое мобильное приложение, способное транслировать мигание светодиода в текстовые сообщения, то какая, по сути, разница?
                  0
                  ну почти ничего, прерывания он все таки обрабатывает

                  Если память мне не изменяет, ничего он не обрабатывает как раз, иначе немного непонятно, кто гарантирует мне, что моя задержка будет длиться ровно 500 мс. Представим, что я отвратительный программист и по приемке байта в UART устроил себе O(N^4) да еще и с пересылкой этого байта пару сотен раз обратно. А в цикле у меня _delay_ms(500);. Контроллер, дойдя до этой строки, радостно делает NOP много-много раз. А на 250-й миллисекунде ему как раз байт прислали. Если бы прерывания не были бы запрещены, _delay_ms(500) легко могло бы превратиться в _delay_ms(650), а это сулит безумные проблемы с программно реализованными протоколами. Так что, первое, что делает delay, это выдает ASM(«CLI»), чтобы неповадно было.
                    0
                    Ну delay() точно не блокирует прерывания. Все примеры с прерываниями в данной статье это иллюстрируют. Можете убедиться, примеры абсолютно рабочие, хотя там сплошные delay() в цикле.
                    А кто сказал, что delay делает NOP много раз? За delay() скорее всего отвечает 0-й таймер, который генерит миллисекунды для функции millis()
                      0
                      Может, с июня '14 много что изменилось в стандартной либе, но вот здесь как раз человеку приходится писать неблокирующий delay() для Arduino.
                      P.S. А что будет, если у меня заняты все таймеры контроллера, но delay я все равно хочу? Там же не RTOS с диспетчером крутится
                        0
                        Вы с delayMicrosecons() не путаете?

                        void delay(unsigned long ms)
                        {
                        	uint16_t start = (uint16_t)micros();
                        
                        	while (ms > 0) {
                        		yield();
                        		if (((uint16_t)micros() - start) >= 1000) {
                        			ms--;
                        			start += 1000;
                        		}
                        	}
                        } 
                        

                        Вот код delay() из ардуины. Где там блокирование прерываний?
                          0
                          p.s. Кстати в той ссылке, что вы привели написано, что один из подходов обхождения «блокировки delay()» это использование прерывания, но это не его метод )))

                          Его метод очень похож на мой второй-третий пример, только все зашито в функцию, а не прямо в цикле условия
                    +1
                    Звуком будет удобнее, чем светом. Займёт также одну ногу. В биосах применяется давно.
                      0
                      С одной стороны вы правы. Звук в Ардуино реализуется встроенной функцией tone() очень просто.
                      С другой стороны, если все устройства моего умного дома буду периодически (при смене режимов) день и ночь попискивать, то через какое то время со мной, как изготовителем может случиться акт насилия )))
                        0
                        Если тон выбрать басовый, раздражать почти не будет. О вкусах не спорят, но меня мигание раздражает больше.
                      0
                      Ужас какой… куда катится гиктаймс?
                        0
                        Мне кажется хорошо было бы создать какой-нибудь унифицированный led-протокол который можно было бы распознавать с помощью камеры и приложения на телефоне. Тогда любое устройство могло бы сообщать свое состояние целыми строками, и может даже заменить экранчики которые любят ставить на самоделки. Вопрос только в скорости передачи информации таким способом.
                          0
                          И снова — звук. Потому что приложения для распознавания морзянки смартфоном через микрофон уже давно написаны. А через камеру — ещё нет.
                          +1
                          делал блок управления насосом в скважине, с контролем уровня в промежуточной емкости, и уровнем сухого хода.
                          Итого 5 датчиков и две реле.

                          Чтобы понять какие датчики в каком положении использовал разные мигания светодиодом, быстрее, медленнее. три вспышки пауза и тд.
                          На момент написания программы восхищался собой — одним светодиодом выведу всю информацию.
                          Подключил блок — светодиод замигал.
                          Я долго и упорно пытался вспомнить что же означает
                            0
                            http://www.state-machine.com/doc/AN_Event-Driven_Arduino-1.6.x.pdf
                            https://bytespeicher.org/2015/compiling-qm-lib-examples-on-arduino/

                            Ну можно несколько потоков запустить мигания и сделать планировщик :-)

                            Only users with full accounts can post comments. Log in, please.