Điều khiển đổi màu LED RGB ESP32 trên Web Server

Trong dự án này, mình sẽ hướng dẫn bạn làm một bộ điều khiển LED RGB ESP32 bằng Color Picker trên Web Server nhé! Qua đó, chúng ta có thể điều khiển màu sắc của LED RGB theo ý thích từ xa qua Internet.

Tuy nhiên, để dễ theo dõi hướng dẫn dưới đây, bạn có thể tham khảo cách hoạt động của đèn LED RGB và cách tạo Web Server ESP32 trên Arduino nhé:

Giới thiệu dự án đổi màu LED RGB ESP32

Mình đã minh họa sẵn cách dự án hoạt động thông qua hình ảnh dưới:

Giới thiệu dự án đổi màu LED RGB ESP32
Giới thiệu dự án đổi màu LED RGB ESP32

Cụ thể:

  • Web Server có sẵn công cụ Picker Color cho chúng ta chọn màu
  • Khi chọn màu xong, trình duyệt sẽ gửi một yêu cầu HTTP là địa chỉ URL có mã màu đã chọn (gồm sự pha trộn giữa màu red, green, blue – RGB)
  • ESP32 nhận yêu cầu và xuất xung tín hiệu PWM tương ứng để điều khiển màu sắc của đèn LED

Chuẩn bị

  • Mạch ESP32
  • Dây đèn LED RGB
  • Bóng bán dẫn NPN
  • Điện trở
  • Dây Jumper
  • Bảng mạch Breadboard

Trong dự án này, mình sử dụng dây đèn LED có nguồn 5V và cấp nguồn thông qua cáp USB, ví dụ như hình:

Dây đèn LED RGB ESP32

Bạn cần lưu ý rằng có một số dây LED RGB cần nguồn 12V. Bạn có thể sử dụng bất kỳ loại nào mình thích, tuy nhiên cần phải cấp nguồn điện phù hợp nhé!

Kết nối

Bạn kết nối LED RGB với ESP32 theo sơ đồ bên dưới:

Bạn kết nối LED RGB với ESP32

Nếu bạn sử dụng mạch EPS8266, bạn có thể tham khảo sơ đồ bên dưới:

Bạn kết nối LED RGB với ESP8266

Với bóng bán dẫn NPN (Transitor NPN), chúng ta có thể kết nối như bên dưới. Tùy thuộc vào số lượng đèn LED có trong chuỗi dây, bạn có thể sử dụng Transitor NPN để hỗ trợ cung cấp dòng điện liên tục:

Kết nối Transistors với LED RGB ESP32

Để xác định dây LED sử dụng lượng điện tối đa là bao nhiêu, bạn có thể đo tổng lượng điện tiêu thụ khi dây LED đang bật với độ sáng cao nhất (với màu trắng nhé).

Khi mình dùng 12 bóng LED, dòng điện tối đa sẽ ở mức khoảng 630mA khi bật độ sáng cao nhất ở mức độ ánh sáng trắng. Do đó, mình sẽ dùng bóng bán dẫn NPN S8050 để xử lý dòng điện ở mức 700mA.

Đo lượng điện do LED RGB ESP32 tiêu thụ
Đo lượng điện do LED RGB ESP32 tiêu thụ

Lưu ý quan trọng khi đo lượng điện này là bạn hãy bật chế độ màu trắng nhé, vì khi đó thì lượng điện tiêu thụ mới ở mức cao nhất được.

Chương trình điều khiển LED RGB ESP32

ESP32

Nếu bạn chưa cài tiện ích ESP32 trong Arduino IDE, bạn nhớ làm theo hướng dẫn sau để cài tiện ích này trước khi nạp chương trình nhé: Cách lập trình ESP32 bằng Arduino IDE (Windows, Linux, Mac OS X).

Dưới đây là chương trình đầy đủ, tuy nhiên bạn nhớ hãy đổi thông tin mạng WiFi thành của bạn rồi hãy nạp chương trình vào ESP32 nhé!

// Load Wi-Fi library
#include <WiFi.h>

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Set web server port number to 80
WiFiServer server(80);

// Decode HTTP GET value
String redString = "0";
String greenString = "0";
String blueString = "0";
int pos1 = 0;
int pos2 = 0;
int pos3 = 0;
int pos4 = 0;

