ESP32多传感器数据采集与传输系统
IOT——VIP
- 系统概述
 - 硬件介绍
 - 主要组件清单
 - 硬件连接原理
 
- 软件介绍
 - 开发环境配置
 - 项目结构
 - 核心代码实现
 
- 示例演示
 - Web界面访问
 - RS485数据格式示例
 - API接口调用
 
- 优化建议
 - 1. 硬件优化
 - 2. 软件优化
 - 3. 功能扩展
 - 4. 部署建议
 
系统概述
本项目基于ESP32开发板,集成温湿度、气压和GPS传感器,实现环境数据的实时采集。系统支持两种数据传输方式:通过WiFi在局域网内分享数据,以及通过RS485有线接口传输给其他单片机设备。采用模块化设计,便于维护和扩展。
硬件介绍
主要组件清单
| 组件 | 型号 | 说明 | 
|---|---|---|
| 主控芯片 | ESP32-WROOM-32 | 双核处理器,集成WiFi和蓝牙 | 
| 环境传感器 | BME280 | 集成温度、湿度、气压测量 | 
| GPS模块 | NEO-6M | 提供地理位置和时间信息 | 
| RS485模块 | MAX485 | TTL转RS485电平转换 | 
| 电源模块 | LM2596 | 5V/3A降压模块 | 
硬件连接原理
ESP32 (3.3V) --- BME280 (VCC)
ESP32 (GND)  --- BME280 (GND)
ESP32 (GPIO21) --- BME280 (SDA)
ESP32 (GPIO22) --- BME280 (SCL)ESP32 (3.3V) --- NEO-6M (VCC)
ESP32 (GND)  --- NEO-6M (GND)
ESP32 (GPIO16) --- NEO-6M (TX)
ESP32 (GPIO17) --- NEO-6M (RX)ESP32 (5V)   --- MAX485 (VCC)
ESP32 (GND)  --- MAX485 (GND)
ESP32 (GPIO4)  --- MAX485 (RO)
ESP32 (GPIO5)  --- MAX485 (DI)
ESP32 (GPIO15) --- MAX485 (DE+RE)
 
软件介绍
开发环境配置
platformio.ini 配置文件
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200lib_deps = adafruit/Adafruit BME280 Library@^2.2.2mikem/Arduino-TinyGPSPlus@^1.0.3bblanchon/ArduinoJson@^6.21.3
 
项目结构
src/
├── main.cpp
├── sensors/
│   ├── BME280Sensor.h
│   ├── GPSSensor.h
│   └── SensorManager.h
├── communication/
│   ├── WebServerManager.h
│   ├── RS485Manager.h
│   └── DataProtocol.h
└── config/└── Config.h
 
核心代码实现
Config.h - 配置文件
#ifndef CONFIG_H
#define CONFIG_H// WiFi配置
const char* WIFI_SSID = "Your_WiFi_SSID";
const char* WIFI_PASSWORD = "Your_WiFi_Password";// 引脚定义
const uint8_t BME280_SDA_PIN = 21;
const uint8_t BME280_SCL_PIN = 22;
const uint8_t GPS_RX_PIN = 16;
const uint8_t GPS_TX_PIN = 17;
const uint8_t RS485_RO_PIN = 4;
const uint8_t RS485_DI_PIN = 5;
const uint8_t RS485_DE_RE_PIN = 15;// 串口配置
const uint32_t GPS_BAUDRATE = 9600;
const uint32_t RS485_BAUDRATE = 9600;// 数据更新间隔(ms)
const uint32_t SENSOR_UPDATE_INTERVAL = 2000;
const uint32_t RS485_SEND_INTERVAL = 1000;#endif
 
