Номинант на премию «БОМЖ-ПРОЕКТ 2020»
В этой части наконец-то будет завершено монументальное строительство сего щщедевра! Для тех, кто пропустил — вот первая и вторая части сей трилогии.
Скорее под кат, там куча картинок (и даже видосики)!
В прошлый раз целиком и полностью (до)разобрались с тем, каким же в итоге получился корпус светильника. В этот раз поговорим про электронную начинку.
Итак, были закуплены следующие детали: esp8266 (с переводом её в категорию маломобильной (читай «без ног»)), кнопочный тумблер а-ля «бабушкина радость», зарядник на 2А и USB-B разъём, чтобы простым и удобным кабелем от принтера запитать всё это великолепие. ESP8266 был куплен у ребят из Мытищ, цена за плату и доставка порадовали.
P.S. От разъёма в дальнейшем отказался, так как расстояние в комнате заказчика от розетки до светильника составляло порядка 4 метров, поэтому был спаян хороший медный провод (1 мм2) длиной в 5 метров от БП до разъёма на светильнике.
Лента была выбрана самая обычная WS2812B, на 5В, 60 диодов на метр, без какой-либо защиты (заказывал ещё давно вот тут, была самая демократичная цена)
На скорую руку спаял всё это дело, чтобы замерить потребление ленты на отрезке в 1 метр:
Потребление в красном цвете было около 1,3А на метр. При включении белого ток лез вверх до 3.5-4А. Для тех, кто в танке: внутри rgb-светодиода находится 3 (микро)светодиода, каждый из которых потребляет ток около 20мА. Все вместе они потребляют под 60мА — это для ситуации, когда вы врубаете на полную катушку белый цвет. БП на 2А брался исключительно для тестов, для работы самого светильника был приобретён БП на 5В/7А в красивом металлическом корпусе с перфорацией :) Весной в наличии «прям сейчас, а лучше вчера» это всё было лишь у ЧиПиДиПа, у них и купил. Там же захватил БП и на 5В/10А на случай, если надо будет (НЕТ, НУ А ВДРУГ?!) увеличить количество светодиодов.
На этой же ленте сразу были проверены некоторые из будущих эффектов светильника:
P.S.S со звуком на видео всё плохо, ощущение будто я в зоопарке это снимал. Поэтому предупреждаю: звук можно и не включать, получите усладу только для глаз!
Эффект старта светильника (из финального кода был убран по желанию
эксплуататоразаказчика):
Красный цвет:
Бегущая волна:
Шрапнель:
Переключение цвета (позже добавились все цвета, которые «каждый охотник желает…»):
Стробоскоп:
В этом месте была допущена ошибка: обман был скрыт в том, что я накинул корпус ПОВЕРХ ленты и решил, что всё супер гуд!
Однако, когда куски светодиодной ленты были приклеены на корпус, спаяны и светильник был повешена на стену, оказалось, что отражённый от стены свет через эпоксидку не пробивается, а только создаёт мощное световое пятно вокруг. Пришлось
колхозитьсоздавать второй слой светильника, о котором я говорил в прошлый раз (будем для удобства звать его Мелким) и переносить светодиодную ленту на него:
Теперь свет бил ровно так, как было надо (на основной корпус были установлены бобышки, а в Мелком просверлены отверстия под саморезы). После проклейки и пайки считаем количество светодиодов: 91шт (для вписывания в будущий код). Итого получилось полтора метра ленты:
Итак, шорты превращаются…
…превращаются шорты….
…в тыкву!Как видно из конструкции, снизу Мелкого был закреплён стандартный круглый разъём питания. Это сделано для того, чтобы в дальнейшем при смене блока питания (например, на такой) ничего не надо было менять в конструкции светильника. Сама плата была установлена в распор на короткие саморезы (всегда можно снять + по высоте есть доступ для кабеля microUSB любой толщины).Теперь тестируем работу кода:
Навеяло:
Празднично вешаем светильник на
засратуютестовую дверь. Вспоминаем, что забыли указать токен Blynk. Празднично снимаем светильник, заливаем обновлённую прошивку и снова вешаем светильник на стену, но уже чуть менее празднично. Что? Откуда в двери дыра? Ээээ…а дыра в двери возникла давным-давно. Правда-правда. И вообще, я художник, я так вижу!
P.S.S.S Как вы уже наверняка догадались, порядок подачи материала немного отличается от фактического течения всего процесса. Понятное дело, что сначала вы скачаете программу, выясните все необходимые значения токена, номера каналов, какие значения к вам прилетают и прочее. Но мы будем более творческими и объединим настройку на стороне мобилки в отдельную подглаву.
Погнали, всё будет офигенски!©
Теперь, пока светильник висит на своём месте (будь оно не ладно), можно быстренько настроить работу смартфона. Качаем приложение Blynk, запускаем (регаемся, получаем токен, указываем с какой платой работаем, ну вот это вот всё) и видим голое поле. Переходим в режим настройки и закидываем на рабочее поле все необходимые кнопки-шуршалки в рамках «бюджета» программы:
Переходим в режим настройки каждого элемента интерфейса:
Если залезть в «кишки» зеАБЫРВАЛГы, то там мы увидим следующее:
Настроим отправку массива с 3 аргументами (R, G, B) по нужному каналу (V4) и укажем диапазон от 0 до 255 (ШИМ-сигнал). По образу и подобию указываем номер канала для отправки управляющего сигнала от других кнопочек-ползунков.
Настройки яркости: номер канала(V1), диапазон (0-255)
Изменение скорости анимации: канал (V2), значение задержки в секундах (0-1023, но тут каждый может сам себе найти отрезок времени по душе)
Настройка кнопки питания: канал (V3), значение (0/1), режим включения кнопки (тумблер)
Настройка выпадающего списка режимов работы светильника
Настройка кнопки сохранения настроек (если хочется после выключения/включения наблюдать тот же эффект)
Получаем в итоге примерно такую картину
Репина:
Как и полагается
жлобамспециалистам, из дефолтного набора баллов выбираем максимальное количество опций (и ещё 200 баллов осталось про запас):
Теперь загрузим код:
Код
#include "SPI.h"
#include "BlynkSimpleEsp8266.h"
#include "ESP8266WiFi.h"
#include "EEPROM.h"
#include "Adafruit_NeoPixel.h"
#define PIN D3
#define PIXELS_NUM 91
Adafruit_NeoPixel pxls = Adafruit_NeoPixel(PIXELS_NUM, PIN, NEO_GRB + NEO_KHZ800);
//==================================================================================================//
#define BLYNK_PRINT Serial
#define RAINBOW_MODE 1
#define RANDOM_COLORS_MODE 2
#define COLOR_WHEEL_MODE 3
#define STROBE_MODE 4
#define SLOW_MOTION_MODE 5
#define CUSTOM_MODE 6
#define RED_VAL 0
#define GREEN_VAL 1
#define BLUE_VAL 2
#define BRIGHTNESS_VAL 3
#define LED_EFFECT_VAL 4
#define BRIGHTNESS 127
#define R_MAX 255
#define G_MAX 255
#define B_MAX 255
#define R_OFF 0
#define G_OFF 0
#define B_OFF 0
#define NUMBER_OF_LED_COLORS 3
#define LED_EFFECT_NUM CUSTOM_MODE
#define STROBE_COUNT 5
#define CHECKPOINT_EEPROM_BYTE 50
#define EEPROM_TIME_DELAY 1000
#define EEPROM_UPDATE_TIME 30000
#define FULL_EEPROM_ARRAY_SIZE 512
#define USER_VAL 1
//==================================================================================================//
byte DefaultSettings[] = {R_MAX, G_MAX, B_MAX, BRIGHTNESS, LED_EFFECT_NUM};
byte Settings[] = {0, 0, 0, 0, 0};
byte ColorWheelArr[PIXELS_NUM][NUMBER_OF_LED_COLORS];
bool EepromUpdateFlag = true;
uint32_t EepromSaveTimer = 0;
uint32_t LedEffectTimer;
uint8_t TemporaryEffectNumber;
uint16_t LedEffectTimeDelay = 25;
uint8_t RainbowCycle, WheelPos;
uint8_t HsvAngle = 0;
bool PowerButtonState = false;
bool AnimationReverseFlag = false;
bool RandomColorsState = true;
char auth[] = "***********************";
char ssid[] = "***********************";
char pass[] = "***********************";
//==================================================================================================//
void eeprom_starting_settings();
void rainbow_mode_func();
void random_colors_mode_func();
void color_wheel_mode_func();
void strobe_mode_func();
void slow_motion_mode_func();
void pixels_off();
void set_each_pixel_color_func(byte, byte, byte, byte, byte);
void set_all_pixels_color_func(byte, byte, byte, byte);
//==================================================================================================//
//==================================================================================================//
void setup() {
Serial.begin(115200);
Blynk.begin(auth, ssid, pass);
pxls.begin();
randomSeed(analogRead(A0));
EEPROM.begin(FULL_EEPROM_ARRAY_SIZE);
eeprom_starting_settings();
TemporaryEffectNumber = Settings[LED_EFFECT_VAL];
pixels_off();
}
void loop() {
Blynk.run();
if(Settings[LED_EFFECT_VAL] != CUSTOM_MODE && PowerButtonState){
if(Settings[LED_EFFECT_VAL] == RAINBOW_MODE ) rainbow_mode_func(); else
if(Settings[LED_EFFECT_VAL] == RANDOM_COLORS_MODE) random_colors_mode_func(); else
if(Settings[LED_EFFECT_VAL] == COLOR_WHEEL_MODE ) color_wheel_mode_func(); else
if(Settings[LED_EFFECT_VAL] == STROBE_MODE ) strobe_mode_func(); else
if(Settings[LED_EFFECT_VAL] == SLOW_MOTION_MODE ) slow_motion_mode_func();
}
}
//==================================================================================================//
//==================================================================================================//
void eeprom_starting_settings(){
if (EEPROM.read(CHECKPOINT_EEPROM_BYTE) == USER_VAL) {
for (int i = 0; i < (sizeof(Settings) / sizeof(Settings[0])); i++) {
byte EepromCurrentValue = EEPROM.read(i);
Settings[i] = EepromCurrentValue;
}
}
else {
for (int i = 0; i < (sizeof(Settings) / sizeof(Settings[0])); i++) {
Settings[i] = DefaultSettings[i];
}
}
}
//==================================================================================================//
void set_each_pixel_color_func(byte WorkingMode, byte Brightness, byte R = Settings[RED_VAL], byte G = Settings[GREEN_VAL], byte B = Settings[BLUE_VAL]){
for (int i = 0; i < PIXELS_NUM; i++) {
if(WorkingMode == RAINBOW_MODE){
WheelPos=((uint16_t)(i*256 / PIXELS_NUM) + RainbowCycle);
if(WheelPos < 85) { R = WheelPos*3; G = 255 - R; B = 0; } else
if(WheelPos < 170){WheelPos -= 85; R = 255 - B; G = 0; B = WheelPos*3;} else
{WheelPos -= 170; R = 0; G = WheelPos*3; B = 255 - G; }
}
if(WorkingMode == RANDOM_COLORS_MODE){
int8_t n = random(3);
if(n == 0){R = 0;} else {R = random(255);}
if(n == 1){G = 0;} else {G = random(255);}
if(n == 2){B = 0;} else {B = random(255);}
}
if(WorkingMode == SLOW_MOTION_MODE){
R = ColorWheelArr[i][RED_VAL];
G = ColorWheelArr[i][GREEN_VAL];
B = ColorWheelArr[i][BLUE_VAL];
}
pxls.setPixelColor(i,
pxls.Color(map(R, 0, 255, 0, Brightness),
map(G, 0, 255, 0, Brightness),
map(B, 0, 255, 0, Brightness)));
}
}
//==================================================================================================//
void set_all_pixels_color_func(byte R = Settings[RED_VAL], byte G = Settings[GREEN_VAL], byte B = Settings[BLUE_VAL], byte Brightness = Settings[BRIGHTNESS_VAL]){
pxls.fill(pxls.Color(map(R, 0, 255, 0, Brightness),
map(G, 0, 255, 0, Brightness),
map(B, 0, 255, 0, Brightness)));
pxls.show();
}
//==================================================================================================//
void pixels_off(){
set_each_pixel_color_func(CUSTOM_MODE, Settings[BRIGHTNESS_VAL], R_OFF, G_OFF, B_OFF);
pxls.show();
}
//==================================================================================================//
void rainbow_mode_func(){
if(millis() - LedEffectTimer > LedEffectTimeDelay){
LedEffectTimer = millis();
RainbowCycle++;
set_each_pixel_color_func(RAINBOW_MODE, Settings[BRIGHTNESS_VAL]);
pxls.show();
}
}
//==================================================================================================//
void random_colors_mode_func(){
if(millis() - LedEffectTimer > LedEffectTimeDelay){
LedEffectTimer = millis();
set_each_pixel_color_func(RANDOM_COLORS_MODE, Settings[BRIGHTNESS_VAL]);
pxls.show();
}
}
//==================================================================================================//
void color_wheel_mode_func(){
if(millis() - LedEffectTimer > LedEffectTimeDelay*2){
LedEffectTimer = millis();
if(HsvAngle == 0 ){HsvAngle = 42; } else
if(HsvAngle == 42 ){HsvAngle = 85; } else
if(HsvAngle == 85 ){HsvAngle = 127;} else
if(HsvAngle == 127){HsvAngle = 170;} else
if(HsvAngle == 170){HsvAngle = 212;} else
if(HsvAngle == 212){HsvAngle = 0; }
if(HsvAngle < 85) {Settings[BLUE_VAL] = 0; Settings[RED_VAL] = HsvAngle*3; Settings[GREEN_VAL] = 255 - Settings[RED_VAL]; } else
if(HsvAngle < 170) {Settings[GREEN_VAL] = 0; Settings[BLUE_VAL] = HsvAngle*3; Settings[RED_VAL] = 255 - Settings[BLUE_VAL]; } else
{Settings[RED_VAL] = 0; Settings[GREEN_VAL] = HsvAngle*3; Settings[BLUE_VAL] = 255 - Settings[GREEN_VAL];}
set_all_pixels_color_func();
}
}
//==================================================================================================//
void strobe_mode_func(){
if(millis() - LedEffectTimer > LedEffectTimeDelay){
LedEffectTimer = millis();
for(int j = 0; j < STROBE_COUNT; j++) {
set_all_pixels_color_func(R_MAX, G_MAX, B_MAX);
delay(LedEffectTimeDelay / 2);
set_all_pixels_color_func(R_OFF, G_OFF, B_OFF);
delay(LedEffectTimeDelay / 2);
}
}
}
//==================================================================================================//
void slow_motion_mode_func(){
if(millis() - LedEffectTimer > LedEffectTimeDelay){
LedEffectTimer = millis();
if(RandomColorsState){
RandomColorsState = false;
for (int i = 0; i < PIXELS_NUM; i++) {
int8_t RandomNumber = random(3);
if(RandomNumber == 0){ColorWheelArr[i][RED_VAL] = 0;} else {ColorWheelArr[i][RED_VAL] = random(255);}
if(RandomNumber == 1){ColorWheelArr[i][GREEN_VAL] = 0;} else {ColorWheelArr[i][GREEN_VAL] = random(255);}
if(RandomNumber == 2){ColorWheelArr[i][BLUE_VAL] = 0;} else {ColorWheelArr[i][BLUE_VAL] = random(255);}
}
}
if(!AnimationReverseFlag){
for(int BrightnessUp = 0; BrightnessUp <= Settings[BRIGHTNESS_VAL]; BrightnessUp++){
if (BrightnessUp == Settings[BRIGHTNESS_VAL]) {AnimationReverseFlag = true;}
set_each_pixel_color_func(SLOW_MOTION_MODE, BrightnessUp);
pxls.show();
delay(LedEffectTimeDelay / 2);
}
}
else {
for(int BrightnessDown = Settings[BRIGHTNESS_VAL]; BrightnessDown >= 0; BrightnessDown--){
if (BrightnessDown == 0) {AnimationReverseFlag = false; RandomColorsState = true;}
set_each_pixel_color_func(SLOW_MOTION_MODE, BrightnessDown);
pxls.show();
delay(LedEffectTimeDelay / 2);
}
}
}
}
//==================================================================================================//
//==================================================================================================//
BLYNK_WRITE(V1) { // BRIGHTNESS HORIZONTAL SLIDER
Settings[BRIGHTNESS_VAL] = param.asInt();
if (PowerButtonState) {
set_each_pixel_color_func(CUSTOM_MODE, Settings[BRIGHTNESS_VAL]);
pxls.show();
}
}
//==================================================================================================//
BLYNK_WRITE(V2) { // ANIMATION SPEED HORIZONTAL SLIDER
LedEffectTimeDelay = param.asInt();
}
//==================================================================================================//
BLYNK_WRITE(V3) { // POWER BUTTON
PowerButtonState = param.asInt();
if (PowerButtonState) {
Settings[LED_EFFECT_VAL] = TemporaryEffectNumber;
if(Settings[LED_EFFECT_VAL] == CUSTOM_MODE){
set_each_pixel_color_func(CUSTOM_MODE, Settings[BRIGHTNESS_VAL]);
pxls.show();
}
else { LedEffectTimer = millis() - LedEffectTimeDelay;}
}
else {
TemporaryEffectNumber = Settings[LED_EFFECT_VAL];
Settings[LED_EFFECT_VAL] = CUSTOM_MODE;
pixels_off();
}
}
//==================================================================================================//
BLYNK_WRITE(V4) { // ZeRGBa
Settings[RED_VAL] = param[0].asInt();
Settings[GREEN_VAL] = param[1].asInt();
Settings[BLUE_VAL] = param[2].asInt();
if (PowerButtonState) {
set_each_pixel_color_func(CUSTOM_MODE, Settings[BRIGHTNESS_VAL]);
pxls.show();
}
}
//==================================================================================================//
BLYNK_WRITE(V5) { // LED EFFECTS MENU
switch (param.asInt()) {
case 1: Settings[LED_EFFECT_VAL] = RAINBOW_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
case 2: Settings[LED_EFFECT_VAL] = RANDOM_COLORS_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
case 3: Settings[LED_EFFECT_VAL] = COLOR_WHEEL_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
case 4: Settings[LED_EFFECT_VAL] = STROBE_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
case 5: Settings[LED_EFFECT_VAL] = SLOW_MOTION_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
case 6: Settings[LED_EFFECT_VAL] = CUSTOM_MODE; LedEffectTimer = millis() - LedEffectTimeDelay; break;
}
}
//==================================================================================================//
BLYNK_WRITE(V6) { // EEPROM WRITE
int pinValue = param.asInt();
if (pinValue && PowerButtonState && (millis() - EepromSaveTimer > EEPROM_TIME_DELAY)) {
EepromSaveTimer = millis();
EEPROM.write(CHECKPOINT_EEPROM_BYTE, 1);
for (int i = 0; i < (sizeof(Settings) / sizeof(Settings[0])); i++) {
byte EepromCurrentValue = Settings[i];
EEPROM.write(i, EepromCurrentValue);
}
EEPROM.commit();
}
}
//==================================================================================================//
Вот и всё, можно переходить к тестированию!
Ну и маленькое видео одного из эффектов:
P.S.S.S.S. Код сего девайса вы также найдёте тут: GitHub. Вдруг пригодится?!
Вот и сказочке конец. Спасибо за внимание!
Засим откланиваюсь. Ваш А.С.
the_matrix Автор
Прошу прощения за кривые ссылки на видео. Поправил.