【STM32实战】ESP8266 通过 MQTT 协议对接 OneNET 云平台实现数据上传与下发(源码可直接移植)

目录

[1. 简介](#1. 简介)

[2. ESP-01S引脚介绍](#2. ESP-01S引脚介绍)

[3. AT指令](#3. AT指令)

[4. 固件烧录](#4. 固件烧录)

[5. ESP-01S连接OneNET云平台](#5. ESP-01S连接OneNET云平台)

[5.1 设备创建](#5.1 设备创建)

[5.2 信息获取](#5.2 信息获取)

[5.2.1 OneNET信息](#5.2.1 OneNET信息)

[5.2.2 token生成信息](#5.2.2 token生成信息)

[5.3 基础AT指令步骤](#5.3 基础AT指令步骤)

[5.3.1 AT](#5.3.1 AT)

[5.3.2 AT+RST](#5.3.2 AT+RST)

[5.3.3 AT+CWMODE=1](#5.3.3 AT+CWMODE=1)

[5.3.4 AT+CWJAP="名称","密码"](#5.3.4 AT+CWJAP="名称","密码")

[5.4 MQTT指令步骤](#5.4 MQTT指令步骤)

[5.4.1 设置用户属性](#5.4.1 设置用户属性)

[5.4.2 连接OneNET服务器](#5.4.2 连接OneNET服务器)

[5.4.3 订阅主题](#5.4.3 订阅主题)

[5.4.4 发送MQTT数据](#5.4.4 发送MQTT数据)

[5.4.5 云平台数据下发](#5.4.5 云平台数据下发)

[5.4.6 发送API调试回应](#5.4.6 发送API调试回应)

[6. STM32功能实现](#6. STM32功能实现)

[6.1 USART2代码编写](#6.1 USART2代码编写)

[6.2 AT指令发送函数](#6.2 AT指令发送函数)

[6.2.1 AT](#6.2.1 AT)

[6.2.2 AT+RST](#6.2.2 AT+RST)

[6.2.3 AT+CWMODE](#6.2.3 AT+CWMODE)

[6.2.4 AT+CWJAP="名称","密码"](#6.2.4 AT+CWJAP="名称","密码")

[6.3 MQTT指令步骤](#6.3 MQTT指令步骤)

[6.3.1 设置用户属性](#6.3.1 设置用户属性)

[6.3.2 连接OneNET服务器](#6.3.2 连接OneNET服务器)

[6.3.3 订阅主题](#6.3.3 订阅主题)

[6.3.4 发送MQTT数据](#6.3.4 发送MQTT数据)

[6.3.5 接收云平台下发的数据](#6.3.5 接收云平台下发的数据)


1. 简介

ESP8266-01S 是由安信可科技 (Ai-Thinker)基于乐鑫 ESP8266EX 芯片开发的一款超低成本、高集成度的 2.4GHz Wi-Fi 模块,ESP8266-01S 集成了完整的 Wi-Fi 协议栈和微控制器功能,既可以作为独立的 MCU 运行应用程序,也可以作为 Wi-Fi 协处理器连接到其他单片机上。它支持标准 IEEE 802.11 b/g/n 协议和完整的 TCP/IP 协议栈,能够快速将传统设备接入互联网。

以下是一些别的型号:

2. ESP-01S引脚介绍

引脚如下:

引脚序号 引脚名称 功能说明 关键注意事项
1 GND 数字地 必须与主控系统共地
2 GPIO2 通用 I/O 口 上电时必须保持高电平,否则无法启动;板载蓝色 LED 连接到此引脚
3 CH_PD(EN) 模块使能端 高电平有效;01S 内置上拉电阻,悬空即为高电平
4 VCC 3.3V 电源输入 必须提供稳定的 3.3V 电源,电流能力≥500mA
5 RST 复位引脚 低电平有效;01S 内置上拉电阻,悬空即为高电平
6 GPIO0 通用 I/O 口 / 启动模式选择 上电时拉低进入下载模式,拉高进入正常运行模式;01S 内置上拉电阻
7 TX(GPIO1) UART0 发送端 连接主控的 RX 引脚
8 RX(GPIO3) UART0 接收端 连接主控的 TX 引脚;与 5V 主控通信时需串联 220Ω 电阻或使用电平转换器

我们这里使用了一块拓展板,接线如下:

3. AT指令

AT 指令是一种标准化的串行通信命令集,全称 "Attention Command"(注意指令),是设备之间通过串口进行通信的最通用、最古老的协议之一。它最初由 Hayes 公司在 1981 年为调制解调器 (Modem) 设计,后来成为了所有串行通信设备的事实标准。

对于 ESP8266-01S 这类无线模块来说,AT 指令就是一套简单的 "遥控器":你不需要懂复杂的 Wi-Fi 协议和底层编程,只需要通过串口发送几个简单的文本命令,就能让模块完成连接 WiFi、发送数据、创建热点等复杂操作。

cpp 复制代码
//格式规范

AT+<命令名>[=<参数1>,<参数2>,...]
  • 前缀:所有命令必须以AT开头(大小写不敏感,at、At、aT都可以)
  • 分隔符:命令名和参数之间用+号分隔
  • 参数:多个参数之间用逗号,分隔,可选参数可以省略
  • 结束符:命令末尾必须加上回车换行符 (\r\n),这是最容易被忽略的点!

安信可官网链接:docs.ai-thinker.com/esp8266/index.html

乐鑫官网链接:AT 命令集 --- ESP-AT 用户指南 文档

后续文末会将文章用到的所有东西打包一份。

AT指令可以细分为一下四大类:

类型 指令格式 核心功能 ESP8266 实际示例与返回值
测试指令 AT+<x>=? 查询该指令支持哪些参数以及参数的取值范围 输入:AT+CWMODE=? 返回:+CWMODE:(1,2,3) 说明:表示 CWMODE 指令只支持 1、2、3 这三个参数值
查询指令 AT+<x>? 查询该参数当前的设置值 输入:AT+CWMODE? 返回:+CWMODE:1 说明:表示当前 WiFi 模式设置为 1(STA 模式)
设置指令 AT+<x>=<...> 给参数设置用户自定义的值 输入:AT+CWMODE=2 返回:OK 说明:将 WiFi 模式设置为 2(AP 模式)
执行指令 AT+<x> 执行一个没有参数的固定功能 输入:AT+RST 返回:重启信息 + OK 说明:立即重启模块

注意:

  • 不是每条 AT 指令都具备上述 4 种类型的命令
  • \] 括号内为缺省值,不必填写或者可能不显示

  • 默认波特率为 115200
  • AT 指令必须大写,并且以回车换行符结尾 (CR LF)

我们可以找到这个软件,取输入AT指令验证一下:

根据手册自行验证,后续我们用到那个指令在具体说明其作用:

4. 固件烧录

我们先在串口调试助手上输入AT+GMR:

其作用:

可以看到我们使用的固件版本:AT 1.2.0.0 + SDK 1.5.4.1,发布于 2016 年,经过了近 10 年的市场验证,几乎没有致命 bug,不过由于版本太老有些东西不支持:

  • 不支持 SSL/TLS:无法进行 HTTPS 加密通信
  • 不支持 MQTT AT 指令:需要自己通过 TCP 实现 MQTT 协议
  • 不支持 OTA 升级:无法通过网络升级固件
  • TCP 连接数有限:最多同时支持 5 个 TCP 连接
  • 部分高级功能缺失:如 WiFi 扫描结果排序、智能配网 (AirKiss) 等

我们需要想要使用MQTT实现一些功能,那么这个版本就不适用了,我们来到官网,找到:

这里我使用的是1471号,下载下来,然后找到我们的固件烧录软甲(最下面链接会整理):

点击ok:

进入到如下界面:

选择固件所在的目录,勾选要下载的路径,固件烧写的地址也要设置好。固件需解压使用,固件只有一个 bin 文件地址设置以 0x00000 开始,固件有多个 bin 文件一般可以在固件文件夹
找到 readme,里面会说明各 bin 文件的烧录地址:

这里我们就先烧写之前下载的固件:

注意将刚刚的串口先关掉;

文件尽量存放到非中文路径下,否则可能会出现报错。

然后按照如下进行配置:

这里的一些参数可以查看官方手册,这里直接给附在下面了:

配置选项 配置说明
Download_Path_Config 选择要下载的文件以及下载地址,点击 start 下载勾选后的文件
CrystalFreq 设置晶振频率。8266 的晶振频率为 26M,此处禁止修改
SPI_SPEED 设置 SPI 速率,默认 40M,此处禁止修改
SPI_MODE 设置 SPI 下载模式,默认为通用下载模式 DOUT
Flash_Size 设置 flash 容量 (参考下一小节 3.2 章节)根据实际编译的配置对应选择的 Flash 大小16Mbit‑C1 为 1024+1024 的布局,32Mbit‑C1 为 1024+1024 的布局
CombineBin 打包合并固件,下载地址为 0x0。参考章节 3.3
DoNotChgBin ・选择该项,Flash 的运行频率、方式、布局会以用户编译时的配置为准・未选择该项,Flash 的运行频率、方式、布局会以下载工具最终的配置为准下载安信可官方 AT 固件时建议勾选,其他不建议勾选
Lock settings 选择该项,将锁住配置页面该选项一般在工厂生产中使用,避免操作过程中改动了软件上的配置,造成生产问题
Default 选择该项,将恢复默认的软件配置。
Detected info 该窗口将会显示 flash 的大小和晶振频率
MAC address - 该窗口将会显示 ESP8266 芯片的 MAC 地址,包括 STA MAC ADDRESS 和 AP MAC ADDRESS。- 安信可生产的 ESP8266 系列模组的 STA MAC ADDRESS 都可以在官网的防伪查询系统中查询到。地址:https://www.ai-thinker.com/service/autifake
COM 设置 COM 口
BAUD 设置下载波特率下载时可以适当降低下载波特率,保证稳定下载 (有些串口工具不支持 1500000 的波特率下载)
START 点击该按钮,开始烧录程序
STOP 点击该按钮,停止烧录程序
ERASE 点击该按钮,擦除整个 flash

我们可以点击ERASE按钮,擦除整个 flash:

然后勾选我们上面下载好的固件,点击START开始下载:

等待一段时间,下载完成:

这里需要注意,想要更新固件需要将:

IO0接GNG

RST碰一下GND

否则烧录不成功。

然后固件烧录软件暂时可以关掉了,回到串口调试助手,重新输入AT+GMR查询版本信息,可以发现已烧录成功:

然后输入AT+MQTTCONN?,确定是否连接到MQTT:

这些参数官网都可以查到:

5. ESP-01S连接OneNET云平台

5.1 设备创建

找到OneNET云平台官网,注册登录:

官网链接:OneNET - 中国移动物联网开放平台

按照如下操作:

找到产品开发,找到创建产品:

产品品类不重要随便选:

然后点击设备接入,对如下信息进行补充,主要注意"接入协议"和"联网方式",其他信息根据自己的来:

然后点击确定即可:

然后找到设备管理:

添加设备,注意不要是中文的:

点击确定即可:

再次回到产品开发,进行产品开发:

可以看到,里面有一些物模型,我们可以使用他们,也可以删掉自己创建,这里我直接删掉:

找到图示位置自己添加物模型相关参数:

例如这里我们设置一个温度,标识符就是一个标志,告诉你什么代表什么,方便调用:

然后根据需求创建自己需要的物模型,这里我创建温湿度和LED三个物模型,然后点击保存:

5.2 信息获取

5.2.1 OneNET信息

为了完成设备到云平台的链接我们需要获取如下信息:

产品 ID:

设备 ID / 名称:

设备密钥:

首先是产品ID,我们找到产品开发,找到设备管理:

找到详情:

在产品信息部分找到自己的产品ID,复制下来,保存到我们上面需求的信息模版内:

设备名称和设备秘钥也都复制下来:

5.2.2 token生成信息

这里我们需要获取:

res 格式:

时间戳:

个人生成的 token:

这个详细了解可以查看:

概述_开发者文档_OneNET

下载token生成工具:

打开:

对于res格式,如下:

products / 产品 ID/devices/ 设备 ID

将我们之前获取的信息替换上去:

注意:不要多复制空格等信息

找一个时间戳转换工具:

时间戳(Unix timestamp)转换工具 - 在线工具

进行时间戳转换,注意时间需要是为了的时间,否则你生成完都已经过期了,生成完复制下来,填写进去:

之后将自己的秘钥填写至第三行,点击生成即可,将下面生成的token复制下来:

至此基础信息获取完成。

5.3 基础AT指令步骤

5.3.1 AT

首先确定是否连接上:

5.3.2 AT+RST

然后复位一下,防止之前数据残留:

这里乱码是正常的,新版固件启动时会用 74880 波特率 输出一段信息,我的串口助手是 115200,所以显示乱码。

5.3.3 AT+CWMODE=1

然后设置WIFI工作模式:

三种工作模式

模式名称 AT 设置指令 核心功能 核心适用场景 关键操作示例 核心特点
STA 模式(站点 / 客户端模式) AT+CWMODE=1 模块作为 WiFi 客户端,连接现有路由器 WiFi,接入互联网 OneNET / 阿里云等平台上云、远程数据上传、远程控制 1. 设置模式:AT+CWMODE=1 2. 重启生效:AT+RST 3. 连接 WiFi:AT+CWJAP="WiFi名称","WiFi密码" 依赖路由器网络,可直接接入公网,是物联网上云项目的首选模式
AP 模式(接入点 / 热点模式) AT+CWMODE=2 模块自身发射 WiFi 热点,手机 / 电脑等设备可直接连接,无需路由器 无网络环境的本地近距离控制、设备直连调试、临时无线组网 1. 设置模式:AT+CWMODE=2 2. 重启生效:AT+RST 3. 创建热点:AT+CWSAP="ESP8266_AP","12345678",8,0 不依赖外部网络,仅支持局域网内通信,无法直接接入公网上云
STA+AP 混合模式 AT+CWMODE=3 同时开启 STA 和 AP 双模式:一边连接路由器上网,一边发射热点供其他设备连接 无线中继、本地 + 远程双控制、多设备级联组网 1. 设置模式:AT+CWMODE=3 2. 重启生效:AT+RST 3. 同时执行 STA 连网 + AP 建热点操作 兼顾公网接入和本地直连,资源占用略高,适合复杂组网场景

这里我使用 STA模式

5.3.4 AT+CWJAP="名称","密码"

输入自己的wifi名称和密码,链接成功:

5.4 MQTT指令步骤

5.4.1 设置用户属性

AT+MQTTUSERCFG=0,1,"设备ID","产品ID","个人生成的token",0,0,""

可以查看官网介绍:

发送:

一定要仔细检查格式,防止空格和中文字符。

5.4.2 连接OneNET服务器

AT+MQTTCONN=0,"mqtts.heclouds.com",1883,1

这里需要注意,很容易卡在这一步:

如果这里报错,查看一下前面那部分可能错误了,我这里因为前面有个单词打错了,一直错误,如果实在不行,将这里的设备秘钥换一下,重新生成token试一下:

5.4.3 订阅主题

AT+MQTTSUB=0,"$sys/产品ID/设备ID/thing/property/post/reply",0 //订阅 "设备属性上报响应" 主题

AT+MQTTSUB=0,"$sys/产品ID/设备ID/thing/property/set",0 //订阅 "设备属性设置请求" 主题 (可选)

只有订阅过主题才能正常收发数据:

5.4.4 发送MQTT数据

AT+MQTTPUBRAW=0,"$sys/产品ID/设备ID/thing/property/post",数据长度,0,0 //发送长数据(常用,先发指令,再发内容,需计算内容长度)

{"id":"123456","params":{"标识符名称":{"value":数据}}} //数据内容 (单个数据上传)

{"id":"123456","params":{"标识符名称1":{"value":数据1},"标识符名称2":{"value":数据2}}} //数据内容 (多个数据一起上传)

对于数据上传,其中标识符名称就是刚才我们创建:

举个例子:

{"id":"123456","params":{"temperature":{"value":36.5},"humidity":{"value":66.6}}} //数据内容 (多个数据一起上传)

相当于传输温度数据36.5,湿度数据66.6

然后我们需要计算一下举例的这串字符串的数据长度:

{"id":"123456","params":{"temperature":{"value":36.5},"humidity":{"value":66.6}}}

然后找个字符串长度计算工具计算一下:

字符串长度计算

然后要在该指令数据长度地方替换掉:

AT+MQTTPUBRAW=0,"$sys/产品ID/设备ID/thing/property/post",数据长度,0,0

//发送长数据(常用,先发指令,再发内容,需计算内容长度)
注意最终值需要加1,因为需要加一个换行符的位数。

发送,返回一个大于号表示开始发送数据:

然后开始发送刚刚我们的举例数据:

然后回到云平台,找到属性,可以看到数据发送成功:

5.4.5 云平台数据下发

首先找到图示位置:

对旁边属性设置,输入我们自己的ID:

编写下发数据:

cpp 复制代码
{
    "LED":"ON",
    "temperature":30.3
}

点击执行可以看到数据正常下发了:

不过我们拉倒下面可以看到,显示的返回值数据是设备响应超时,那是因为我们此时只有下发,但是设备并没有告诉云平台我收到数据了:

5.4.6 发送API调试回应

AT+MQTTPUBRAW=0,"$sys/产品ID/设备ID/thing/property/set_reply",数据长度,0,0

{"id":"下一个id数字","code":200,"msg":"user_succ"}

对于这个下一个id数据,我们可以多下发几次数据,可以发现ID是递增的,没发一次累加1:

因此之后我们想要正常响应,这里此时需要填写5:

{"id":"5","code":200,"msg":"user_succ"}

计算一下,该串的字符长度(依旧注意换行符加一):

先发送第一条命令,注意第二条命令不要发送的,因为该命令主要是为了,平台发完数据给返回值的:

我们需要现在平台发送完数据,马上发送该指令:

此时可以发现不会响应失败了:

6. STM32功能实现

首先我们先找一个工程模版,这里我用我之前写的串口的代码:

基于STM32通过USART实现DMA环形缓冲区接收数据资源-CSDN下载

也可以使用江协的串口打印的代码,在此基础上进行后续代码编写,将按键部分代码删除,只保留OLED屏便于显示,串口1用于打印数据:

然后通过串口2与esp8266相连,用于AT指令的收发,我们首先编写串口2的代码。

6.1 USART2代码编写

这里可以直接参考江协USART1的代码更改USART2:

cpp 复制代码
#include "stm32f10x.h" 
#include "esp8266.h"
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];

char USART2_RX_BUF[1024]={0};
uint16_t USART2_RX_LEN=0;
uint8_t USART2_RX_FINISH=0;

void ESP8266_USART_Config(void)
{
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART2,ENABLE);//开启USART2的时钟
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate=115200;//波特率
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不使用流控
	USART_InitStructure.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;//发送+接收模式
	USART_InitStructure.USART_Parity=USART_Parity_No;//校验位,无校验
	USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位,1位
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长
	USART_Init (USART2,&USART_InitStructure);
	
	USART_ITConfig (USART2,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出,RXNE标志位一旦为1,就申请中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;//中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_Init (&NVIC_InitStructure);
	
	USART_Cmd (USART2,ENABLE);	
}

void ESP8266_USART_SendByte(uint8_t Byte)//发送一个字节
{
	USART_SendData (USART2,Byte);
	while (USART_GetFlagStatus (USART2,USART_FLAG_TXE) == RESET);//判断标志位,可自动清零	
}

void ESP8266_USART_SendArray(uint8_t *Array, uint16_t Length)//发送一个数组
{
	uint16_t i;
	for (i=0;i<Length;i++)
	{
		ESP8266_USART_SendByte(Array[i]);
	}	
}

void ESP8266_USART_SendString(char *String)//串口发送一个字符串
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		ESP8266_USART_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

uint32_t ESP8266_USART_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

void ESP8266_USART_SendNumber(uint32_t Number, uint8_t Length)//串口发送数字
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		ESP8266_USART_SendByte(Number / ESP8266_USART_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

int ESP8266_USART_fputc(int ch, FILE *f)//使用printf需要重定向的底层函数
{
	ESP8266_USART_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

void ESP8266_USART_Printf(char *format, ...)
{
	char String[500];										//定义字符数组
	va_list arg;												//定义可变参数列表数据类型的变量arg
	va_start(arg, format);							//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);			//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);												//结束变量arg
	ESP8266_USART_SendString(String);		//串口发送字符数组(字符串)
}

void USART2_IRQHandler(void)
{
	uint8_t temp;
	if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
	{
		temp = USART_ReceiveData(USART2);//读取接收字节
		USART2_RX_BUF[USART2_RX_LEN++] = temp;
		
		// 缓冲区防溢出
		if (USART2_RX_LEN >= 1023) USART2_RX_LEN = 0;
		
		// 检测到指令结束符\n(对应\r\n),标记接收完成
		if(temp == '\n') 
		{
			USART2_RX_FINISH = 1;
		}
		
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);
	}
}

这里不在做过多描述就是一个串口代码,为了方便后续移植更改一下宏定义:

cpp 复制代码
#include "stm32f10x.h" 
#include "esp8266.h"
#include <stdio.h>
#include <stdarg.h>

uint8_t ESP8266_TxPacket[4];

char ESP8266_RX_BUF[1024]={0};
uint16_t ESP8266_RX_LEN=0;
uint8_t ESP8266_RX_FINISH=0;

void ESP8266_USART_Config(void)
{
	ESP8266_USART_APBxClkCmd(ESP8266_USART_CLK,ENABLE);//开启USART2的时钟
	ESP8266_USART_GPIO_APBxClkCmd(ESP8266_USART_GPIO_CLK,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin=ESP8266_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(ESP8266_USART_TX_GPIO_PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin=ESP8266_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(ESP8266_USART_RX_GPIO_PORT,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate=ESP8266_USART_BAUDRATE;//波特率
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不使用流控
	USART_InitStructure.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;//发送+接收模式
	USART_InitStructure.USART_Parity=USART_Parity_No;//校验位,无校验
	USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位,1位
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长
	USART_Init(ESP8266_USARTx,&USART_InitStructure);
	
	USART_ITConfig(ESP8266_USARTx,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出,RXNE标志位一旦为1,就申请中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=ESP8266_USART_IRQ;//中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(ESP8266_USARTx,ENABLE);	
}

void ESP8266_USART_SendByte(uint8_t Byte)//发送一个字节
{
	USART_SendData (ESP8266_USARTx,Byte);
	while (USART_GetFlagStatus (ESP8266_USARTx,USART_FLAG_TXE) == RESET);//判断标志位,可自动清零	
}

void ESP8266_USART_SendArray(uint8_t *Array, uint16_t Length)//发送一个数组
{
	uint16_t i;
	for (i=0;i<Length;i++)
	{
		ESP8266_USART_SendByte(Array[i]);
	}	
}

void ESP8266_USART_SendString(char *String)//串口发送一个字符串
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		ESP8266_USART_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

uint32_t ESP8266_USART_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

void ESP8266_USART_SendNumber(uint32_t Number, uint8_t Length)//串口发送数字
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		ESP8266_USART_SendByte(Number / ESP8266_USART_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

int ESP8266_USART_fputc(int ch, FILE *f)//使用printf需要重定向的底层函数
{
	ESP8266_USART_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

void ESP8266_USART_Printf(char *format, ...)
{
	char String[100];										//定义字符数组
	va_list arg;												//定义可变参数列表数据类型的变量arg
	va_start(arg, format);							//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);			//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);												//结束变量arg
	ESP8266_USART_SendString(String);		//串口发送字符数组(字符串)
}

void ESP8266_USART_IRQHandler(void)
{
	uint8_t temp;
	if (USART_GetITStatus(ESP8266_USARTx, USART_IT_RXNE) != RESET)
	{
		temp = USART_ReceiveData(ESP8266_USARTx);//读取接收字节
		ESP8266_RX_BUF[ESP8266_RX_LEN++] = temp;
		
		// 缓冲区防溢出
		if (ESP8266_RX_LEN >= 1023) ESP8266_RX_LEN = 0;
		
		// 检测到指令结束符\n(对应\r\n),标记接收完成
		if(temp == '\n') 
		{
			ESP8266_RX_FINISH = 1;
		}
		
		USART_ClearITPendingBit(ESP8266_USARTx, USART_IT_RXNE);
	}
}

串口代码这里不再做过多解释,详细可以去看一下江协的视频:

cpp 复制代码
/************************ 串口基础配置 ************************/
#define  ESP8266_USARTx                   USART2
#define  ESP8266_USART_CLK                RCC_APB1Periph_USART2
#define  ESP8266_USART_APBxClkCmd         RCC_APB1PeriphClockCmd
#define  ESP8266_USART_BAUDRATE           115200

/************************ GPIO配置 ************************/
#define  ESP8266_USART_GPIO_CLK           RCC_APB2Periph_GPIOA
#define  ESP8266_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
#define  ESP8266_USART_TX_GPIO_PORT       GPIOA   
#define  ESP8266_USART_TX_GPIO_PIN        GPIO_Pin_2
#define  ESP8266_USART_RX_GPIO_PORT       GPIOA
#define  ESP8266_USART_RX_GPIO_PIN        GPIO_Pin_3

/************************ 中断配置 ************************/
#define  ESP8266_USART_IRQ                USART2_IRQn
#define  ESP8266_USART_IRQHandler         USART2_IRQHandler

主函数代码如下,验证一下,串口是否移植成功:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_USART_SendString("ESP8266 USART2 Test\r\n");
	Delay_ms(100);
	
	ESP8266_USART_Printf("Hello STM32! Num=%d\r\n", 666);

	while (1)
	{
		// 检测一帧接收完成
		if(ESP8266_RX_FINISH == 1)
		{
			ESP8266_USART_SendArray((uint8_t*)ESP8266_RX_BUF, ESP8266_RX_LEN);
			
			// 清空接收
			ESP8266_RX_LEN = 0;
			ESP8266_RX_FINISH = 0;
			memset(ESP8266_RX_BUF, 0, 1024);
		}
	}
}

可以发现数据正常收发:

6.2 AT指令发送函数

现在改一下接线,将USART1链接到电脑上,将USART2与ESP8266相连,编写AT指令发送函数:

cpp 复制代码
/*
 * 函数名:ESP8266_Cmd
 * 描述  :对WF-ESP8266模块发送AT指令
 * 输入  :cmd,待发送的指令
 *         reply1,reply2,期待的响应,为NULL表不需响应,两者为或逻辑关系
 *         waittime,等待响应的时间
 * 返回  : 1,指令发送成功
 *         0,指令发送失败
 * 调用  :被外部调用
 */
bool ESP8266_Cmd(char *cmd, char *reply1, char *reply2, u32 waittime)

首先清空缓冲区,把上次接收的数据全部清空,防止新指令和旧数据混在一起,保证每次接收都是干净的:

cpp 复制代码
ESP8266_RX_LEN = 0;
ESP8266_RX_FINISH = 0;
memset(ESP8266_RX_BUF, 0, 1024);

然后通过串口给ESP8266发送AT指令:

cpp 复制代码
ESP8266_USART_Printf("%s\r\n", cmd);

有些指令不需要等回复,直接返回成功:

cpp 复制代码
if (reply1 == NULL && reply2 == NULL)
{
    return 1;
}

等待一段时间,让 ESP8266 处理指令并返回数据:

cpp 复制代码
Delay_ms(waittime);

给接收数据加结束符:

cpp 复制代码
ESP8266_RX_BUF[ESP8266_RX_LEN] = '\0';

通过串口1打印数据,方便观察:

cpp 复制代码
printf("ESP8266 RECV: %s\r\n", ESP8266_RX_BUF);

检查是否收到期望的关键词,strstr(缓冲区, 关键词) 去查找有没有这个词,找到返回非空,表示成功,返回 NULL,表示失败:

cpp 复制代码
	uint8_t res = 0;
	if (reply1 != NULL && reply2 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply1) != NULL) || (strstr(ESP8266_RX_BUF, reply2) != NULL);
	}
	else if (reply1 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply1) != NULL);
	}
	else if (reply2 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply2) != NULL);
	}

清空状态等待下次指令:

cpp 复制代码
ESP8266_RX_LEN = 0;
ESP8266_RX_FINISH = 0;

此时的函数:

cpp 复制代码
bool ESP8266_Cmd(char *cmd, char *reply1, char *reply2, u32 waittime)
{
	//清空接收缓冲区
	ESP8266_RX_LEN = 0;
	ESP8266_RX_FINISH = 0;
	memset(ESP8266_RX_BUF, 0, 1024);

	ESP8266_USART_Printf("%s\r\n", cmd);   // 发送指令

	if (reply1 == NULL && reply2 == NULL)
	{
		return 1;
	}
	
	Delay_ms(waittime);//等待模块返回数据
	
	ESP8266_RX_BUF[ESP8266_RX_LEN] = '\0';//给接收缓冲区加上字符串结束符

	printf("ESP8266 RECV: %s\r\n", ESP8266_RX_BUF);//打印数据到电脑上
	
	//判断是否收到期望的关键字
	uint8_t res = 0;
	if (reply1 != NULL && reply2 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply1) != NULL) || (strstr(ESP8266_RX_BUF, reply2) != NULL);
	}
	else if (reply1 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply1) != NULL);
	}
	else if (reply2 != NULL)
	{
		res = (strstr(ESP8266_RX_BUF, reply2) != NULL);
	}

	//清空接收,准备下一次
	ESP8266_RX_LEN = 0;
	ESP8266_RX_FINISH = 0;

	return res;
}

来到主函数验证一下:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_Cmd("AT",            "OK",      NULL,    500);    // 测试通讯
	ESP8266_Cmd("AT+RST",        "ready",   "OK",    2000);   // 重启
	ESP8266_Cmd("AT+CWMODE=1",   "OK",      NULL,    1000);   // 设置STA模式

	while (1)
	{

	}
}

可以发现数据正常收发并打印出来:

下面我们开始对上面我们介绍过的指令开始一一实现。

6.2.1 AT

首先判断模块是否启动:

cpp 复制代码
void ESP8266_AT(void)
{
	uint8_t test = 0;
	uint8_t retry = 0;

    printf("\r\nAT测试.....\r\n");

	while(retry < 10)
	{
		test = ESP8266_Cmd("AT", "OK", NULL, 500);

		if(test == 1)
		{
			printf("ESP8266 启动成功!\r\n");
			break;   // 成功就退出循环
		}
		else
		{
			printf("ESP8266 第 %d 次检测失败,正在重试...\r\n", retry+1);
			retry++;
			Delay_ms(500);
		}
	}

	if(retry >= 10)
	{
		printf("ESP8266 初始化失败!请检查接线/模块!\r\n");
	}
}

我们将模块拔掉主函数调用启动函数:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_AT();

	while (1)
	{

	}
}

经过十次初始化,未接收到对应返回值,判断重启失败:

再把模块插回去,复位一下,可以发现正常启动:

6.2.2 AT+RST

调用一下重启命令,上面已经演示过了:

cpp 复制代码
void ESP8266_RST(void)
{
	uint8_t test = 0;

	test = ESP8266_Cmd("AT+RST","ready","OK",2000);

	if(test == 1)
	{
		printf("ESP8266 软重启成功!\r\n");
	}
	else
	{
		printf("ESP8266 软重启失败!\r\n");
	}
}

这里为了方便后续验证在创建一个验证函数:

cpp 复制代码
void ESP8266_test(void)
{
	ESP8266_AT();
	ESP8266_RST();
}

这样主函数直接调用test即可:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_test();

	while (1)
	{

	}
}

6.2.3 AT+CWMODE

对于ESP8266我们知道有三种工作模式,这里通过switch进行切换:

cpp 复制代码
typedef enum{
    STA = 1,
  AP,
  STA_AP  
} ENUM_Net_ModeTypeDef;


bool ESP8266_Net_Mode_Choose (ENUM_Net_ModeTypeDef enumMode)
{
	switch (enumMode)
	{
		case STA:// 客户端模式(连路由器)
			return ESP8266_Cmd("AT+CWMODE=1", "OK", "no change", 2500); 
		
	  case AP:// 热点模式(手机/电脑连它)
		  return ESP8266_Cmd("AT+CWMODE=2", "OK", "no change", 2500); 
		
		case STA_AP:// 双模式
		  return ESP8266_Cmd("AT+CWMODE=3", "OK", "no change", 2500); 
		
	  default:
		  return false;
  }
}

对test进行简单的修改:

cpp 复制代码
void ESP8266_test(void)
{
	ENUM_Net_ModeTypeDef mode = STA;//先定义变量保存模式

	ESP8266_AT();
	ESP8266_RST();

	printf("开始配置ESP8266模工作模式\r\n");

	if(ESP8266_Net_Mode_Choose(mode) == 1)
	{
		printf("模式设置成功:%d 模式\r\n",mode);
	}
	else
	{
		printf("模式设置失败\r\n");		
	}
}

6.2.4 AT+CWJAP="名称","密码"

声明两个宏定义用于存放自己wifi名称和密码:

cpp 复制代码
#define WIFI_NAME         "xiaomi"
#define WIFI_PASSWORD     "12345678"

然后通过sprintf将数据拼接起来,进行发送:

cpp 复制代码
bool ESP8266_JoinAP(char *pSSID, char *pPassWord)
{
	char cCmd[120];

	// 拼接指令:AT+CWJAP="WiFi名称","WiFi密码"
	sprintf(cCmd, "AT+CWJAP=\"%s\",\"%s\"",pSSID,pPassWord);
	
	// 发送指令,等待 OK,超时 5 秒
	return ESP8266_Cmd(cCmd, "OK", NULL, 5000);
}

来到test验证一下:

cpp 复制代码
void ESP8266_test(void)
{
	ENUM_Net_ModeTypeDef mode = STA;//先定义变量保存模式

	ESP8266_AT();
	ESP8266_RST();

	printf("开始配置ESP8266模工作模式\r\n");
	if(ESP8266_Net_Mode_Choose(mode) == 1)
	{
		printf("模式设置成功:%d 模式\r\n",mode);
	}
	else
	{
		printf("模式设置失败\r\n");		
	}

	printf("正在连接 WiFi...\r\n");
	if(ESP8266_JoinAP(WIFI_NAME,WIFI_PASSWORD) == 1)
	{
		printf("WiFi 连接成功!\r\n");
	}
	else
	{
		printf("WiFi 连接失败!\r\n");
	}
}

wifi正常连接:

6.3 MQTT指令步骤

下面我们来测试一下上面演示的MQTT的指令。

6.3.1 设置用户属性

根据指令:

AT+MQTTUSERCFG=0,1,"设备ID","产品ID","个人生成的token",0,0,""

我们生成对应的宏定义,方便后续更改,注意改成自己的:

cpp 复制代码
#define MQTT_CLIENT_ID  "6Fpqz8oCHv"  																	//产品ID
#define MQTT_USER_NAME  "mytest"               													
#define MQTT_TOKEN      "version=2018-10-31&res=products%2F6Fpqz8oCHv%2Fdevices%2Fmytest&et=1842685190&method=md5&sign=j0TaohKG2eRX90cPDI2kfw%3D%3D"//个人生成的 token

使用就非常简单了,直接调用sprintf对命令进行融合,然后调用发送函数发送:

cpp 复制代码
bool ESP8266_MQTT_USERCFG(void)
{
	char cStr[300];
	
	sprintf(cStr, "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"", MQTT_USER_NAME,MQTT_CLIENT_ID,MQTT_TOKEN );
	
	return ESP8266_Cmd(cStr, "OK", 0, 5000);
}


void ESP8266_MQTT_test(void)
{
	uint8_t test = 0;

	test = ESP8266_MQTT_USERCFG();

	if(test == 1)
	{
		printf("MQTT初始化成功!\r\n");
	}
	else
	{
		printf("MQTT初始化失败!\r\n");		
	}
}

主函数调用test:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"
#include "esp8266_mqtt.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_test();

	ESP8266_MQTT_test();

	while (1)
	{

	}
}

初始化成功:

6.3.2 连接OneNET服务器

AT+MQTTCONN=0,"mqtts.heclouds.com",1883,1

同样的操作:

cpp 复制代码
bool ESP8266_MQTT_CONN(void)
{
	char cStr [200];
	
	sprintf(cStr, "AT+MQTTCONN=0,\"%s\",1883,1", MQTT_BROKERADDRESS);
	
	return ESP8266_Cmd( cStr, "OK", 0, 5000);
	
}


void ESP8266_MQTT_test(void)
{
	uint8_t test = 0;

	test = ESP8266_MQTT_USERCFG();

	if(test == 1)
	{
		printf("MQTT初始化成功!\r\n");
	}
	else
	{
		printf("MQTT初始化失败!\r\n");		
	}

	Delay_ms(100);

	test = 0;
	test = ESP8266_MQTT_CONN();

	if(test == 1)
	{
		printf("MQTT连接成功!\r\n");
	}
	else
	{
		printf("MQTT连接失败!\r\n");		
	}
}

这里需要注意自己的token,还有ID是否正确:

6.3.3 订阅主题

AT+MQTTSUB=0,"$sys/产品ID/设备ID/thing/property/post/reply",0 //订阅 "设备属性上报响应" 主题

AT+MQTTSUB=0,"$sys/产品ID/设备ID/thing/property/set",0 //订阅 "设备属性设置请求" 主题 (可选)

这几个指令都差不多:

cpp 复制代码
#define MQTT_SUBSCRIBE_TOPIC  "$sys/6Fpqz8oCHv/mytest/thing/property/post/reply"    		//订阅主题

/*
 * 函数名:ESP8266_MQTT_SUB
 * 描述  :WF-ESP8266模块进行MQTT的信息订阅
 * 输入  :无
 * 返回  : 1,配置成功
 *         0,配置失败
 * 调用  :被外部调用
 */
bool ESP8266_MQTT_SUB(void)
{
	char cStr [200];
	
	sprintf(cStr, "AT+MQTTSUB=0,\"%s\",0", MQTT_SUBSCRIBE_TOPIC);
	
	return ESP8266_Cmd(cStr, "OK", 0, 500);
	
}

void ESP8266_MQTT_test(void)
{
	uint8_t test = 0;

	test = ESP8266_MQTT_USERCFG();

	if(test == 1)
	{
		printf("MQTT初始化成功!\r\n");
	}
	else
	{
		printf("MQTT初始化失败!\r\n");		
	}

	Delay_ms(100);

	test = 0;
	test = ESP8266_MQTT_CONN();

	if(test == 1)
	{
		printf("MQTT连接成功!\r\n");
	}
	else
	{
		printf("MQTT连接失败!\r\n");		
	}

	Delay_ms(100);

	test = 0;
	test = ESP8266_MQTT_SUB();

	if(test == 1)
	{
		printf("MQTT信息订阅成功!\r\n");
	}
	else
	{
		printf("MQTT信息订阅失败!\r\n");		
	}
}

6.3.4 发送MQTT数据

AT+MQTTPUBRAW=0,"$sys/产品ID/设备ID/thing/property/post",数据长度,0,0 //发送长数据(常用,先发指令,再发内容,需计算内容长度)

{"id":"123456","params":{"标识符名称":{"value":数据}}} //数据内容 (单个数据上传)

{"id":"123456","params":{"标识符名称1":{"value":数据1},"标识符名称2":{"value":数据2}}} //数据内容 (多个数据一起上传)

首先拼接一下这段代码:

cpp 复制代码
{"id":"123456","params":{"标识符名称1":{"value":数据1},"标识符名称2":{"value":数据2}}} 

拼接JSON数据:

cpp 复制代码
	char data[200];

	snprintf(data, sizeof(data), "{\"id\":\"123456\",\"params\":{\"temperature\":{\"value\":%.2f},\"humidity\":{\"value\":%.2f}}}",temperature, humidity);

然后拼接需要发送的原始指令:

cpp 复制代码
AT+MQTTPUBRAW=0,"$sys/产品ID/设备ID/thing/property/post",数据长度,0,0

通过strlen计算数据原始指令数据长度:

cpp 复制代码
	char at_cmd[200];
	uint16_t data_len = strlen(data);
	
	//发送原始发布指令 AT+MQTTPUBRAW
	snprintf(at_cmd,sizeof(at_cmd),"AT+MQTTPUBRAW=0,\"%s\",%d,0,0", MQTT_PUBLISH_TOPIC, data_len);

然后判断">",如果遇到大于号,表示数据发送成功,然后发送:

cpp 复制代码
	printf("准备发送数据...\r\n");
	if(ESP8266_Cmd(at_cmd, ">", NULL, 6000) == 1)
	{
		printf("准备发送数据成功\r\n");
	}
	else
	{
		printf("准备发送数据失败\r\n");
		return;
	}

	printf("正在发送数据...\r\n");
	ESP8266_USART_SendString(data); // 直接发送数据
	
	//等待返回 OK
	if(ESP8266_Cmd("", "OK", NULL, 6000) == 1)
	{
		printf("数据上报成功!\r\n");
	}
	else
	{
		printf("数据上报失败\r\n");
		return;
	}

此时代码:

cpp 复制代码
void MQTT_Publish_Data(float temperature,float humidity)
{
	char data[200];
	char at_cmd[200];

	//拼接JSON数据
	snprintf(data, sizeof(data), "{\"id\":\"123456\",\"params\":{\"temperature\":{\"value\":%.2f},\"humidity\":{\"value\":%.2f}}}",temperature, humidity);
	
	uint16_t data_len = strlen(data);
	
	//发送原始发布指令 AT+MQTTPUBRAW
	snprintf(at_cmd,sizeof(at_cmd),"AT+MQTTPUBRAW=0,\"%s\",%d,0,0", MQTT_PUBLISH_TOPIC, data_len);

	printf("准备发送数据...\r\n");
	if(ESP8266_Cmd(at_cmd, ">", NULL, 6000) == 1)
	{
		printf("准备发送数据成功\r\n");
	}
	else
	{
		printf("准备发送数据失败\r\n");
		return;
	}

	printf("正在发送数据...\r\n");
	ESP8266_USART_SendString(data); // 直接发送数据
	
	//等待返回 OK
	if(ESP8266_Cmd("", "OK", NULL, 6000) == 1)
	{
		printf("数据上报成功!\r\n");
	}
	else
	{
		printf("数据上报失败\r\n");
		return;
	}
}

主函数调用函数:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"
#include "esp8266_mqtt.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_test();

	ESP8266_MQTT_test();

	MQTT_Publish_Data(40.5f, 55.2f);

	while (1)
	{

	}
}

来到平台可以看到数据上传成功:

6.3.5 接收云平台下发的数据

这个不需要下发指令,只需要对串口接收的数据尽心读取即可:

cpp 复制代码
uint8_t MQTT_Get_Data(char *name, char *data1, char *data2)
{
	if(strstr((char*)ESP8266_RX_BUF, name) != NULL)
	{
		uint8_t data = 0;

		printf("成功接收到MQTT下发数据\r\n");
		printf("接收内容:%s\r\n", ESP8266_RX_BUF);

		if(strstr((char*)ESP8266_RX_BUF, data1) != NULL)
		{
			data = 1;
		}
		else if(strstr((char*)ESP8266_RX_BUF, data2) != NULL)
		{
			data = 2;
		}
		else
		{
			data = 0;
		}

		memset(ESP8266_RX_BUF, 0, sizeof(ESP8266_RX_BUF));
		ESP8266_RX_LEN = 0;

		return data;
	}
	return 0;
}

主要是strstr关键字的运用。

来到主函数:

cpp 复制代码
#include "stm32f10x.h"           
#include "Delay.h"

#include "Bsp_LED_Gpio.h"
#include "Usartx_DMA.h"
#include "esp8266.h"
#include "esp8266_test.h"
#include "esp8266_mqtt.h"

#include <stdio.h>
#include <string.h>

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断分组

	LED_GPIO_Config();//LED引脚初始化
	USARTx_DMA_Init();//串口DMA初始化
	ESP8266_USART_Config();

	ESP8266_test();
	ESP8266_MQTT_test();

	MQTT_Publish_Data(40.5f, 55.2f);

	while (1)
	{
		uint8_t mode = MQTT_Get_Data("property/set", "\"LED\":\"on\"", "\"LED\":\"off\"");
		
		if (mode==1)//收到指令1
		{
			printf("LED 打开\r\n");
			GPIO_SetBits(GPIOB,GPIO_Pin_0);//开灯
		}
		else if(mode==2)//收到指令2
		{
			printf("LED 关闭\r\n");
			GPIO_ResetBits(GPIOB,GPIO_Pin_0);//开灯
		}
	}
}

来到云平台,按照步骤操作:

更改里面的值,可以看到LED灯进行亮灭,且正常打印数据:

STM32学习笔记_时光の尘的博客-CSDN博客
ESP8266开发工具包.zip资源-CSDN下载

相关推荐
十年编程老舅3 小时前
读懂 MCU 启动:从上电到程序运行全链路
单片机·嵌入式硬件·mcu·嵌入式·cpu·嵌入式开发·ram
BY组态3 小时前
Ricon组态系统:工业4.0时代的Web可视化解决方案
物联网·信息可视化·iot·web组态·组态
北京盟通科技官方账号4 小时前
Windows如何实现硬实时?LxWin双系统隔离架构深度解析
stm32·嵌入式硬件·具身智能·ethercat·人形机器人·实时系统·windows实时扩展
Qt程序员4 小时前
从上电到系统就绪:ARM+U-Boot 嵌入式 Linux 启动流程
linux·运维·c++·内核·设备树·嵌入式·ram
半条-咸鱼5 小时前
【STM32】HAL库的本质 及 芯片内部GPIO模块细节
stm32·单片机·嵌入式硬件
振南的单片机世界5 小时前
硬件PWM:定时器自动翻转引脚,CPU不费心
stm32·单片机·嵌入式硬件
0南城逆流06 小时前
【STM32】RTT-Studio中HAL库开发教程十二:FreeRTOS移植
stm32·单片机·嵌入式硬件
bubiyoushang8886 小时前
STM32 DAC8734 4路16位DA驱动程序
stm32·单片机·嵌入式硬件
SmartRadio7 小时前
STM32WLE5 LoRa Smart TDMA 完整协议栈工程实现 -【2】
stm32·单片机·嵌入式硬件·lora·tdma·自组网·smart tdma