
目录
[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:
这个详细了解可以查看:
下载token生成工具:

打开:

对于res格式,如下:
products / 产品 ID/devices/ 设备 ID
将我们之前获取的信息替换上去:

注意:不要多复制空格等信息
找一个时间戳转换工具:
进行时间戳转换,注意时间需要是为了的时间,否则你生成完都已经过期了,生成完复制下来,填写进去:


之后将自己的秘钥填写至第三行,点击生成即可,将下面生成的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功能实现
首先我们先找一个工程模版,这里我用我之前写的串口的代码:
也可以使用江协的串口打印的代码,在此基础上进行后续代码编写,将按键部分代码删除,只保留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灯进行亮灭,且正常打印数据:


