Projekt zawiera moduły RGB, czyli takie komponenty, które integrują w sobie trzy diody LED – czerwoną (red), zieloną (green) oraz niebieską (blue). Są to kolory podstawowe, które można sumować w różnych proporcjach dla uzyskania dowolnego koloru ze spektrum światła widzialnego. Regulując niezależnie jasność każdej z diod w module, można sterować kolorem wypadkowym. Na ogół diodami steruje się za pomocą sygnałów PWM (impulsy o zmiennym wypełnieniu), ale wiele modułów LED RGB jest wyposażonych w zintegrowane sterowniki cyfrowe, które regulują intensywność barw (generując sygnał PWM) na podstawie odpowiednich komend cyfrowych.
Moduł WS2812B jest dość popularny, ponieważ jest łatwy w użyciu i oprogramowaniu, a jednocześnie jest dosyć tani. Do sterowania tym modułem wymagane jest tylko jedno wyjście mikrokontrolera. Dodatkowo, można je łączyć ze sobą szeregowo, dzięki czemu nawet wiele modułów można kontrolować w łatwy sposób. Dzięki temu można wybrać niewielki mikrokontroler, który ma tylko kilka wyprowadzeń GPIO, aby sterować wieloma modułami LED RGB.
Autor projektu zastosował moduł ESP-01 w roli kontrolera LED RGB. Moduł ten nie ma wielu pinów GPIO, ale jest idealny do tego projektu. Ma między innymi kompletny interfejs Wi-Fi, więc można go używać do sterowania przez Internet. Moduł RGB WS2812B korzysta tylko z jednego cyfrowego pinu do sterowania. Dzięki unikalnemu interfejsowi szeregowemu kontrolowanie tych diod jest niesamowicie proste. W środowiskach takich jak Arduino, obsługa tego rodzaju modułów często jest już zaimplementowana w dostępnych bibliotekach.
Potrzebne elementy
W systemie zastosowano komponenty SMD oraz przewlekane, aby uzyskać kompaktowy moduł sterujący diodami RGB. Do zestawienia urządzenia potrzebne będą:
- moduł ESP-01,
- stabilizator 3,3 V LM1117,
- cztery rezystory 10 kΩ (SMD),
- dwa kondensatory 10 μF (SMD),
- dwa przyciski typu microswitch,
- złącza śrubowe (2-torowe oraz 3-torowe),
- kilka szpilek typu goldpin.
Schemat układu
W systemie zaimplementowano najprostszy, podstawowy schemat użycia modułu ESP-01. Rezystor 10 kΩ służy do podciągnięcia pinów konfiguracyjnych ESP-01, wymuszając tryb normalnej pracy. W systemie są da przyciski – jeden służy do restartowania systemu, a drugi do uruchomienia trybu programowania. Jak pokazano na rysunku 1, przyciski podłączone są pod linie RST oraz GPIO0. Jest to bardzo istotne, gdyż pozwala na wykorzystanie wbudowanego w ESP-01 bootloadera i programowanie mikrokontrolera przez prosty interfejs UART.
Złącze goldpin o 5 wyprowadzeniach (J1) służy do programowania ESP-01. Odbywa się to poprzez podłączenie modułu do portu USB z użyciem emulatora portu szeregowego. W razie potrzeby do programowania posłużyć może dodatkowe złącze z trzema pinami (J2). Na płytce, która została pokazana na rysunku 2, widać także dwa większe złącza.
Mniejsze, 2-pinowe, służy do podłączania do układu zewnętrznego zasilacza. Złącze z trzema pinami służy do podłączenia modułu z diodą (lub diodami) WS2812B.
Na płytce znajduje się także stabilizator IC1. Jest to liniowy stabilizator LDO, który stabilizuje napięcie zasilania dla ESP-01 do wartości 3,3 V z zasilania wejściowego o napięciu 5 V. Stabilizatorowi towarzyszą dwa kondensatory, które są konieczne do zapewnienia mu stabilnej pracy.
Na stronie z projektem (link znajduje się na końcu artykułu) znajdują się pliki schematu oraz płytki drukowanej, wykonane w programie Eagle. To pozwala każdemu zainteresowanemu na pobranie i wygenerowanie plików produkcyjnych do projektu. W ten sposób można je zamówić w wybranym przez siebie zakładzie (autor projektu zamawiał je w PCBWay. Na fotografii 1 pokazano gotowy, zmontowany moduł.
Oprogramowanie firmware
Oprogramowanie samego kontrolera bazuje na przykładzie pochodzącym z biblioteki WS2812 FX dla Arduino, opracowanej przez Harma Aldicka. Skrypt, który służy do sterowania modułu LED RGB, pozwala na kontrolowanie jego działania poprzez sieć. Na listingu 1 zaprezentowano w jaki sposób mikrokontroler hostuje stronę web.
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WS2812FX.h>
extern const char index_html[];
extern const char main_js[];
#define WIFI_SSID "YOURSSID" // SSID sieci Wi-Fi
#define WIFI_PASSWORD "YOURPASSWORD" // Hasło do sieci Wi-Fi
//#define STATIC_IP // Odkomentuj, aby ustawić statyczne IP (patrz niżej)
#ifdef STATIC_IP
IPAddress ip(192,168,0,123);
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
#endif
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define LED_PIN 2 // Pin do którego podłączono wejście LED RGB 0 = GPIO0, 2=GPIO2
#define LED_COUNT 24 // Ilość połączonych ze sobą szeregowo modułó RGB
#define WIFI_TIMEOUT 30000 // Okres sprawdzania WiFi (w ms). Resetuje się po tym czasie, jeśli nie można dołączyć Wi-Fi
#define HTTP_PORT 80
#define DEFAULT_COLOR 0xFF5900
#define DEFAULT_BRIGHTNESS 128
#define DEFAULT_SPEED 1000
#define DEFAULT_MODE FX_MODE_STATIC
unsigned long auto_last_change = 0;
unsigned long last_wifi_check_time = 0;
String modes = "";
uint8_t myModes[] = {};
bool auto_cycle = false;
WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
ESP8266WebServer server(HTTP_PORT);
void setup(){
Serial.begin(115200);
delay(500);
Serial.println("\n\nStarting...");
modes.reserve(5000);
modes_setup();
Serial.println("WS2812FX setup");
ws2812fx.init();
ws2812fx.setMode(DEFAULT_MODE);
ws2812fx.setColor(DEFAULT_COLOR);
ws2812fx.setSpeed(DEFAULT_SPEED);
ws2812fx.setBrightness(DEFAULT_BRIGHTNESS);
ws2812fx.start();
Serial.println("Wifi setup");
wifi_setup();
Serial.println("HTTP server setup");
server.on("/", srv_handle_index_html);
server.on("/main.js", srv_handle_main_js);
server.on("/modes", srv_handle_modes);
server.on("/set", srv_handle_set);
server.onNotFound(srv_handle_not_found);
server.begin();
Serial.println("HTTP server started.");
Serial.println("ready!");
}
void loop() {
unsigned long now = millis();
server.handleClient();
ws2812fx.service();
if(now - last_wifi_check_time > WIFI_TIMEOUT) {
Serial.print("Checking WiFi... ");
if(WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi connection lost. Reconnecting...");
wifi_setup();
} else {
Serial.println("OK");
}
last_wifi_check_time = now;
}
if(auto_cycle && (now - auto_last_change > 10000)) { // cycle effect mode every 10 seconds
uint8_t next_mode = (ws2812fx.getMode() + 1) % ws2812fx.getModeCount();
if(sizeof(myModes) > 0) { // if custom list of modes exists
for(uint8_t i=0; i < sizeof(myModes); i++) {
if(myModes[i] == ws2812fx.getMode()) {
next_mode = ((i + 1) < sizeof(myModes)) ? myModes[i + 1] : myModes[0];
break;
}
}
}
ws2812fx.setMode(next_mode);
Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode()));
auto_last_change = now;
}
}
void wifi_setup() {
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.mode(WIFI_STA);
#ifdef STATIC_IP
WiFi.config(ip, gateway, subnet);
#endif
unsigned long connect_start = millis();
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if(millis() - connect_start > WIFI_TIMEOUT) {
Serial.println();
Serial.print("Tried ");
Serial.print(WIFI_TIMEOUT);
Serial.print("ms. Resetting ESP now.");
ESP.reset();
}
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println();
}
void modes_setup() {
modes = "";
uint8_t num_modes = sizeof(myModes) > 0 ? sizeof(myModes) : ws2812fx.getModeCount();
for(uint8_t i=0; i < num_modes; i++) {
uint8_t m = sizeof(myModes) > 0 ? myModes[i] : i;
modes += "<li><a href=’#’>";
modes += ws2812fx.getModeName(m);
modes += "</a></li>";
}
}
void srv_handle_not_found() {
server.send(404, "text/plain", "File Not Found");
}
void srv_handle_index_html() {
server.send_P(200,"text/html", index_html);
}
void srv_handle_main_js() {
server.send_P(200,"application/javascript", main_js);
}
void srv_handle_modes() {
server.send(200,"text/plain", modes);
}
void srv_handle_set() {
for (uint8_t i=0; i < server.args(); i++){
if(server.argName(i) == "c") {
uint32_t tmp = (uint32_t) strtol(server.arg(i).c_str(), NULL, 10);
if(tmp >= 0x000000 && tmp <= 0xFFFFFF) {
ws2812fx.setColor(tmp);
}
}
if(server.argName(i) == "m") {
uint8_t tmp = (uint8_t) strtol(server.arg(i).c_str(), NULL, 10);
uint8_t new_mode = sizeof(myModes) > 0 ? myModes[tmp % sizeof(myModes)] : tmp % ws2812fx.getModeCount();
ws2812fx.setMode(new_mode);
Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode()));
}
if(server.argName(i) == "b") {
if(server.arg(i)[0] == ‘-’) {
ws2812fx.setBrightness(ws2812fx.getBrightness() * 0.8);
} else if(server.arg(i)[0] == ‘ ‘) {
ws2812fx.setBrightness(min(max(ws2812fx.getBrightness(), 5) * 1.2, 255));
} else { // set brightness directly
uint8_t tmp = (uint8_t) strtol(server.arg(i).c_str(), NULL, 10);
ws2812fx.setBrightness(tmp);
}
Serial.print("brightness is "); Serial.println(ws2812fx.getBrightness());
}
if(server.argName(i) == "s") {
if(server.arg(i)[0] == ‘-’) {
ws2812fx.setSpeed(max(ws2812fx.getSpeed(), 5) * 1.2);
} else if(server.arg(i)[0] == ‘ ‘) {
ws2812fx.setSpeed(ws2812fx.getSpeed() * 0.8);
} else {
uint16_t tmp = (uint16_t) strtol(server.arg(i).c_str(), NULL, 10);
ws2812fx.setSpeed(tmp);
}
Serial.print("speed is "); Serial.println(ws2812fx.getSpeed());
}
if(server.argName(i) == "a") {
if(server.arg(i)[0] == ‘-’) {
auto_cycle = false;
} else {
auto_cycle = true;
auto_last_change = 0;
}
}
}
server.send(200, "text/plain", "OK");
}
W skrypcie tym trzeba zmodyfikować nazwę SSID oraz hasło do naszej sieci, aby moduł z ESP mógł zalogować się do naszej sieci Wi-Fi. Jeżeli chcemy, aby pracował on ze stałym IP (domyślnie korzysta z DHCP w sieci), należy odkomentować odpowiednią linię oraz uzupełnić adres IP urządzenia, bramy i maskę sieci.
Z skryptu na listingu 1 usunięto elementy odpowiedzialne za komunikację poprzez port szeregowy – są one istotne tylko na etapie debugowania, oraz definicję funkcji zapewniających interfejs sieciowy. Do jego działania potrzebne są jeszcze dodatkowe pliki (index.html.cpp oraz index.js.cpp), które znajdują się w repozytorium. Szkic korzysta z biblioteki WS2812FX.h, która ma wszystkie zaawansowane funkcje do obsługi modułów z rodziny WS2812.
Programowanie
Aby zaprogramować moduł ESP-01 będziemy potrzebowali przejściówki USB-UART z wyjściem 3,3 V TTL. Można ją podłączyć bezpośrednio do modułu ESP-01, jak pokazano na rysunku 3, lub do 5-pinowego złącza na płytce kontrolera, zgodnie z oznaczeniami na schemacie układu (rysunek 1).
Do przeprowadzenia programowania niezbędne jest środowisko Arduino IDE. Po upewnieniu się, że posiadamy na komputerze wymaganą bibliotekę WS2812FX.h oraz pobrane z repozytorium GitHub pliki szkicu, można skompilować całość, a następnie załadować do modułu. Aby przetestować działanie należy dołączyć moduł LED RGB i zasilanie, tak jak pokazano na rysunku 4.
Obudowa
Lampa zawiera opisany powyżej kontroler oraz panel RGB, który stanowi macierz 8×8 modułów LED RGB. Cały system uzupełnia akumulator typu 18650, zasilający całość i czyniący całe urządzenie przenośnym. Obudowa lampy wykonana jest w technologii druku 3D. Projekt obudowy można pobrać ze strony z projektem. Jest ona relatywnie prostą konstrukcją otwartą z jednej strony.
Jak widać na fotografii 2 jest ona podzielona na dwie sekcje – jedną, w której znajduje się elektronika i drugą, gdzie instalowany jest panel z modułami WS2812B.
Koszyk dla akumulatora instalowany jest na zewnątrz, na tylnej ścianie lampki. Po wydrukowaniu i wykończeniu obudowy można umieścić w niej przyciski i przełączniki, które następnie podłączone zostaną do kontrolera. Na fotografii 3 pokazano sposób osadzenia przycisków w obudowie.
Aby uprościć proces montażu, w kontrolerze nie zainstalowano złącz, a przewody zostały przylutowane bezpośrednio do kontrolera. Schemat połączenia poszczególnych elementów pokazano na rysunku 5.
Po połączeniu wszystkich elementów, możliwe jest osadzenie obu kluczowych płytek w obudowie, aby uzyskać konstrukcje taką, jak pokazano na fotografii tytułowej.
Oprogramowanie
Oprogramowanie, które opracował autor, jest dość proste. Pozwala na wyświetlanie kilku kolorów. Aby zmienić kolor, wystarczy raz nacisnąć przycisk trybu. Można dodać dowolny inny kolor lampy, po prostu modyfikując kilka linijek kodu, gdzie zawarto definicję barw. Kod źródłowy tej aplikacji zaprezentowano na listingu 2.
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Wymagane do działania 16 MHz Adafruit Trinket
#endif
#define PIN 2 // Pin podłączenia diody
#define NUMPIXELS 64 // Liczba diod
#define BUTTON 0 // Pin podłączenia przycisku
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
int button;
int count;
void setup() {
pixels.begin(); // Inicjalizacja obiektu NeoPixel
pixels.setBrightness(255);
pinMode(BUTTON, INPUT);
}
void loop() {
button = digitalRead(BUTTON);
pixels.clear();
if(button == 0){
count++;
delay(300);
if(count==1){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(128,128,128));
}
}
pixels.show();
}
if(count==2){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(255,0,0));
}
pixels.show();
}
if(count==3){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0,255,0));
}
pixels.show();
}
if(count==4){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(128,225,0));
}
pixels.show();
}
if(count==5){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0,0,255));
}
pixels.show();
}
if(count==6){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0,128,255));
}
pixels.show();
}
if(count==7){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(255,128,0));
}
pixels.show();
}
if(count==8){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(255,255,0));
}
pixels.show();
}
if(count==9){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0,25,255));
}
pixels.show();
}
if(count==10){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(127,0,255));
}
pixels.show();
}
if(count==11){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(255,0,255));
}
pixels.show();
}
if(count==12){
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(255,0,127));
}
pixels.show();
count=0;
}
}
Aby uprościć działanie systemu, szkic ten wykorzystuje bibliotekę Adafruit_NeoPixel.h, która również pozwala na obsługę diod RGB, takich jak WS2812B.
Podsumowanie
Gotowa lampa doskonale oświetla biurko. Na fotografii 5 pokazano, w jaki sposób różne oświetlenie może zmieniać zdjęcie.
Teraz każdy może osiągnąć podobne efekty samodzielnie. Kontroler może sterować nawet większą ilością paneli RGB, aby uzyskać lampę o większym natężeniu światła. Należy tylko pamiętać o dostarczeniu do diod odpowiedniego zasilania.
Nikodem Czechowski, EP
Źródła:
https://bit.ly/3DbtpiH
https://bit.ly/387CN8K
https://bit.ly/3gmOigM
https://bit.ly/3zaGd6q