一、工程介绍
本工程使用STM32F103最小系统板、ESP8266-01SWIFI模块、DHT11温湿度传感器和OLED模块,实现实时读取传感器环境温度并通过OLED屏幕显示,随后通过ESP8266WIFI模块上传数据至华为云。
二、工程元器件
以下是本工程所需的元件及数量:
元件名 | 数量 |
STM32F103C8T6最小系统板 | 1 |
DHT11温湿度传感器 | 1 |
ESP8266-01SWIFI模块 | 1 |
0.96寸4针脚OLED显示屏 | 1 |
USB转TTL烧录板(CH340) | 1 |
跳线 | 若干 |
- 工程接线
以下是本项目不同模块对应单片机的接线表格:
DHT11引脚 | 单片机引脚 |
DATA | PB12 |
VCC | 3.3V |
GND | GND |
OLED引脚 | 单片机引脚 |
SCL | PB8 |
SDA | PB9 |
VCC | 3.3V |
GND | GND |
ESP8266引脚 | 单片机引脚 |
TX | PA10 |
RX | PA9 |
VCC | 3.3V |
GND | GND |
三、实物演示
图中第一行为WIFI状态,在初始化时会显示不同的提示,当前显示HUAWEI OK 证明STM32已经连接到华为云服务器,是可以上传数据状态。数据每三秒上次一次华为云。
Temp是DHT11读取到当前环境的温度,Humi是DHT11当前读当前环境的湿度数据。数据每一秒读取一次。
四、模块及协议介绍
ESP8266-01S WIFI模块介绍
1.模块介绍
ESP8266-01S是由安信可科技开发的 Wi-Fi 模块,该模块核心处理器ESP8266封装了超低功耗32位微型MCU,主频支持80MHz和160MHz,支持RTOS实时操作系统,集成Wi-Fi MAC/BB/RF/PA /LNA。
ESP8266的WiFi模块支持标准的IEEE802.11 b/g/n协议,完整的TCP/IP协议。支持作为独立网络控制器(主机),也可作为网络模块(从机)对当前设备进行拓展。
2.模块特点
- 完整的 802.11b/g/n Wi-Fi模块
- 集成 Wi-Fi MAC/ BB/RF/PA/LNA
- 内置超低功耗 32 位微型 MCU,主频支持 80 MHz 和 160 MHz,支持 RTOS
- 内置 1 路 10 bit 高精度 ADC
- 支持 UART/GPIO/PWM 接口
- 支持多种休眠模式, 待机功耗低至 1.0mW
- 串口速率最高可达 4Mbps
- 支持 STA/AP/STA+AP 工作模式
- 用 AT 指令可快速上手
- 支持二次开发, 集成了 Windows、 Linux 开发环境
MQTT协议介绍
1.协议特点
- 基于Publish/Subscribe(发布订阅)模式的物联网通信协议
- 简单易实现
- 支持Qos(服务质量)
- 报文精简
- 基于TCP/IP
2.协议介绍
MQTT(Message Queuing Telemetry Transport, 消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为远程连接设备提过实时可靠的消息服务,作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(loT)。
3.MQTT协议数据包结构
固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识,如连接,发布,订阅,心跳等。其中固定头是必须的,所有类型的MQTT协议中,都必须包含固定头。
可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。可变头部不是可选的意思,而是指这部分在有些协议类型中存在,在有些协议中不存在。
消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。与可变头一样,在有些协议类型中有消息内容,有些协议类型中没有消息内容。
五、工程实现
(一)华为云创建产品
1.打开华为云官网
2.注册华为云账户
3.点击控制台,进入控制台界面
4.进入控制台界面后,点击页面上方的搜索,搜索:设备接入 IoTDA
5.点击购买实例,这里我们使用免费版,不用付费,配置基本不用改
6.购买实例并且创建完成后,点击产品界面,创建产品
填写相关信息,所属空间选择你的默认就行
7.选择设备界面,点击所有设备,点击注册设备
按照要求填写相关信息
8.点击产品,点击详情,选择新增属性,这里的属性就是我们要上传数据的属性,这个可以根据实际需求填写
9.添加属性后,点击设备->所有设备->详情。现在我们查看设备的参数,后面要用。在页面点击查看MQTT链接参数,点击一键复制,然后会自动下载一份TXT文件,里面就有我们要用的参数。
下图为华为云下载的TXT文件,该文件记录的我所需的所有连接信息,请务必妥善保存。图中的username为登录用户名,password为登陆密码,clientId为客户端名,hostname为连接网址,port为连接端口,我们改为1883
到处为止华为云创建工作就结束了
(二)ESP8266刷入MQTT AT透传固件
ESP8266自带8Mbit的Flash芯片,故可以支持刷入不同的固件实现不同的功能。本工程将使用安信可官方提供的MQTT AT透传固件,该固件通过集成MQTT协议实现由ESP8266硬件进行MQTT报文生成,无需使用软件生成MQTT报文。
1.将ESP8266插入烧录板
按照烧录板背面的引脚提示正确插入ESP8266
请一定注意引脚对应,否则会导致烧录失败!
2.打开烧录工具,并选择MQTT AT固件
MQTT AT透传固件下载:
网址: https://docs.ai-thinker.com/%E5%9B%BA%E4%BB%B6%E6%B1%87%E6%80%BB
烧录工具下载:
网址: https://docs.ai-thinker.com/tools
3.连接烧录工具,烧录信息配置,并烧录
显示FINISH表示烧录成功
(三)ESP8266测试
烧录成功后可以进行ESP8266测试判断AT固件是否正确,及ESP8266模块是否正常。
1.打开串口调试助手,先把ESP8266和烧录工具插上电脑在选择串口,配置波特率并连接
串口助手下载:
2.首次连接会弹出ESP866的相关信息
3.输入 AT + (换行)回车键,点击发送,ESP8266会回复OK
相关AT指令可以参考乐鑫官网:
(四)工程AT代码流程
可以复制到本地并替相关数据进行测试
- 测试:
AT
回复:
OK
- 软重启:
AT+RST
回复:
OK
- 关闭回显:
ATE0
回复:
OK
- 设置AP模式:
AT+CWMODE=1
回复:
OK
- 连接WIFI:
AT+CWJAP="Wi-Fi_Name","Wi-Fi_Password"
回复:
WIFI CONNECTED
WIFI GOT IP
OK
- 配置用户信息:
AT+MQTTUSERCFG=0,1,"clientId","username","password",0,0,""
回复:
OK
- 设置连接属性:
AT+MQTTCONNCFG=0,120,0,"","",0,0
回复:
OK
- 连接华为云:
AT+MQTTCONN=0,"5732791758.st1.iotda-device.cn-east-3.myhuaweicloud.com",1883,0
回复:
OK
- 发布主题:
AT+MQTTPUBRAW=0,"$oc/devices/username/sys/properties/report",100,0,0
回复:
OK
>
{"services":[{"service_id":"esp8266","properties":{"Temp":13.45,"Humi":22.22}}]}
回复:
+MQTTPUB:OK
发布主题json代码:
{
"services": [
{
"service_id": "esp8266",
"properties": {
"Temp":13.45,
"Humi":22.22
}
}
]
}
关闭连接:
AT+MQTTCLEAN=0
(五)工程AT代码详解
- AT测试指令:
AT
- AT ESP866软重启:
AT+RST
- AT关闭回显:
ATE0
- AT设置AP模式:
AT+CWMODE=1
- AT配置MQTT用户信息:
AT+MQTTUSERCFG=<LinkID>,<scheme>,<"client_id">,<"username">,<"password">,<cert_key_ID>,<CA_ID>,<"path">
< LinkID>:当前仅支持 link ID 0。
< scheme>:
1: MQTT over TCP;
2: MQTT over TLS(不校验证书);
3: MQTT over TLS(校验 server 证书);
4: MQTT over TLS(提供 client 证书);
5: MQTT over TLS(校验 server 证书并且提供 client 证书);
6: MQTT over WebSocket(基于 TCP);
更多参考官方资料。
< client_id>:MQTT 客户端 ID,最大长度:256 字节。
< username>:用户名,用于登陆 MQTT broker,最大长度:64 字节。
< password>:密码,用于登陆 MQTT broker,最大长度:64 字节。
< cert_key_ID>:证书 ID,目前 ESP-AT 仅支持一套 cert 证书,参数为 0。
< CA_ID>:CA ID,目前 ESP-AT 仅支持一套 CA 证书,参数为 0。
< path>:资源路径,最大长度:32 字节。
- AT设置MQTT连接属性:
AT+MQTTCONNCFG=<LinkID>,<keepalive>,<disable_clean_session>,<"lwt_topic">,<"lwt_msg">,<lwt_qos>,<lwt_retain>
<LinkID>:当前仅支持 link ID 0。
<keepalive>:MQTT ping 超时时间,单位:秒。范围:[0,7200]。默认值:0,会被强制改为 120 秒。
<disable_clean_session>:设置 MQTT 清理会话标志,有关该参数的更多信息请参考 MQTT 3.1.1 协议中的 Clean Session 章节。
0: 使能清理会话
1: 禁用清理会话
<”lwt_topic”>:遗嘱 topic,最大长度:128 字节。
<”lwt_msg”>:遗嘱 message,最大长度:128 字节。
<lwt_qos>:遗嘱 QoS,参数可选 0、1、2,默认值:0。
<lwt_retain>:遗嘱 retain,参数可选 0 或 1,默认值:0。
- AT连接MQTT代理:
AT+MQTTPUBRAW=<LinkID>,<"topic">,<length>,<qos>,<retain>
< LinkID>:当前仅支持 link ID 0。
< host>:MQTT broker 域名,最大长度:128 字节。
< port>:MQTT broker 端口,最大端口:65535。
< reconnect>:
0: MQTT 不自动重连。如果 MQTT 建立连接后又断开,则无法再次使用本命令重新建立连接,您需要先发送 AT+MQTTCLEAN=0 命令清理信息,重新配置参数,再建立新的连接。
1: MQTT 自动重连,会消耗较多的内存资源。
- AT发布MQTT长主题数据:
AT+MQTTPUBRAW=<LinkID>,<"topic">,<length>,<qos>,<retain>
<LinkID>:当前仅支持 link ID 0。
<”topic”>:MQTT topic,最大长度:128 字节。
<length>:MQTT 消息长度,不同 ESP32 设备的最大长度受到可利用内存的限制。
<qos>:发布消息的 QoS,参数可选 0、1、或 2,默认值:0。
<retain>:发布 retain。
回复:
OK
>
则可以发送JSON数据包
传输成功回复:
+MQTTPUB:OK
- AT发送JSON数据包
{"services":[{"service_id":"esp8266","properties":{"Temp":13.45,"Humi":22.22}}]}
正常格式JSON:
{
"services": [
{
"service_id": "esp8266",
"properties": {
"Temp":13.45,
"Humi":22.22
}
}
]
}
- AT断开MQTT连接:
AT+MQTTCLEAN=<LinkID>
<LinkID>:当前仅支持 link ID 0。
六、代码
DHT11相关代码请查看我的另一篇文章:
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_RCC_NVIC_Init(void);
void Timer1_Init(void);
void Timer2_Init(void);
#endif
Timer.c
#include "stm32f10x.h" // Device header
void Timer_RCC_NVIC_Init(void)
{
//打开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
void Timer1_Init(void){
// 选择内部时钟作为中断
TIM_InternalClockConfig(TIM2);
// 初始化定时器配置
TIM_TimeBaseInitTypeDef TIM_TimeInitStruceture;
TIM_TimeInitStruceture.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeInitStruceture.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeInitStruceture.TIM_Period = 10000 - 1; //1s触发一次
TIM_TimeInitStruceture.TIM_Prescaler = 7200 - 1;
TIM_TimeInitStruceture.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeInitStruceture);
/** 清除标志位,解决系统重启后数字从1开始自加 **/
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
// 启动更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void Timer2_Init(void){
// 选择内部时钟作为中断
TIM_InternalClockConfig(TIM3);
// 初始化定时器配置
TIM_TimeBaseInitTypeDef TIM_TimeInitStruceture;
TIM_TimeInitStruceture.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeInitStruceture.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeInitStruceture.TIM_Period = 30000 - 1; //3s触发一次
TIM_TimeInitStruceture.TIM_Prescaler = 7200 - 1;
TIM_TimeInitStruceture.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeInitStruceture);
/** 清除标志位,解决系统重启后数字从1开始自加 **/
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
// 启动更新中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM3,ENABLE);
}
//void TIM2_IRQHandler(void){
//
// //判断标志位
// if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
//
//
// //清除标志位
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}
//void TIM3_IRQHandler(void){
//
// //判断标志位
// if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET){
//
//
// //清除标志位
// TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
// }
//}
MyUSART.h
#ifndef __MYUSART_H
#define __MYUSART_H
#define UART_PORT GPIOA
#define UART_TX GPIO_Pin_9
#define UART_RX GPIO_Pin_10
#define MAX_RXBUFFER_LEN 512 //接收缓冲区数组长度
extern uint8_t RxBuffer[]; //接收字符缓冲区
extern uint8_t RxFlag; //接收标志位
void RxPointer_Setter(uint16_t Num);
uint16_t RxPointer_Getter(void);
void MyUSART_Init(void);
void MyUSART_SendString(uint8_t *String);
#endif
MyUSART.c
#include "stm32f10x.h" // Device header
#include "MyUSART.h"
static uint16_t RxPointer; //接收指针 记录当前字符位置
uint8_t RxBuffer[MAX_RXBUFFER_LEN]={0}; //接收字符缓冲区
uint8_t RxFlag=0; //接收标志位
/**
* @brief 设置RxPointer值函数
* @param Num 整形变量,传入设置RxPointer的值
* @retval 无
*/
void RxPointer_Setter(uint16_t Num)
{
RxPointer = Num;
}
/**
* @brief 获取RxPointer值函数
* @param 无
* @retval RxPointer当前值
*/
uint16_t RxPointer_Getter(void)
{
return RxPointer;
}
/**
* @brief 串口发送数据
* @param String 字符串数组,用于存放要发送的数据
* @retval 无
*/
void MyUSART_SendString(uint8_t *String)
{
for(int i=0;String[i] != '\0';i++)
{
USART_SendData(USART1,String[i]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
}
/**
* @brief USART硬件初始化函数
* @param 无
* @retval 无
*/
void MyUSART_Init(void)
{
//打开gpioa 和usart1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//gpioa初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = UART_TX;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = UART_RX;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART_PORT,&GPIO_InitStructure);
//usart1初始化
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控 不开启
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //usart模式
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字节长度
USART_Init(USART1,&USART_InitStructure);
//启动usart1
USART_Cmd(USART1,ENABLE);
//使能usart1的nvic
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //接收数据寄存器非空时触发
//usart1中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //中断分组
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void)
{
uint8_t RxTemp; //接收缓冲字符
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
RxTemp = USART_ReceiveData(USART1);
// 处理结束符(兼容\r、\n、\r\n)
if(RxTemp == '\n')
{
// 移除前导\r(如果存在)
if(RxPointer > 0 && RxBuffer[RxPointer-1] == '\r')
{
RxPointer--;
}
RxBuffer[RxPointer] = '\0'; // 添加结束符
RxPointer = 0;
RxFlag = 1;
// 注意:不重置RxPointer,等待主程序处理
}
// 存储非换行符
else if(RxTemp != '\r') // 忽略单独的\r
{
RxBuffer[RxPointer++] = RxTemp;
}
// 缓冲区溢出检查
if(RxPointer >= MAX_RXBUFFER_LEN-1)
{
RxBuffer[MAX_RXBUFFER_LEN-1] = '\0';
RxFlag = 1;
RxPointer = 0;
}
//清除中断标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
ESP8266.h
#ifndef __ESP8266_H
#define __ESP8266_H
void ESP8266_GetData(uint8_t *Data);
uint8_t ESP8266_SendCmd(uint8_t *Cmd, uint8_t *Res, uint8_t RetryNum, uint16_t DelayTime);
void ESP8266_Init(void);
uint8_t ESP8266_ConnetWIFI(uint8_t *Name,uint8_t *Pwd);
uint8_t ESP8266_ConfigUserInfo(uint8_t *DevId,uint8_t *username,uint8_t *pwd);
uint8_t ESP8266_ConfigConnectionInfo(uint16_t Maxtime);
uint8_t ESP8266_ConnectNET(uint8_t *TargetUrl,uint16_t port);
uint8_t ESP8266_PublishData(uint8_t *DevId,uint8_t *JsonPackage);
uint8_t ESP8266_CloseConnection(void);
#endif
ESP8266.c
#include "stm32f10x.h"
#include "ESP8266.h"
#include "MyUSART.h"
#include "Delay.h"
#include "OLED.h"
#include "PC_Serial.h"
#include <string.h>
#include <stdio.h>
/**
* @brief 清空 ESP8266 接收缓冲区并重置接收标志
* @note 该函数会清零 RxBuffer 内容、清除接收完成标志 RxFlag,并将接收指针复位
* @param 无
* @retval 无
*/
void ESP8266_Clear(void)
{
memset(RxBuffer,0,MAX_RXBUFFER_LEN);
RxFlag = 0;
RxPointer_Setter(0);
}
/**
* @brief 检查 ESP8266 接收缓冲区中是否包含指定关键字
* @param Res 指向期望匹配的回应关键字字符串(如 "OK"、"ERROR" 等)
* @retval 1 表示缓冲区中包含指定关键字
* 0 表示未检测到关键字
*/
uint8_t ESP8266_CheckRes(uint8_t *Res)
{
return ((strstr((char *)RxBuffer,(char *)Res)) ? 1 : 0);
}
/**
* @brief 等待 ESP8266 返回指定关键字的回应,带超时机制
* @param Res 指向期望匹配的回应关键字字符串(如 "OK"、"ERROR" 等)
* @retval 1 表示在超时前检测到期望回应
* 0 表示超时未收到期望回应
* @note 本函数会轮询 RxFlag,并在每次接收到数据后判断是否包含目标回应。
* 若未匹配,将清除标志继续等待,直到超时。
*/
uint8_t ESP8266_WaitRes(uint8_t *Res)
{
uint16_t Timeout = 50000; // 超时时间,单位为周期
uint16_t delayCount = 0;
while(delayCount < Timeout)
{
if (RxFlag == 1) // 接收到数据
{
if (ESP8266_CheckRes(Res)) // 判断是否包含回应
{
RxFlag = 0; // 重置标志位
return 1; // 成功
}
else
{
RxFlag = 0; // 若数据不符,重置标志位继续等待
}
}
Delay_ms(10); // 简单延时
delayCount++;
}
return 0; // 超时失败
}
/**
* @brief 通过串口发送 JSON 数据到 ESP8266,并等待指定回应,支持重试机制
* @param Json 指向要发送的 JSON 字符串(函数内部自动添加 "\r\n")
* @param Res 指向期望接收到的回应关键字(如 "OK")
* @param RetryNum 最大重试次数
* @param DelayTime 每次重试之间的延时(单位:毫秒)
* @retval 1 表示发送成功并收到正确回应
* 0 表示重试多次后仍未收到回应
* @note 适用于以 AT 指令形式传输 JSON 的场景(如 MQTT 发布封装)
*/
uint8_t ESP8266_SendJSON(uint8_t *Json, uint8_t *Res, uint8_t RetryNum, uint16_t DelayTime)
{
char TxBuf[512];
for(uint8_t i = 0; i < RetryNum; i++)
{
ESP8266_Clear(); // 清空接收缓冲
sprintf(TxBuf, "%s\r\n", Json); // 拼接回车换行
MyUSART_SendString((uint8_t *)TxBuf); // 发送 JSON 数据
if (ESP8266_WaitRes(Res)) // 检查回应
{
return 1; // 成功
}
Delay_ms(DelayTime); // 重试前延时
}
return 0; // 所有尝试失败
}
/**
* @brief 向 ESP8266 发送 AT 指令并等待指定回应,支持多次重试机制
* @param Cmd 指令字符串(需包含 "\r\n" 结尾)
* @param Res 期望收到的回应关键字(如 "OK"、">" 等)
* @param RetryNum 重试次数(例如设置为 3 表示最多尝试发送 3 次)
* @param DelayTime 每次重试之间的延时(单位:毫秒)
* @retval 1 表示指令发送成功并收到期望回应
* 0 表示重试多次后仍未收到正确回应,发送失败
*/
uint8_t ESP8266_SendCmd(uint8_t *Cmd, uint8_t *Res, uint8_t RetryNum, uint16_t DelayTime)
{
const uint8_t Retry = RetryNum;
const uint16_t Delay = DelayTime; // 毫秒
for(uint8_t i = 0; i < Retry; i++)
{
ESP8266_Clear(); // 清空缓冲
MyUSART_SendString(Cmd); // 发送指令
if (ESP8266_WaitRes(Res)) // 等待回应
{
return 1; // 成功
}
// 重试前延时
Delay_ms(Delay);
}
return 0; // 所有尝试失败
}
/**
* @brief 连接指定的 WiFi 网络
* @param Name 指向 WiFi 名称字符串
* @param Pwd 指向 WiFi 密码字符串
* @retval 1 连接成功
* 0 连接失败
* @note 使用带重试的 ESP8266_SendCmd,最多重试3次,每次延时100ms
*/
uint8_t ESP8266_ConnetWIFI(uint8_t *Name, uint8_t *Pwd)
{
char TxBuf[512];
sprintf(TxBuf, "AT+CWJAP=\"%s\",\"%s\"\r\n", Name, Pwd);
if(ESP8266_SendCmd((uint8_t *)TxBuf, "WIFI CONNECTED", 3, 100))
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 配置 MQTT 连接的用户信息
* @param DevId 设备标识(设备名称)
* @param username 设备登录用户名
* @param pwd 设备登录密码
* @retval 1 配置成功
* 0 配置失败
* @note 使用带重试机制的 ESP8266_SendCmd,最多重试3次,每次延时100ms
*/
uint8_t ESP8266_ConfigUserInfo(uint8_t *DevId,uint8_t *username,uint8_t *pwd)
{
char TxBuf[512];
sprintf(TxBuf,"AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n",DevId,username,pwd);
if(ESP8266_SendCmd((uint8_t *)TxBuf,"OK",3,100))
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 配置 MQTT 连接的保活时间等信息
* @param Maxtime 最大保活时间(单位秒)
* @retval 1 配置成功
* 0 配置失败
* @note 使用带重试机制的 ESP8266_SendCmd,最多重试5次,每次延时200ms
*/
uint8_t ESP8266_ConfigConnectionInfo(uint16_t Maxtime)
{
char TxBuf[512];
sprintf(TxBuf,"AT+MQTTCONNCFG=0,%d,0,\"\",\"\",0,0\r\n",Maxtime);
if(ESP8266_SendCmd((uint8_t *)TxBuf,"OK",5,200))
{
return 1;
}
return 0;
}
/**
* @brief 连接到 MQTT 云服务器
* @param TargetUrl 服务器地址(域名或IP)
* @param port 服务器端口号
* @retval 1 连接成功
* 0 连接失败
* @note 使用带重试机制的 ESP8266_SendCmd,最多重试3次,每次延时100ms
*/
uint8_t ESP8266_ConnectNET(uint8_t *TargetUrl,uint16_t port)
{
char TxBuf[512];
sprintf(TxBuf,"AT+MQTTCONN=0,\"%s\",%d,0\r\n",TargetUrl,port);
ESP8266_Clear(); // 清空接收缓冲
if(ESP8266_SendCmd((uint8_t *)TxBuf,"OK",3,100))
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 发布 JSON 消息(使用 RAW 模式),适用于华为云格式
* @param ServeId 设备标识,用于构建主题字符串
* @param JsonPackage 要发送的 JSON 数据(不带回车换行)
* @retval 1 发布成功
* 0 发布失败
* @note 先发送发布命令并等待 '>' 提示符,再发送原始 JSON 数据
*/
uint8_t ESP8266_PublishData(uint8_t *ServeId, uint8_t *JsonPackage)
{
char TxBuf[512];
uint16_t jsonLen = strlen((char *)JsonPackage); //获取JSON长度
//设置payload长度参数为jsonLen
sprintf(TxBuf,
"AT+MQTTPUBRAW=0,\"$oc/devices/%s/sys/properties/report\",%d,0,0\r\n",
ServeId,jsonLen);
//发送AT命令并等待 > 提示符
if(ESP8266_SendCmd((uint8_t *)TxBuf,">",3,100))
{
Delay_ms(10); // 小延迟让ESP8266进入数据接收状态
//发送JSON原始数据 无\r\n
ESP8266_Clear(); //清空接收缓冲
MyUSART_SendString(JsonPackage); //注意 不要加 \r\n
//等待回应
if (ESP8266_WaitRes((uint8_t *)"OK"))
{
return 1;
}
else
{
return 0;
}
}
else
{
return 0;
}
}
/**
* @brief 关闭 ESP8266 MQTT 连接并清理会话
* @retval 1 关闭成功
* 0 关闭失败
* @note 使用带重试机制的 ESP8266_SendCmd,最多重试3次,每次延时100ms
*/
uint8_t ESP8266_CloseConnection(void)
{
if(ESP8266_SendCmd((uint8_t *)"AT+MQTTCLEAN=0\r\n","OK",3,100))
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief ESP8266 初始化函数,完成硬件初始化、模块复位、基本配置
* @param 无
* @retval 无
* @note 包括串口初始化、模块复位、AT指令测试、关闭回显、设置为站点模式(AP模式)
* 每一步命令带重试机制,确保指令执行成功
* OLED 显示各步骤状态信息
*/
void ESP8266_Init(void)
{
MyUSART_Init(); //硬件初始化
Delay_s(1); //启动延迟
ESP8266_Clear();
//esp8266复位
OLED_ShowString(1,1,"Rst...");
while(ESP8266_SendCmd("AT+RST\r\n","OK",3,100) == 0)
Delay_ms(100);
OLED_Clear();
//延迟
Delay_s(2);
//AT指令测试
OLED_ShowString(1,1,"AT...");
while(ESP8266_SendCmd("AT\r\n","OK",3,100) == 0)
Delay_ms(100);
OLED_Clear();
//关闭回显
OLED_ShowString(1,1,"ATE1...");
while(ESP8266_SendCmd("ATE1\r\n","OK",3,100) == 0)
Delay_ms(100);
OLED_Clear();
//设置AP模式
OLED_ShowString(1,1,"AP...");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n","OK",3,100) == 0)
Delay_ms(100);
OLED_Clear();
//初始化完毕
OLED_ShowString(1,1,"esp8266 Init Ok");
}
Main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "ESP8266.h"
#include "DHT11.h"
#include "Timer.h"
#include <string.h>
//Wi-Fi信息
#define WIFI_Name (uint8_t *)""
#define WIFI_Pwd (uint8_t *)""
//华为云信息
#define CloudUrl (uint8_t *)"5732791758.st1.iotda-device.cn-east-3.myhuaweicloud.com"
#define CloudPort (uint16_t)1883
//设备信息
#define ClinetId (uint8_t *)""
#define Username (uint8_t *)""
#define Password (uint8_t *)""
//json数据包
//#define JsonPack (uint8_t *)"{\"services\":[{\"service_id\":\"esp8266\",\"properties\":{\"Temp\":33.45,\"Humi\":66.22}}]}"
#define JsonPack (uint8_t *)"{\"services\":[{\"service_id\":\"esp8266\",\"properties\":{\"Temp\":%d,\"Humi\":%d}}]}"
uint8_t Temp=0.0,Humi=0.0;
uint8_t Ready=0;
int main(void)
{
//外设初始化
Timer_RCC_NVIC_Init();
OLED_Init();
PC_UART_Init();
DHT11_Init();
//定时器初始哈
Timer1_Init();
Timer2_Init();
//DHT11复位
DHT11_Rst();
//esp8266基本初始化
ESP8266_Init();
//连接wifi
OLED_Clear();
OLED_ShowString(1,1,"WIFI...");
while(ESP8266_ConnetWIFI(WIFI_Name,WIFI_Pwd) == 0);
Delay_s(1);
OLED_ShowString(1,1,"WIFI OK");
//配置用户信息
OLED_Clear();
OLED_ShowString(1,1,"UserConfig...");
while(ESP8266_ConfigUserInfo(ClinetId,Username,Password) == 0);
Delay_s(1);
OLED_ShowString(1,1,"UserConfig OK");
//配置连接信息
OLED_Clear();
OLED_ShowString(1,1,"LinkConfig...");
while(ESP8266_ConfigConnectionInfo(120) == 0);
Delay_s(1);
OLED_ShowString(1,1,"LinkConfig OK");
//连接华为云
OLED_Clear();
OLED_ShowString(1,1,"HUAWEI NET...");
while(ESP8266_ConnectNET(CloudUrl,CloudPort) == 0);
Delay_s(1);
OLED_ShowString(1,1,"HUAWEI NET OK");
Ready = 1;
// //关闭连接
// OLED_Clear();
// OLED_ShowString(1,1,"Close...");
// while(ESP8266_CloseConnection() == 0);
// Delay_s(1);
// OLED_ShowString(1,1,"Close OK");
OLED_ShowString(3,1,"Temp:");
OLED_ShowString(4,1,"Humi:");
while (1)
{
OLED_ShowNum(3,6,Temp,2);
OLED_ShowNum(4,6,Humi,2);
}
}
//每秒读取一次数据
void TIM2_IRQHandler(void){
//判断标志位
if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
if(Ready == 1)
{
DHT11_ReadData(&Temp,&Humi);
}
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
//每3秒上次一次数据
void TIM3_IRQHandler(void){
//判断标志位
if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET){
if(Ready == 1)
{
char jsonBuf[256];
sprintf(jsonBuf,(char *)JsonPack,(uint8_t)Temp,(uint8_t)Humi);
//发布主题
ESP8266_PublishData(Username, (uint8_t *)jsonBuf);
}
//清除标志位
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}