// Variable to store the HTTP req  uest
String header;

// Red, green, and blue pins for PWM control
const int redPin = 13;     // 13 corresponds to GPIO13
const int greenPin = 12;   // 12 corresponds to GPIO12
const int bluePin = 14;    // 14 corresponds to GPIO14

// Setting PWM frequency, channels and bit resolution
const int freq = 5000;
const int redChannel = 0;
const int greenChannel = 1;
const int blueChannel = 2;
// Bit resolution 2^8 = 256
const int resolution = 8;

// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

void setup() {
  Serial.begin(115200);
  // configure LED PWM functionalitites
  ledcSetup(redChannel, freq, resolution);
  ledcSetup(greenChannel, freq, resolution);
  ledcSetup(blueChannel, freq, resolution);
  
  // attach the channel to the GPIO to be controlled
  ledcAttachPin(redPin, redChannel);
  ledcAttachPin(greenPin, greenChannel);
  ledcAttachPin(bluePin, blueChannel);
  
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected() && currentTime - previousTime <= timeoutTime) {            // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
                   
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\">");
            client.println("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js\"></script>");
            client.println("</head><body><div class=\"container\"><div class=\"row\"><h1>ESP Color Picker</h1></div>");
            client.println("<a class=\"btn btn-primary btn-lg\" href=\"#\" id=\"change_color\" role=\"button\">Change Color</a> ");
            client.println("<input class=\"jscolor {onFineChange:'update(this)'}\" id=\"rgb\"></div>");
            client.println("<script>function update(picker) {document.getElementById('rgb').innerHTML = Math.round(picker.rgb[0]) + ', ' +  Math.round(picker.rgb[1]) + ', ' + Math.round(picker.rgb[2]);");
            client.println("document.getElementById(\"change_color\").href=\"?r\" + Math.round(picker.rgb[0]) + \"g\" +  Math.round(picker.rgb[1]) + \"b\" + Math.round(picker.rgb[2]) + \"&\";}</script></body></html>");
            // The HTTP response ends with another blank line
            client.println();

            // Request sample: /?r201g32b255&
            // Red = 201 | Green = 32 | Blue = 255
            if(header.indexOf("GET /?r") >= 0) {
              pos1 = header.indexOf('r');
              pos2 = header.indexOf('g');
              pos3 = header.indexOf('b');
              pos4 = header.indexOf('&');
              redString = header.substring(pos1+1, pos2);
              greenString = header.substring(pos2+1, pos3);
              blueString = header.substring(pos3+1, pos4);
              /*Serial.println(redString.toInt());
              Serial.println(greenString.toInt());
              Serial.println(blueString.toInt());*/
              ledcWrite(redChannel, redString.toInt());
              ledcWrite(greenChannel, greenString.toInt());
              ledcWrite(blueChannel, blueString.toInt());
            }
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}
1

Nếu bạn đã từng xây dựng một Web Server trên ESP32 thì bạn sẽ quá quen thuộc với chương trình này, chỉ khác một cái là chúng ta sử dụng công cụ Picker Color và gửi yêu cầu HTTP có chứa mã màu.

ESP8266

Nếu sử dụng ESP8266, bạn cũng nhớ cài tiện ích ESP8622 trong Arduino IDE nhé! Sau đó, bạn hãy thay đổi thông tin mạng WiFi của đoạn code sau và nạp vào mạch:

/*********
  Rui Santos
  Complete project details at http://randomnerdtutorials.com  
*********/

// Load Wi-Fi library
#include <ESP8266WiFi.h>

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Set web server port number to 80
WiFiServer server(80);

// Decode HTTP GET value
String redString = "0";
String greenString = "0";
String blueString = "0";
int pos1 = 0;
int pos2 = 0;
int pos3 = 0;
int pos4 = 0;

// Variable to store the HTTP req  uest
String header;

// Red, green, and blue pins for PWM control
const int redPin = 13;     // 13 corresponds to GPIO13
const int greenPin = 12;   // 12 corresponds to GPIO12
const int bluePin = 14;    // 14 corresponds to GPIO14

// Setting PWM bit resolution
const int resolution = 256;

// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

