STM32标准库-ESP8266WIFI模块+DHT11连接华为云上传温湿度数据

一、工程介绍

本工程使用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.协议特点

  1. 基于Publish/Subscribe(发布订阅)模式的物联网通信协议
  2. 简单易实现
  3. 支持Qos(服务质量)
  4. 报文精简
  5. 基于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.打开华为云官网

共建智能世界云底座-华为云华为云提供稳定可靠、安全可信、可持续发展的云服务,致力于让云无处不在,让智能无所不及,共建智能世界云底座。助力企业降本增效,全球300万客户的共同选择。7x24小时专业服务支持,5天内无理由退订,免费快速备案。https://www.huaweicloud.com/

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和烧录工具插上电脑在选择串口,配置波特率并连接

串口助手下载:

GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。https://gitcode.com/open-source-toolkit/35260

2.首次连接会弹出ESP866的相关信息

3.输入 AT + (换行)回车键,点击发送,ESP8266会回复OK

相关AT指令可以参考乐鑫官网:

MQTT AT 命令集 - ESP32 - — ESP-AT 用户指南 latest 文档https://docs.espressif.com/projects/esp-at/zh_CN/latest/esp32/AT_Command_Set/MQTT_AT_Commands.html#mqttnote

(四)工程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相关代码请查看我的另一篇文章:

STM32标准库-DHT11温湿度模块-CSDN博客文章浏览阅读686次,点赞12次,收藏8次。DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。 https://blog.csdn.net/StrayFatCat/article/details/148790318?fromshare=blogdetail&sharetype=blogdetail&sharerId=148790318&sharerefer=PC&sharesource=StrayFatCat&sharefrom=from_link

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);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值