Эта статья будет о программировании. О простом и сложном одновременно — мигании светодиодом.
Зачем все это?
Бывает при программирование какого-нибудь устройства не хватает портов ввода-вывода микроконтроллера. Или из экономических соображений, а может нехватки места в корпусе, не хочется устанавливать дисплей, а как то сигнализировать о режимах работы устройства очень хотелось бы. Часто достаточно сигнализировать о этих режимах горением или миганием светодиода. А если режимов много?
На мысль меня навела автомобильная сигнализация, в которой я как то программировал режим автозапуска. Там, чтобы установить 14-й бит определенного регистра нужно было после входа в режим программирования этого регистра 14 раз нажать на определенную кнопку брелка, а потом дождаться 14-ти коротких сигналов (или мигания поворотников). Затем нажать кнопку в подтверждения и услышать длинный сигнал.
А почему бы это же самое ни использовать в моих прошивках микроконтроллеров:
Режим 1 — мигаем светодиодом один раз в секунду, режим 2 — два раза и так далее …
Как это работает
Рассмотрим мигание светодиодом, подключенным к 13 порту на Ардуино. Это первая программа которую осваивают при изучении Ардуино. Во многих контроллерах, которые мне попадались в последнее время, эта программа зашита на заводе, видимо для тех кто не осилил и это.
Казалось бы чего проще
1
2
3
4
5
6
7
8
9
10
11
voidsetup(){
pinMode(13,OUTPUT);
digitalWrite(13,LOW);
}
voidloop(){
digitalWrite(13,HIGH);
delay(500);
digitalWrite(13,LOW);
delay(500);
}
Чередование высокого и низкого уровня на выводе 13 каждые 0.5 секунды.
Можно было установить и два мигания в секунду и три. Но при этом контроллер целиком занят миганием светодиода. Функция delay() подразумевает, что долгие 500 мс больше ничего не происходит (ну разве что обрабатываются прерывания).
Обработка события с использованием millis()
Несложный код, выполняемый в основном цикле делает тоже самое.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
voidsetup(){
pinMode(13,OUTPUT);
digitalWrite(13,LOW);
}
uint32_t ms,ms1=0;
boolled_stat=true;
voidloop(){
ms=millis();
// Событие срабатывающее каждые 500 мс
if((ms-ms1)>500||ms<ms1){
ms1=ms;
// Инвертируем светодиод
digitalWrite(13,led_stat);
led_stat=!led_stat;
}
}
Событие может сдвигаться, если в цикле выполняется еще ряд действий, занимающих какое то время. В этом примере мы добились главного — мигаем светодиодом практически не занимая процессорного времени у основной программы.
Правда чтобы определить двойное и тройное мигание светодиода, нужно написать несколько событий с разным интервалом и реализовать логику включения выключения. Сложно.
Обработка битовой матрицы состояния светодиода
Уменьшаем время срабатывания события до 1/8 секунды и в 1 байте кодируем 8 бит состояний, отображаемых последовательно.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Массив режимов работы светодиода
bytemodes[]={
0B00000000,//Светодиод выключен
0B11111111,//Горит постоянно
0B00001111,//Мигание по 0.5 сек
0B00000001,//Короткая вспышка раз в секунду
0B00000101,//Две короткие вспышки раз в секунду
0B00010101,//Три короткие вспышки раз в секунду
0B01010101//Частые короткие вспышки (4 раза в секунду)
// Этот код служит для демонстрации переключения режимов
// Один раз в 5 секунд меняем эффект
if((ms-ms2)>5000||ms<ms2){
ms2=ms;
blink_mode=modes[modes_count++];
if(modes_count>=7)modes_count=1;
}
}
Первый и второй и третий режимы слишком просты а вот дальше начинается интересное. Что может уже нормально использоваться для отображения режимов.
4-й режим. Короткая вспышка 1 раз в секунду
5-й режим. Две короткие вспышки в секунду
6-й режим. Три вспышки.
Ну и постоянная череда коротких вспышек
В принципе, на этом можно было и остановиться, так как для большинства проектов этого бы хватило. Но если этого мало и вам нужно будет разрабатывать программирование автосигнализации )))
Что если 8 бит состояний светодиодов мало?
Можно использовать несколько байт. Например, для кодирования сигнала SOS азбукой Морзе я использовал 4 байта, которые используются последовательно
Получаем циклический сигнал SOS — три коротких, три длинных и снова три коротких сигнала светодиодом, повторяемый каждые 4 секунды
Для тех, кто считает, что программировать микроконтроллеры в цикле loop() это не по Фен шую Несмотря на то, что millis() использует прерывание по первому таймеру
Только хардкор. Только прерывания!
Берем 16-ти битный Таймер 1. Устанавливаем прерывание на переполнение за 125мс
blink_mode=0B00000001;//Короткая вспышка раз в секунду
delay(5000);
blink_mode=0B00000101;//Две короткие вспышки раз в секунду
delay(5000);
blink_mode=0B00010101;//Три короткие вспышки раз в секунду
delay(5000);
blink_mode=0B01010101;//Частые короткие вспышки (4 раза в секунду)
delay(5000);
}
Использовать прерывания в ESP следует осторожно, так как очень часто это вызывает срабатывание злобного сторожевого таймера WDT, который считает, что на обработку встроенных WiFi функций выделяется слишком мало времени.
Надеюсь, эта статья будет немного полезной для любителей помигать светодиодами )))
Огромное спасибо за изложенный материал!
Мне очень помогла данная статья. Искал пример реализации прерывания по таймеру для esp8266 и наткнулся на вашу статью.
Подумалось, что для индикации режимов можно использовать RGB светодиод, поскольку человеческий глаз различает достаточное количество цветов и оттенков (навскидку, основных цветов + оттенков, различимых всеми, думаю, 255 наберется). Конечно, нужны еще 3 пина, но можно их подключить через микросхему сдвига (будем задействован 1 пин).
В случае RGB светодиода можно завязать интенсивность цвета на какой-то из параметров. Например, температура: бледно светим синим при низкой температуре, интенсивнее при повышении, при переходе определенной границы, начинаем светить бледным красным. Светим ярко-красным когда горячо.
В автосигнализации все сделано для экономии деталей в ущерб юзабилити. Ну правда, считать 14 импульсов задолбаешься.
Отличная статья! За библиотеку для работы с таймером отдельное спасибо!
Огромное спасибо за изложенный материал!

