Совмещаем несовмещаемое, да ещё и с гирляндой

Всем доброго времени суток. Появилось желание повторить проект с бесконечным кубом (пример выполнения:


Из железок используется: arduino uno, лента ws2812b, БП. Можно просто взять готовый код, банку клея (нужно ведь как-то это всё “клеить”? :slight_smile:) и повторить, однако было принято решение, что изменение режимов свечения по времени - не интересно, хочется управлять этим самостоятельно. Для этой цели определён инфракрасный модуль с пультом HX1838 (самый стандартный пульт для ардуинок). Вроде бы цели понятны, задачи определены и даже клей уже открыт, но руки упорно не хотят написать код, хоть немного похожий на правду. Вдохновение и немного букв программы черпал из кода от Gyver-а

Спойлер
#define EDGE_LEDS 11    // кол-во диодов на ребре куба
#define LED_DI 2        // пин подключения
#define BRIGHT 255      // яркость
#define CHANGE_PRD 10   // смена режима, секунд
#define CUR_LIMIT 2000  // лимит тока в мА (0 - выкл)

// ===== ДЛЯ РАЗРАБОВ =====
const int NUM_LEDS = (EDGE_LEDS * 24);
#define USE_MICROLED 0
#include <FastLED.h>
CRGBPalette16 currentPalette;
const int FACE_SIZE = EDGE_LEDS * 4;
const int LINE_SIZE = EDGE_LEDS;
#define PAL_STEP 30
CRGB leds[NUM_LEDS];

int perlinPoint;
int curBright = BRIGHT;
bool fadeFlag = false;
bool mode = true;
bool colorMode = true;
uint16_t counter = 0;
byte speed = 15;
uint32_t tmrDraw, tmrColor, tmrFade;

#define DEBUG(x) //Serial.println(x)

uint32_t getPixColor(CRGB color) {
 return (((uint32_t)color.r << 16) | (color.g << 8) | color.b);
}

void setup() {
 Serial.begin(9600);
 FastLED.addLeds<WS2812, LED_DI, GRB>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
 if (CUR_LIMIT > 0) FastLED.setMaxPowerInVoltsAndMilliamps(5, CUR_LIMIT);
 FastLED.setBrightness(BRIGHT);
 FastLED.clear();
  
 randomSeed(getEntropy(A0));   // My system's in decline, EMBRACING ENTROPY!
 perlinPoint = random(0, 32768);
 fadeFlag = true;  // сразу флаг на смену режима
}

void loop() {
 // отрисовка
 if (millis() - tmrDraw >= 40) {
   tmrDraw = millis();
   for (int i = 0; i < FACE_SIZE; i++) {
     if (mode) fillSimple(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP + counter, counter), 255, LINEARBLEND));
     else fillVertex(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP / 4 + counter, counter), 255, LINEARBLEND));
   }
   FastLED.show();
   counter += speed;
 }

 // смена режима и цвета
 if (millis() - tmrColor >= CHANGE_PRD * 1000L) {
   tmrColor = millis();
   fadeFlag = true;
 }

 // фейдер для смены через чёрный
 if (fadeFlag && millis() - tmrFade >= 30) {
   static int8_t fadeDir = -1;
   tmrFade = millis();
   if (fadeFlag) {
     curBright += 5 * fadeDir;

     if (curBright < 5) {
       curBright = 5;
       fadeDir = 1;
       changeMode();
     }
     if (curBright > BRIGHT) {
       curBright = BRIGHT;
       fadeDir = -1;
       fadeFlag = false;
     }
     FastLED.setBrightness(curBright);
   }
 }
} // луп

