IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

(Chia sẻ kinh nghiệm) ESP32-C3 SuperMini rất nóng khi gửi MQTT liên tục qua Wi-Fi: nguyên nhân thường gặp & cách tối ưu

Nếu bạn dùng ESP32-C3 SuperMini và đang chạy kiểu “connect Wi-Fi → retry MQTT đơn giản → cứ 60s publish”, việc board ấm lên là bình thường vì Wi-Fi tiêu thụ dòng cao. Nhưng nếu nóng rõ rệt, thường là do reconnect quá nhiều, Wi-Fi phát công suất cao, CPU chạy bận liên tục, hoặc nguồn/LDO nóng.

Bài này giúp bạn xử lý theo từng case và nâng dần lên mức ổn định – mát – ít hao điện, phù hợp cho dự án IoT chạy dài ngày.

1) Hiểu đúng: ESP32-C3 nóng ở đâu?

Trước khi tối ưu, cần xác định “nóng do chip ESP32 hay do nguồn (LDO/regulator)”:

  • Nóng quanh chip ESP32 (khu vực anten/Wi-Fi): thường do Wi-Fi TX cao, reconnect nhiều, RF hoạt động liên tục.
  • Nóng gần cổng 5V/USB hoặc IC nguồn nhỏ: thường do LDO hạ áp 5V→3.3V bị tải nặng, dây/nguồn kém, sụt áp.

✅ Mẹo nhanh: đổi adapter 5V 2A tốt + cáp USB ngắn. Nếu giảm nóng rõ rệt → nguyên nhân nghiêng về nguồn/cáp/LDO.

2) Bảng chẩn đoán nhanh theo triệu chứng

Tình huống → nguyên nhân → hướng xử lý

Triệu chứngNguyên nhân hay gặpCách xử lý ưu tiên
Nóng mạnh ngay cả khi chỉ publish 60s/lầnWi-Fi TX power cao / RSSI yếuGiảm TX power, cải thiện tín hiệu, đặt router gần hơn
Nóng tăng theo thời gian + thỉnh thoảng lagMQTT reconnect liên tục (handshake nhiều)Giữ kết nối MQTT, backoff retry, tránh connect/disconnect mỗi chu kỳ
CPU nóng + dòng cao dù không publishVòng lặp “busy loop” không delayDùng timer (millis), thêm delay/vTaskDelay, tránh while() bận
Nóng ở khu vực nguồn/regulatorLDO nóng do tải Wi-Fi peak + nguồn kémNguồn 5V tốt, cáp tốt; cân nhắc cấp 3.3V ổn định (nếu hiểu rõ phần cứng)
Gửi QoS cao / payload lớnRetransmit nhiều, thời gian radio dàiDùng QoS0 cho telemetry, giảm payload, gửi theo thay đổi (deadband)
Chạy pin mà nóng/hao pinWi-Fi luôn bậtModem-sleep hoặc deep-sleep giữa các lần gửi

3) Case 1 — Sai lầm phổ biến nhất: bạn đang “reconnect MQTT” mỗi 60s

Rất nhiều code “đơn giản” làm thế này:

  • Mỗi 60s: connectMQTT()publish() → (có khi) disconnect()
  • Hoặc Wi-Fi rớt là lập tức connect lại liên tục, không có nhịp “thở”

Hậu quả: handshake TCP + MQTT + TLS (nếu có) khiến radio hoạt động lâu, dòng tăng vọt → nóng.

Cách đúng

  • Wi-Fi: connect 1 lần, giữ.
  • MQTT: connect 1 lần, giữ.
  • Mỗi 60s chỉ publish().
  • Khi rớt: reconnect theo backoff (2s, 5s, 10s, 30s…), không spam.

4) Case 2 — Wi-Fi phát công suất quá cao (TX Power) / RSSI yếu

Nếu tín hiệu yếu (RSSI ~ -75dBm trở xuống), ESP32 phải phát mạnh hơn và retry nhiều hơn ⇒ nóng + hao điện.

Việc cần làm

  1. In RSSI ra Serial để biết tình trạng:
  • RSSI tốt: -40 ~ -60 dBm
  • Trung bình: -60 ~ -70 dBm
  • Yếu: < -70 dBm
  1. Giảm TX power nếu bạn ở gần router (rất hiệu quả để giảm nóng).

Lưu ý: API có thể khác nhau theo Arduino core. Dưới đây là cách “an toàn” cho ESP32 (C3) theo ESP-IDF API.

#include <esp_wifi.h>

