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

6c1fe46ee52048a993dad9ec33602c56.png

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

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

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

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

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

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

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

void loop() {
  digitalWrite(13,HIGH);
  delay(500);
  digitalWrite(13,LOW);
  delay(500);
}



7fd0831a2e2b4f249444ffef1984a301.jpg
Казалась бы задавай различные интервалы между высокими и низкими уровнями порта и будет нужное. Но при этом контроллер больше ничего не делает (ну почти ничего, прерывания он все таки обрабатывает). Делать что-то еще он конечно может, но не в основном цикле 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 раз в секунду
70fe9cf188cc4a318c761299914dd565.jpg
Две вспышки в секунду
fc07a614c0754ad093128a0cd199e1ef.jpg
Три вспышки
c7b301484d644685a4be4503460968dc.jpg
И постоянные вспышки четыре раза в секунду
bc1ef3bab4c04a5ea743dcd311f2d1ad.jpg

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

Что если 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 секунды
e34709153b414e87a76e20d899e36b9a.jpg

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

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

Берем 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<

Подробно по программированию таймера можно почитать здесь. При этом 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 
 
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 функций выделяется слишком мало времени.

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

О всех моих экспериментах с микроконтроллерами и умным домом читайте в мое блоге

© Geektimes