Предисловие
Летом прошлого года заинтересовался созданием системы декоративной монохромной подсветки на светодиодах для ремонтируемой квартиры, и встал вопрос, на основе чего её собирать.
Хотелось, чтобы была возможность:
- Управлять режимами подсветки (скорость затухания, яркость свечения) удаленно, с Android-телефона по синезубу или пульта ДУ домашней техники по ИК
- Возможность легкого перепрограммирования режимов работы на самом устройстве
- Стоимость — чем меньше, тем лучше
- Доступность компонентов
Выбор был несложным — и я стал обладателем китайской копии Arduino Uno3 под названием DK-Duino Uno, купленной за 15 вечнозеленых на ебее. Одновременно с контроллером были приобретены собственно светодиоды (синие, по 3 бакса за сотню), модуль синезуба HC-05 за 12 долларов и блок питания на 12В/5А за 7 долларов — итого на круг вышло 37 долларов, или 1100 рублей. На ебее есть возможность купить наборы светодиодная лента 5м+контроллер+пульт ИК за 17 долларов, но данный вариант не подходил из-за необходимости направлять пульт на приемник контроллера, который планируется спрятать за мебель/плинтус.
Оригинальностью задумка не блистала, просто хотелось создать нечто удобное, долговечное и понятное в управлении всем жильцам дома.
Задумка
Расположенные вдоль стен светодиоды находятся в отверстиях, просверленных в ламинате/паркете, на небольшом удалении от стены (на хабре уже была пара постов про подобное расположение светодиодов). При включении — плавное изменение яркости свечения отдельных светодиодов с одновременным смещением вдоль стены наиболее яркого — наподобие посадочных огней на ВПП.
Что хотелось воплотить в системе подсветки
- Изменение скорости и яркости свечения, вплоть до полного отключения
- Режим неизменной яркости
- Режим затухания без бега
Реализация — Arduino
Для управления выбрал телефон на Андроиде и синезуб, как наиболее простой способ, не завязанный на систему команд определенного пульта, не подверженный проблемам передачи команд через препятствия вроде стен-мебели-котов.
За основу была взята одна из многочисленных схем бегущего огня с затуханием, найденная на просторах бесконечного.
Максимум числа светодиодов — 6, по количеству PWM выходов выбранного Ардуино.
Что требовалось доделать: возможность подключения модуля синезуба для приема команд управления и отсыла данных о текущем состоянии на телефон для отображения.
С этим я успешно справился, результатом стал скетч для Ардуино:
#include <SoftwareSerial.h>
enum LedState { LED_ON, LED_OFF };
boolean isFadeMode = false;
#define LED_CNT 6
int ledPins[LED_CNT] = {11, 10, 9, 6, 5, 3}; // pwm pins
int ledBrightnessesWave[LED_CNT] = {0, 50, 100, 150, 205, 255}; // represents initial brightness for wave mode
int ledStepsWave[LED_CNT] = {-5, 5, 5, 5, 5, 5}; // represents initial change for wave mode
int ledBrightnesses[LED_CNT]; // represents brightness for pin
int ledSteps[LED_CNT]; // represents change, each pin gets its own change so it wont interfere with any other pin
int ledSpeed = 50;
int maxLedBrightness = 255;
int ledBrightnessesConst = 255; // represents brightness for pins in no wave or fade mode
LedState led_state;
#define rxPin 2
#define txPin 4
#define SPEED_PREFIX 'G'
#define SPEED_PREFIX_MAX 'P'
#define BRIGHTNESS_PREFIX 'Q'
#define BRIGHTNESS_PREFIX_MAX 'Z'
#define MIN_SPEED_DELAY 5
#define MAX_SPEED_DELAY 100
#define MIN_BRIGHTNESS 5
#define MAX_BRIGHTNESS 255
// set up a new serial port
SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin);
void setup()
{
for (int i = 0; i < LED_CNT; i++)
{
pinMode(ledPins[i], OUTPUT); //set pwm pins to output
//copy initial values
ledBrightnesses[i] = ledBrightnessesWave[i];
ledSteps[i] = ledStepsWave[i];
}
led_state = LED_ON;
Serial.begin(9600);
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
mySerial.begin(115200);
}
int getBrightness(int b)
{
return b;
}
int valueToDelay(int value)
{
return MIN_SPEED_DELAY + (MAX_SPEED_DELAY - MIN_SPEED_DELAY) * value / (SPEED_PREFIX_MAX - SPEED_PREFIX);
}
int valueToBrightness(int value)
{
return MIN_BRIGHTNESS + (MAX_BRIGHTNESS - MIN_BRIGHTNESS) * value / (BRIGHTNESS_PREFIX_MAX - BRIGHTNESS_PREFIX);
}
void recalculateBrightness(int brValue)
{
ledBrightnessesConst = valueToBrightness(brValue);
}
void ledFade()
{
if (led_state == LED_OFF)
{
for (int i = 0; i < LED_CNT; i++)
{
analogWrite(ledPins[i], ledBrightnessesConst); // update all pins
}
return;
}
String s = "###";
for (int i = 0; i < LED_CNT; i++)
{
analogWrite(ledPins[i], getBrightness(ledBrightnesses[i]));
int newBr = ledBrightnesses[i] + ledSteps[i];
if (newBr <= 0 || newBr >= maxLedBrightness)
{
ledSteps[i] =- ledSteps[i]; //change direction if exceeds max/min value
}
else
{
ledBrightnesses[i] = newBr;
}
s.concat(ledBrightnesses[i]);
if (i < LED_CNT - 1) //skip separator for last entity
s.concat("-");
}
s.concat("***");
char charBuf[1000];
s.toCharArray(charBuf, 1000);
mySerial.write(charBuf);
mySerial.flush();
delay(ledSpeed);
}
void loop()
{
if (mySerial.available())
{
char command = mySerial.read();
Serial.println("command is -> " + command);
boolean handled = false;
boolean changeFadeMode = false;
switch (command)
{
case 'a':
led_state = LED_OFF;
handled = true;
break;
case 'b':
led_state = LED_ON;
ledSpeed = 50;
handled = true;
//do these lines to set wave mode
changeFadeMode = true;
isFadeMode = true;
break;
case 'c':
led_state = LED_ON;
ledSpeed = 30;
handled = true;
//do these lines to set wave mode
changeFadeMode = true;
isFadeMode = true;
break;
case 'd':
led_state = LED_ON;
ledSpeed = 10;
handled = true;
//do these lines to set wave mode
changeFadeMode = true;
isFadeMode = true;
break;
case 'e':
led_state = LED_ON;
changeFadeMode = true;
handled = true;
break;
default:
break;
}
if (changeFadeMode)
{
if (isFadeMode)
{
Serial.println("Set fade mode off");
for (int i = 0; i < LED_CNT; i++)
{
//copy initial values
ledBrightnesses[i] = ledBrightnessesWave[i];
ledSteps[i] = ledStepsWave[i];
}
}
else
{
Serial.println("Set fade mode on");
for (int i = 0; i < LED_CNT; i++)
{
ledBrightnesses[i] = 0;
ledSteps[i] = 5;
}
}
isFadeMode = !isFadeMode;
}
else
{
//do nothing
}
if (!handled)
{
boolean isSpeedCommand = command >= SPEED_PREFIX && command <= SPEED_PREFIX_MAX;
boolean isBrCommand = command >= BRIGHTNESS_PREFIX && command <= BRIGHTNESS_PREFIX_MAX;
if (isSpeedCommand)
{
led_state = LED_ON;
int speedValue = command - SPEED_PREFIX; //from 0 to 9
ledSpeed = valueToDelay(speedValue);
}
if (isBrCommand)
{
led_state = LED_OFF;
int brValue = command - BRIGHTNESS_PREFIX; //from 0 to 9
recalculateBrightness(brValue);
}
}
}
ledFade();
}
В зависимости от принятой команды контроллер либо изменяет скорость, либо яркость свечения светодиодов. Так же можно запрограммировать любые произвольные действия на принятую команду — в примере я отключаю подсветку и меняю скорость.
В каждом цикле контроллер шлет по синезубу на сопряженное устройство строку вида ###0-50-100-150-200-250***, где цифры — значения яркости шести светодиодов, с максимумом в 255.
Схема:
Проблемы
С чем столкнулся: невозможность приема многосимвольных команд от телефона. Например, посылая команду s150 (по задумке — установить скорость 150 попугаев при максимуме в 255), я получал на Ардуино вместо переданной строки полный бред вида s###, где вместо шарпа шли любые ASCII-символы. Возможно, это связано с использованием класса SoftwareSerial для подключения синезуба. Курение гугла показало, что я не один такой счастливчик с этой проблемой, но нет ни одного решения, кроме как изменение режимов модуля HC-05. Заморачиваться с командами AT не было желания, поэтому решил использовать только односимвольные команды из определенных диапазонов, тем более для поставленной задачи этого вполне хватало. Например — для скорости 10 значений и команды от G до P, для яркости — диапазон от Q до Z соответственно.
Реализация — Android
Для телефона было написано приложение, с возможностью задания передаваемых команд без переписывания и компиляции кода — для универсальности.
Скриншоты приложения:
Особых заморочек не было — благо документация есть, и весьма хорошая, как и громадное количество советов на StackOverflow. Но кое с чем пришлось столкнуться — например невозможность подключиться к Ардуино, используя стандартный метод класса BluetoothDevice:
// Get a BluetoothSocket to connect with the given BluetoothDevice
try
{
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
}
catch (IOException e) { }
Вместо этого пришлось лезть в рефлекшн и дергать приватный метод:
try
{
Class class1 = device.getClass();
Class aclass[] = new Class[1];
aclass[0] = Integer.TYPE;
Method method = class1.getMethod("createRfcommSocket", aclass);
Object aobj[] = new Object[1];
aobj[0] = Integer.valueOf(1);
tmp = (BluetoothSocket)method.invoke(device, aobj);
}
С чем связана эта проблема — неизвестно, но возникает она у многих.
Еще одна проблема возникла при парсинге принимаемой от микроконтроллера строки. Вместо строк вида ###0-50-100-150-200-250***###0-50-100-150-200-250***… иногда приходили строки с ошибочным количеством элементов, неправильными разделителями или маркерами начала/окончания последовательности. Решил проблему написанием анализатора принятой строки и выкидыванием неверных последовательностей. После этого была несложно реализовать вьюшку с отображением текущего состояния светодиодов.
Итоги
Работающее приложение для телефона и скетч для Ардуино, работающая схема на макетке.
Видео примера работы схемы:
Что дальше:
- Собственно окончательная пайка схемы и разводка всех проводов-светодиодов по комнате
- Подключение модулей объема-присутствия для включения/отключения системы в зависимости от присутствия человека в комнате
- Подключение модуля часов — зачем подсветка днем?
Все исходники доступны здесь: code.google.com/p/arduinopad/
Автор: alexpp