// 8.5 dBm ~ 20, 11 dBm ~ 28, 13 dBm ~ 34, 15 dBm ~ 40, 17 dBm ~ 44, 19.5 dBm ~ 52, 20.5 dBm ~ 56, 21 dBm ~ 60, 22 dBm ~ 66, 24 dBm ~ 72, 25 dBm ~ 78 (đơn vị: 0.25 dBm)
void wifiSetTxPowerQuarterDbm(int8_t qdbm) {
  // qdbm: 8..78 tùy mức, bắt đầu thử 40–52 nếu router gần
  esp_wifi_set_max_tx_power(qdbm);
}

Gợi ý thực tế:

  • Router gần (cùng phòng): thử 40–52
  • Router xa: giữ cao hơn nhưng ưu tiên cải thiện vị trí/anten trước

5) Case 3 — Busy loop làm CPU 100%

Một lỗi âm thầm làm board nóng: loop chạy quá nhanh, gọi check/retry liên tục.

Nguyên tắc

  • Publish theo timer (millis()).
  • Loop vẫn phải có nhịp thở: delay(10) hoặc vTaskDelay().

6) Case 4 — Nguồn & LDO nóng (rất hay gặp trên board nhỏ như SuperMini)

ESP32-C3 Wi-Fi có dòng peak cao. Trên board nhỏ, regulator dễ nóng nếu:

  • adapter yếu / cáp dài
  • tải kéo mạnh khi phát Wi-Fi
  • bạn cấp 5V qua USB nhưng sụt áp

Checklist nguồn

  • ✅ Dùng adapter 5V 2A chất lượng + cáp ngắn
  • ✅ Tránh lấy nguồn từ cổng USB hub yếu
  • ✅ Nếu có thiết bị ngoại vi (relay, cảm biến dòng lớn), tách nguồn

Nếu bạn muốn cấp 3.3V trực tiếp để giảm nhiệt LDO: chỉ làm khi bạn nắm chắc sơ đồ nguồn của board (tránh cấp sai gây hỏng).

7) Tối ưu “chuẩn production”: cấu trúc code theo state machine + backoff

Dưới đây là skeleton (Arduino + PubSubClient) tối ưu cho case bạn đang gặp: connect wifi + retry MQTT chưa tối ưu.

Mục tiêu

  • Không reconnect dồn dập
  • Giữ kết nối ổn định
  • Publish mỗi 60s
  • Tự phục hồi khi Wi-Fi/MQTT rớt
  • Giảm nóng bằng cách giảm tần suất handshake, giảm loop bận
#include <WiFi.h>
#include <PubSubClient.h>
#include <esp_wifi.h>

const char* WIFI_SSID = "your_ssid";
const char* WIFI_PASS = "your_pass";

const char* MQTT_HOST = "broker.yourdomain.com";
const int   MQTT_PORT = 1883;
const char* MQTT_USER = "user";
const char* MQTT_PASSW = "pass";

WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);

unsigned long lastPublishMs = 0;
const unsigned long PUBLISH_INTERVAL_MS = 60UL * 1000UL;

// Backoff cho MQTT reconnect
unsigned long nextMqttRetryMs = 0;
unsigned long mqttRetryDelayMs = 2000;        // start 2s
const unsigned long MQTT_RETRY_MAX_MS = 30000; // cap 30s

// Backoff cho Wi-Fi reconnect (đơn giản)
unsigned long nextWifiRetryMs = 0;
unsigned long wifiRetryDelayMs = 2000;
const unsigned long WIFI_RETRY_MAX_MS = 30000;

String deviceId = "c3_supermini_001";

void wifiSetTxPowerQuarterDbm(int8_t qdbm) {
  // router gần: thử 40–52; xa: tăng dần
  esp_wifi_set_max_tx_power(qdbm);
}

bool wifiEnsureConnected() {
  if (WiFi.status() == WL_CONNECTED) return true;

  unsigned long now = millis();
  if (now < nextWifiRetryMs) return false;

  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  // lên lịch retry tiếp theo
  nextWifiRetryMs = now + wifiRetryDelayMs;
  wifiRetryDelayMs = min(wifiRetryDelayMs * 2, WIFI_RETRY_MAX_MS);

  return false;
}

