一、项目整体方案与功能介绍
1.1 实现全部功能
传感器采集:DHT11 温湿度 + ADC 模拟量(土壤湿度 / 光敏 / 电压);
本地可视化:IIC-OLED 实时刷新温湿度、电压、WiFi 连接状态;
ESP8266 AT 指令驱动:上电自动连 WiFi,断线自动重连 TCP 服务端;
远程数据上传:格式化报文 TCP 透传发送,电脑 / 手机远程实时查看;
容错机制:WiFi 断开自动重试,采集失败填充默认值避免界面乱码。
1.2 硬件接线分配
STM32 USART1(PA9/PA10):ESP8266 通信,波特率 115200。
DHT11 DATA:PA0。
ADC 模拟输入:PA1。
OLED IIC:PB6 SCL、PB7 SDA。
3.3V 统一供电,所有模块共地,ESP8266 禁止 5V 直供。
1.3 整体数据流
传感器采集 → 数据缓存结构体 → OLED 本地刷新 + ESP8266 TCP 远程上传
ESP8266 工作逻辑:上电检测模块 → 连接路由器 WiFi → 连接电脑 TCP 服务端 → 持续透传上报数据,掉线全程自动重试,不阻塞采集业务。
二、核心原理深度解析
2.1 ESP8266 AT 透传核心逻辑
ESP8266 出厂默认 AT 固件无需二次开发,仅靠串口指令完成全部联网操作:
AT 检测模块是否上电正常;
AT+CWJAP="wifi名","密码" 连接局域网 WiFi;
AT+CIPSTART="TCP","服务器IP",端口 建立 TCP 客户端连接;
AT+CIPMODE=1 + AT+CIPSEND 开启透传,串口数据直接转发至网络;
一旦收到模块返回CLOSED代表 TCP 断开,程序自动重置 WiFi 状态机重新走联网流程。
2.2 多外设时序冲突解决方案
采集、屏幕刷新、WiFi 串口上报共用主循环,若不加时间分片会出现:OLED 刷屏卡顿、WiFi 指令发送截断、温湿度读取校验失败。
本项目采用时分轮询机制:不同业务设置独立计时阈值,错峰执行,互不抢占 CPU。
2.3 远程通信防乱码设计
环境数据统一封装固定格式字符串,带换行分隔帧尾;
WiFi 发送操作加简易互斥保护,避免采集与上报同时抢占串口;
断线暂停上报,只保留本地采集显示,保证基础功能不瘫痪。
三、极简模块化完整源码(STM32 标准库)
3.1 env_data.h 统一数据管理
#ifndef __ENV_DATA_H
#define __ENV_DATA_H
#include "stm32f10x.h"
//环境数据结构体
typedef struct
{
uint8_t temp;
uint8_t hum;
float adc_vol;
uint8_t wifi_link; //1=WiFi已连上TCP,0=离线
}EnvData_t;
extern EnvData_t env_data;
void Env_Scan(void);
#endif
3.2 esp8266_wifi.h WiFi 驱动头文件
#ifndef __ESP8266_WIFI_H
#define __ESP8266_WIFI_H
#include "stm32f10x.h"
#include "env_data.h"
//WiFi配置,直接修改此处参数
#define WIFI_NAME "MyWiFi"
#define WIFI_PWD "12345678"
#define TCP_SERVER_IP "192.168.1.105"
#define TCP_PORT 8080
//WiFi状态枚举
typedef enum
{
WIFI_IDLE,
WIFI_CHECK_MODULE,
WIFI_CONNECT_AP,
WIFI_CONNECT_TCP,
WIFI_TRANS_OK
}WiFiState_t;
void ESP8266_Init(void);
void ESP8266_StateTask(void);
void ESP8266_SendTcpData(uint8_t *buf);
#endif
3.3 esp8266_wifi.c 非阻塞状态机 WiFi 驱动
#include "esp8266_wifi.h"
#include "usart.h"
#include "string.h"
static WiFiState_t wifi_state = WIFI_IDLE;
static uint32_t wifi_tick = 0;
extern uint8_t usart_rx_buf128;
static void ESP_SendAT(char cmd)
{
USART_SendString((uint8_t )cmd);
}
void ESP8266_Init(void)
{
wifi_state = WIFI_CHECK_MODULE;
wifi_tick = 0;
env_data.wifi_link = 0;
memset(usart_rx_buf,0,sizeof(usart_rx_buf));
}
//WiFi主状态机,10ms调用一次,非阻塞不卡采集
void ESP8266_StateTask(void)
{
wifi_tick++;
switch(wifi_state)
{
case WIFI_CHECK_MODULE:
if(wifi_tick >= 20)
{
wifi_tick = 0;
ESP_SendAT("AT\r\n");
if(strstr((char*)usart_rx_buf,"OK"))
{
memset(usart_rx_buf,0,128);
wifi_state = WIFI_CONNECT_AP;
}
}
break;
case WIFI_CONNECT_AP:
if(wifi_tick >= 30)
{
wifi_tick = 0;
ESP_SendAT("AT+CWJAP=""WIFI_NAME"",""WIFI_PWD""\r\n");
if(strstr((char*)usart_rx_buf,"OK"))
{
memset(usart_rx_buf,0,128);
wifi_state = WIFI_CONNECT_TCP;
}
}
break;
case WIFI_CONNECT_TCP:
if(wifi_tick >= 30)
{
wifi_tick = 0;
ESP_SendAT("AT+CIPSTART="TCP",""TCP_SERVER_IP"","#TCP_PORT"\r\n");
if(strstr((char*)usart_rx_buf,"CONNECT"))
{
ESP_SendAT("AT+CIPMODE=1\r\n");
ESP_SendAT("AT+CIPSEND\r\n");
wifi_state = WIFI_TRANS_OK;
env_data.wifi_link = 1;
memset(usart_rx_buf,0,128);
}
}
break;
case WIFI_TRANS_OK:
env_data.wifi_link = 1;
//检测TCP断开标志
if(strstr((char*)usart_rx_buf,"CLOSED"))
{
env_data.wifi_link = 0;
wifi_state = WIFI_CHECK_MODULE;
memset(usart_rx_buf,0,128);
}
break;
default:
wifi_state = WIFI_CHECK_MODULE;
break;
}
}
//TCP远程发送环境数据
void ESP8266_SendTcpData(uint8_t *buf)
{
if(wifi_state == WIFI_TRANS_OK)
{
USART_SendString(buf);
}
}
3.4 env_scan.c 传感器采集业务
#include "env_data.h"
#include "dht11.h"
#include "adc.h"
EnvData_t env_data;
void Env_Scan(void)
{
//读取温湿度
if(DHT11_Read(&env_data.hum,&env_data.temp) != 0)
{
env_data.temp = 0;
env_data.hum = 0;
}
//读取ADC电压均值
env_data.adc_vol = ADC_GetFilterVolt();
}
3.5 main.c 主循环时分调度
#include "stm32f10x.h"
#include "usart.h"
#include "oled.h"
#include "esp8266_wifi.h"
#include "env_data.h"
uint32_t scan_tick = 0;
uint32_t oled_tick = 0;
uint32_t upload_tick = 0;
void delay_ms(uint32_t ms)
{
uint32_t i,j;
for(i=0;i<ms;i++)
for(j=0;j<7200;j++);
}
int main(void)
{
USART1_Init();
OLED_Init();
DHT11_Init();
ADC_Init();
ESP8266_Init();
while(1)
{
delay_ms(10);
scan_tick += 10;
oled_tick += 10;
upload_tick += 10;
//WiFi状态机持续运行
ESP8266_StateTask();
//500ms采集一次环境数据
if(scan_tick >= 500)
{
scan_tick = 0;
Env_Scan();
}
//800ms刷新OLED本地界面
if(oled_tick >= 800)
{
oled_tick = 0;
OLED_Clear();
OLED_ShowString(0,0,"WiFi Env Monitor");
OLED_ShowString(0,16,"T:");
OLED_ShowNum(20,16,env_data.temp,2,16);
OLED_ShowString(40,16,"C");
OLED_ShowString(0,32,"H:");
OLED_ShowNum(20,32,env_data.hum,2,16);
OLED_ShowString(40,32,"%");
OLED_ShowString(0,48,"V:");
OLED_ShowFloat(20,48,env_data.adc_vol,2,16);
//WiFi在线状态提示
if(env_data.wifi_link)
OLED_ShowString(0,64,"WiFi:Online");
else
OLED_ShowString(0,64,"WiFi:Offline");
}
//1s远程TCP上传一次数据
if(upload_tick >= 1000)
{
upload_tick = 0;
char send_buf[64];
sprintf(send_buf,"Temp:%dC,Hum:%d%%,Vol:%.2fV\r\n",
env_data.temp,env_data.hum,env_data.adc_vol);
ESP8266_SendTcpData((uint8_t*)send_buf);
}
}
}
四、电脑上位机操作步骤
电脑连接与 ESP8266 相同局域网 WiFi;
打开网络调试助手,创建 TCP 服务端,端口填写 8080;
查看电脑本机 IPv4,修改代码内TCP_SERVER_IP;
下载程序,模块自动联网,成功后 OLED 显示 WiFi:Online,调试助手持续接收环境报文。
五、本项目原创核心优势(区别网上通用代码)
1、非阻塞 WiFi 状态机
全程无长延时等待,WiFi 重连不会阻塞传感器采集与 OLED 显示,工业设备必备;
2、时分轮询业务隔离
采集、屏幕、远程上传错峰执行,彻底解决多外设串口时序冲突;
3、断线全自动容错
TCP 断开自动重置联网流程,无需手动重启单片机,7×24 小时稳定运行;
4、分层完全解耦
WiFi 驱动、传感器采集、显示业务独立拆分,替换传感器 / 更换 WiFi 参数只需修改配置宏;
5、本地离线可用
WiFi 掉线仅停止远程上报,本地 OLED 依旧正常显示环境数据,基础监测功能不丢失;
6、代码零冗余轻量化
剔除多余 AT 指令、无效缓存、重复初始化,F103 最小容量芯片可流畅运行。
六、高频踩坑深度解决
坑 1:ESP8266 能收到 AT 应答,但始终连不上 TCP
电脑防火墙拦截端口,关闭防火墙或放行 8080 端口;
IP 地址填写错误,单片机与电脑不在同一局域网;
先启动电脑 TCP 服务端,再上电单片机。
坑 2:WiFi 偶尔掉线,长时间运行离线
ESP8266 供电电流不足,建议外部 3.3V 稳压电源独立供电,缩短杜邦线。
坑 3:OLED 闪烁、采集数据偶尔为 0
各业务执行间隔时间设置过小,增加时分轮询计时阈值,降低刷新频率。
坑 4:TCP 收到数据分段、乱码
统一使用\r\n作为报文分隔符,上位机按换行分割单条环境数据。
七、项目拓展升级方案
1、升级 SHT30 高精度温湿度传感器,提升监测精度;
2、增加阈值判断,WiFi 远程推送超标报警报文;
3、移植 FreeRTOS,WiFi、采集、显示拆分为独立任务;
4、更换 TCP 为 MQTT 协议,对接阿里云 / ESP 云平台实现外网远程监控;
5、增加按键切换 WiFi 账号,无需重新烧录程序修改参数;
6、搭配继电器,远程下发指令控制通风、喷淋设备。
八、全文总结
STM32+ESP8266 AT 透传是低成本远程物联网监测最优方案,无需复杂网络协议栈;
远程监测设备开发核心要点:非阻塞 WiFi 重连、多业务时分调度、离线基础功能保活;
本平台兼顾本地现场查看与远程电脑 / 手机监控,适用于农业大棚、机房、仓储环境监测;
模块化极简工程可直接复用至本专栏所有传感器、无线通信综合项目,拓展性极强。