Предисловие
Летом прошлого года заинтересовался созданием системы декоративной монохромной подсветки на светодиодах для ремонтируемой квартиры, и встал вопрос, на основе чего её собирать.
Хотелось, чтобы была возможность:
- Управлять режимами подсветки (скорость затухания, яркость свечения) удаленно, с 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/
ссылка на оригинал статьи http://habrahabr.ru/post/165861/
Добавить комментарий