bool mqttEnsureConnected() {
  if (mqtt.connected()) return true;

  unsigned long now = millis();
  if (now < nextMqttRetryMs) return false;

  // Khi Wi-Fi chưa có, đừng connect MQTT
  if (WiFi.status() != WL_CONNECTED) return false;

  // ClientID nên unique
  String clientId = "iotlabs-" + deviceId + "-" + String((uint32_t)ESP.getEfuseMac(), HEX);

  // LWT (optional): theo dõi online/offline
  String willTopic = "devices/" + deviceId + "/status";
  const char* willMsg = "offline";

  bool ok = mqtt.connect(
    clientId.c_str(),
    MQTT_USER,
    MQTT_PASSW,
    willTopic.c_str(),
    1,      // QoS 1 cho status
    true,   // retained
    willMsg
  );

  if (ok) {
    // reset backoff khi connect ok
    mqttRetryDelayMs = 2000;
    nextMqttRetryMs = 0;

    // Publish online retained
    mqtt.publish(willTopic.c_str(), "online", true);

    // subscribe nếu cần
    // mqtt.subscribe(("devices/" + deviceId + "/cmd").c_str());
    return true;
  }

  // lên lịch retry theo backoff
  nextMqttRetryMs = now + mqttRetryDelayMs;
  mqttRetryDelayMs = min(mqttRetryDelayMs * 2, MQTT_RETRY_MAX_MS);
  return false;
}

void publishTelemetry() {
  // Telemetry thường QoS0 để giảm overhead
  String topic = "devices/" + deviceId + "/telemetry";

  // Payload gọn
  // Nếu có sensor: thay bằng giá trị thật + giới hạn số chữ số
  float temp = 28.3;
  float hum  = 65.1;

  char payload[96];
  snprintf(payload, sizeof(payload), "{\"temp\":%.1f,\"hum\":%.1f}", temp, hum);

  mqtt.publish(topic.c_str(), payload, false); // retained=false
}

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.setSleep(true); // bật sleep nhẹ cho Wi-Fi (modem sleep khi idle, tùy core)
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  // Sau khi Wi-Fi init, set TX power (thử giảm nếu router gần)
  // Giá trị hợp lý để bắt đầu: 52 (~19.5dBm) hoặc thấp hơn nếu router gần
  wifiSetTxPowerQuarterDbm(52);

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setKeepAlive(60); // keepalive 60s, bạn có thể tăng 90-120 nếu mạng ổn
}

void loop() {
  // 1) Ensure Wi-Fi
  wifiEnsureConnected();

  // 2) Ensure MQTT
  mqttEnsureConnected();

  // 3) MQTT loop (chỉ gọi khi có kết nối)
  if (mqtt.connected()) {
    mqtt.loop();
  }

  // 4) Publish theo chu kỳ
  unsigned long now = millis();
  if (mqtt.connected() && now - lastPublishMs >= PUBLISH_INTERVAL_MS) {
    lastPublishMs = now;
    publishTelemetry();
  }

  // 5) Nhịp thở chống busy loop
  delay(10);
}

Vì sao code này “mát” hơn?

  • Không reconnect MQTT liên tục mỗi 60s
  • Có backoff → giảm spam handshake khi mạng lỗi
  • delay(10) → tránh CPU 100%
  • Payload gọn, telemetry QoS0
  • Bật Wi-Fi sleep + tùy chỉnh TX power

8) Tối ưu thêm theo nhu cầu thực tế

A) Gửi theo thay đổi (deadband) thay vì cố định 60s

Ví dụ nhiệt độ thay đổi nhỏ thì không cần gửi → Wi-Fi hoạt động ít hơn.

Logic gợi ý:

  • Chỉ gửi khi abs(temp - lastTemp) >= 0.3 hoặc sau mỗi 5 phút “heartbeat”.

B) Nếu mục tiêu chỉ “cập nhật 60s/lần” và không cần online liên tục: deep sleep

Đây là cách giảm nóng/hao pin mạnh nhất:

  • Wake lên → connect Wi-Fi → publish → sleep 55s

Đổi lại: thời gian connect lại Wi-Fi/MQTT mỗi chu kỳ (nhưng tổng thời gian radio hoạt động vẫn có thể thấp hơn nhiều so với giữ online 24/7 tùy use-case).

C) Tách “status” và “telemetry”

  • status (online/offline): QoS1 + retained (để dashboard biết trạng thái)
  • telemetry: QoS0 + non-retained (nhẹ, mát)

9) Checklist tối ưu nhanh cho ESP32-C3 SuperMini (áp dụng theo thứ tự)

  1. ✅ Không connect/disconnect MQTT theo chu kỳ → giữ kết nối
  2. ✅ Thêm backoff retry cho Wi-Fi/MQTT
  3. ✅ Tránh busy loop → delay(10) / timer publish
  4. ✅ Giảm TX power nếu router gần
  5. ✅ Bật Wi-Fi sleep (modem sleep) nếu phù hợp
  6. ✅ Dùng QoS0 cho telemetry + payload gọn
  7. ✅ Kiểm tra nguồn/cáp; nếu regulator nóng → cải thiện nguồn
  8. ✅ Cân nhắc deep sleep nếu không cần online liên tục