Address strip WS2812B: различия между версиями
(не показана 31 промежуточная версия 2 участников) | |||
Строка 1: | Строка 1: | ||
СТАТЬЯ НАХОДИТСЯ В СТАДИИ РАЗРАБОТКИ | |||
=Управление адресной лентой WS2812B из Wirenboard= | |||
здесь будет рассмотрен один из способов управления адресной лентой WS2812B из Wirenboard с использованием ESP32. | |||
здесь не будут рассмотрены характеристики железной части, этому уделено очень много внимания на просторах Интернет, тут будет сугубо техническая информация по процессу достижения цели, с небольшими лирическими отступлениями. | |||
==Что нам понадобится== | |||
* любая отладочная плата, основанная на esp32 | |||
Что нам понадобится | * две bread board платы | ||
* любая отладочная плата, основанная на esp32 | * соединительные проводки | ||
* две bread board платы | * лента ws2812b нужной длины (в моём случае бухта 5м с мощностью 14.4Вт/м) | ||
* соединительные проводки | * блок питания 5V соответствующей мощности (в моём случае 100W) | ||
* лента ws2812b нужной длины (в моём случае бухта 5м с мощностью 14.4Вт/м) | * резистор 220 ом | ||
* блок питания 5V соответствующей мощности (в моём случае 100W) | * microUSB дата-кабель (обычный, от зарядки, может не подойти!) | ||
* резистор 220 ом | * комп/ноут для сборки и заливки прошивки | ||
* microUSB дата-кабель (обычный, от зарядки, может не подойти!) | * VSCode + PlatformIO | ||
* комп/ноут для сборки и заливки прошивки | * контроллер Wirenboard с wb-rules v2.x | ||
* VSCode + PlatformIO | * wi-fi сеть в диапазоне 2.4G | ||
* контроллер Wirenboard с wb-rules v2.x | |||
* wi-fi сеть в диапазоне 2.4G | |||
==Сборка физической схемы== | ==Сборка физической схемы== | ||
Для сборки нам нужно две bread board платы, собираем по схеме: | Для сборки нам нужно две bread board платы, собираем по схеме: | ||
[[Файл:Ws2812b_BB_Schema.png|600px]] | |||
* пин 5V подключаем к +5V ленты | * пин 5V подключаем к +5V ленты | ||
* пин GND | * пин GND | ||
* пинг GPIO27 подключаем через резистор 220ом к DIN ленты | * пинг GPIO27 подключаем через резистор 220ом к DIN ленты | ||
Обращаю внимание на то, что не рекомендуется тестировать конфигурацию более чем на 5 светодиодах, такого количества будет достаточно, чтобы понять как выглядит световая картинка в том или ином алгоритме. | Обращаю внимание на то, что не рекомендуется тестировать конфигурацию более чем на 5 светодиодах, такого количества будет достаточно, чтобы понять как выглядит световая картинка в том или ином алгоритме. Подключение большего количества светодиодов может привести к выгоранию USB-порта на компе, т.к. там обычно предельно допустимый ток 350мА, а один светодиод на такой ленте потребляет около 50мА. | ||
Подключение большего количества светодиодов может привести к выгоранию USB-порта на | |||
== | ==Заливаем первую прошивку== | ||
Заливаем первую прошивку | Подразумевается что на PC уже установлена среда разработки VSCode и расширение PlatformIO. Создадим новый проект. | ||
Подразумевается что на | |||
[[Файл:Pio-create-prj.jpg]] | [[Файл:Pio-create-prj.jpg]] | ||
Далее нам нужно подключить к проекту библиотеку для работы с лентой, они есть разные, я использовал FastLED | |||
Далее нам нужно подключить к проекту библиотеку для работы с лентой, они есть разные, я использовал FastLED | |||
[[Файл:Снимок экрана 2023-12-06 201634.jpg]] | [[Файл:Снимок экрана 2023-12-06 201634.jpg]] | ||
добавляем в проект | |||
[[Файл:Снимок экрана 2023-12-06 203931.jpg]] | [[Файл:Снимок экрана 2023-12-06 203931.jpg]] | ||
Строка 158: | Строка 47: | ||
#include <FastLED.h> | #include <FastLED.h> | ||
#define | #define LED_PIN 27 | ||
#define NUM_LEDS 5 | #define NUM_LEDS 5 | ||
#define BRIGHTNESS 50 | #define BRIGHTNESS 50 | ||
#define LED_TYPE | #define LED_TYPE WS2811_400 | ||
#define COLOR_ORDER GRB | #define COLOR_ORDER GRB | ||
CRGB leds[NUM_LEDS]; | CRGB leds[NUM_LEDS]; | ||
Строка 195: | Строка 84: | ||
void setup() { | void setup() { | ||
delay( 3000 ); // power-up safety delay | delay( 3000 ); // power-up safety delay | ||
FastLED.addLeds<LED_TYPE, | FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); | ||
FastLED.setBrightness( BRIGHTNESS ); | FastLED.setBrightness( BRIGHTNESS ); | ||
Строка 352: | Строка 241: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
сейчас можно залить попробовать скомпилировать прошивку, для этого в нижней панели жмём кнопку [[Файл:Снимок экрана 2023-12-06 205145.jpg]] | |||
если всё собралось, только можно шить это дело в esp32: | |||
* подключаем esp32 к компу при помощи microUSB data кабеля | |||
* инициируем заливку прошивки при помощи кнопки [[Файл:Снимок экрана 2023-12-06 205336.jpg]], когда система напишет "Connecting..." - зажимаем кнопку Boot на dev-плате esp32 | |||
* если прошивка залилась успешно, перезагружаем esp32 соответствующей кнопкой, после этого у вас должна заработать лента, алгоритм будет менять каждые 5 секунд, всего 12 алгоритмов | |||
Подробно останавливаться на коде прошивки мы не будем, в Интернете полно разных алгоритмов, их можно добавлять к нашей оснастке, переключать их реже или чаще, можно даже кнопку прикрутить под это дело на один из пинов, тут кому насколько фантазии хватит. | Подробно останавливаться на коде прошивки мы не будем, в Интернете полно разных алгоритмов, их можно добавлять к нашей оснастке, переключать их реже или чаще, можно даже кнопку прикрутить под это дело на один из пинов, тут кому насколько фантазии хватит. | ||
==Подключаемся к wifi== | |||
Приступаем к следующему этапу - нам нужно подключить нашу 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); | |||
} | |||
</syntaxhighlight> | |||
а также модифицируем функцию setup: | |||
<syntaxhighlight lang="c++"> | |||
void setup() { | |||
setupWifi(); | |||
} | |||
</syntaxhighlight> | |||
теперь после прошивки esp32 подключится к вашей wifi сети, IP-адрес можно будет посмотреть на роутере, либо в отладочных сообщения на Serial-порте, который связывает ваш комп с esp32, для этого нажмите кнопку [[Файл:Снимок экрана 2023-12-06 210510.jpg]] на нижней панели | |||
==Подключение к Modbus TCP== | ==Подключение к Modbus TCP== | ||
После настройки | После настройки wifi мы можем подключаться к mqtt-брокеру wirenboard, для этого нужно установить библиотеку, как мы это делали ранее, называется она PubSubClient | ||
подключимся к брокеру, для этого добавим немного кода: | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
#include <Arduino.h> | #include <Arduino.h> | ||
Строка 395: | Строка 332: | ||
pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT); | pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT); | ||
pubSubClient.setCallback(modbusCallback); | pubSubClient.setCallback(modbusCallback); | ||
pubSubClient.publish("outTopic", "hello world"); | |||
pubSubClient.subscribe("inTopic"); | |||
} | } | ||
Строка 407: | Строка 346: | ||
if (pubSubClient.connect(clientId.c_str())) { | if (pubSubClient.connect(clientId.c_str())) { | ||
Serial.println("connected"); | Serial.println("connected"); | ||
} else { | } else { | ||
Serial.print("failed, rc="); | Serial.print("failed, rc="); | ||
Строка 433: | Строка 370: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
что мы здесь видим: мы можем создавать подписку на нужные топики либо сами публиковать какие то значения в нужные нам топики. | |||
в нашем случае понадобится управлять настройками ленты, поэтому нам надо слушать некоторые топики, изменения которых будут влиять на поведение ленты, допустим, мы хотим управлять: | |||
- яркостью | |||
- выключать (яркость 0) | |||
- включать (яркость 50%) | |||
- останавливать и запускать автоматическую смену режима | |||
==Виртуальное устройство в | ==Виртуальное устройство в Wirenboard== | ||
Создадим | Создадим виртуальное устройство нашей ленты в wirenboard: | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
defineVirtualDevice("ws2812b", { | defineVirtualDevice("ws2812b", { | ||
Строка 448: | Строка 385: | ||
cells: { | cells: { | ||
enabled: { | enabled: { | ||
type: "switch", | type: "switch", | ||
value: false, | value: false, | ||
}, | }, | ||
brightness: { | brightness: { | ||
type: "range", | type: "range", | ||
min: 0, | min: 0, | ||
Строка 460: | Строка 395: | ||
}, | }, | ||
hueLoop: { | hueLoop: { | ||
type: "switch", | type: "switch", | ||
value: true, | value: true, | ||
Строка 480: | Строка 414: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
подписываем на топики: | |||
<syntaxhighlight lang="c++"> | |||
void setupModbus() { | |||
pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT); | |||
pubSubClient.setCallback(modbusCallback); | |||
pubSubClient.subscribe("/devices/ws2812b/controls/brightness"); | |||
pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop"); | |||
} | |||
</syntaxhighlight> | |||
теперь надо константу BRIGHTNESS переделать в переменную, а также ввести переменную hue_loop: | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
#define BRIGHTNESS 50 | #define BRIGHTNESS 50 | ||
Строка 488: | Строка 432: | ||
void modbusCallback(char* topic, byte* payload, unsigned int length) { | void modbusCallback(char* topic, byte* payload, unsigned int length) { | ||
char payloadChar[sizeof(payload)]; | char payloadChar[sizeof(payload)]; | ||
Serial.print("Message arrived ["); | Serial.print("Message arrived ["); | ||
Serial.print(topic); | Serial.print(topic); | ||
Строка 498: | Строка 443: | ||
String payloadStr = String(payloadChar); | String payloadStr = String(payloadChar); | ||
if | if (topic == "/devices/ws2812b/controls/brightness") { | ||
BRIGHTNESS = payloadStr.toInt(); | BRIGHTNESS = payloadStr.toInt(); | ||
} | } | ||
if | if (topic == "/devices/ws2812b/controls/hueLoop") { | ||
if( | if(payloadChar == "true") { | ||
hueLoop = true; | hueLoop = true; | ||
} else { | } else { | ||
Строка 516: | Строка 459: | ||
if(!hueLoop) { | if(!hueLoop) { | ||
return; | return; | ||
} | } | ||
... | ... | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Финальный код скетча== | |||
== | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
#include <Arduino.h> | #include <Arduino.h> | ||
#include < | #include <FastLED.h> | ||
#include < | #include <PubSubClient.h> | ||
#include < | #include <WiFiClient.h> | ||
#include < | #include <WiFi.h> | ||
#define LED_PIN 27 | |||
#define NUM_LEDS 5 | |||
#define LED_TYPE WS2811_400 | |||
#define COLOR_ORDER GRB | |||
CRGB leds[NUM_LEDS]; | |||
#define UPDATES_PER_SECOND 50 | |||
uint8_t BRIGHTNESS = 50; | |||
bool hueLoop = true; | |||
// This example shows several ways to set up and use 'palettes' of colors | |||
// with FastLED. | |||
// | |||
// These compact palettes provide an easy way to re-colorize your | |||
// animation on the fly, quickly, easily, and with low overhead. | |||
// | |||
// USING palettes is MUCH simpler in practice than in theory, so first just | |||
// run this sketch, and watch the pretty lights as you then read through | |||
// the code. Although this sketch has eight (or more) different color schemes, | |||
// the entire sketch compiles down to about 6.5K on AVR. | |||
// | |||
// FastLED provides a few pre-configured color palettes, and makes it | |||
// extremely easy to make up your own color schemes with palettes. | |||
// | |||
// Some notes on the more abstract 'theory and practice' of | |||
// FastLED compact palettes are at the bottom of this file. | |||
CRGBPalette16 currentPalette; | |||
TBlendType currentBlending; | |||
extern CRGBPalette16 myRedWhiteBluePalette; | |||
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM; | |||
void | #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 setupFastLED(){ | |||
delay( 3000 ); // power-up safety delay | |||
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); | |||
FastLED.setBrightness( BRIGHTNESS ); | |||
currentPalette = RainbowColors_p; | |||
currentBlending = LINEARBLEND; | |||
} | } | ||
#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) { | void modbusCallback(char* topic, byte* payload, unsigned int length) { | ||
char payloadChar[sizeof(payload)]; | char payloadChar[sizeof(payload)]; | ||
Serial.print("Message arrived ["); | Serial.print("Message arrived ["); | ||
Serial.print(topic); | Serial.print(topic); | ||
Строка 613: | Строка 560: | ||
String payloadStr = String(payloadChar); | String payloadStr = String(payloadChar); | ||
if | if (topic == "/devices/ws2812b/controls/brightness") { | ||
BRIGHTNESS = payloadStr.toInt(); | BRIGHTNESS = payloadStr.toInt(); | ||
} | } | ||
if | if (topic == "/devices/ws2812b/controls/hueLoop") { | ||
if( | if(payloadChar == "true") { | ||
hueLoop = true; | hueLoop = true; | ||
} else { | } else { | ||
Строка 626: | Строка 571: | ||
} | } | ||
} | } | ||
} | |||
void setupModbus() { | |||
pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT); | |||
pubSubClient.setCallback(modbusCallback); | |||
pubSubClient.subscribe("/devices/ws2812b/controls/brightness"); | |||
pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop"); | |||
} | } | ||
Строка 641: | Строка 590: | ||
if (pubSubClient.connect(clientId.c_str())) { | if (pubSubClient.connect(clientId.c_str())) { | ||
Serial.println("connected"); | Serial.println("connected"); | ||
} else { | } else { | ||
Serial.print("failed, rc="); | Serial.print("failed, rc="); | ||
Строка 653: | Строка 599: | ||
} | } | ||
} | } | ||
void modbusLoop() { | |||
if (!pubSubClient.connected()) { | |||
modbusReconnect(); | |||
} | |||
pubSubClient.loop(); | |||
} | |||
void setup() { | |||
setupWifi(); | |||
setupFastLED(); | |||
setupModbus(); | |||
} | |||
void ChangePalettePeriodically(); | |||
void FillLEDsFromPaletteColors( uint8_t colorIndex); | |||
void loop() | |||
{ | |||
modbusLoop(); | |||
if(!hueLoop) { | |||
return; | |||
} | |||
ChangePalettePeriodically(); | |||
static uint8_t startIndex = 0; | |||
startIndex = startIndex + 1; /* motion speed */ | |||
FillLEDsFromPaletteColors( startIndex); | |||
FastLED.show(); | |||
FastLED.delay(1000 / UPDATES_PER_SECOND); | |||
} | |||
void FillLEDsFromPaletteColors( uint8_t colorIndex) | |||
{ | |||
uint8_t brightness = 255; | |||
for( int i = 0; i < NUM_LEDS; ++i) { | |||
leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending); | |||
colorIndex += 3; | |||
} | |||
} | |||
// There are several different palettes of colors demonstrated here. | |||
// | |||
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p, | |||
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p. | |||
// | |||
// Additionally, you can manually define your own color palettes, or you can write | |||
// code that creates color palettes on the fly. All are shown here. | |||
void SetupTotallyRandomPalette(); | |||
void SetupPurpleAndGreenPalette(); | |||
void SetupPurpleAndGreenPalette(); | |||
void SetupBlackAndWhiteStripedPalette(); | |||
== | void ChangePalettePeriodically() | ||
{ | |||
uint8_t secondHand = (millis() / 1000) % 60; | |||
static uint8_t lastSecond = 99; | |||
if( lastSecond != secondHand) { | |||
lastSecond = secondHand; | |||
if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; } | |||
[ | if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; } | ||
if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; } | |||
if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; } | |||
if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; } | |||
[[ | if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; } | ||
if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; } | |||
if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; } | |||
if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; } | |||
if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; } | |||
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; } | |||
} | |||
} | |||
// This function fills the palette with totally random colors. | |||
void SetupTotallyRandomPalette() | |||
{ | |||
for( int i = 0; i < 16; ++i) { | |||
currentPalette[i] = CHSV( random8(), 255, random8()); | |||
} | |||
} | |||
// This function sets up a palette of black and white stripes, | |||
// using code. Since the palette is effectively an array of | |||
// sixteen CRGB colors, the various fill_* functions can be used | |||
// to set them up. | |||
void SetupBlackAndWhiteStripedPalette() | |||
{ | |||
// 'black out' all 16 palette entries... | |||
fill_solid( currentPalette, 16, CRGB::Black); | |||
// and set every fourth one to white. | |||
currentPalette[0] = CRGB::White; | |||
currentPalette[4] = CRGB::White; | |||
currentPalette[8] = CRGB::White; | |||
currentPalette[12] = CRGB::White; | |||
} | |||
// This function sets up a palette of purple and green stripes. | |||
void SetupPurpleAndGreenPalette() | |||
{ | |||
CRGB purple = CHSV( HUE_PURPLE, 255, 255); | |||
CRGB green = CHSV( HUE_GREEN, 255, 255); | |||
CRGB black = CRGB::Black; | |||
currentPalette = CRGBPalette16( | |||
green, green, black, black, | |||
purple, purple, black, black, | |||
green, green, black, black, | |||
purple, purple, black, black ); | |||
} | |||
// This example shows how to set up a static color palette | |||
// which is stored in PROGMEM (flash), which is almost always more | |||
// plentiful than RAM. A static PROGMEM palette like this | |||
// takes up 64 bytes of flash. | |||
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM = | |||
{ | |||
CRGB::Red, | |||
CRGB::Gray, // 'white' is too bright compared to red and blue | |||
CRGB::Blue, | |||
CRGB::Black, | |||
CRGB::Red, | |||
CRGB::Gray, | |||
CRGB::Blue, | |||
CRGB::Black, | |||
CRGB::Red, | |||
CRGB::Red, | |||
CRGB::Gray, | |||
CRGB::Gray, | |||
CRGB::Blue, | |||
CRGB::Blue, | |||
CRGB::Black, | |||
CRGB::Black | |||
}; | |||
// Additional notes on FastLED compact palettes: | |||
// | |||
// Normally, in computer graphics, the palette (or "color lookup table") | |||
// has 256 entries, each containing a specific 24-bit RGB color. You can then | |||
// index into the color palette using a simple 8-bit (one byte) value. | |||
// A 256-entry color palette takes up 768 bytes of RAM, which on Arduino | |||
// is quite possibly "too many" bytes. | |||
// | |||
// FastLED does offer traditional 256-element palettes, for setups that | |||
// can afford the 768-byte cost in RAM. | |||
// | |||
// However, FastLED also offers a compact alternative. FastLED offers | |||
// palettes that store 16 distinct entries, but can be accessed AS IF | |||
// they actually have 256 entries; this is accomplished by interpolating | |||
// between the 16 explicit entries to create fifteen intermediate palette | |||
// entries between each pair. | |||
// | |||
// So for example, if you set the first two explicit entries of a compact | |||
// palette to Green (0,255,0) and Blue (0,0,255), and then retrieved | |||
// the first sixteen entries from the virtual palette (of 256), you'd get | |||
// Green, followed by a smooth gradient from green-to-blue, and then Blue. | |||
</syntaxhighlight> |
Версия 23:20, 6 декабря 2023
СТАТЬЯ НАХОДИТСЯ В СТАДИИ РАЗРАБОТКИ
Управление адресной лентой WS2812B из Wirenboard
здесь будет рассмотрен один из способов управления адресной лентой WS2812B из Wirenboard с использованием ESP32.
здесь не будут рассмотрены характеристики железной части, этому уделено очень много внимания на просторах Интернет, тут будет сугубо техническая информация по процессу достижения цели, с небольшими лирическими отступлениями.
Что нам понадобится
- любая отладочная плата, основанная на esp32
- две bread board платы
- соединительные проводки
- лента ws2812b нужной длины (в моём случае бухта 5м с мощностью 14.4Вт/м)
- блок питания 5V соответствующей мощности (в моём случае 100W)
- резистор 220 ом
- microUSB дата-кабель (обычный, от зарядки, может не подойти!)
- комп/ноут для сборки и заливки прошивки
- VSCode + PlatformIO
- контроллер Wirenboard с wb-rules v2.x
- wi-fi сеть в диапазоне 2.4G
Сборка физической схемы
Для сборки нам нужно две bread board платы, собираем по схеме:
- пин 5V подключаем к +5V ленты
- пин GND
- пинг GPIO27 подключаем через резистор 220ом к DIN ленты
Обращаю внимание на то, что не рекомендуется тестировать конфигурацию более чем на 5 светодиодах, такого количества будет достаточно, чтобы понять как выглядит световая картинка в том или ином алгоритме. Подключение большего количества светодиодов может привести к выгоранию USB-порта на компе, т.к. там обычно предельно допустимый ток 350мА, а один светодиод на такой ленте потребляет около 50мА.
Заливаем первую прошивку
Подразумевается что на PC уже установлена среда разработки VSCode и расширение PlatformIO. Создадим новый проект.
Далее нам нужно подключить к проекту библиотеку для работы с лентой, они есть разные, я использовал FastLED
добавляем в проект
Открываем src/main.cpp и приводим его к следующему виду:
#include <Arduino.h>
#include <FastLED.h>
#define LED_PIN 27
#define NUM_LEDS 5
#define BRIGHTNESS 50
#define LED_TYPE WS2811_400
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define UPDATES_PER_SECOND 50
// This example shows several ways to set up and use 'palettes' of colors
// with FastLED.
//
// These compact palettes provide an easy way to re-colorize your
// animation on the fly, quickly, easily, and with low overhead.
//
// USING palettes is MUCH simpler in practice than in theory, so first just
// run this sketch, and watch the pretty lights as you then read through
// the code. Although this sketch has eight (or more) different color schemes,
// the entire sketch compiles down to about 6.5K on AVR.
//
// FastLED provides a few pre-configured color palettes, and makes it
// extremely easy to make up your own color schemes with palettes.
//
// Some notes on the more abstract 'theory and practice' of
// FastLED compact palettes are at the bottom of this file.
CRGBPalette16 currentPalette;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
void setup() {
delay( 3000 ); // power-up safety delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness( BRIGHTNESS );
currentPalette = RainbowColors_p;
currentBlending = LINEARBLEND;
}
void ChangePalettePeriodically();
void FillLEDsFromPaletteColors( uint8_t colorIndex);
void loop()
{
ChangePalettePeriodically();
static uint8_t startIndex = 0;
startIndex = startIndex + 1; /* motion speed */
FillLEDsFromPaletteColors( startIndex);
FastLED.show();
FastLED.delay(1000 / UPDATES_PER_SECOND);
}
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
uint8_t brightness = 255;
for( int i = 0; i < NUM_LEDS; ++i) {
leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
colorIndex += 3;
}
}
// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly. All are shown here.
void SetupTotallyRandomPalette();
void SetupPurpleAndGreenPalette();
void SetupPurpleAndGreenPalette();
void SetupBlackAndWhiteStripedPalette();
void ChangePalettePeriodically()
{
uint8_t secondHand = (millis() / 1000) % 60;
static uint8_t lastSecond = 99;
if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; }
if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; }
if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; }
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
}
}
// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
for( int i = 0; i < 16; ++i) {
currentPalette[i] = CHSV( random8(), 255, random8());
}
}
// This function sets up a palette of black and white stripes,
// using code. Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;
}
// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green = CHSV( HUE_GREEN, 255, 255);
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
green, green, black, black,
purple, purple, black, black,
green, green, black, black,
purple, purple, black, black );
}
// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM. A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
CRGB::Red,
CRGB::Gray, // 'white' is too bright compared to red and blue
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Gray,
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Red,
CRGB::Gray,
CRGB::Gray,
CRGB::Blue,
CRGB::Blue,
CRGB::Black,
CRGB::Black
};
// Additional notes on FastLED compact palettes:
//
// Normally, in computer graphics, the palette (or "color lookup table")
// has 256 entries, each containing a specific 24-bit RGB color. You can then
// index into the color palette using a simple 8-bit (one byte) value.
// A 256-entry color palette takes up 768 bytes of RAM, which on Arduino
// is quite possibly "too many" bytes.
//
// FastLED does offer traditional 256-element palettes, for setups that
// can afford the 768-byte cost in RAM.
//
// However, FastLED also offers a compact alternative. FastLED offers
// palettes that store 16 distinct entries, but can be accessed AS IF
// they actually have 256 entries; this is accomplished by interpolating
// between the 16 explicit entries to create fifteen intermediate palette
// entries between each pair.
//
// So for example, if you set the first two explicit entries of a compact
// palette to Green (0,255,0) and Blue (0,0,255), and then retrieved
// the first sixteen entries from the virtual palette (of 256), you'd get
// Green, followed by a smooth gradient from green-to-blue, and then Blue.
сейчас можно залить попробовать скомпилировать прошивку, для этого в нижней панели жмём кнопку
если всё собралось, только можно шить это дело в esp32:
- подключаем esp32 к компу при помощи microUSB data кабеля
- инициируем заливку прошивки при помощи кнопки , когда система напишет "Connecting..." - зажимаем кнопку Boot на dev-плате esp32
- если прошивка залилась успешно, перезагружаем esp32 соответствующей кнопкой, после этого у вас должна заработать лента, алгоритм будет менять каждые 5 секунд, всего 12 алгоритмов
Подробно останавливаться на коде прошивки мы не будем, в Интернете полно разных алгоритмов, их можно добавлять к нашей оснастке, переключать их реже или чаще, можно даже кнопку прикрутить под это дело на один из пинов, тут кому насколько фантазии хватит.
Подключаемся к wifi
Приступаем к следующему этапу - нам нужно подключить нашу esp32 к wifi (чтобы далее подключиться к modbus tcp от wirenboard)
добавим к нашему файлу main.cpp следующий код:
#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);
}
а также модифицируем функцию setup:
void setup() {
setupWifi();
}
теперь после прошивки esp32 подключится к вашей wifi сети, IP-адрес можно будет посмотреть на роутере, либо в отладочных сообщения на Serial-порте, который связывает ваш комп с esp32, для этого нажмите кнопку на нижней панели
Подключение к Modbus TCP
После настройки wifi мы можем подключаться к mqtt-брокеру wirenboard, для этого нужно установить библиотеку, как мы это делали ранее, называется она PubSubClient
подключимся к брокеру, для этого добавим немного кода:
#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);
pubSubClient.publish("outTopic", "hello world");
pubSubClient.subscribe("inTopic");
}
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");
} 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();
}
что мы здесь видим: мы можем создавать подписку на нужные топики либо сами публиковать какие то значения в нужные нам топики.
в нашем случае понадобится управлять настройками ленты, поэтому нам надо слушать некоторые топики, изменения которых будут влиять на поведение ленты, допустим, мы хотим управлять: - яркостью - выключать (яркость 0) - включать (яркость 50%) - останавливать и запускать автоматическую смену режима
Виртуальное устройство в Wirenboard
Создадим виртуальное устройство нашей ленты в wirenboard:
defineVirtualDevice("ws2812b", {
title: "ws2812b",
cells: {
enabled: {
type: "switch",
value: false,
},
brightness: {
type: "range",
min: 0,
max: 255,
value: 50,
},
hueLoop: {
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
}
}
})
подписываем на топики:
void setupModbus() {
pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT);
pubSubClient.setCallback(modbusCallback);
pubSubClient.subscribe("/devices/ws2812b/controls/brightness");
pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop");
}
теперь надо константу BRIGHTNESS переделать в переменную, а также ввести переменную hue_loop:
#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);
if (topic == "/devices/ws2812b/controls/brightness") {
BRIGHTNESS = payloadStr.toInt();
}
if (topic == "/devices/ws2812b/controls/hueLoop") {
if(payloadChar == "true") {
hueLoop = true;
} else {
hueLoop = false;
}
}
}
void ChangePalettePeriodically() {
if(!hueLoop) {
return;
}
...
}
Финальный код скетча
#include <Arduino.h>
#include <FastLED.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <WiFi.h>
#define LED_PIN 27
#define NUM_LEDS 5
#define LED_TYPE WS2811_400
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define UPDATES_PER_SECOND 50
uint8_t BRIGHTNESS = 50;
bool hueLoop = true;
// This example shows several ways to set up and use 'palettes' of colors
// with FastLED.
//
// These compact palettes provide an easy way to re-colorize your
// animation on the fly, quickly, easily, and with low overhead.
//
// USING palettes is MUCH simpler in practice than in theory, so first just
// run this sketch, and watch the pretty lights as you then read through
// the code. Although this sketch has eight (or more) different color schemes,
// the entire sketch compiles down to about 6.5K on AVR.
//
// FastLED provides a few pre-configured color palettes, and makes it
// extremely easy to make up your own color schemes with palettes.
//
// Some notes on the more abstract 'theory and practice' of
// FastLED compact palettes are at the bottom of this file.
CRGBPalette16 currentPalette;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
#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 setupFastLED(){
delay( 3000 ); // power-up safety delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness( BRIGHTNESS );
currentPalette = RainbowColors_p;
currentBlending = LINEARBLEND;
}
#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) {
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);
if (topic == "/devices/ws2812b/controls/brightness") {
BRIGHTNESS = payloadStr.toInt();
}
if (topic == "/devices/ws2812b/controls/hueLoop") {
if(payloadChar == "true") {
hueLoop = true;
} else {
hueLoop = false;
}
}
}
void setupModbus() {
pubSubClient.setServer(MODBUS_SERVER, MODBUS_PORT);
pubSubClient.setCallback(modbusCallback);
pubSubClient.subscribe("/devices/ws2812b/controls/brightness");
pubSubClient.subscribe("/devices/ws2812b/controls/hueLoop");
}
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");
} else {
Serial.print("failed, rc=");
Serial.print(pubSubClient.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void modbusLoop() {
if (!pubSubClient.connected()) {
modbusReconnect();
}
pubSubClient.loop();
}
void setup() {
setupWifi();
setupFastLED();
setupModbus();
}
void ChangePalettePeriodically();
void FillLEDsFromPaletteColors( uint8_t colorIndex);
void loop()
{
modbusLoop();
if(!hueLoop) {
return;
}
ChangePalettePeriodically();
static uint8_t startIndex = 0;
startIndex = startIndex + 1; /* motion speed */
FillLEDsFromPaletteColors( startIndex);
FastLED.show();
FastLED.delay(1000 / UPDATES_PER_SECOND);
}
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
uint8_t brightness = 255;
for( int i = 0; i < NUM_LEDS; ++i) {
leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
colorIndex += 3;
}
}
// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly. All are shown here.
void SetupTotallyRandomPalette();
void SetupPurpleAndGreenPalette();
void SetupPurpleAndGreenPalette();
void SetupBlackAndWhiteStripedPalette();
void ChangePalettePeriodically()
{
uint8_t secondHand = (millis() / 1000) % 60;
static uint8_t lastSecond = 99;
if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; }
if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; }
if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; }
if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; }
if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; }
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
}
}
// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
for( int i = 0; i < 16; ++i) {
currentPalette[i] = CHSV( random8(), 255, random8());
}
}
// This function sets up a palette of black and white stripes,
// using code. Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;
}
// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green = CHSV( HUE_GREEN, 255, 255);
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
green, green, black, black,
purple, purple, black, black,
green, green, black, black,
purple, purple, black, black );
}
// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM. A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
CRGB::Red,
CRGB::Gray, // 'white' is too bright compared to red and blue
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Gray,
CRGB::Blue,
CRGB::Black,
CRGB::Red,
CRGB::Red,
CRGB::Gray,
CRGB::Gray,
CRGB::Blue,
CRGB::Blue,
CRGB::Black,
CRGB::Black
};
// Additional notes on FastLED compact palettes:
//
// Normally, in computer graphics, the palette (or "color lookup table")
// has 256 entries, each containing a specific 24-bit RGB color. You can then
// index into the color palette using a simple 8-bit (one byte) value.
// A 256-entry color palette takes up 768 bytes of RAM, which on Arduino
// is quite possibly "too many" bytes.
//
// FastLED does offer traditional 256-element palettes, for setups that
// can afford the 768-byte cost in RAM.
//
// However, FastLED also offers a compact alternative. FastLED offers
// palettes that store 16 distinct entries, but can be accessed AS IF
// they actually have 256 entries; this is accomplished by interpolating
// between the 16 explicit entries to create fifteen intermediate palette
// entries between each pair.
//
// So for example, if you set the first two explicit entries of a compact
// palette to Green (0,255,0) and Blue (0,0,255), and then retrieved
// the first sixteen entries from the virtual palette (of 256), you'd get
// Green, followed by a smooth gradient from green-to-blue, and then Blue.