DataProtocol.h - 数据结构定义
#ifndef DATA_PROTOCOL_H
#define DATA_PROTOCOL_H#include <Arduino.h>struct SensorData {float temperature;float humidity;float pressure;double latitude;double longitude;String time;int satellites;bool gpsValid;String toJSON() {String json = "{";json += "\"temperature\":" + String(temperature, 1) + ",";json += "\"humidity\":" + String(humidity, 1) + ",";json += "\"pressure\":" + String(pressure, 2) + ",";json += "\"latitude\":" + String(latitude, 6) + ",";json += "\"longitude\":" + String(longitude, 6) + ",";json += "\"time\":\"" + time + "\",";json += "\"satellites\":" + String(satellites) + ",";json += "\"gpsValid\":" + String(gpsValid ? "true" : "false");json += "}";return json;}
};#endif
 
BME280Sensor.h - 环境传感器类
#ifndef BME280_SENSOR_H
#define BME280_SENSOR_H#include <Adafruit_BME280.h>
#include "DataProtocol.h"class BME280Sensor {
private:Adafruit_BME280 bme;bool sensorFound = false;public:bool begin(uint8_t sdaPin, uint8_t sclPin) {Wire.begin(sdaPin, sclPin);sensorFound = bme.begin(0x76);if (!sensorFound) {sensorFound = bme.begin(0x77);}return sensorFound;}bool readData(SensorData& data) {if (!sensorFound) return false;data.temperature = bme.readTemperature();data.humidity = bme.readHumidity();data.pressure = bme.readPressure() / 100.0F;return !isnan(data.temperature) && !isnan(data.humidity) && !isnan(data.pressure);}
};#endif
 
GPSSensor.h - GPS传感器类
#ifndef GPS_SENSOR_H
#define GPS_SENSOR_H#include <TinyGPSPlus.h>
#include <HardwareSerial.h>
#include "DataProtocol.h"class GPSSensor {
private:TinyGPSPlus gps;HardwareSerial& gpsSerial;public:GPSSensor(HardwareSerial& serial) : gpsSerial(serial) {}void begin(unsigned long baudrate) {gpsSerial.begin(baudrate);}void update() {while (gpsSerial.available() > 0) {gps.encode(gpsSerial.read());}}void readData(SensorData& data) {data.gpsValid = gps.location.isValid() && gps.location.isUpdated();if (data.gpsValid) {data.latitude = gps.location.lat();data.longitude = gps.location.lng();} else {data.latitude = 0.0;data.longitude = 0.0;}if (gps.time.isValid() && gps.time.isUpdated()) {char timeStr[9];snprintf(timeStr, sizeof(timeStr), "%02d:%02d:%02d", gps.time.hour(), gps.time.minute(), gps.time.second());data.time = String(timeStr);}data.satellites = gps.satellites.value();}bool isGPSValid() {return gps.location.isValid();}
};#endif
 
RS485Manager.h - RS485通信管理
#ifndef RS485_MANAGER_H
#define RS485_MANAGER_H#include <HardwareSerial.h>
#include "DataProtocol.h"class RS485Manager {
private:HardwareSerial& rs485Serial;uint8_t deRePin;String createDataPacket(const SensorData& data) {String packet = "$SENSOR,";packet += String(data.temperature, 1) + ",";packet += String(data.humidity, 1) + ",";packet += String(data.pressure, 2) + ",";packet += String(data.latitude, 6) + ",";packet += String(data.longitude, 6) + ",";packet += data.time + ",";packet += String(data.satellites) + ",";packet += String(data.gpsValid ? "1" : "0");packet += "*";// 计算校验和char checksum = 0;for (size_t i = 0; i < packet.length(); i++) {checksum ^= packet[i];}packet += String(checksum, HEX);packet += "\r\n";return packet;}public:RS485Manager(HardwareSerial& serial, uint8_t pin) : rs485Serial(serial), deRePin(pin) {}void begin(unsigned long baudrate) {pinMode(deRePin, OUTPUT);digitalWrite(deRePin, LOW); // 初始化为接收模式rs485Serial.begin(baudrate);}void sendData(const SensorData& data) {digitalWrite(deRePin, HIGH); // 切换到发送模式delay(2);String packet = createDataPacket(data);rs485Serial.print(packet);rs485Serial.flush();delay(2);digitalWrite(deRePin, LOW); // 切换回接收模式Serial.println("[RS485] Sent: " + packet);}
};#endif
 
WebServerManager.h - Web服务器管理
#ifndef WEB_SERVER_MANAGER_H
#define WEB_SERVER_MANAGER_H#include <WebServer.h>
#include <ArduinoJson.h>
#include "DataProtocol.h"class WebServerManager {
private:WebServer server;SensorData* currentData;void handleRoot() {String html = R"(
<!DOCTYPE html>
<html>
<head><title>ESP32 Sensor Data</title><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }.container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; }.card { background: #e3f2fd; padding: 15px; margin: 10px 0; border-radius: 5px; }.gps-valid { color: green; }.gps-invalid { color: red; }</style>
</head>
<body><div class="container"><h1>📊 ESP32 传感器数据监控</h1><div class="card"><h2>🌡️ 环境数据</h2><p><b>温度:</b> <span id="temp">--</span> °C</p><p><b>湿度:</b> <span id="humi">--</span> %</p><p><b>气压:</b> <span id="pres">--</span> hPa</p></div><div class="card"><h2>📍 GPS 数据</h2><p><b>纬度:</b> <span id="lat">--</span></p><p><b>经度:</b> <span id="lng">--</span></p><p><b>时间:</b> <span id="time">--</span></p><p><b>卫星数:</b> <span id="sats">--</span></p><p><b>状态:</b> <span id="gpsStatus">--</span></p></div><button onclick="refreshData()">🔄 刷新数据</button><p>最后更新: <span id="lastUpdate">--</span></p></div><script>function refreshData() {fetch('/data').then(response => response.json()).then(data => {document.getElementById('temp').textContent = data.temperature;document.getElementById('humi').textContent = data.humidity;document.getElementById('pres').textContent = data.pressure;document.getElementById('lat').textContent = data.latitude;document.getElementById('lng').textContent = data.longitude;document.getElementById('time').textContent = data.time;document.getElementById('sats').textContent = data.satellites;const gpsStatus = document.getElementById('gpsStatus');if (data.gpsValid) {gpsStatus.textContent = '✅ 定位有效';gpsStatus.className = 'gps-valid';} else {gpsStatus.textContent = '❌ 等待定位';gpsStatus.className = 'gps-invalid';}document.getElementById('lastUpdate').textContent = new Date().toLocaleString();});}// 页面加载时自动刷新,之后每5秒刷新一次document.addEventListener('DOMContentLoaded', function() {refreshData();setInterval(refreshData, 5000);});</script>
</body>
</html>)";server.send(200, "text/html", html);}void handleData() {String json = currentData->toJSON();server.send(200, "application/json", json);}public:WebServerManager() : server(80) {}void begin(SensorData* data) {currentData = data;server.on("/", [this]() { handleRoot(); });server.on("/data", [this]() { handleData(); });server.begin();Serial.println("[WebServer] HTTP server started");}void handleClient() {server.handleClient();}String getIPAddress() {return WiFi.localIP().toString();}
};#endif
 
main.cpp - 主程序
#include <Arduino.h>
#include <WiFi.h>
#include "config/Config.h"
#include "sensors/BME280Sensor.h"
#include "sensors/GPSSensor.h"
#include "communication/WebServerManager.h"
#include "communication/RS485Manager.h"
#include "communication/DataProtocol.h"// 传感器和管理器对象
BME280Sensor bmeSensor;
GPSSensor gpsSensor(GPSSerial);
WebServerManager webServer;
RS485Manager rs485(RS485Serial, RS485_DE_RE_PIN);// 传感器数据
SensorData sensorData;// 定时器变量
unsigned long lastSensorUpdate = 0;
unsigned long lastRS485Send = 0;void setup() {Serial.begin(115200);// 初始化传感器Serial.println("[System] Initializing sensors...");if (!bmeSensor.begin(BME280_SDA_PIN, BME280_SCL_PIN)) {Serial.println("[BME280] Error: Sensor not found!");} else {Serial.println("[BME280] Sensor initialized successfully");}gpsSensor.begin(GPS_BAUDRATE);Serial.println("[GPS] Sensor initialized successfully");// 初始化RS485rs485.begin(RS485_BAUDRATE);Serial.println("[RS485] Communication initialized");// 连接WiFiSerial.println("[WiFi] Connecting to: " + String(WIFI_SSID));WiFi.begin(WIFI_SSID, WIFI_PASSWORD);while (WiFi.status() != WL_CONNECTED) {delay(1000);Serial.print(".");}Serial.println("\n[WiFi] Connected! IP address: " + WiFi.localIP().toString());// 启动Web服务器webServer.begin(&sensorData);Serial.println("[System] All systems ready!");
}void loop() {// 处理Web客户端请求webServer.handleClient();// 更新GPS数据gpsSensor.update();unsigned long currentMillis = millis();// 定期更新传感器数据if (currentMillis - lastSensorUpdate >= SENSOR_UPDATE_INTERVAL) {lastSensorUpdate = currentMillis;// 读取BME280数据bool envDataValid = bmeSensor.readData(sensorData);// 读取GPS数据gpsSensor.readData(sensorData);if (envDataValid) {Serial.println("[Sensors] Environment data updated");}if (sensorData.gpsValid) {Serial.println("[GPS] GPS data updated - Lat: " + String(sensorData.latitude, 6) + ", Lng: " + String(sensorData.longitude, 6));}}// 定期通过RS485发送数据if (currentMillis - lastRS485Send >= RS485_SEND_INTERVAL) {lastRS485Send = currentMillis;rs485.sendData(sensorData);}
}
 
示例演示
Web界面访问
- 将程序上传到ESP32后,打开串口监视器查看IP地址
 - 在浏览器中输入 
http://[ESP32-IP]访问监控界面 - 界面将显示实时传感器数据并每5秒自动更新
 
RS485数据格式示例
$SENSOR,25.3,60.1,1013.25,22.123456,114.123456,12:34:56,8,1*1A\r\n
 
字段说明:温度,湿度,气压,纬度,经度,时间,卫星数,GPS状态*校验和
API接口调用
# 获取JSON格式数据
curl http://192.168.1.100/data# 返回示例
{"temperature": 25.3,"humidity": 60.1,"pressure": 1013.25,"latitude": 22.123456,"longitude": 114.123456,"time": "12:34:56","satellites": 8,"gpsValid": true
}
 
优化建议
1. 硬件优化
- 电源管理:添加锂电池和充放电管理电路,实现便携使用
 - 信号稳定性:在RS485总线两端添加120Ω终端电阻
 - 防干扰:为GPS模块使用有源天线,提高定位精度
 
2. 软件优化
// 添加数据滤波处理
class DataFilter {
public:static float movingAverage(float newValue, float previousAverage, int count) {return (previousAverage * (count - 1) + newValue) / count;}
};// 添加SD卡数据存储功能
#include <SD.h>
class DataLogger {
public:bool logData(const SensorData& data) {File file = SD.open("/datalog.txt", FILE_APPEND);if (file) {file.println(data.toJSON());file.close();return true;}return false;}
};
 
3. 功能扩展
- MQTT支持:添加物联网平台接入能力
 - OTA更新:支持无线固件更新
 - 数据告警:设置阈值触发通知
 - 历史数据:添加数据趋势图表显示
 
4. 部署建议
- 使用防水外壳保护电子元件
 - 为天线预留合适的位置
 - 考虑散热设计,避免阳光直射
 - 使用工业级连接器提高可靠性
 
这个项目提供了完整的传感器数据采集和传输解决方案,具有良好的可扩展性和稳定性,适合各种物联网应用场景。
