Address strip WS2812B: различия между версиями

18 922 байта добавлено ,  5 месяцев назад
 
(не показано 38 промежуточных версий 2 участников)
Строка 1: Строка 1:
СТАТЬЯ НАХОДИТСЯ В СТАДИИ РАЗРАБОТКИ
{{DISPLAYTITLE:Подключение адресной ленты WS2812B к контроллеру Wiren Board}}
==Описание==
{{Wbincludes:Community}}


=Управление адресной лентой WS2812B из Wirenboard=
Автор статьи: [https://t.me/Paulstrong Paul Strong]
здесь будет рассмотрен один из способов управления адресной лентой WS2812B из Wirenboard с использованием ESP32.


здесь не будут рассмотрены характеристики железной части, этому уделено очень много внимания на просторах Интернет, тут будет сугубо техническая информация по процессу достижения цели, с небольшими лирическими отступлениями.
В этой статье рассмотрим один из способов управления адресной лентой WS2812B из Wiren Board с использованием ESP32.


==Что нам понадобится==
Здесь не будут рассмотрены характеристики железной части, этому уделено очень много внимания на просторах Интернет, тут будет сугубо техническая информация по процессу достижения цели, с небольшими лирическими отступлениями.
* любая отладочная плата, основанная на esp32
 
* две bread board платы
Что нам понадобится:
* соединительные проводки
* любая отладочная плата, основанная на esp32;
* лента ws2812b нужной длины (в моём случае бухта 5м с мощностью 14.4Вт/м)
* две bread board платы;
* блок питания 5V соответствующей мощности (в моём случае 100W)
* соединительные проводки;
* резистор 220 ом
* лента ws2812b нужной длины (в моём случае бухта 5м с мощностью 14.4Вт/м);
* microUSB дата-кабель (обычный, от зарядки, может не подойти!)
* блок питания 5V соответствующей мощности (в моём случае 100W);
* комп/ноут для сборки и заливки прошивки
* резистор 220 ом;
* VSCode + PlatformIO
* microUSB дата-кабель (обычный, от зарядки, может не подойти!);
* контроллер Wirenboard с wb-rules v2.x
* комп/ноут для сборки и заливки прошивки;
* wi-fi сеть в диапазоне 2.4G
* VSCode + PlatformIO;
* контроллер Wirenboard с wb-rules v2.x;
* wi-fi сеть в диапазоне 2.4G.


==Сборка физической схемы==
==Сборка физической схемы==
[[Файл:Ws2812b_BB_Schema.png|thumb|right|600px| Схема сборки]]
Для сборки нам нужно две bread board платы, собираем по схеме:
Для сборки нам нужно две bread board платы, собираем по схеме:
[[Файл:Ws2812b_BB_Schema.png|600px]]
* пин 5V подключаем к +5V ленты
* пин 5V подключаем к +5V ленты
* пин GND  
* пин GND  
* пинг GPIO27 подключаем через резистор 220ом к DIN ленты
* пинг GPIO27 подключаем через резистор 220ом к DIN ленты


Обращаю внимание на то, что не рекомендуется тестировать конфигурацию более чем на 5 светодиодах, такого количества будет достаточно, чтобы понять как выглядит световая картинка в том или ином алгоритме. Подключение большего количества светодиодов может привести к выгоранию USB-порта на компе, т.к. там обычно предельно допустимый ток 350мА, а один светодиод на такой ленте потребляет около 50мА.
Обращаю внимание на то, что не рекомендуется тестировать конфигурацию более чем на 5 светодиодах, такого количества будет достаточно, чтобы понять как выглядит световая картинка в том или ином алгоритме.  


==Заливаем первую прошивку==
Подключение большего количества светодиодов может привести к выгоранию USB-порта на компьютере, т.к. там обычно предельно допустимый ток 350мА, а один светодиод на такой ленте потребляет около 50мА.
Подразумевается что на PC уже установлена среда разработки VSCode и расширение PlatformIO. Создадим новый проект.
 
==Первый скетч==
Заливаем первую прошивку.
 
Подразумевается что на компьютере уже установлена среда разработки VSCode и расширение PlatformIO. Создадим новый проект.


[[Файл:Pio-create-prj.jpg]]
[[Файл:Pio-create-prj.jpg]]


Далее нам нужно подключить к проекту библиотеку для работы с лентой, они есть разные, я использовал FastLED
Чтобы понимать, что esp32 работает, сделаем код, который постоянно зажигает и тушит встроенный светодиод синего цвета. Запускаем это дело в виде асинхронной функции.
 
Файл <code>src/main.cpp</code>:
<syntaxhighlight lang="c++">
#include <Arduino.h>
#define LED_PIN    2
int led_status = HIGH;
 
void ledUpdate(int v) {
  digitalWrite(LED_PIN, v);
}
 
void ledBlink( void * parameter ) {
  const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;
  for(;;) {
    switch (led_status)
    {
    case HIGH:
      led_status = LOW;
      break; 
    default:
      led_status = HIGH;
      break;
    }
    ledUpdate(led_status);
    vTaskDelay(xDelay);
    Serial.printf("blink %d\n", led_status);
  }
}
 
void setupLedBlink()  {
    pinMode(LED_PIN, OUTPUT);
 
    xTaskCreate(
                    ledBlink,          /* Task function. */
                    "ledBlink",        /* String with name of task. */
                    10000,            /* Stack size in bytes. */
                    NULL,            /* Parameter passed as input of the task */
                    1,                /* Priority of the task. */
                    NULL);            /* Task handle. */
}
 
 
void setup() {
    setupLedBlink();
}
 
void loop(){}
</syntaxhighlight>
 
Сейчас можно залить попробовать скомпилировать прошивку, для этого в нижней панели жмём кнопку [[Файл:Снимок экрана 2023-12-06 205145.jpg]]
 
Если всё собралось, только можно шить это дело в esp32:
* подключаем esp32 к компу при помощи microUSB data кабеля;
* инициируем заливку прошивки при помощи кнопки [[Файл:Снимок экрана 2023-12-06 205336.jpg]], когда система напишет «Connecting...» — зажимаем кнопку Boot на dev-плате esp32;
* если прошивка залилась успешно, перезагружаем esp32 соответствующей кнопкой, после этого у вас должен замигать синий светодиод на плате.
 
==Подключаемся к Wi-Fi==
Приступаем к следующему этапу — нам нужно подключить нашу esp32 к wifi (чтобы далее подключиться к modbus tcp от wirenboard)
 
Добавим к нашему файлу main.cpp следующий код:
<syntaxhighlight lang="c++">
#include <Arduino.h>
#include <WiFi.h>
 
#define WIFI_SSID "ssid"
#define WIFI_PASSWORD "password"
 
void setupWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  char emptyMsg[1] = "";
  char pointMsg[2] = ".";
  Serial.println(emptyMsg);
 
 
    // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println(pointMsg);
  }
  Serial.println(emptyMsg);
  char connectedToMsg[14] = "Connected to ";
  Serial.println(connectedToMsg);
  Serial.println(WIFI_SSID);
  char ipAddrMsg[30];
  sprintf(ipAddrMsg, "IP address: %s", WiFi.localIP().toString().c_str());
  Serial.println(ipAddrMsg);
}
void setup() {
  setupWiFi();
}
</syntaxhighlight>
 
