Привет Хабр!
Предисловие
Идея создания многоцветной лампы пришла мне в голову после того, как я увидела, какое завораживающее действие оказывает мигающая гирлянда на моего шестимесячного сына.Захотелось создать что-то похожее, только выполняло бы какую-нибудь полезную функцию - например, ночник, с возможностью гибкой настройки режимов и с возможностью дистанционного управления.
И, конечно же, созданное устройство должно быть не менее привлекательным для ребенка, чем китайская гирлянда.
Внимание, под катом много фотографий.
Предварительные шаги
Выбор пал на создание торшера, который за счет затемнения света абажуром не светился бы ночью, как прожектор, занимал мало места и мог одновременно отображать несколько цветов одновременно.Нечто подобное уже создавал житель Хабраво – тогда это был светильник, показывающий погоду, собранный на малиновом дереве.
Задачи серфить в интернете у меня не было, хотелось просто «мигать светодиодами», поэтому в качестве мозга было решено взять Ардуино, так как дома у меня завалялась его маленькая версия, Нано, купил от дяди Ляо.
Для дистанционного управления я решил использовать самый простой блютуз-модуль Bolutek для пары вечнозеленых растений.
Далее встал вопрос о создании самого светильника.
Было решено не изобретать велосипед, а взять готовый.
Основой послужил торшер с бумажным абажуром, купленный в ближайшей Икее за очень похожие 500 рублей.
Было решено, что внутри стержня можно будет прикрепить несколько площадок для наклеивания светодиодной ленты, что создаст освещение приличной яркости.
Я взял цветную ленту модели 5050, 60 светодиодов на метр, без индивидуального управления светодиодами.
Платформы были изготовлены из монтажной металлической ленты для крепления полов с подогревом – продается во всех строительных магазинах.
Достаточно было 20-метрового рулона ленты.
Из ленты я сделала кольца диаметром примерно 14 см с возможностью крепления их к стержню лампы.
При этом кольца выполняют роль теплоотвода - т.к.
лента нагревается весьма ощутимо.
Тогда я начал думать о том, как самостоятельно управлять цветом всех уровней, имея на Ардуино ограниченное количество ШИМ-выходов.
Я выбрал 8 уровней, потому что.
чем больше, тем больше лампа начинала прогибаться под тяжестью металлических подушек.
И все же шведские конструкторы не ожидали, что к стержню лампы будет прикреплено что-то еще.
Таким образом у меня появилась необходимость управлять 24 выходами, с возможностью плавной регулировки выходного напряжения на них.
Ни у одной Arduino нет такого количества выходов (может быть и у Mega, но такое решение примитивно), поэтому для решения задачи я использовал библиотеку ShiftPWM, которая удовлетворит любые разумные требования по количеству уровней.
С помощью недорогих и доступных микроустройств — сдвигового регистра 74hc595 и ключей uln2803 — я обеспечил необходимое количество управляемых выходов и ток, необходимый для питания лент.
Вариант осуществления
Первым делом я сделал из металлической ленты 8 площадок, которые прикрепил к стержню лампы.Выдержать одинаковое расстояние между уровнями не удалось из-за особенностей монтажа штатного источника света, но это оказалось неважно:
|
|
|
Контроллер решил разместить лампы внизу, ближе к блоку питания.
Все кабели к стержню я привязал изолентой, чтобы они не болтались:
|
|
Типичную схему подключения сдвигового регистра 74hc595 я взял с сайта мануалов ShiftPWM. Схема подключения сдвигового регистра
Схема подключения uln2803
Собрал на макетке схему с одиночными светодиодами, загрузил пример скетча и убедился, что все работает как надо:
После этого пришло время собрать схему в серийный вариант. Потому что мне хотелось собрать схему как можно быстрее, а опробовать технологию ЛУТ не было времени — я собрал контроллер на двух печатных платах-прототипах размером 4 на 6 см.
На первом я разместил ардуино и модуль блютуз для дистанционного управления, на втором — регистры, ключи и разъемы для подключения лент:
Потом скрутил обе доски между собой, получился своеобразный сэндвич:
В качестве блока питания я взял недорогой китайский, на 12В, с выходным током до 5А:
В качестве корпуса я использовал распределительную коробку следующих размеров:
Далее разместил блок питания внутри корпуса и бутерброд из плат, протянув внутрь провода от лент:
В результате получилось довольно компактное устройство, которое можно было легко спрятать под абажуром, чтобы оно не привлекало внимания.
Для управления контроллером я ранее написал программу для Android. В целом для управления схемой подойдет любой Bluetooth-терминал — достаточно будет запрограммировать несколько предустановленных команд для передачи, а для выбора пользовательского цвета подсветки — задать цветовой код RGB в формате Р,Г,Б .
Ниже приведен код скетча, который необходимо загрузить.
Схема имеет 10 предустановленных режимов, а также возможность задать свои цвета.
Эскиз
На видео показан режим радуги, ИМХО самый красивый режим работы: И пару фотографий готового устройства — жаль, что камера не может передать богатство цветов:/* * ShiftPWM non-blocking RGB fades example, (c) Elco Jacobs, updated August 2012. * * This example for ShiftPWM shows how to control your LED's in a non-blocking way: no delay loops. * This example receives a number from the serial port to set the fading mode. Instead you can also read buttons or sensors. * It uses the millis() function to create fades. The block fades example might be easier to understand, so start there. * * Please go to www.elcojacobs.com/shiftpwm for documentation, fuction reference and schematics. * If you want to use ShiftPWM with LED strips or high power LED's, visit the shop for boards. */ // ShiftPWM uses timer1 by default. To use a different timer, before '#include <ShiftPWM.h>', add // #define SHIFTPWM_USE_TIMER2 // for Arduino Uno and earlier (Atmega328) // #define SHIFTPWM_USE_TIMER3 // for Arduino Micro/Leonardo (Atmega32u4) // Clock and data pins are pins from the hardware SPI, you cannot choose them yourself. // Data pin is MOSI (Uno and earlier: 11, Leonardo: ICSP 4, Mega: 51, Teensy 2.0: 2, Teensy 2.0++: 22) // Clock pin is SCK (Uno and earlier: 13, Leonardo: ICSP 3, Mega: 52, Teensy 2.0: 1, Teensy 2.0++: 21) // You can choose the latch pin yourself. const int ShiftPWM_latchPin=8; // ** uncomment this part to NOT use the SPI port and change the pin numbers. This is 2.5x slower ** // #define SHIFTPWM_NOSPI // const int ShiftPWM_dataPin = 11; // const int ShiftPWM_clockPin = 13; // If your LED's turn on if the pin is low, set this to true, otherwise set it to false. const bool ShiftPWM_invertOutputs = false; // You can enable the option below to shift the PWM phase of each shift register by 8 compared to the previous. // This will slightly increase the interrupt load, but will prevent all PWM signals from becoming high at the same time. // This will be a bit easier on your power supply, because the current peaks are distributed. const bool ShiftPWM_balanceLoad = false; #define rxPin 2 #define txPin 4 #include <SoftwareSerial.h> #include <ShiftPWM.h> // include ShiftPWM.h after setting the pins! // Function prototypes (telling the compiler these functions exist).
void oneByOne(void); void inOutTwoLeds(void); void inOutAll(void); void alternatingColors(void); void hueShiftAll(void); void randomColors(void); void fakeVuMeter(void); void rgbLedRainbow(unsigned long cycleTime, int rainbowWidth); void printInstructions(void); void setColor(int r, int g, int b); // Here you set the number of brightness levels, the update frequency and the number of shift registers. // These values affect the load of ShiftPWM. // Choose them wisely and use the PrintInterruptLoad() function to verify your load. unsigned char maxBrightness = 255; unsigned char pwmFrequency = 75; unsigned int numRegisters = 6; unsigned int numOutputs = numRegisters*8; unsigned int numRGBLeds = numRegisters*8/3; unsigned int fadingMode = 0; //start with all LED's off. int r = 0; int g = 0; int b = 0; unsigned long startTime = 0; // start time for the chosen fading mode SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); void setup(){ while(!Serial){ delay(100); } Serial.begin(9600); pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); // Sets the number of 8-bit registers that are used. ShiftPWM.SetAmountOfRegisters(numRegisters); // SetPinGrouping allows flexibility in LED setup. // If your LED's are connected like this: RRRRGGGGBBBBRRRRGGGGBBBB, use SetPinGrouping(4).
ShiftPWM.SetPinGrouping(1); //This is the default, but I added here to demonstrate how to use the funtion ShiftPWM.Start(pwmFrequency,maxBrightness); printInstructions(); mySerial.begin(9600); } void loop() { String command = ""; while (mySerial.available() > 0) { char c = mySerial.read(); Serial.println(c); command.concat(c); } command.trim(); if (command == "") { } else { startTime = millis(); } int firstCommaPos = -1; int lastCommaPos = -1; firstCommaPos = command.indexOf(','); lastCommaPos = command.lastIndexOf(','); if (firstCommaPos != -1 && lastCommaPos != -1 && lastCommaPos != firstCommaPos) { String rStr = command.substring(0, firstCommaPos); String gStr = command.substring(firstCommaPos + 1, lastCommaPos); String bStr = command.substring(lastCommaPos + 1); // Serial.println("r is -> " + rStr); // Serial.println("g is -> " + gStr); // Serial.println("b is -> " + bStr); r = rStr.toInt(); g = gStr.toInt(); b = bStr.toInt(); fadingMode = 10; } if (command == "a") fadingMode = 0; if (command == "b") fadingMode = 1; if (command == "c") fadingMode = 2; if (command == "d") fadingMode = 3; if (command == "e") fadingMode = 4; if (command == "f") fadingMode = 5; if (command == "g") fadingMode = 6; if (command == "h") fadingMode = 7; if (command == "i") fadingMode = 8; if (command == "j") fadingMode = 9; Serial.println("command is -> " + command); switch(fadingMode){ case 0: // Turn all LED's off. ShiftPWM.SetAll(0); break; case 1: oneByOne(); break; case 2: inOutAll(); break; case 3: inOutTwoLeds(); break; case 4: alternatingColors(); break; case 5: hueShiftAll(); break; case 6: randomColors(); break; case 7: fakeVuMeter(); break; case 8: rgbLedRainbow(3000,numRGBLeds); break; case 9: rgbLedRainbow(10000,5*numRGBLeds); break; case 10: setColor(r,g,b); break; default: Serial.println("Unknown Mode!"); delay(1000); break; } } void setColor(int r, int g, int b) { ShiftPWM.SetAll(0); ShiftPWM.SetAllRGB(r,g,b); } void oneByOne(void){ // Fade in and fade out all outputs one at a time unsigned char brightness; unsigned long fadeTime = 500; unsigned long loopTime = numOutputs*fadeTime*2; unsigned long time = millis()-startTime; unsigned long timer = time%loopTime; unsigned long currentStep = timer%(fadeTime*2); int activeLED = timer/(fadeTime*2); if(currentStep <= fadeTime ){ brightness = currentStep*maxBrightness/fadeTime; ///fading in } else{ brightness = maxBrightness-(currentStep-fadeTime)*maxBrightness/fadeTime; ///fading out; } ShiftPWM.SetAll(0); ShiftPWM.SetOne(activeLED, brightness); } void inOutTwoLeds(void){ // Fade in and out 2 outputs at a time unsigned long fadeTime = 500; unsigned long loopTime = numOutputs*fadeTime; unsigned long time = millis()-startTime; unsigned long timer = time%loopTime; unsigned long currentStep = timer%fadeTime; int activeLED = timer/fadeTime; unsigned char brightness = currentStep*maxBrightness/fadeTime; ShiftPWM.SetAll(0); ShiftPWM.SetOne((activeLED+1)%numOutputs,brightness); ShiftPWM.SetOne(activeLED,maxBrightness-brightness); } void inOutAll(void){ // Fade in all outputs unsigned char brightness; unsigned long fadeTime = 2000; unsigned long time = millis()-startTime; unsigned long currentStep = time%(fadeTime*2); if(currentStep <= fadeTime ){ brightness = currentStep*maxBrightness/fadeTime; ///fading in } else{ brightness = maxBrightness-(currentStep-fadeTime)*maxBrightness/fadeTime; ///fading out; } ShiftPWM.SetAll(brightness); } void alternatingColors(void){ // Alternate LED's in 6 different colors unsigned long holdTime = 2000; unsigned long time = millis()-startTime; unsigned long shift = (time/holdTime)%6; for(unsigned int led=0; led<numRGBLeds; led++){ switch((led+shift)%6){ case 0: ShiftPWM.SetRGB(led,255,0,0); // red break; case 1: ShiftPWM.SetRGB(led,0,255,0); // green break; case 2: ShiftPWM.SetRGB(led,0,0,255); // blue break; case 3: ShiftPWM.SetRGB(led,255,128,0); // orange break; case 4: ShiftPWM.SetRGB(led,0,255,255); // turqoise break; case 5: ShiftPWM.SetRGB(led,255,0,255); // purple break; } } } void hueShiftAll(void){ // Hue shift all LED's unsigned long cycleTime = 10000; unsigned long time = millis()-startTime; unsigned long hue = (360*time/cycleTime)%360; ShiftPWM.SetAllHSV(hue, 255, 255); } void randomColors(void){ // Update random LED to random color. Funky! unsigned long updateDelay = 100; static unsigned long previousUpdateTime; if(millis()-previousUpdateTime > updateDelay){ previousUpdateTime = millis(); ShiftPWM.SetHSV(random(numRGBLeds),random(360),255,255); } } void fakeVuMeter(void){ // imitate a VU meter static unsigned int peak = 0; static unsigned int prevPeak = 0; static unsigned long currentLevel = 0; static unsigned long fadeStartTime = startTime; unsigned long fadeTime = (currentLevel*2);// go slower near the top unsigned long time = millis()-fadeStartTime; currentLevel = time%(fadeTime); if(currentLevel==peak){ // get a new peak value prevPeak = peak; while(abs(peak-prevPeak)<5){ peak = random(numRGBLeds); // pick a new peak value that differs at least 5 from previous peak } } if(millis() - fadeStartTime > fadeTime){ fadeStartTime = millis(); if(currentLevel<peak){ //fading in currentLevel++; } else{ //fading out currentLevel--; } } // animate to new top for(unsigned int led=0;led<numRGBLeds;led++){ if(led<currentLevel){ int hue = (numRGBLeds-1-led)*120/numRGBLeds; // From green to red ShiftPWM.SetHSV(led,hue,255,255); } else if(led==currentLevel){ int hue = (numRGBLeds-1-led)*120/numRGBLeds; // From green to red int value; if(currentLevel<peak){ //fading in value = time*255/fadeTime; } else{ //fading out value = 255-time*255/fadeTime; } ShiftPWM.SetHSV(led,hue,255,value); } else{ ShiftPWM.SetRGB(led,0,0,0); } } } void rgbLedRainbow(unsigned long cycleTime, int rainbowWidth){ // Displays a rainbow spread over a few LED's (numRGBLeds), which shifts in hue. // The rainbow can be wider then the real number of LED's. unsigned long time = millis()-startTime; unsigned long colorShift = (360*time/cycleTime)%360; // this color shift is like the hue slider in Photoshop. for(unsigned int led=0;led<numRGBLeds;led++){ // loop over all LED's int hue = ((led)*360/(rainbowWidth-1)+colorShift)%360; // Set hue from 0 to 360 from first to last led and shift the hue ShiftPWM.SetHSV(led, hue, 255, 255); // write the HSV values, with saturation and value at maximum } } void printInstructions(void){ Serial.println("---- ShiftPWM Non-blocking fades demo ----"); Serial.println(""); Serial.println("Type 'l' to see the load of the ShiftPWM interrupt (the % of CPU time the AVR is busy with ShiftPWM)"); Serial.println(""); Serial.println("Type any of these numbers to set the demo to this mode:"); Serial.println(" 0. All LED's off"); Serial.println(" 1. Fade in and out one by one"); Serial.println(" 2. Fade in and out all LED's"); Serial.println(" 3. Fade in and out 2 LED's in parallel"); Serial.println(" 4. Alternating LED's in 6 different colors"); Serial.println(" 5. Hue shift all LED's"); Serial.println(" 6. Setting random LED's to random color"); Serial.println(" 7. Fake a VU meter"); Serial.println(" 8. Display a color shifting rainbow as wide as the LED's"); Serial.println(" 9. Display a color shifting rainbow wider than the LED's"); Serial.println(""); Serial.println("Type 'm' to see this info again"); Serial.println(""); Serial.println("----"); }
|
|
Полученные результаты
Собранный светильник полностью соответствует оригинальной задумке, выглядит как готовое изделие, провода не торчат, легко управляется, сын доволен - а это главное! Все компоненты контроллера куплены на Ebay, найти их там может любой желающий - цены минимальные.Общая стоимость лампы составила примерно: 500 рублей сама лампа, 800 рублей светодиодная лента, 100 рублей блок питания, микросхемы - 50 рублей, блютуз 200 рублей, корпус и стальная лента для крепления - 200 рублей.
.
Итого – около 1900 руб.
Сборка заняла около 2 недель в свободное время по вечерам.
Наверняка вы сможете сделать это быстрее, если не будете отвлекаться.
Что еще можно сделать:
- Используйте LUT вместо плат разработки.
- Улучшите ПО для телефона, чтобы можно было регулировать яркость/скорость эффектов и т.д.
- Вместо Адруинки возьмите Малину - и получите возможность автоматически включать освещение, вручную управлять существующими режимами или создавать новые без перезагрузки прошивки
- Вместо ленты 5050 возьмите ленту ws2811 - и вы сможете управлять не целыми уровнями, а каждым отдельным светодиодом
- Теперь штатная лампочка никак не связана с микроконтроллером - но можно установить реле и управлять им удаленно с телефона.
- Включайте его хлопком в ладоши - чтобы не приходилось каждый раз проходить по программе для включения
- .
- Еще десятки улучшений
Теги: #Android #Сделай сам или Сделай сам #arduino #Bluetooth #декоративное освещение дома
-
Кошки И Скрам
19 Oct, 24 -
Windows Server И Azure Объединяются
19 Oct, 24 -
Позиционирование Плагина Jquery V1.1
19 Oct, 24 -
Правила Жизни Тима О'рейли
19 Oct, 24