ESP32 Nâng Cao

MQTT, FreeRTOS, Deep Sleep & IoT Platform

1. MQTT Protocol - Giao Thức IoT

MQTT (Message Queuing Telemetry Transport) là giao thức nhẹ, hiệu quả cho IoT. Cần cài thư viện: PubSubClient

MQTT Client - Kết nối Broker
#include <WiFi.h>
#include <PubSubClient.h>

const char* ssid = "TenWiFi";
const char* password = "MatKhau";
const char* mqtt_server = "broker.hivemq.com";  // Public broker

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Nhận tin: [");
  Serial.print(topic);
  Serial.print("] ");
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Đang kết nối MQTT...");
    
    if (client.connect("ESP32Client")) {
      Serial.println("Đã kết nối!");
      client.subscribe("esp32/led");  // Subscribe topic
    } else {
      Serial.print("Lỗi, rc=");
      Serial.print(client.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  
  // Publish dữ liệu mỗi 5 giây
  static unsigned long lastMsg = 0;
  unsigned long now = millis();
  
  if (now - lastMsg > 5000) {
    lastMsg = now;
    
    float temp = random(20, 30);
    char msg[50];
    snprintf(msg, 50, "Nhiệt độ: %.1f°C", temp);
    
    client.publish("esp32/temp", msg);
    Serial.println(msg);
  }
}

2. FreeRTOS - Đa Nhiệm

FreeRTOS cho phép chạy nhiều task đồng thời trên ESP32.

FreeRTOS - Hai Task độc lập
void Task1(void *pvParameters) {
  while(1) {
    Serial.println("Task 1: Đang chạy...");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Task2(void *pvParameters) {
  while(1) {
    Serial.println("Task 2: Đang chạy...");
    vTaskDelay(500 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  
  // Tạo Task 1 trên Core 0
  xTaskCreatePinnedToCore(
    Task1,      // Hàm task
    "Task1",    // Tên task
    10000,      // Stack size
    NULL,       // Tham số
    1,          // Priority
    NULL,       // Task handle
    0);         // Core
  
  // Tạo Task 2 trên Core 1
  xTaskCreatePinnedToCore(Task2, "Task2", 10000, NULL, 1, NULL, 1);
}

void loop() {
  // Loop để trống vì các task độc lập
}

3. Deep Sleep - Tiết Kiệm Pin

Deep Sleep giúp ESP32 tiêu thụ chỉ vài microampere, rất quan trọng cho thiết bị pin.

Deep Sleep với Timer
#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 10  // Ngủ 10 giây

void setup() {
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("ESP32 đã thức dậy!");
  
  // Đọc cảm biến, gửi dữ liệu...
  Serial.println("Đang gửi dữ liệu...");
  delay(2000);
  
  // Cấu hình wakeup sau 10 giây
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  
  Serial.println("Đi ngủ 10 giây...");
  esp_deep_sleep_start();
}

void loop() {
  // Không chạy vào đây
}

Deep Sleep với External Wakeup (nút nhấn)

Wakeup bằng nút nhấn
#define BUTTON_PIN 33

void setup() {
  Serial.begin(115200);
  
  // Kiểm tra nguyên nhân thức dậy
  print_wakeup_reason();
  
  // Cấu hình wakeup bằng nút nhấn
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0);  // LOW = nhấn nút
  
  Serial.println("Đi ngủ. Nhấn nút để đánh thức...");
  delay(1000);
  esp_deep_sleep_start();
}

void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  
  switch(wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0:
      Serial.println("Thức dậy bởi nút nhấn");
      break;
    case ESP_SLEEP_WAKEUP_TIMER:
      Serial.println("Thức dậy bởi timer");
      break;
    default:
      Serial.println("Khởi động lần đầu");
      break;
  }
}

void loop() {}

4. OTA Update - Cập Nhật Từ Xa

OTA (Over-The-Air) cho phép cập nhật firmware ESP32 qua WiFi không cần cáp USB.

OTA Update cơ bản
#include <WiFi.h>
#include <ArduinoOTA.h>

const char* ssid = "TenWiFi";
const char* password = "MatKhau";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("\nĐã kết nối WiFi!");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
  
  // Cấu hình OTA
  ArduinoOTA.setHostname("ESP32-OTA");
  ArduinoOTA.setPassword("admin");  // Optional
  
  ArduinoOTA.onStart([]() {
    Serial.println("Bắt đầu OTA...");
  });
  
  ArduinoOTA.onEnd([]() {
    Serial.println("\nOTA hoàn tất!");
  });
  
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Tiến độ: %u%%\r", (progress / (total / 100)));
  });
  
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Lỗi[%u]: ", error);
  });
  
  ArduinoOTA.begin();
  Serial.println("OTA đã sẵn sàng!");
}

void loop() {
  ArduinoOTA.handle();
  // Code khác...
}
💡 Cách upload OTA: Trong Arduino IDE, chọn Tools → Port → ESP32-OTA at [IP] thay vì cổng COM.

5. NVS - Lưu Dữ Liệu Vĩnh Viễn

NVS (Non-Volatile Storage) cho phép lưu dữ liệu không bị mất khi tắt nguồn.

Lưu và đọc dữ liệu với Preferences
#include <Preferences.h>

Preferences preferences;

void setup() {
  Serial.begin(115200);
  
  // Mở namespace "myapp"
  preferences.begin("myapp", false);
  
  // Đọc counter (mặc định = 0)
  unsigned int counter = preferences.getUInt("counter", 0);
  Serial.printf("Lần khởi động thứ: %u\n", counter);
  
  // Tăng counter và lưu
  counter++;
  preferences.putUInt("counter", counter);
  
  // Lưu các kiểu dữ liệu khác
  preferences.putString("name", "ESP32");
  preferences.putFloat("temp", 25.5);
  preferences.putBool("state", true);
  
  // Đóng
  preferences.end();
}

void loop() {}

6. Tích Hợp IoT Platform

Blynk - App điều khiển IoT

Bước 1: Tải app Blynk trên điện thoại
Bước 2: Tạo project mới, lấy Auth Token
Bước 3: Cài thư viện Blynk trong Arduino IDE
Kết nối Blynk
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>

char auth[] = "YourAuthToken";
char ssid[] = "TenWiFi";
char pass[] = "MatKhau";

void setup() {
  Serial.begin(115200);
  Blynk.begin(auth, ssid, pass);
}

void loop() {
  Blynk.run();
}

ThingSpeak - Lưu dữ liệu đám mây

Gửi dữ liệu lên ThingSpeak
#include <WiFi.h>
#include <HTTPClient.h>

String apiKey = "YourAPIKey";
const char* server = "http://api.thingspeak.com/update";

void sendToThingSpeak(float temp, float humid) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    
    String url = String(server) + "?api_key=" + apiKey + 
                 "&field1=" + String(temp) + 
                 "&field2=" + String(humid);
    
    http.begin(url);
    int httpCode = http.GET();
    
    if (httpCode > 0) {
      Serial.println("Đã gửi dữ liệu!");
    }
    
    http.end();
  }
}

Bài Tập Nâng Cao

  1. Nhà thông minh: Hệ thống điều khiển đèn, quạt qua MQTT + Dashboard
  2. Trạm thời tiết IoT: Đọc nhiều cảm biến, gửi ThingSpeak, Deep Sleep
  3. Camera giám sát: ESP32-CAM stream video qua web + phát hiện chuyển động
  4. Robot tự hành: Điều khiển qua Blynk + tránh vật cản + line following