ESP32 物聯網開發實戰:MQTT + LoRa 完整應用
ESP32 是當前最熱門的物聯網開發板,整合 WiFi、藍芽、雙核心處理器,價格親民且功能強大。本文將分享ESP32物聯網開發的完整實戰經驗。
ESP32 核心優勢
硬體規格
CPU: Xtensa LX6 雙核心 @ 240MHz
RAM: 520KB SRAM
Flash: 4MB (可擴展)
WiFi: 802.11 b/g/n (2.4GHz)
藍芽: BLE 4.2 + Classic
GPIO: 34 個可程式化 I/O
ADC: 18 通道 12-bit
DAC: 2 通道 8-bit
觸控感測: 10 個觸控引腳
開發環境設置
# Arduino IDE 方式
# 1. 安裝 Arduino IDE
# 2. 新增 ESP32 開發板管理員網址
# https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
# PlatformIO 方式(推薦)
pip install platformio
# 建立專案
pio project init --board esp32dev
# platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
# 編譯上傳
pio run --target upload
pio device monitor
MQTT 物聯網通訊
基礎 MQTT 實作
// mqtt_basic.ino
#include <WiFi.h>
#include <PubSubClient.h>
// WiFi 設定
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// MQTT 設定
const char* mqtt_server = "mqtt.example.com";
const int mqtt_port = 1883;
const char* mqtt_user = "your_username";
const char* mqtt_password = "your_password";
WiFiClient espClient;
PubSubClient mqtt(espClient);
// 主題定義
const char* TOPIC_STATUS = "sensor/esp32/status";
const char* TOPIC_TEMPERATURE = "sensor/esp32/temperature";
const char* TOPIC_COMMAND = "command/esp32";
void setup() {
Serial.begin(115200);
// 連接 WiFi
setupWiFi();
// 設定 MQTT
mqtt.setServer(mqtt_server, mqtt_port);
mqtt.setCallback(mqttCallback);
// 連接 MQTT
connectMQTT();
}
void setupWiFi() {
Serial.print("連接 WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n✓ WiFi 已連接");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("連接 MQTT Broker...");
String clientId = "ESP32-" + String(WiFi.macAddress());
if (mqtt.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println("✓ MQTT 已連接");
// 訂閱主題
mqtt.subscribe(TOPIC_COMMAND);
// 發送上線通知
mqtt.publish(TOPIC_STATUS, "online");
} else {
Serial.print("✗ 連接失敗, rc=");
Serial.println(mqtt.state());
delay(5000);
}
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("收到訊息 [");
Serial.print(topic);
Serial.print("]: ");
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// 處理命令
if (String(topic) == TOPIC_COMMAND) {
handleCommand(message);
}
}
void handleCommand(String command) {
if (command == "LED_ON") {
digitalWrite(LED_BUILTIN, HIGH);
mqtt.publish(TOPIC_STATUS, "LED: ON");
}
else if (command == "LED_OFF") {
digitalWrite(LED_BUILTIN, LOW);
mqtt.publish(TOPIC_STATUS, "LED: OFF");
}
else if (command == "RESTART") {
ESP.restart();
}
}
void loop() {
// 確保 MQTT 連接
if (!mqtt.connected()) {
connectMQTT();
}
mqtt.loop();
// 每 30 秒發送一次溫度數據
static unsigned long lastSend = 0;
if (millis() - lastSend > 30000) {
float temperature = readTemperature();
char tempString[8];
dtostrf(temperature, 6, 2, tempString);
mqtt.publish(TOPIC_TEMPERATURE, tempString);
lastSend = millis();
}
}
float readTemperature() {
// 這裡使用 DHT22 或其他溫度感測器
return 25.5; // 示例值
}
LoRa 遠距通訊
LoRa 硬體接線
ESP32 LoRa SX1278
─────────────────────
3.3V → VCC
GND → GND
GPIO5 → NSS (CS)
GPIO18 → SCK
GPIO19 → MISO
GPIO23 → MOSI
GPIO2 → RST
GPIO26 → DIO0
LoRa 通訊實作
// lora_communication.ino
#include <SPI.h>
#include <LoRa.h>
#define SS 5
#define RST 2
#define DIO0 26
// LoRa 頻段設定
#define BAND 915E6 // 915MHz for AS923 (台灣)
String deviceId = "NODE_001";
void setup() {
Serial.begin(115200);
// 初始化 LoRa
SPI.begin();
LoRa.setPins(SS, RST, DIO0);
if (!LoRa.begin(BAND)) {
Serial.println("LoRa 初始化失敗!");
while (1);
}
// LoRa 參數配置
LoRa.setSpreadingFactor(7); // 擴頻因子 7-12
LoRa.setSignalBandwidth(125E3); // 帶寬 125KHz
LoRa.setCodingRate4(5); // 編碼率 4/5
LoRa.setTxPower(20); // 發射功率 20dBm
Serial.println("✓ LoRa 已初始化");
}
void loop() {
// 發送數據
sendLoRaData();
// 接收數據
receiveLoRaData();
delay(5000);
}
void sendLoRaData() {
// 讀取感測器
float temperature = 25.5;
float humidity = 60.0;
int battery = 85;
// 建立 JSON 封包
String packet = "{";
packet += "\"id\":\"" + deviceId + "\",";
packet += "\"temp\":" + String(temperature, 2) + ",";
packet += "\"hum\":" + String(humidity, 2) + ",";
packet += "\"bat\":" + String(battery);
packet += "}";
// 發送封包
LoRa.beginPacket();
LoRa.print(packet);
LoRa.endPacket();
Serial.println("📤 發送: " + packet);
}
void receiveLoRaData() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
String received = "";
// 讀取封包
while (LoRa.available()) {
received += (char)LoRa.read();
}
// 取得 RSSI
int rssi = LoRa.packetRssi();
float snr = LoRa.packetSnr();
Serial.println("📥 收到: " + received);
Serial.printf(" RSSI: %d dBm, SNR: %.2f dB\n", rssi, snr);
// 解析並處理數據
parseLoRaData(received);
}
}
void parseLoRaData(String data) {
// 使用 ArduinoJson 解析
// 或簡單的字串處理
}
低功耗設計
// deep_sleep_example.ino
#include "esp_sleep.h"
#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 60 // 60 秒
RTC_DATA_ATTR int bootCount = 0;
void setup() {
Serial.begin(115200);
delay(1000);
// 增加啟動計數
++bootCount;
Serial.println("啟動次數: " + String(bootCount));
// 讀取感測器並發送數據
float data = readSensorAndSend();
// 進入深度睡眠
goToDeepSleep();
}
void loop() {
// 深度睡眠模式不會執行 loop
}
void goToDeepSleep() {
Serial.println("進入深度睡眠 " + String(TIME_TO_SLEEP) + " 秒");
Serial.flush();
// 設定喚醒時間
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
// 或使用外部喚醒
// esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);
// 進入深度睡眠
esp_deep_sleep_start();
}
float readSensorAndSend() {
// 讀取感測器
float value = 25.5;
// 快速連接 WiFi 並發送
quickConnectAndSend(value);
return value;
}
void quickConnectAndSend(float data) {
// 使用靜態 IP 加速連接
IPAddress local_IP(192, 168, 1, 100);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(local_IP, gateway, subnet);
WiFi.begin(ssid, password);
// 等待連接(最多 10 秒)
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 20) {
delay(500);
timeout++;
}
if (WiFi.status() == WL_CONNECTED) {
// 發送數據到伺服器
sendDataToServer(data);
}
WiFi.disconnect(true);
}
OTA 遠端更新
// ota_update.ino
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// 配置 OTA
ArduinoOTA.setHostname("ESP32-OTA");
ArduinoOTA.setPassword("admin123");
ArduinoOTA.onStart([]() {
String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem";
Serial.println("開始 OTA 更新: " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\n✓ OTA 更新完成");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("進度: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("✗ 錯誤[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("認證失敗");
else if (error == OTA_BEGIN_ERROR) Serial.println("開始失敗");
else if (error == OTA_CONNECT_ERROR) Serial.println("連接失敗");
else if (error == OTA_RECEIVE_ERROR) Serial.println("接收失敗");
else if (error == OTA_END_ERROR) Serial.println("結束失敗");
});
ArduinoOTA.begin();
Serial.println("✓ OTA 已啟動");
}
void loop() {
ArduinoOTA.handle();
// 你的程式邏輯...
}
完整專案:環境監測站
// environment_monitor.ino
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_BMP280.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
Adafruit_BMP280 bmp;
WiFiClient espClient;
PubSubClient mqtt(espClient);
struct SensorData {
float temperature;
float humidity;
float pressure;
float altitude;
int airQuality;
unsigned long timestamp;
};
void setup() {
Serial.begin(115200);
// 初始化感測器
dht.begin();
if (!bmp.begin(0x76)) {
Serial.println("BMP280 初始化失敗");
}
// WiFi 和 MQTT
setupWiFi();
mqtt.setServer("mqtt.example.com", 1883);
connectMQTT();
}
void loop() {
if (!mqtt.connected()) {
connectMQTT();
}
mqtt.loop();
// 每分鐘讀取一次
static unsigned long lastRead = 0;
if (millis() - lastRead > 60000) {
SensorData data = readAllSensors();
publishSensorData(data);
lastRead = millis();
}
}
SensorData readAllSensors() {
SensorData data;
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.pressure = bmp.readPressure() / 100.0;
data.altitude = bmp.readAltitude(1013.25);
data.airQuality = analogRead(35); // MQ-135
data.timestamp = millis();
return data;
}
void publishSensorData(SensorData data) {
char payload[256];
sprintf(payload,
"{\"temp\":%.2f,\"hum\":%.2f,\"pres\":%.2f,\"alt\":%.2f,\"aqi\":%d}",
data.temperature, data.humidity, data.pressure,
data.altitude, data.airQuality
);
mqtt.publish("sensor/environment", payload);
Serial.println("📤 " + String(payload));
}
總結
ESP32 是物聯網開發的理想選擇,掌握要點:
- WiFi 穩定性 - 實作重連機制
- 低功耗設計 - 深度睡眠與快速喚醒
- 通訊協定 - MQTT 雲端、LoRa 遠距
- OTA 更新 - 遠端維護能力
- 錯誤處理 - 看門狗與異常恢復
在 BASHCAT,我們精通 ESP32 物聯網開發,可協助您快速打造穩定可靠的IoT解決方案。歡迎與我們聯繫!