Мигание светодиода в Ардуино, что может быть проще и бесполезнее. На самом деле практическую пользу от этой простой функции можно найти.
Бывает при программирование какого-нибудь устройства не хватает портов ввода-вывода микроконтроллера. Или из экономических соображений, а может нехватки места в корпусе, не хочется устанавливать дисплей, а как то сигнализировать о режимах работы устройства очень хотелось бы. Часто достаточно сигнализировать о этих режимах горением или миганием светодиода. А если режимов много?
На мысль меня навела автомобильная сигнализация, в которой я как то программировал режим автозапуска. Там, чтобы установить, например, 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()
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-х байт для определения состояния светодиода
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
#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
#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 функций выделяется слишком мало времени.
Надеюсь, эта статья будет немного полезной для всех любителей мигать светодиодами в Ардуино и не только им.
О всех моих экспериментах с микроконтроллерами и умным домом читайте в мое блоге
Комментарии (28)
Celtis
07.02.2016 13:32Использовать прерывания в ESP следует осторожно, так как очень часто это вызывает срабатывание злобного сторожевого таймера WDT, который считает, что на обработку встроенных WiFi функций выделяется слишком мало времени.
В NodeMCU это решается вызовом в длинном цикле tmr.wdclr(), который сбрасывает watchdog- счетчик. Но тут нужно быть уверенным, что цикл не станет бесконечным ни при каких условиях.
sav13
07.02.2016 13:42Даже если бы сброс WDT работал без глюков, это не всегда спасает.
Например, делаю выгрузку LOG-фала с SD-карточки через WEB-сервер.
При размере файла 100-200к WDT гарантированно срабатывает.Celtis
07.02.2016 14:26Ва подключили SD-карточку непосредстенно к 8266?
Было бы очень интересно увидеть статью на эту тему!
sav13
07.02.2016 14:36А что там подключать?
Пример есть в ESP8266 Core для Arduino IDE
Внешний SPI там работает:
pinMode(SCK, SPECIAL); ///< GPIO14
pinMode(MISO, SPECIAL); ///< GPIO12
pinMode(MOSI, SPECIAL); ///< GPIO13
ну а CS к любому GPIO
Там даже есть WEB-сервер с файлами на карточке!
Что не попробовал еще, это зарузку программы с карты памяти
icbook
07.02.2016 18:09Передача данных с использованием манчестерского кода давно уже не новость в использовании светодиодов.
sav13
07.02.2016 19:54+1Реализация манчестерского кода несколько сложнее, чем описано в данной статье. В ту же тиньку 13 ее еще нужно умудриться запихнуть.
icbook
07.02.2016 22:00Это правда. Зато появляется возможность общаться на «понятном языке», как это делает Intel и многие другие известные бренды. Азбука Морзе в данном случае видится не лучшим выходом для коммуникации. Хотя… Если будет вменяемое мобильное приложение, способное транслировать мигание светодиода в текстовые сообщения, то какая, по сути, разница?
GrakovNe
07.02.2016 19:24ну почти ничего, прерывания он все таки обрабатывает
Если память мне не изменяет, ничего он не обрабатывает как раз, иначе немного непонятно, кто гарантирует мне, что моя задержка будет длиться ровно 500 мс. Представим, что я отвратительный программист и по приемке байта в UART устроил себе O(N^4) да еще и с пересылкой этого байта пару сотен раз обратно. А в цикле у меня _delay_ms(500);. Контроллер, дойдя до этой строки, радостно делает NOP много-много раз. А на 250-й миллисекунде ему как раз байт прислали. Если бы прерывания не были бы запрещены, _delay_ms(500) легко могло бы превратиться в _delay_ms(650), а это сулит безумные проблемы с программно реализованными протоколами. Так что, первое, что делает delay, это выдает ASM(«CLI»), чтобы неповадно было.sav13
07.02.2016 19:51Ну delay() точно не блокирует прерывания. Все примеры с прерываниями в данной статье это иллюстрируют. Можете убедиться, примеры абсолютно рабочие, хотя там сплошные delay() в цикле.
А кто сказал, что delay делает NOP много раз? За delay() скорее всего отвечает 0-й таймер, который генерит миллисекунды для функции millis()GrakovNe
07.02.2016 20:17Может, с июня '14 много что изменилось в стандартной либе, но вот здесь как раз человеку приходится писать неблокирующий delay() для Arduino.
P.S. А что будет, если у меня заняты все таймеры контроллера, но delay я все равно хочу? Там же не RTOS с диспетчером крутитсяsav13
07.02.2016 20:22Вы с 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() из ардуины. Где там блокирование прерываний?sav13
07.02.2016 20:32p.s. Кстати в той ссылке, что вы привели написано, что один из подходов обхождения «блокировки delay()» это использование прерывания, но это не его метод )))
Его метод очень похож на мой второй-третий пример, только все зашито в функцию, а не прямо в цикле условия
tormozedison
07.02.2016 21:41+1Звуком будет удобнее, чем светом. Займёт также одну ногу. В биосах применяется давно.
sav13
07.02.2016 21:48С одной стороны вы правы. Звук в Ардуино реализуется встроенной функцией tone() очень просто.
С другой стороны, если все устройства моего умного дома буду периодически (при смене режимов) день и ночь попискивать, то через какое то время со мной, как изготовителем может случиться акт насилия )))tormozedison
07.02.2016 23:24Если тон выбрать басовый, раздражать почти не будет. О вкусах не спорят, но меня мигание раздражает больше.
nommoke
08.02.2016 05:10Мне кажется хорошо было бы создать какой-нибудь унифицированный led-протокол который можно было бы распознавать с помощью камеры и приложения на телефоне. Тогда любое устройство могло бы сообщать свое состояние целыми строками, и может даже заменить экранчики которые любят ставить на самоделки. Вопрос только в скорости передачи информации таким способом.
tormozedison
08.02.2016 06:12И снова — звук. Потому что приложения для распознавания морзянки смартфоном через микрофон уже давно написаны. А через камеру — ещё нет.
alexhott
08.02.2016 07:52+1делал блок управления насосом в скважине, с контролем уровня в промежуточной емкости, и уровнем сухого хода.
Итого 5 датчиков и две реле.
Чтобы понять какие датчики в каком положении использовал разные мигания светодиодом, быстрее, медленнее. три вспышки пауза и тд.
На момент написания программы восхищался собой — одним светодиодом выведу всю информацию.
Подключил блок — светодиод замигал.
Я долго и упорно пытался вспомнить что же означает
ignat99
08.02.2016 21:40http://www.state-machine.com/doc/AN_Event-Driven_Arduino-1.6.x.pdf
https://bytespeicher.org/2015/compiling-qm-lib-examples-on-arduino/
Ну можно несколько потоков запустить мигания и сделать планировщик :-)
AntonSor
Спасибо, очень интересно. Надо будет где-нибудь применить такой метод индикации. Только таблицу с расшифровкой морганий придется вешать на готовом устройстве, а то через неделю забудется, что там чему соответствует.
sav13
Или азбуку Морзе учить и первые буквы режимов траслировать )))
kalbasa
в последнее время перешёл на вот такую конструкцию
pause = millis() % 200; // pause will loop from 0 to 199
if(pause == 0)
{
digitalWrite(13,!digitalRead(13));
}
sav13
Может тогда так для классики жанра?
kalbasa
как вариант. только в последней версии трындуино айди касячок обнаружил, что в одну строчку работать так не будет. нужно соблюдать эту дьявольскую конструкцию с фигурными скобками.
sav13
Последняя 1.6.7?
Она еще и с ESP8266 core плохо дружит. Уже сколько народу присылало претензии, что код не хочет компилироваться. При откате на 1.6.5 все собиралось как часы
Кстати, кто мешает фигурные скобки в одну строчку использовать?
Это Си, великий и могучий!
kalbasa
трындуино айди мешает :))
точнее скомпилируется то нормально и зальётся на ура. вот только в цикле эта строчка работать не будет и светодиод естественно не будет моргать. такую фигню заметил в последней версии трындуино айди когда размашистый код марафетил, что бы читать одной строчкой. за более ранние версии ничего сказать не могу. но на поиск касячка потратил время. потому и сказал, что надо придерживаться дьявольской конструкции в предыдущем комменте.
kalbasa
последняя это 1.6.7