Мне очень помогла данная статья. Искал пример реализации прерывания по таймеру для esp8266 и наткнулся на вашу статью.
Спасибо за статью, полезная.
Подумалось, что для индикации режимов можно использовать RGB светодиод, поскольку человеческий глаз различает достаточное количество цветов и оттенков (навскидку, основных цветов + оттенков, различимых всеми, думаю, 255 наберется). Конечно, нужны еще 3 пина, но можно их подключить через микросхему сдвига (будем задействован 1 пин).
В случае RGB светодиода можно завязать интенсивность цвета на какой-то из параметров. Например, температура: бледно светим синим при низкой температуре, интенсивнее при повышении, при переходе определенной границы, начинаем светить бледным красным. Светим ярко-красным когда горячо.
В автосигнализации все сделано для экономии деталей в ущерб юзабилити. Ну правда, считать 14 импульсов задолбаешься.
Ну это ШИМ задействовать.
Я это для индикатора батареи реализовывал
Всё хорошо, но что за типы данных uint8_t, uint16_t и uint32_t? В описании по программированию ардуино такого не нашёл…
uint8_t = unsigned short int; — 1 байт
uint16_t = unsigned int; — 2 байта
uint32_t = unsigned long int; — 4 байта
Добрый человек, написавший TimerOne, кажется, Jesse Tane, вполне заслуживает чтобы упомянули именно так, а не просто как безымянного человека