void changeMode() {
 if (!random(3)) mode = !mode;
 speed = random(4, 12);
 colorMode = !colorMode;
 int thisDebth = random(0, 32768);
 byte thisStep = random(2, 7) * 5;
 bool sparkles = !random(5);

 if (colorMode) {
   for (int i = 0; i < 16; i++) {
     currentPalette[i] = CHSV(getMaxNoise(thisDebth + i * thisStep, thisDebth),
                              sparkles ? (!random(9) ? 30 : 255) : 255,
                              constrain((i + 7) * (i + 7), 0, 255));
   }
 } else {
   for (int i = 0; i < 4; i++) {
     CHSV color = CHSV(random(0, 256), random(0, 256), (uint8_t)(i + 1) * 64 - 1);
     for (byte j = 0; j < 4; j++) {
       currentPalette[i * 4 + j] = color;
     }
   }
 }
}

// масштабируем шум
byte getMaxNoise(uint16_t x, uint16_t y) {
 return constrain(map(inoise8(x, y), 50, 200, 0, 255), 0, 255);
}

// заливка всех 6 граней в одинаковом порядке
void fillSimple(int num, CRGB color) {  // num 0-NUM_LEDS / 6
 for (byte i = 0; i < 6; i++) {
   leds[i * FACE_SIZE + num] = color;
 }
}

// заливка из четырёх вершин
void fillVertex(int num, CRGB color) { // num 0-NUM_LEDS / 6
 num /= 4;
 byte thisRow = 0;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
 }
 thisRow = 13;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
 }
}

// рандом сид из сырого аналога
uint32_t getEntropy(byte pin) {
 unsigned long seed = 0;
 for (int i = 0; i < 400; i++) {
   seed = 1;
   for (byte j = 0; j < 16; j++) {
     seed *= 4;
     seed += analogRead(pin) & 3;
   }
 }
 return seed;
}

Здесь представлены 2 режима отрисовки: по граням и из вершин. Насколько я понимаю, это регулируется значением mode (true/false). По моей логике, если привязать функцию mode= !mode к кнопке, то я смогу менять эти 2 режима отрисовки. В розовых мечтах была ещё регулировка яркости и т.д., но хотел остановиться хотя бы на этом.
Мои потуги под спойлером:

Спойлер
#include <FastLED.h> // Для ленты
#include <IRremote.h> // Для пульта

#define EDGE_LEDS 11    // кол-во диодов на ребре куба
#define LED_PIN 2        // пин подключения ленты
#define IR_PIN 8        // пин подключения пульта
#define PAL_STEP 30 //шаг по палитре
#define USE_MICROLED 0
CRGBPalette16 currentPalette; //палитра
const int NUM_LEDS = (EDGE_LEDS * 24); //всего светодиодов
const int FACE_SIZE = EDGE_LEDS * 4; //светодиодов на грани
const int LINE_SIZE = EDGE_LEDS;//светодиодов на линии
CRGB leds[NUM_LEDS];
int perlinPoint; //точка шума Перлина
uint32_t getPixColor(CRGB color) {
 return (((uint32_t)color.r << 16) | (color.g << 8) | color.b);
} //преобразует цвет из формата CRGB в 32-битное целое число для удобства работы с цветами

IRrecv irrecv(IR_PIN); //пульт
decode_results results;//пульт
// Коды кнопок ИК-пульта
#define BTN_0       0x97483BFB
#define BTN_LEFT    0x8C22657B
#define BTN_RIGHT   0x449E79F
int step = 25;  // Шаг регулировки яркости
byte speed = 15;
uint16_t counter = 0;
uint32_t  tmrColor, tmrFade; //Таймеры
bool mode = true; //переменная для режима отрисовки

void setup() {
 Serial.begin(9600);
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
   //FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
 FastLED.setBrightness(255); // Изначально светим на всю
 FastLED.clear(); // Очистим светодиоды от груза прошлого
 perlinPoint = random(0, 32768); //Перлин
 irrecv.enableIRIn();
}

