文章目录
-
- 一、项目概述
-
- 1.1 硬件清单
- 1.2 软件环境
- 1.3 系统整体流程
- 二、硬件连接步骤
-
- 2.1 STM32与ESP8266连接
- 2.2 STM32与DHT11连接
- 2.3 STM32与LED连接
- 2.4 下载电路连接
- 三、ESP8266 AT指令配置(MQTT连接)
-
- 3.1 前期准备
- 3.2 逐行发送AT指令配置MQTT
- 四、STM32程序开发(Keil MDK)
-
- 4.1 新建Keil工程
- 4.2 工程文件结构
- 4.3 核心代码实现
-
- 4.3.1 文件1:usart.c(串口驱动,用于和ESP8266通信)
- 4.3.2 文件2:usart.h(串口头文件)
- 4.3.3 文件3:dht11.c(DHT11温湿度传感器驱动)
- 4.3.4 文件4:dht11.h(DHT11头文件)
- 4.3.5 文件5:led.c(LED驱动)
- 4.3.6 文件6:led.h(LED头文件)
- 4.3.7 文件7:esp8266.c(ESP8266 MQTT通信驱动)
- 4.3.8 文件8:esp8266.h(ESP8266头文件)
- 4.3.9 文件9:main.c(主函数)
- 4.4 编译下载程序
- 五、MQTT服务器部署与测试
-
- 5.1 本地部署EMQ X服务器
- 5.2 测试MQTT通信
- 六、故障排查
-
- 6.1 ESP8266无法连接WiFi
- 6.2 DHT11读取数据失败
- 6.3 MQTT发布数据失败
- 七、功能扩展建议
-
- 总结
一、项目概述
本项目实现基于STM32F103C8T6单片机+ESP8266模块的MQTT物联网远程监控系统,STM32采集温湿度(DHT11)数据,通过ESP8266接入MQTT服务器,实现数据的远程上传;同时可通过MQTT下发指令,控制STM32外接的LED灯,完成双向的物联网交互。
1.1 硬件清单
| STM32F103C8T6最小系统板 | 1 | 主控芯片,采集传感器数据、控制外设 |
| ESP8266-01S模块 | 1 | 无线通信,实现MQTT协议交互 |
| DHT11温湿度传感器 | 1 | 采集环境温湿度数据 |
| LED灯 + 220Ω电阻 | 1 | 远程控制执行器件 |
| 杜邦线 | 若干 | 硬件连接 |
| USB转TTL模块 | 1 | STM32下载程序、ESP8266调试 |
| 5V电源 | 1 | 给整个系统供电 |
1.2 软件环境
- 开发工具:Keil MDK-ARM V5.38
- STM32固件库:STM32F10x_StdPeriph_Lib_V3.5.0
- MQTT服务器:EMQ X(本地部署或使用公共服务器)
- 串口调试工具:SSCOM V5.13.1
- ESP8266固件:AT固件(支持MQTT指令,版本推荐AT_V1.7.4)
1.3 系统整体流程
#mermaid-svg-6LHZMyicNvSIae3d{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6LHZMyicNvSIae3d .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6LHZMyicNvSIae3d .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6LHZMyicNvSIae3d .error-icon{fill:#552222;}#mermaid-svg-6LHZMyicNvSIae3d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6LHZMyicNvSIae3d .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6LHZMyicNvSIae3d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6LHZMyicNvSIae3d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6LHZMyicNvSIae3d .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6LHZMyicNvSIae3d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6LHZMyicNvSIae3d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6LHZMyicNvSIae3d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6LHZMyicNvSIae3d .marker.cross{stroke:#333333;}#mermaid-svg-6LHZMyicNvSIae3d svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6LHZMyicNvSIae3d p{margin:0;}#mermaid-svg-6LHZMyicNvSIae3d .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6LHZMyicNvSIae3d .cluster-label text{fill:#333;}#mermaid-svg-6LHZMyicNvSIae3d .cluster-label span{color:#333;}#mermaid-svg-6LHZMyicNvSIae3d .cluster-label span p{background-color:transparent;}#mermaid-svg-6LHZMyicNvSIae3d .label text,#mermaid-svg-6LHZMyicNvSIae3d span{fill:#333;color:#333;}#mermaid-svg-6LHZMyicNvSIae3d .node rect,#mermaid-svg-6LHZMyicNvSIae3d .node circle,#mermaid-svg-6LHZMyicNvSIae3d .node ellipse,#mermaid-svg-6LHZMyicNvSIae3d .node polygon,#mermaid-svg-6LHZMyicNvSIae3d .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6LHZMyicNvSIae3d .rough-node .label text,#mermaid-svg-6LHZMyicNvSIae3d .node .label text,#mermaid-svg-6LHZMyicNvSIae3d .image-shape .label,#mermaid-svg-6LHZMyicNvSIae3d .icon-shape .label{text-anchor:middle;}#mermaid-svg-6LHZMyicNvSIae3d .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6LHZMyicNvSIae3d .rough-node .label,#mermaid-svg-6LHZMyicNvSIae3d .node .label,#mermaid-svg-6LHZMyicNvSIae3d .image-shape .label,#mermaid-svg-6LHZMyicNvSIae3d .icon-shape .label{text-align:center;}#mermaid-svg-6LHZMyicNvSIae3d .node.clickable{cursor:pointer;}#mermaid-svg-6LHZMyicNvSIae3d .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6LHZMyicNvSIae3d .arrowheadPath{fill:#333333;}#mermaid-svg-6LHZMyicNvSIae3d .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6LHZMyicNvSIae3d .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6LHZMyicNvSIae3d .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LHZMyicNvSIae3d .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6LHZMyicNvSIae3d .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LHZMyicNvSIae3d .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6LHZMyicNvSIae3d .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6LHZMyicNvSIae3d .cluster text{fill:#333;}#mermaid-svg-6LHZMyicNvSIae3d .cluster span{color:#333;}#mermaid-svg-6LHZMyicNvSIae3d div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6LHZMyicNvSIae3d .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6LHZMyicNvSIae3d rect.text{fill:none;stroke-width:0;}#mermaid-svg-6LHZMyicNvSIae3d .icon-shape,#mermaid-svg-6LHZMyicNvSIae3d .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LHZMyicNvSIae3d .icon-shape p,#mermaid-svg-6LHZMyicNvSIae3d .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6LHZMyicNvSIae3d .icon-shape rect,#mermaid-svg-6LHZMyicNvSIae3d .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LHZMyicNvSIae3d .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6LHZMyicNvSIae3d .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6LHZMyicNvSIae3d :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
STM32初始化
DHT11采集温湿度
串口发送数据到ESP8266
ESP8266通过AT指令连接MQTT服务器
ESP8266发布温湿度数据到MQTT主题
MQTT客户端(手机/电脑)
订阅温湿度主题接收数据
发布控制指令到MQTT主题
ESP8266订阅控制主题
ESP8266接收指令并通过串口发送到STM32
STM32解析指令控制LED
二、硬件连接步骤
2.1 STM32与ESP8266连接
| PA9(USART1_TX) | RX | STM32发送数据到ESP8266 |
| PA10(USART1_RX) | TX | STM32接收ESP8266数据 |
| 3.3V | VCC | 供电(ESP8266切勿接5V,会烧毁) |
| GND | GND | 共地 |
| (可选)PA12 | RST | 复位ESP8266(可不接) |
2.2 STM32与DHT11连接
| PB0 | DATA | 数据传输 |
| 5V | VCC | 供电 |
| GND | GND | 共地 |
2.3 STM32与LED连接
| PB1 | LED正极(串220Ω电阻) | 控制LED亮灭 |
| GND | LED负极 | 共地 |
2.4 下载电路连接
| TX | PA10 | 下载程序时的数据接收 |
| RX | PA9 | 下载程序时的数据发送 |
| 5V | 5V | 给STM32供电 |
| GND | GND | 共地 |
| (可选)RTS | NRST | 自动复位(可不接) |
三、ESP8266 AT指令配置(MQTT连接)
3.1 前期准备
- 波特率:115200
- 数据位:8
- 停止位:1
- 校验位:无
- 勾选“发送新行”
3.2 逐行发送AT指令配置MQTT
// 1. 测试ESP8266是否正常
AT
// 响应:OK 表示正常
// 2. 恢复出厂设置
AT+RESTORE
// 响应:OK
// 3. 设置STA模式(连接路由器)
AT+CWMODE=1
// 响应:OK
// 4. 连接WiFi(替换为自己的WiFi名称和密码)
AT+CWJAP="WiFi名称","WiFi密码"
// 响应:WIFI CONNECTED + WIFI GOT IP 表示连接成功
// 5. 开启多连接模式
AT+CIPMUX=1
// 响应:OK
// 6. 设置MQTT服务器(替换为自己的MQTT服务器IP和端口,默认1883)
AT+MQTTUSERCFG=0,1,"NULL","用户名","密码",0,0,""
// 说明:0为连接号,1为MQTT版本,NULL为客户端ID,后接用户名密码(无则填"")
// 7. 连接MQTT服务器(替换为服务器IP)
AT+MQTTCONN=0,"MQTT服务器IP",1883,1
// 响应:MQTTCONN:0,1 表示连接成功
// 8. 订阅控制主题(用于接收STM32控制指令)
AT+MQTTSUB=0,"stm32/control",1
// 说明:0为连接号,stm32/control为订阅主题,1为QoS等级
// 9. 发布测试数据(验证发布功能)
AT+MQTTPUB=0,"stm32/data","test",0,0
// 说明:0为连接号,stm32/data为发布主题,test为数据,0为QoS,0为保留位
四、STM32程序开发(Keil MDK)
4.1 新建Keil工程
4.2 工程文件结构
STM32_MQTT_Project/
├── Core/
│ ├── startup_stm32f10x_md.s
│ ├── stm32f10x_conf.h
│ ├── stm32f10x_it.c
│ ├── stm32f10x_it.h
├── Drivers/
│ ├── DHT11/
│ │ ├── dht11.c
│ │ ├── dht11.h
│ ├── ESP8266/
│ │ ├── esp8266.c
│ │ ├── esp8266.h
│ ├── LED/
│ │ ├── led.c
│ │ ├── led.h
│ ├── USART/
│ │ ├── usart.c
│ │ ├── usart.h
├── main.c
├── main.h
└── STM32F10x_StdPeriph_Driver/(固件库文件)
4.3 核心代码实现
4.3.1 文件1:usart.c(串口驱动,用于和ESP8266通信)
#include "usart.h"
#include "stm32f10x.h"
#include <stdio.h>
// 串口接收缓冲区
u8 USART1_RX_BUF[USART1_REC_LEN];
// 接收状态标记
u16 USART1_RX_STA = 0;
/**
* @brief 初始化USART1串口
* @param bound: 波特率
* @retval 无
*/
void USART1_Init(u32 bound)
{
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
// TX引脚(PA9)推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX引脚(PA10)浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置USART参数
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 4. 配置串口中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 5. 开启串口和接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
/**
* @brief USART1中断服务函数
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
res = USART_ReceiveData(USART1); // 读取接收到的数据
// 接收状态判断:未完成接收且未溢出
if((USART1_RX_STA & 0x8000) == 0)
{
// 接收到第一个字节
if(USART1_RX_STA & 0x4000)
{
if(res != 0x0A) // 不是换行符
{
USART1_RX_BUF[USART1_RX_STA & 0x3FFF] = res;
USART1_RX_STA++;
// 接收缓冲区溢出保护
if(USART1_RX_STA > (USART1_REC_LEN – 1))
{
USART1_RX_STA = 0; // 重置接收状态
}
}
else // 接收到换行符,接收完成
{
USART1_RX_STA |= 0x8000; // 标记接收完成
}
}
else // 等待接收回车符
{
if(res == 0x0D)
{
USART1_RX_STA |= 0x4000; // 标记接收到回车符
}
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志
}
}
/**
* @brief 串口发送一个字节
* @param data: 要发送的字节
* @retval 无
*/
void USART1_SendByte(u8 data)
{
USART_SendData(USART1, data);
// 等待发送完成
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
/**
* @brief 串口发送字符串
* @param str: 要发送的字符串
* @retval 无
*/
void USART1_SendString(u8 *str)
{
while(*str != '\\0')
{
USART1_SendByte(*str);
str++;
}
// 等待所有数据发送完成
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
// 重定向printf到USART1
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (u8)ch);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
4.3.2 文件2:usart.h(串口头文件)
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
// 串口接收缓冲区长度
#define USART1_REC_LEN 200
// 类型定义
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
// 全局变量声明
extern u8 USART1_RX_BUF[USART1_REC_LEN];
extern u16 USART1_RX_STA;
// 函数声明
void USART1_Init(u32 bound);
void USART1_SendByte(u8 data);
void USART1_SendString(u8 *str);
#endif
4.3.3 文件3:dht11.c(DHT11温湿度传感器驱动)
#include "dht11.h"
#include "stm32f10x.h"
#include "delay.h" // 需要自行添加延时函数库
// 定义DHT11数据引脚
#define DHT11_PIN GPIO_Pin_0
#define DHT11_PORT GPIOB
/**
* @brief DHT11引脚初始化(输出模式)
* @param 无
* @retval 无
*/
void DHT11_Mode_Out_PP(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
/**
* @brief DHT11引脚初始化(输入模式)
* @param 无
* @retval 无
*/
void DHT11_Mode_IN_FLOATING(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
/**
* @brief 复位DHT11传感器
* @param 无
* @retval 无
*/
void DHT11_Rst(void)
{
DHT11_Mode_Out_PP(); // 设置为输出模式
DHT11_DQ_OUT=0; // 拉低引脚
delay_ms(20); // 拉低至少18ms
DHT11_DQ_OUT=1; // 释放引脚
delay_us(30); // 拉高20-40us
}
/**
* @brief 等待DHT11响应
* @param 无
* @retval 0: 响应成功 1: 响应失败
*/
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_Mode_IN_FLOATING(); // 设置为输入模式
while(DHT11_DQ_IN && retry<100) // 等待DHT11拉低
{
retry++;
delay_us(1);
}
if(retry>=100) return 1; // 无响应
else retry=0;
while(!DHT11_DQ_IN && retry<100) // 等待DHT11拉高
{
retry++;
delay_us(1);
}
if(retry>=100) return 1; // 响应超时
return 0; // 响应成功
}
/**
* @brief 从DHT11读取一个位
* @param 无
* @retval 读取到的位值
*/
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN && retry<100) // 等待引脚拉低
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN && retry<100) // 等待引脚拉高
{
retry++;
delay_us(1);
}
delay_us(40); // 等待40us
if(DHT11_DQ_IN) return 1; // 高电平表示1
else return 0; // 低电平表示0
}
/**
* @brief 从DHT11读取一个字节
* @param 无
* @retval 读取到的字节值
*/
u8 DHT11_Read_Byte(void)
{
u8 i,dat=0;
for(i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
/**
* @brief 读取DHT11温湿度数据
* @param temp: 温度值指针
* @param humi: 湿度值指针
* @retval 0: 读取成功 1: 读取失败
*/
u8 DHT11_Read_Data(u8 *temp, u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst(); // 复位传感器
if(DHT11_Check()==0) // 响应成功
{
for(i=0;i<5;i++) // 读取40位数据
{
buf[i]=DHT11_Read_Byte();
}
// 校验数据
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi=buf[0]; // 湿度整数部分
*temp=buf[2]; // 温度整数部分
}
return 0;
}
else
{
return 1; // 读取失败
}
}
/**
* @brief DHT11初始化
* @param 无
* @retval 0: 初始化成功 1: 初始化失败
*/
u8 DHT11_Init(void)
{
DHT11_Rst();
return DHT11_Check();
}
4.3.4 文件4:dht11.h(DHT11头文件)
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"
// 引脚操作宏定义
#define DHT11_DQ_OUT PBout(0) // 输出
#define DHT11_DQ_IN PBin(0) // 输入
// 函数声明
u8 DHT11_Init(void);
u8 DHT11_Read_Data(u8 *temp, u8 *humi);
u8 DHT11_Read_Byte(void);
u8 DHT11_Read_Bit(void);
void DHT11_Rst(void);
u8 DHT11_Check(void);
#endif
4.3.5 文件5:led.c(LED驱动)
#include "led.h"
#include "stm32f10x.h"
/**
* @brief LED初始化
* @param 无
* @retval 无
*/
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置PB1引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始状态:LED熄灭
GPIO_SetBits(GPIOB, GPIO_Pin_1);
}
/**
* @brief LED亮
* @param 无
* @retval 无
*/
void LED_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}
/**
* @brief LED灭
* @param 无
* @retval 无
*/
void LED_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_1);
}
/**
* @brief LED状态翻转
* @param 无
* @retval 无
*/
void LED_Toggle(void)
{
if(GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
LED_OFF();
}
else
{
LED_ON();
}
}
4.3.6 文件6:led.h(LED头文件)
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
// 函数声明
void LED_Init(void);
void LED_ON(void);
void LED_OFF(void);
void LED_Toggle(void);
#endif
4.3.7 文件7:esp8266.c(ESP8266 MQTT通信驱动)
#include "esp8266.h"
#include "usart.h"
#include "delay.h"
#include <string.h>
// MQTT配置参数(替换为自己的参数)
#define MQTT_SERVER "192.168.1.100" // MQTT服务器IP
#define MQTT_PORT "1883" // MQTT端口
#define WIFI_SSID "YourWiFiName" // WiFi名称
#define WIFI_PWD "YourWiFiPassword" // WiFi密码
#define MQTT_TOPIC_DATA "stm32/data" // 发布数据主题
#define MQTT_TOPIC_CTRL "stm32/control" // 订阅控制主题
/**
* @brief 向ESP8266发送AT指令并等待响应
* @param cmd: 要发送的指令
* @param res: 期望的响应
* @param time: 等待时间(ms)
* @retval 0: 响应成功 1: 响应失败
*/
u8 ESP8266_SendATCmd(u8 *cmd, u8 *res, u16 time)
{
u8 flag = 0;
USART1_RX_STA = 0; // 清空接收状态
USART1_SendString(cmd); // 发送指令
delay_ms(time); // 等待响应
// 查找响应字符串
if(strstr((char*)USART1_RX_BUF, (char*)res) != NULL)
{
flag = 0;
}
else
{
flag = 1;
}
return flag;
}
/**
* @brief ESP8266初始化(连接WiFi和MQTT服务器)
* @param 无
* @retval 0: 初始化成功 1: 初始化失败
*/
u8 ESP8266_Init(void)
{
delay_ms(1000); // 等待ESP8266上电稳定
// 1. 测试AT指令
if(ESP8266_SendATCmd("AT\\r\\n", "OK", 500))
{
return 1; // AT指令测试失败
}
delay_ms(200);
// 2. 恢复出厂设置
ESP8266_SendATCmd("AT+RESTORE\\r\\n", "OK", 1000);
delay_ms(1000);
// 3. 设置STA模式
if(ESP8266_SendATCmd("AT+CWMODE=1\\r\\n", "OK", 500))
{
return 1; // 设置模式失败
}
delay_ms(200);
// 4. 连接WiFi
u8 cmd[64];
sprintf((char*)cmd, "AT+CWJAP=\\"%s\\",\\"%s\\"\\r\\n", WIFI_SSID, WIFI_PWD);
if(ESP8266_SendATCmd(cmd, "GOT IP", 5000))
{
return 1; // WiFi连接失败
}
delay_ms(1000);
// 5. 开启多连接
if(ESP8266_SendATCmd("AT+CIPMUX=1\\r\\n", "OK", 500))
{
return 1;
}
delay_ms(200);
// 6. 配置MQTT用户参数
if(ESP8266_SendATCmd("AT+MQTTUSERCFG=0,1,\\"NULL\\",\\"\\",\\"\\",0,0,\\"\\"\\r\\n", "OK", 1000))
{
return 1;
}
delay_ms(200);
// 7. 连接MQTT服务器
sprintf((char*)cmd, "AT+MQTTCONN=0,\\"%s\\",%s,1\\r\\n", MQTT_SERVER, MQTT_PORT);
if(ESP8266_SendATCmd(cmd, "CONN", 2000))
{
return 1; // MQTT连接失败
}
delay_ms(200);
// 8. 订阅控制主题
sprintf((char*)cmd, "AT+MQTTSUB=0,\\"%s\\",1\\r\\n", MQTT_TOPIC_CTRL);
if(ESP8266_SendATCmd(cmd, "SUB", 1000))
{
return 1; // 订阅失败
}
delay_ms(200);
return 0; // 初始化成功
}
/**
* @brief 通过MQTT发布温湿度数据
* @param temp: 温度值
* @param humi: 湿度值
* @retval 0: 发布成功 1: 发布失败
*/
u8 ESP8266_MQTT_Publish(u8 temp, u8 humi)
{
u8 cmd[128];
u8 data[32];
// 格式化温湿度数据
sprintf((char*)data, "Temp:%d°C,Humi:%d%%", temp, humi);
// 构建发布指令
sprintf((char*)cmd, "AT+MQTTPUB=0,\\"%s\\",\\"%s\\",0,0\\r\\n", MQTT_TOPIC_DATA, data);
// 发送发布指令
if(ESP8266_SendATCmd(cmd, "PUB", 1000))
{
return 1; // 发布失败
}
return 0;
}
/**
* @brief 解析ESP8266接收到的MQTT控制指令
* @param 无
* @retval 0: 无指令 1: 开灯指令 2: 关灯指令
*/
u8 ESP8266_Parse_Cmd(void)
{
u8 cmd = 0;
// 检查是否接收到控制指令
if(USART1_RX_STA & 0x8000) // 接收完成
{
// 查找开灯指令
if(strstr((char*)USART1_RX_BUF, "ON") != NULL)
{
cmd = 1;
}
// 查找关灯指令
else if(strstr((char*)USART1_RX_BUF, "OFF") != NULL)
{
cmd = 2;
}
USART1_RX_STA = 0; // 清空接收状态
}
return cmd;
}
4.3.8 文件8:esp8266.h(ESP8266头文件)
#ifndef __ESP8266_H
#define __ESP8266_H
#include "usart.h"
// 函数声明
u8 ESP8266_Init(void);
u8 ESP8266_SendATCmd(u8 *cmd, u8 *res, u16 time);
u8 ESP8266_MQTT_Publish(u8 temp, u8 humi);
u8 ESP8266_Parse_Cmd(void);
#endif
4.3.9 文件9:main.c(主函数)
#include "stm32f10x.h"
#include "usart.h"
#include "dht11.h"
#include "led.h"
#include "esp8266.h"
#include "delay.h"
int main(void)
{
u8 temp = 0; // 温度值
u8 humi = 0; // 湿度值
u8 esp_status = 0; // ESP8266状态
u8 cmd = 0; // 控制指令
// 1. 初始化外设
delay_init(); // 延时初始化
USART1_Init(115200); // 串口1初始化(115200波特率)
LED_Init(); // LED初始化
DHT11_Init(); // DHT11初始化
// 2. 初始化ESP8266(连接WiFi和MQTT服务器)
esp_status = ESP8266_Init();
if(esp_status == 0)
{
printf("ESP8266 MQTT Init OK!\\r\\n");
}
else
{
printf("ESP8266 MQTT Init Error!\\r\\n");
}
// 3. 主循环
while(1)
{
// 3.1 读取温湿度数据
if(DHT11_Read_Data(&temp, &humi) == 0)
{
printf("Temp: %d°C, Humi: %d%%\\r\\n", temp, humi);
// 3.2 发布温湿度数据到MQTT服务器
if(ESP8266_MQTT_Publish(temp, humi) == 0)
{
printf("MQTT Publish OK!\\r\\n");
}
else
{
printf("MQTT Publish Error!\\r\\n");
}
}
else
{
printf("DHT11 Read Error!\\r\\n");
}
// 3.3 解析MQTT控制指令
cmd = ESP8266_Parse_Cmd();
switch(cmd)
{
case 1: // 开灯指令
LED_ON();
printf("LED ON!\\r\\n");
break;
case 2: // 关灯指令
LED_OFF();
printf("LED OFF!\\r\\n");
break;
default:
break;
}
// 3.4 延时5秒
delay_ms(5000);
}
}
4.4 编译下载程序
五、MQTT服务器部署与测试
5.1 本地部署EMQ X服务器
- Windows:双击安装包,按提示安装
- Linux:sudo dpkg -i emqx-xxx.deb
- Windows:在安装目录下运行emqx start
- Linux:sudo systemctl start emqx
5.2 测试MQTT通信
- 连接MQTT服务器:填写服务器IP、端口1883
- 订阅主题:stm32/data,可看到STM32上传的温湿度数据
- 发布主题:stm32/control,发布"ON"或"OFF",可控制STM32的LED灯