а также модифицируем функцию setup:
<syntaxhighlight lang="c++">
void setup() {
  setupWifi();
}
</syntaxhighlight>
 
Теперь после прошивки esp32 подключится к вашей wifi сети, IP-адрес можно будет посмотреть на роутере, либо в отладочных сообщения на Serial-порте, который связывает ваш комп с esp32, для этого нажмите кнопку [[Файл:Снимок экрана 2023-12-06 210510.jpg]] на нижней панели.
 
==Библиотека для работы с адресной лентой==
 
Далее нам нужно подключить к проекту библиотеку для работы с лентой, они есть разные, я использовал FastLED.


[[Файл:Снимок экрана 2023-12-06 201634.jpg]]
[[Файл:Снимок экрана 2023-12-06 201634.jpg]]


добавляем в проект
Добавляем в проект


[[Файл:Снимок экрана 2023-12-06 203931.jpg]]
[[Файл:Снимок экрана 2023-12-06 203931.jpg]]
Строка 47: Строка 158:
#include <FastLED.h>
#include <FastLED.h>
   
   
#define LED_PIN     27
#define DIN_PIN     27
#define NUM_LEDS    5
#define NUM_LEDS    5
#define BRIGHTNESS  50
#define BRIGHTNESS  50
#define LED_TYPE    WS2811_400
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
CRGB leds[NUM_LEDS];
Строка 84: Строка 195:
void setup() {
void setup() {
     delay( 3000 ); // power-up safety delay
     delay( 3000 ); // power-up safety delay
     FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
     FastLED.addLeds<LED_TYPE, DIN_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
     FastLED.setBrightness(  BRIGHTNESS );
     FastLED.setBrightness(  BRIGHTNESS );
      
      
Строка 241: Строка 352:
</syntaxhighlight>
</syntaxhighlight>


сейчас можно залить попробовать скомпилировать прошивку, для этого в нижней панели жмём кнопку [[Файл:Снимок экрана 2023-12-06 205145.jpg]]
Прошиваемся, в случае успеха у вас должна заработать лента, алгоритм будет менять каждые 5 секунд, всего 12 алгоритмов.
 
Подробно останавливаться на коде прошивки мы не будем, в Интернете полно разных алгоритмов, их можно добавлять к нашей оснастке, переключать их реже или чаще, можно даже кнопку прикрутить под это дело на один из пинов, тут кому насколько фантазии хватит.
 
==Подключение к Modbus TCP==
После настройки Wi-Fi мы можем подключаться к mqtt-брокеру контроллера Wiren Board, для этого нужно установить библиотеку, как мы это делали ранее, называется она PubSubClient.
 
Подключимся к брокеру, для этого добавим немного кода:
<syntaxhighlight lang="c++">
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
 
#define MODBUS_SERVER "192.168.0.10"
#define MODBUS_PORT 1883
 
WiFiClient wifiClient;
PubSubClient pubSubClient(wifiClient);
 
void modbusCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
 
  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);  // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is active low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
  }
 
}
 