void loop() {
 //отрисовка
   if (millis() - tmrDraw >= 40) {
   tmrDraw = millis();
   for (int i = 0; i < FACE_SIZE; i++) {
     if (mode) fillSimple(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP + counter, counter), 255, LINEARBLEND));
     else fillVertex(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP / 4 + counter, counter), 255, LINEARBLEND));
   }
   FastLED.show();
   counter += speed;
 }
 if (irrecv.decode(&results)) {
   unsigned long value = results.value;
   Serial.print("Received IR code: 0x");
   Serial.println(value, HEX);
 
 
    switch (value) {
     case BTN_0:
      mode= !mode; //Меняем режим отрисовки 
       break;
     default:
       Serial.println("Unknown or unhandled button pressed");
       break;
   }

   irrecv.resume();
 }
}

// масштабируем шум
byte getMaxNoise(uint16_t x, uint16_t y) {
 return constrain(map(inoise8(x, y), 50, 200, 0, 255), 0, 255);
}

// заливка всех 6 граней в одинаковом порядке
void fillSimple(int num, CRGB color) {  // num 0-NUM_LEDS / 6
 for (byte i = 0; i < 6; i++) {
   leds[i * FACE_SIZE + num] = color;
 }
}

// заливка из четырёх вершин
void fillVertex(int num, CRGB color) { // num 0-NUM_LEDS / 6
 num /= 4;
 byte thisRow = 0;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
 }
 thisRow = 13;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
 }
}

void adjustBrightness(bool increase) { //Регулировка яркости
 if (increase) {
   FastLED.setBrightness(min(255, FastLED.getBrightness() + step));
 } else {
   FastLED.setBrightness(max(0, FastLED.getBrightness() - step));
 }
 FastLED.show();
}

При загрузке этого кода, лента не горит, нажатия пульта регистрируются, но в мониторе порта отображаются любые адреса, кроме правильных. Т.е. даже при нескольких нажатиях на одну и ту же кнопку, мне высветится несколько разных адресов. Если я вставлю кусочек кода из loop про отрисовку в if пульта (irrecv.decode(&results), тогда регистрация нажатий кнопок корректна, однако лента всё равно не горит.
Стандартный набор косяков: неправильные адреса кнопок/подключил не в те пины/ брак модулей и проводов/разные версии библиотек - исправлены.
В других кодах у меня работает как лента, так и пульт (по раздельности), а вместе их подружить в этом коде не получается.
С лентой работаю первый день, пока добился того, что получается нажатием на кнопки пульта зажигать определённый светодиод или все сразу, а также поменять их цвет, отрегулировать яркость.
Но вот совместить пульт с кубом не выходит. Подскажите, где косяк в коде и как его поправить, чтобы я всё же мог управлять режимами с помощью пульта (в идеале, и яркостью)

Problems with Neopixels, FastLed etc.

IRremote will not work right when you use Neopixels (aka WS2811/WS2812/WS2812B) or other libraries blocking interrupts for a longer time (> 50 µs).

IRremote не будет работать правильно, если вы используете Neopixels (также известные как WS2811/WS2812/WS2812B) или другие библиотеки, блокирующие прерывания на более длительное время ( > 50 мкс).
Независимо от того, используете ли вы библиотеку Adafruit Neopixel или FastLED, прерывания отключаются на многих процессорах нижнего уровня, таких как базовые Arduino, на время более 50 мкс. В свою очередь, это останавливает запуск обработчика прерываний IR, когда это необходимо. См. также это видео.

Один обходной путь — дождаться, пока ИК-приемник перейдет в режим ожидания, прежде чем отправлять данные Neopixel с помощью if (IrReceiver.isIdle()) { strip.show();} .
Это предотвращает, по крайней мере, нарушение работающей ИК-передачи и - в зависимости от частоты обновления Neopixel - может работать достаточно хорошо.
Есть и другие решения этой проблемы на более мощных процессорах. см. эту страницу от Марка МЕРЛИНА

Данный код невозможно загрузить, т.к. он не компилируется, ибо в строках №№ 43 и 44 используется нигде не описанная переменная tmrDraw.

Будьте любезны выложить тот код, который у Вас что-то там делает или не делает “при загрузке”.

Ага, значит вот для чего так активно продвигаются специальные пульты для гирлянд. Тогда буду пробовать писать через библиотеку ‘‘IRLremote’’. Во всяком случае, я знаю, что на ней точно реализован проект с теми же комплектующими. Благодарю за ответ!

Или другой - взять мою реализацию неопикселя, которая этим не страдает на самых обычных 8-битных AVR-ках.

Прошу прощения, пока стирал лишние строчки, стёр и не “лишнее”

Спойлер
// ===== НАСТРОЙКИ =====
#include <FastLED.h> // Для ленты
#include <IRremote.h> // Для пульта

#define EDGE_LEDS 11    // кол-во диодов на ребре куба
#define LED_PIN 2        // пин подключения ленты
#define IR_PIN 8        // пин подключения пульта
#define PAL_STEP 30 //шаг по палитре
#define USE_MICROLED 0
CRGBPalette16 currentPalette; //палитра
const int NUM_LEDS = (EDGE_LEDS * 24); //всего светодиодов
const int FACE_SIZE = EDGE_LEDS * 4; //светодиодов на грани
const int LINE_SIZE = EDGE_LEDS;//светодиодов на линии
CRGB leds[NUM_LEDS];
int perlinPoint; //точка шума Перлина
uint32_t getPixColor(CRGB color) {
 return (((uint32_t)color.r << 16) | (color.g << 8) | color.b);
} //преобразует цвет из формата CRGB в 32-битное целое число для удобства работы с цветами

IRrecv irrecv(IR_PIN); //пульт
decode_results results;//пульт
// Коды кнопок ИК-пульта
#define BTN_0       0x97483BFB
#define BTN_1       0xE318261B
#define BTN_2       0x00511DBB
#define BTN_3       0xEE886D7F
#define BTN_4       0x52A3D41F
#define BTN_5       0xD7E84B1B
#define BTN_6       0x20FE4DBB
#define BTN_7       0xF076C13B
#define BTN_8       0xA3C8EDDB
#define BTN_9       0xE5CFBD7F
#define BTN_LEFT    0x8C22657B
#define BTN_RIGHT   0x449E79F
#define BTN_UP      0x3D9AE3F7
#define BTN_DOWN    0x1BC0157B
#define BTN_SHARP   0xF0C41643
#define BTN_STAR    0xC101E57B
int step = 25;  // Шаг регулировки яркости
byte speed = 15;
uint16_t counter = 0;
uint32_t tmrDraw, tmrColor, tmrFade; //Таймеры
bool mode = true; //переменная для режима отрисовки

void setup() {
 Serial.begin(9600);
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
   //FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
 FastLED.setBrightness(255); // Изначально светим на всю
 FastLED.clear(); // Очистим светодиоды от груза прошлого
 perlinPoint = random(0, 32768); //Перлин
 irrecv.enableIRIn();
}

void loop() {
 if (irrecv.decode(&results)) {
   unsigned long value = results.value;
   Serial.print("Received IR code: 0x");
   Serial.println(value, HEX);

  //отрисовка
   if (millis() - tmrDraw >= 40) {
   tmrDraw = millis();
   for (int i = 0; i < FACE_SIZE; i++) {
     if (mode) fillSimple(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP + counter, counter), 255, LINEARBLEND));
     else fillVertex(i, ColorFromPalette(currentPalette, getMaxNoise(i * PAL_STEP / 4 + counter, counter), 255, LINEARBLEND));
   }
   FastLED.show();
   counter += speed;
 }
 
    switch (value) {
     case BTN_0:
      mode= !mode; //Меняем режим отрисовки 
       break;
     case BTN_1:
   //    turnOnLed(0);
       break;
     case BTN_2:
//        turnOnLed(1);
       break;
     case BTN_3:
//       turnOnLed(2);
       break;
     case BTN_4:
//       turnOnLed(3);
       break;
     case BTN_5:
 //      turnOnLed(4);
       break;
     case BTN_6:
 //      turnOnLed(5);
       break;
     case BTN_7:
  //     turnOnLed(6);
       break;
     case BTN_8:
  //     turnOnLed(7);
       break;
     case BTN_9:
 //      turnOnLed(8);
       break;
     case BTN_LEFT:
      adjustBrightness(false);  // Уменьшить яркость
       break;
     case BTN_RIGHT:
      adjustBrightness(true);   // Увеличить яркость
       break;
       case BTN_UP:
     // adjustSpeed(true);;  // Увеличить скорость
       break;
     case BTN_DOWN:
      //adjustSpeed(false); // Уменьшаем скорость
       break;
     default:
       Serial.println("Unknown or unhandled button pressed");
       break;
   }

   irrecv.resume();
 }
}

// масштабируем шум
byte getMaxNoise(uint16_t x, uint16_t y) {
 return constrain(map(inoise8(x, y), 50, 200, 0, 255), 0, 255);
}

// заливка всех 6 граней в одинаковом порядке
void fillSimple(int num, CRGB color) {  // num 0-NUM_LEDS / 6
 for (byte i = 0; i < 6; i++) {
   leds[i * FACE_SIZE + num] = color;
 }
}

// заливка из четырёх вершин
void fillVertex(int num, CRGB color) { // num 0-NUM_LEDS / 6
 num /= 4;
 byte thisRow = 0;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
 }
 thisRow = 13;
 for (byte i = 0; i < 3; i++) {
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
   leds[LINE_SIZE * thisRow - num - 1] = color;
   leds[LINE_SIZE * thisRow + num] = color;
   thisRow += 2;
 }
}



void adjustBrightness(bool increase) { //Регулировка яркости
 if (increase) {
   FastLED.setBrightness(min(255, FastLED.getBrightness() + step));
 } else {
   FastLED.setBrightness(max(0, FastLED.getBrightness() - step));
 }
 FastLED.show();
}

Здесь происходит адекватная регистрация нажатий кнопок (но и только). Если же вынести отрисовку выше loop из
if (irrecv.decode(&results)) { , то и она пропадёт

почему бы и не взять )))

На видео предложили через прерывания подружить пульт и адресную ленту. Это не панацея, т.к. работает не всегда, но всё же, тоже, как один из вариантов.

Кстати, посмотрел на имеющуюся у меня версию FastLED и не нашёл там, чтобы она закрывала прерывания на такие огромные промежутки времени. Она точно это делает? Можете сказать где?

1 лайк

@ЕвгенийП так а вывод на ленту с жёсткими таймингами? Там же вроде запрещены прерывания? Не утверджаю, сам лично не смотрел. По идее время зависит от кол-ва лампочек этих…умных.

Возьми вторую ардуину на пульт, пусть нажимает кнопки на первой виртуально. Работы на полчаса.

Появилась мысль, задействовать вместо пульта обычные кнопки с фиксацией. Ведь тогда прерывания не будут влиять или я ошибаюсь?

Если я правильно понял, то в одну ардуинку я вставляю ик-светодиод, а на второй у меня приёмник. Предположим, но разве я тогда не буду задействовать всё те же библиотеки => прерывания вновь начнут мешать?

библиотеку не копал, доверился разработчику

Внутри одного бита тайминги жесткие. Между битами можно делать паузу, но не более 50мкс. Я открываю прерывания между байтами. Просто открыл и тут же закрыл. Если ждёт необработанное прерывание, то оно обработается. Но пользоваться надо с умом, не превышать те самые 50 мкс.

1 лайк

В тексте статьи ссылки на старые, совместимые версии пар библиотек. Или аппаратный изврат - две ардуино по уарт связаны, на одной одна библиотека, на второй вторая.

1 лайк

еще обратите внимание, что судя по вышеприведенной строчке, ваш код использует страую версию библиотеки, несовместимую с той, что сейчас лежит на гитхабе. Если это код Гайвера - то используйте библиотеку из архива проекта.

Прошу к наглядности (хоть и глупо звучит):

Да, раньше “начинающие” компьютеры собирали, а теперь в трёх строчках блуждают))) Расслабились) Иногда в охоточку читать старенькие журнальчики, чаще смешно, но иногда интересное попадается.

1 лайк