void setup() {
  Serial.begin(115200);
  
  // configure LED PWM resolution/range and set pins to LOW
  analogWriteRange(resolution);
  analogWrite(redPin, 0);
  analogWrite(greenPin, 0);
  analogWrite(bluePin, 0);
  
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected() && currentTime - previousTime <= timeoutTime) {            // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
                   
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\">");
            client.println("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js\"></script>");
            client.println("</head><body><div class=\"container\"><div class=\"row\"><h1>ESP Color Picker</h1></div>");
            client.println("<a class=\"btn btn-primary btn-lg\" href=\"#\" id=\"change_color\" role=\"button\">Change Color</a> ");
            client.println("<input class=\"jscolor {onFineChange:'update(this)'}\" id=\"rgb\"></div>");
            client.println("<script>function update(picker) {document.getElementById('rgb').innerHTML = Math.round(picker.rgb[0]) + ', ' +  Math.round(picker.rgb[1]) + ', ' + Math.round(picker.rgb[2]);");
            client.println("document.getElementById(\"change_color\").href=\"?r\" + Math.round(picker.rgb[0]) + \"g\" +  Math.round(picker.rgb[1]) + \"b\" + Math.round(picker.rgb[2]) + \"&\";}</script></body></html>");
            // The HTTP response ends with another blank line
            client.println();

            // Request sample: /?r201g32b255&
            // Red = 201 | Green = 32 | Blue = 255
            if(header.indexOf("GET /?r") >= 0) {
              pos1 = header.indexOf('r');
              pos2 = header.indexOf('g');
              pos3 = header.indexOf('b');
              pos4 = header.indexOf('&');
              redString = header.substring(pos1+1, pos2);
              greenString = header.substring(pos2+1, pos3);
              blueString = header.substring(pos3+1, pos4);
              /*Serial.println(redString.toInt());
              Serial.println(greenString.toInt());
              Serial.println(blueString.toInt());*/
              analogWrite(redPin, redString.toInt());
              analogWrite(greenPin, greenString.toInt());
              analogWrite(bluePin, blueString.toInt());
            }
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}
1

Giải thích chương trình

Vì chương trình điều khiển LED RGB trên ESP32 và ESP8266 đều khá giống nhau, nên dưới đây mình chỉ giải thích chung nhé! Có một số phần khác biệt như cách tạo tín hiệu xung PWM và thư viện WiFi thì mình cũng sẽ giải thích chi tiết kèm theo bên dưới.

Tạo thư viện WiFi trong ESP32:

#include <WiFi.h>

Với ESP8266, chúng ta sử dụng thư viện ESP8266WiFi:

#include <ESP8266WiFi.h>

Tạo các dòng lệnh để lưu giá trị của màu đỏ (R-red), xanh lá (G-green) và xanh dương (B-blue) theo yêu cầu:

String redString = "0";
String greenString = "0";
String blueString = "0";

Tạo 4 biến khác nhau để giải mã yêu cầu HTTP:

int pos1 = 0;
int pos2 = 0;
int pos3 = 0;
int pos4 = 0;

Tạo 3 biến cho các cổng GPIO để kiểm soát giá trị của giá trị R, G, B của đèn LED RGB ESP32. Mình đang dùng cổng GPIO 13 (red), 12 (green) và 14 (blue) nên chương trình như sau:

const int redPin = 13;     
const int greenPin = 12;  
const int bluePin = 14; 

Xuất tín hiệu PWM với tần số 5000Hz, đồng thời liên kết các kênh PWM cho từng màu riêng biệt (lưu ý với mạch ESP8266 thì chúng ta không cần liên kết);

const int freq = 5000;
const int redChannel = 0;
const int greenChannel = 1;
const int blueChannel = 2;

Cấu hình độ phân giải của xung tín hiệu PWM thành 8 bit (điều này cũng không cần thiết trong ESP8266):

const int resolution = 8;

Trong setting(), gán thuộc tính PWM cho các kênh PWM (không cần thiết trong ESP8266)

ledcSetup(redChannel, freq, resolution);
ledcSetup(greenChannel, freq, resolution);
ledcSetup(blueChannel, freq, resolution);

Gắn PWM vào các chân GPIO tương ứng (không cần thiết trong ESP8266):

ledcAttachPin(redPin, redChannel);
ledcAttachPin(greenPin, greenChannel);
ledcAttachPin(bluePin, blueChannel);

Hiển thị bảng màu Color Picker để người dùng thay đổi màu sắc LED RGB ESP32 tùy thích theo nhu cầu, sau đó gửi yêu cầu HTTP:

