Умное мигание светодиодом в Ардуино
Мигание светодиода в Ардуино, что может быть проще и бесполезнее. На самом деле практическую пользу от этой простой функции можно найти.
Бывает при программирование какого-нибудь устройства не хватает портов ввода-вывода микроконтроллера. Или из экономических соображений, а может нехватки места в корпусе, не хочется устанавливать дисплей, а как то сигнализировать о режимах работы устройства очень хотелось бы. Часто достаточно сигнализировать о этих режимах горением или миганием светодиода. А если режимов много?
На мысль меня навела автомобильная сигнализация, в которой я как то программировал режим автозапуска. Там, чтобы установить, например, 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<
Подробно по программированию таймера можно почитать здесь. При этом 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
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 функций выделяется слишком мало времени.
Надеюсь, эта статья будет немного полезной для всех любителей мигать светодиодами в Ардуино и не только им.
О всех моих экспериментах с микроконтроллерами и умным домом читайте в мое блоге