void setupModbus() {
  pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT);
  pubSubClient.setCallback(modbusCallback);
}
 
void modbusReconnect() {
  // Loop until we're reconnected
  while (!pubSubClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (pubSubClient.connect(clientId.c_str())) {
      Serial.println("connected");
      pubSubClient.publish("outTopic", "hello world");
      pubSubClient.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(pubSubClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
 
void setup() {
  setupWifi();
  setupModbus();
}
 
void loop() {
  if (!pubSubClient.connected()) {
    modbusReconnect();
  }
  pubSubClient.loop();
}
 
</syntaxhighlight>
 
Что мы здесь видим: мы можем создавать подписку на нужные топики либо сами публиковать какие то значения в нужные нам топики.
 
В нашем случае понадобится управлять настройками ленты, поэтому нам надо слушать некоторые топики, изменения которых будут влиять на поведение ленты, допустим, мы хотим управлять:
* яркостью
* выключать (яркость 0)
* включать (яркость 50%)
* останавливать и запускать автоматическую смену режима
 
==Виртуальное устройство в Wiren Board==
Создадим на [[wb-rules]] виртуальное устройство нашей ленты в контроллере Wiren Board:
<syntaxhighlight lang="javascript">
defineVirtualDevice("ws2812b", {
    title: "ws2812b",
    cells: {
      enabled: {
        title: "состояние",
        type: "switch",
        value: false,
      },
      brightness: {
        title: "яркость",
        type: "range",
        min: 0,
        max: 255,
        value: 50,
      },     
  hueLoop: {
        title: "смена оттенков",
        type: "switch",
        value: true,  
  }
}
})
 
 
defineRule("ws2812b/enabled", {
  whenChanged: "ws2812b/enabled",
  then: function (newValue, devName, cellName) {
    if(newValue) {
      dev["ws2812b"]["brightness"] = 50
    } else {
      dev["ws2812b"]["brightness"] = 0
    }
  }
})
</syntaxhighlight>
 
Теперь надо константу BRIGHTNESS переделать в переменную, ввести переменную hue_loop, а также подписаться на топики
<syntaxhighlight lang="c++">
#define BRIGHTNESS  50
uint8_t BRIGHTNESS = 50;
bool hueLoop = true;
 
void modbusCallback(char* topic, byte* payload, unsigned int length) {
  char payloadChar[sizeof(payload)];
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    payloadChar[i] = (char)payload[i];
  }
  Serial.println();
 
  String payloadStr = String(payloadChar);
  payloadStr.trim();
 
  if (strcmp(topic, "/devices/ws2812b/controls/brightness") == 0) {
    BRIGHTNESS = payloadStr.toInt();
    Serial.printf("brithness set to %d\n", BRIGHTNESS);
  }
  if (strcmp(topic, "/devices/ws2812b/controls/hueLoop") == 0) {
    if(payloadStr.toInt() == 1) {
      hueLoop = true;
    } else {
      hueLoop = false;
    }
  }
}
 
void ChangePalettePeriodically() {
  if(!hueLoop) {
      return;
  }
  ...
}
 
void modbusReconnect() {
  // Loop until we're reconnected
  while (!pubSubClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (pubSubClient.connect(clientId.c_str())) {
      Serial.println("connected");
      pubSubClient.subscribe("/devices/ws2812b/controls/brightness");
      pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop");
    } else {
      Serial.print("failed, rc=");
      Serial.print(pubSubClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
 
</syntaxhighlight>
 
Теперь можно управлять runtime-переменными esp32 через Wiren Board.
 
[[Файл:Снимок экрана 2023-12-07 095833.jpg]]
 
==Загрузка прошивки по WiFi==
С учетом того что esp32 не всегда будет находиться рядом с компом, прошивать эту железка будет неудобно через кабель. Можно прикрутить возможность прошиваться через WiFi.
 
Установим модули
* AsyncElegantOTA
* ESPAsyncWebServer-esphome
 
Кодим функционал:
<syntaxhighlight lang="c++">
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
 
unsigned long ota_progress_millis = 0;
AsyncWebServer server(80);
 
 
void setupOTA(void) {
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Hi! This is a sample response.");
  });
 
  AsyncElegantOTA.begin(&server);    // Start AsyncElegantOTA
  server.begin();
  Serial.println("HTTP server started");
}
void setup() {
  setupOTA();
}
</syntaxhighlight>
 
Теперь после запуска железки и получения ip-адреса будет доступен web-интерфейс (http://192.168.0.10/update), в котором можно выбрать прошивку и залить её по wi-fi, бонусом будет автоматический перезапуск железки после прошивки, то есть она сразу активируется.
 
[[Файл:Снимок экрана 2023-12-07 102038.jpg]]
 
Есть и минусы: если вы совершите ошибку в коде и будет возникал kernel panic / fatal, тогда вам уже точно придется тащить железку к компу и прошивать фикс через кабель
 
==Добавление настроек==
Допустим, мы захотели управлять какой-то настройкой еще, пускай это будет скорость обновления светодиодов. Ранее у нас была определена константа
 
<syntaxhighlight lang="c++">
#define UPDATES_PER_SECOND 500
</syntaxhighlight>
 
Теперь нам её придётся сделать обычной переменной:
<syntaxhighlight lang="c++">
int UPDATES_PER_SECOND = 500;
</syntaxhighlight>
 
Теперь нужно подписать на новый топик:
<syntaxhighlight lang="c++">
void modbusCallback(char* topic, byte* payload, unsigned int length) {
  char payloadChar[sizeof(payload)];
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    payloadChar[i] = (char)payload[i];
  }
  Serial.println();
 
  String payloadStr = String(payloadChar);
  payloadStr.trim();
 
  if (strcmp(topic, "/devices/ws2812b/controls/brightness") == 0) {
    BRIGHTNESS = payloadStr.toInt();
    Serial.printf("brithness set to %d\n", BRIGHTNESS);
  }
  if (strcmp(topic, "/devices/ws2812b/controls/hueLoop") == 0) {
    if(payloadStr.toInt() == 1) {
      hueLoop = true;
    } else {
      hueLoop = false;
    }
  }
  if (strcmp(topic, "/devices/ws2812b/controls/updatesPerSecond") == 0) {
      UPDATES_PER_SECOND = payloadStr.toInt();
  }
}
 
void modbusReconnect() {
  // Loop until we're reconnected
  while (!pubSubClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (pubSubClient.connect(clientId.c_str())) {
      Serial.println("connected");
      pubSubClient.subscribe("/devices/ws2812b/controls/brightness");
      pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop");
      pubSubClient.subscribe("/devices/ws2812b/controls/updatesPerSecond");
    } else {
      Serial.print("failed, rc=");
      Serial.print(pubSubClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
</syntaxhighlight>
 
Заливаем прошивку в esp32.
 
Осталось обновить виртуальное устройство в контроллере Wiren Board:
<syntaxhighlight lang="javascript">
defineVirtualDevice("ws2812b", {
    title: "ws2812b",
    cells: {
      enabled: {
        title: "состояние",
        type: "switch",
        value: false,
      },
      brightness: {
        title: "яркость",
        type: "range",
        min: 0,
        max: 255,
        value: 50,
      },     
  hueLoop: {
        title: "смена оттенков",
        type: "switch",
        value: true,  
  },
      updatesPerSecond: {
        title: "скорость обновления светодиодов",
        type: "range",
        min: 10,
        max: 1000,
        value: 100,
      },
}
})
</syntaxhighlight>
 
После применения виртуального устройства можно управлять скорость обновления светодиодов из веб-интерфейса контроллера Wiren Board.
 
==Паяем на плату==
 
Понадобится:
* макетная плата размером 40х60;
* две винтовые клеммы на 2 и 3 контакта;
* паяльник 40Вт с тонким жалом;
* припой с флюсом.
 
[[Image: Photo 2023-12-22 17-04-06.jpg |300px ]]
 
Девборд в длину занимает почти всю плату - остаётся один ряд, поэтому решил клеммы разместить сбоку
 
[[Image: Photo 2023-12-22 17-04-07.jpg |300px ]]
 
Клеммы соединяем через пазы
 
[[Image: Photo 2023-12-22 17-04-06 2.jpg |300px ]]
 
Припаиваем к шахтам макетной платы все пять пинов от клемм, а также прихватываем крайние пины девборда. Есть важный нюанс - клеммы нужно расположить таким образом, чтобы было минимальное расстояние до нужных пинов девборда
 
[[Image: Photo 2023-12-22 17-04-07 (2).jpg |300px ]]
 
Клеммы расположены прямо напротив нужных пинов, поэтому нет необходимости соединять проводами, напаял шлейфы из олова, от IO27 до клеммы идут два последовательных резистора по 100ом (какие были), скрутку резисторов приаял к шахте, чтобы не болталось ничего.
 
[[Image: Photo 2023-12-22 17-04-08.jpg |300px ]]
 
Чтобы всё это упаковать, взял распаечную коробку небольшую, но в итоге совсем чуток не вместился туда, пришлось пока что на стяжках к корпусу блока питания закрепить, потом намучу какой-то подходящий корпус и переселю.
 
[[Image: Photo 2023-12-22 17-04-34.jpg |300px ]]
 
==Какие могут быть проблемы==
# изначально лента тестировалась на arduino nano, и на LED_TYPE==WS2812B оно работало странно, но с учетом того что какие-то отдельные светодиоды загорались, я пришёл к выводу что цифра работает, но есть какие-то проблемы с частотой, в итоге заработало на WS2811_400, то есть на частоте 400кгц, однако, в случае с esp32 всё заработало со штатным типом WS2812B;
# важно учитывать тот факт, что нельзя допускать подключения ленты без стабильного плюса, то есть в случае подключения ленты к GND и DIN-пину, есть риск, что пин спалится, чтобы подстраховаться - ставится резистор на 220ом, но всё равно лучше избегать таких вот нештатных ситуаций;
# на самом деле проблемы могут быть самые разнообразные, потому что это DIY, если что пишите в чатике - попробуем разобраться.


если всё собралось, только можно шить это дело в esp32:
==Финальный код==
* подключаем esp32 к компу при помощи microUSB data кабеля
Исходный код скетча и виртуального устройства на wb-rules смотрите в репозитори на ГитХабе [https://github.com/wirenboard/wb-community/tree/main/firmwares/ws2812b-to-wb ws2812b-to-wb]
* инициируем заливку прошивки при помощи кнопки [[Файл:Снимок экрана 2023-12-06 205336.jpg]], когда система напишет "Connecting..." - зажимаем кнопку Boot на dev-плате esp32
* если прошивка залилась успешно, перезагружаем esp32 соответствующей кнопкой, после этого у вас должна заработать лента, алгоритм будет менять каждые 5 секунд, всего 12 алгоритмов