client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\">");
client.println("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js\"></script>");
client.println("</head><body><div class=\"container\"><div class=\"row\"><h1>ESP Color Picker</h1></div>");
client.println("<a class=\"btn btn-primary btn-lg\" href=\"#\" id=\"change_color\" role=\"button\">Change Color</a> ");
client.println("<input class=\"jscolor {onFineChange:'update(this)'}\" id=\"rgb\"></div>");
client.println("<script>function update(picker) {document.getElementById('rgb').innerHTML = Math.round(picker.rgb[0]) + ', ' +  Math.round(picker.rgb[1]) + ', ' + Math.round(picker.rgb[2]);");        client.println("document.getElementById(\"change_color\").href=\"?r\" + Math.round(picker.rgb[0]) + \"g\" +  Math.round(picker.rgb[1]) + \"b\" + Math.round(picker.rgb[2]) + \"&\";}</script></body></html>");
// The HTTP response ends with another blank line
client.println();

Sau khi LED RGB ESP32 được chọn một màu bất kỳ, mạch ESP32 sẽ nhận được một yêu cầu theo định dạng như bên dưới:

/?r201g32255&b

Khi đó, ESP32 cần phân tách yêu cầu để lấy mã màu R, G, B tương ứng nhằm điều khiển đèn LED RGB:

pos1 = header.indexOf('r');
pos2 = header.indexOf('g');
pos3 = header.indexOf('b');
pos4 = header.indexOf('&');
redString = header.substring(pos1+1, pos2);
greenString = header.substring(pos2+1, pos3);
blueString = header.substring(pos3+1, pos4);

Xuất xung tín hiệu PWM để điều khiển đèn LED RGB ESP32 đổi màu theo yêu cầu từ HTTP:

ledcWrite(redChannel, redString.toInt());
ledcWrite(greenChannel, greenString.toInt());
ledcWrite(blueChannel, blueString.toInt());

Còn riêng với ESP8266, để điều khiển đèn LED RGB qua Web Server, bạn chỉ cần sử dụng analogWrite() để tạo xung tín hiệu PWM và giải mã yêu cầu HTTP:

analogWrite(redPin, redString.toInt());
analogWrite(greenPin, greenString.toInt());
analogWrite(bluePin, blueString.toInt())

toInt giúp chúng ta đổi giá trị trong biến chuỗi (String) thành một số nguyên mà EPS32 có thể hiểu được.

Kết quả

Sau khi thay đổi mạng thông tin WiFi, bạn hãy nạp chương trình vào mạch ESP32 và mở Serial Monitor lên nhé (ở tốc độ 115200). Sau đó, bạn hãy reset lại mạch để lấy địa chỉ IP của mạch:

Lấy địa chỉ IP của mạch ESP32

Sau đó, bạn hãy copy địa chỉ IP này và paste vào trình duyệt Google Chrome/ Cốc Cốc,… trên điện thoại hoặc máy tính. Một Web Server sẽ hiện ra như hình dưới, để bạn điều chỉnh màu đèn LED RGB ESP32:

Điều khiển đổi màu LED RGB ESP32
Điều khiển đổi màu LED RGB ESP32

Bạn có thể thay đổi màu sắc và nhấn nút Change Color để xem hiệu quả nhé:

Thử nghiệm điều khiển LED RGB ESP32
Thử nghiệm điều khiển LED RGB ESP32

Để tắt đèn LED, bạn chỉ cần bật màu đen. Khi bạn chọn cường độ màu mạnh nhất nằm ở trên cùng của Color Picker như hình dưới, đèn LED sẽ sáng hơn:

Điều khiển Color Picker để đổi màu LED RGB ESP32

Bạn hãy thử trang trí dãy đèn này ở bất kỳ nơi nào trong nhà mà bạn thích, chẳng hạn như dưới gầm bàn gaming, dưới gầm giường, viền tivi,… tùy thích nhé!

Lời kết

Trong bài viết này, IoTZone đã hướng dẫn bạn cách điều khiển LED RGB ESP32 thông qua Web Server. Bạn có thể dùng điện thoại, máy tính,… hoặc thiết bị điện tử nào có thể truy cập Internet để điều khiển đổi màu đèn LED nhé! Chúc các bạn thành công.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *