欢迎光临
我们一直在努力

STM32嵌入式开发:基于ESP8266的MQTT物联网远程监控系统

文章目录

    • 一、项目概述
      • 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连接

STM32引脚ESP8266-01S引脚说明
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连接

STM32引脚DHT11引脚说明
PB0 DATA 数据传输
5V VCC 供电
GND GND 共地

2.3 STM32与LED连接

STM32引脚连接对象说明
PB1 LED正极(串220Ω电阻) 控制LED亮灭
GND LED负极 共地

2.4 下载电路连接

USB转TTL引脚STM32引脚说明
TX PA10 下载程序时的数据接收
RX PA9 下载程序时的数据发送
5V 5V 给STM32供电
GND GND 共地
(可选)RTS NRST 自动复位(可不接)

三、ESP8266 AT指令配置(MQTT连接)

3.1 前期准备

  • 将ESP8266通过USB转TTL连接到电脑,打开串口调试工具(SSCOM):
    • 波特率: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工程

  • 打开Keil MDK,新建工程,选择STM32F103C8T6芯片
  • 添加STM32F10x标准固件库到工程
  • 配置工程:勾选Use MicroLIB,设置堆栈大小,添加头文件路径
  • 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 编译下载程序

  • 在Keil MDK中点击“Build”编译工程,确保无错误
  • 连接STM32最小系统板到电脑,使用下载工具(如ST-Link或J-Link)将编译后的hex文件下载到STM32
  • 下载完成后,给整个系统上电
  • 五、MQTT服务器部署与测试

    5.1 本地部署EMQ X服务器

  • 下载EMQ X:访问EMQ官网(https://www.emqx.io/)下载对应系统版本
  • 安装EMQ X:
    • Windows:双击安装包,按提示安装
    • Linux:sudo dpkg -i emqx-xxx.deb
  • 启动EMQ X:
    • Windows:在安装目录下运行emqx start
    • Linux:sudo systemctl start emqx
  • 访问EMQ X Dashboard:打开浏览器,输入http://服务器IP:18083,默认用户名admin,密码public
  • 5.2 测试MQTT通信

  • 使用MQTT客户端工具(如MQTT.fx、MQTT X):
    • 连接MQTT服务器:填写服务器IP、端口1883
    • 订阅主题:stm32/data,可看到STM32上传的温湿度数据
    • 发布主题:stm32/control,发布"ON"或"OFF",可控制STM32的LED灯
  • 六、故障排查

    6.1 ESP8266无法连接WiFi

  • 检查WiFi名称和密码是否正确(区分大小写)
  • 检查ESP8266供电是否稳定(必须3.3V)
  • 检查STM32与ESP8266的TX/RX引脚是否接反
  • 6.2 DHT11读取数据失败

  • 检查DHT11接线是否正确
  • 确保DHT11有5V供电(3.3V可能不稳定)
  • 检查延时函数是否准确
  • 6.3 MQTT发布数据失败

  • 检查MQTT服务器IP和端口是否正确
  • 确保ESP8266已成功连接MQTT服务器
  • 检查主题名称是否正确
  • 七、功能扩展建议

  • 添加更多传感器(如光照、土壤湿度、气压等)
  • 实现历史数据存储(接入InfluxDB、MySQL等数据库)
  • 开发Web/APP监控界面(使用Vue、React、Flutter等)
  • 添加异常报警功能(短信、邮件、微信推送)
  • 实现设备OTA远程升级
  • 总结

  • 本项目核心是STM32通过串口与ESP8266通信,ESP8266作为MQTT客户端接入物联网平台,实现数据上传和指令接收。
  • 关键代码包含串口驱动(USART)、DHT11传感器驱动、ESP8266 AT指令封装、MQTT发布/订阅逻辑,所有代码均可直接落地使用。
  • 硬件连接需注意ESP8266的3.3V供电、串口TX/RX交叉连接,软件层面需确保AT指令配置正确、MQTT服务器可访问。
  • 赞(0)
    未经允许不得转载:171主机测评 » STM32嵌入式开发:基于ESP8266的MQTT物联网远程监控系统
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址