在本次物联网温湿度检测中,采用esp8266-01s模块作为wifi模块。实现ONENET云平台与STM32单片机的数据通信。看本文对部分内容感到迷惑的地方可以查看整个教程:【Freertos实战】零基础制作基于stm32的物联网温湿度检测(教程非常简易),如有错误之处,望大家批评指正。
一、ESP8266-01S固件烧入
1.引脚定义
当我们拿到模块的时候首先要看下引脚定义和电压,为我们烧入固件做准备,对于刚到手的wifi模块内部是没有固件的,无法实现联网通信的功能。
这里到手后,可以按照下面的引脚定义进行接线操作。在烧入固件下,IO0也必须要接地,IO0也必须要接地,IO0也必须要接地。着重强调。建议大家买一个专门的烧录器,这样更方便些
引脚名称 |
描述 |
GND |
GND |
IO2 |
通用IO内部已上拉 |
IO0 |
工作模式选择 |
RXD |
串口接收 |
3V3 |
电源正极3.3V |
RST |
复位 |
EN |
使能 |
TX |
串口发送 |
GPIO0为高电平正常Flash
启动
GPIO0
为低电平代表进入刷固件状态,此时可以经过串口升级内部固件 RST(GPIO16)可做外部硬件复位使用
2.固件烧入
在接好引脚后,大家可以去这个地址下载固件资料等。资料下载
这个是我们待会要烧入的固件
先进入烧写工具目录下,双击我们的烧入工具
选择esp8266,选择完成后点击OK
这几个参数大家也跟我一样
下载完成后会显示“完成“ ,我们在按照下面的方式重新进行接线。
二、数据通信
在完成固件烧入以后,我们就可以开始测试通信,看看能否与云平台进行数据交互。采用的方式是ONENET云平台的MQTT协议。基于新版Onenet搭建云服务(stm32物联网)参考该文章完成云服务搭建。
1.ESP8266的工作模式
ESP8266WIFI 模式有两种,一种叫 AP 模式,一种叫 Station 模式,AP 就是我们平时所说的热点,如 WIFI 路由器,开了热点的手机,或者是公共热点等,这些 AP 设备可以允许其他设备(如手机,笔记本电脑等)输入热点名和密码(也可不设置密码)后接入,Station 则是前面说的连接 AP 的设备,如:手机,笔记本电脑等,ESP8266 还有第三种模式:AP+Station,即:将 AP 和 Station 的功能合二为一,但是应用的场景不多,这里不做展示。
2.测试数据流
大家可以用下面这段数据流去按顺序逐条发送,同时观察自己的云平台数据变化情况,我这边是没有问题的。
这里有疑惑的小伙伴一定要去看下这篇文章:基于新版Onenet搭建云服务(stm32物联网)
1.AT //测试esp8266是否正常工作
2.AT+RST //将设备进行复位,类似重启
3.AT+CWMODE=1 //将esp8266的工作模式选择为station
4.AT+CWDHCP=1,1 //开启 Station 模式下的 DHCP 功能(自动获取 IP 地址)
5.AT+CWJAP="CMCC-yr24","4fy@brba" //WIFI名称CMCC-yr24 密码4fy@brba 大家根据自己的WIFI名称和密码进行修改
6.AT+MQTTUSERCFG=0,1,"DB01","PtAFXPCG49","version=2018-10-31&res=products%2FPtAFXPCG49%2Fdevices%2FDB01&et=1756260513&method=sha1&sign=TcuMb4Vdl%2FQiGc6AkEceJHmt2pI%3D",0,0,""//设备名称或设备ID:DB01 产品ID:PtAFXPCG49 version为token 这部分也是大家根据自己的情况进行修改整合
AT+MQTTCONN=0,"mqtts.heclouds.com",1883,1//连接到ONENT云平台上
AT+MQTTSUB=0,"$sys/PtAFXPCG49/DB01/thing/property/post/reply",0 //MQTT 主题订阅
AT+MQTTSUB=0,"$sys/PtAFXPCG49/DB01/thing/property/set",0 //订阅 “属性设置” 主题
//以下就是stm32往云平台上同步数据流,这里用的是温湿度传感器的信息
AT+MQTTPUB=0,"$sys/PtAFXPCG49/DB01/thing/property/post","{\"id\":\"123\"\,\"params\":{\"Temp\":{\"value\":16\}\,\"Humi\":{\"value\":66\}}}",0,0
或
AT+MQTTPUBRAW=0,"$sys/PtAFXPCG49/DB01/thing/property/post",65,0,0
{"id":"123","params":{"Temp":{"value":33},"Humi":{"value":58}}}
在发送每一条信息后,设备都会返回一段OK的消息。
三、STM32F103C8T6主程序设计
我们之前都是用串口调试助手的方式去给esp8266发送报文信息,这里开始直接用单片机的串口通信去控制,不了解 串口通信的建议大家去看看这篇文章:串口通信(基于stm32)
贴上单片机uart1的代码文件
#ifndef __USART_H
#define __USART_H
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "sys.h"
//////////////////////////////////////////////////////////////////////////////////
#define USART_REC_LEN 512 //接收消息长度
#define EN_USART1_RX 1 //
extern uint8_t USART_RxFlag;
extern uint8_t Recv_LED_Flag;
extern char USART_RX_BUF[USART_REC_LEN];
extern int ok_received;
extern int count;
//extern u16 USART_RX_STA;
void uart_init(u32 bound);
void Serial_SendByte(uint8_t Byte);
int wait_for_ok(char *str,long int wait,char *ack);
int parse_json_command(char *json_str, int *parsed_id, bool *led_status, bool *is_led_command);
#endif
#include "sys.h"
#include "usart.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "Task.h"
//////////////////////////////////////////////////////////////////////////////////
//如果使用UCOS,则包括下面的头文件即可
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
//////////////////////////////////////////////////////////////////
//加入以下代码,支持printf函数
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义 _sys_exit以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名奇妙的错误
char USART_RX_BUF[USART_REC_LEN]; //接收缓冲
uint8_t USART_RxFlag; //接收完成标志位
u8 Temp_Recv[3];
uint8_t Recv_LED_Flag; //是否接收到需要的LED信息
int count = 0;//计数各内容
int ok_received = 0;//判断是否接收到了ok
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断
USART_Cmd(USART1, ENABLE); //使能串口1
USART_RxFlag = 0;
Recv_LED_Flag = 0;
memset(Temp_Recv, 0, 3);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
/**
* 发送AT指令并检查响应
* @param command AT指令字符串
* @param expected_response 期望的响应字符串
* @param timeout_ms 超时时间(毫秒)
* @return 成功返回1,失败返回0
*/
int wait_for_ok(char *command,long int timeout_ms,char *expected_response)
{
int timeout = 0;
USART_RxFlag=1;
printf("%s",command);
while (!ok_received && timeout < timeout_ms)
{
// 检查是否接收到 "OK"
if (count >= 2 && strstr(USART_RX_BUF, expected_response) != NULL)
{
ok_received = 1;
}
vTaskDelay(1);
timeout++;
}
USART_RxFlag = 0;
if (ok_received)
{
// 清空缓冲区
memset(USART_RX_BUF, 0, USART_REC_LEN);
count = 0;
ok_received = 0;
return 1; // 成功收到 "OK"
}
else
{
return 0; // 超时未收到 "OK"
}
}
/**
* 等待并解析JSON报文,带超时判断
* @param timeout_ms 超时时间(毫秒)
* @param parsed_id 解析出的ID(引用传递)
* @param led_status 解析出的LED状态(引用传递)
* @param is_led_command 是否为LED控制指令(引用传递)
* @return 解析成功返回1,失败或超时返回0
*/
int wait_and_parse_json(long int timeout_ms, int *parsed_id, bool *led_status, bool *is_led_command)
{
int timeout = 0;
bool json_complete = false;
// 重置接收缓冲区
memset(USART_RX_BUF, 0, USART_REC_LEN);
count = 0;
// 等待JSON结束标记 "}}" 或超时
while (!json_complete && timeout < timeout_ms)
{
// 检查是否接收到 "}}"
if (count >= 2 && strstr(USART_RX_BUF, "}}") != NULL)
{
json_complete = true;
}
vTaskDelay(1);
timeout++;
}
if (json_complete)
{
// 解析JSON报文
if (parse_json_command(USART_RX_BUF, parsed_id, led_status, is_led_command))
{
// 清空缓冲区
memset(USART_RX_BUF, 0, USART_REC_LEN);
count = 0;
return 1; // 解析成功
}
else
{
return 0; // 解析失败
}
}
else
{
// 清空缓冲区
memset(USART_RX_BUF, 0, USART_REC_LEN);
count = 0;
return 0; // 超时
}
}
/**
* 解析JSON报文中的ID和LED状态(C语言指针版本)
* @param json_str 接收到的JSON字符串
* @param parsed_id 解析出的ID(指针传递)
* @param led_status 解析出的LED状态(指针传递)
* @param is_led_command 是否为LED控制指令(指针传递)
* @return 解析成功返回1,失败返回0
*/
int parse_json_command(char *json_str, int *parsed_id, bool *led_status, bool *is_led_command) {
char id_str[10] = {0};
char led_str[5] = {0};
// 1. 提取ID(格式:"id":"3")
char *id_pos = strstr(json_str, "\"id\":\"");
if (id_pos == NULL) return 0;
id_pos += 6; // 跳过"id":"
char *id_end = strchr(id_pos, '"');
if (id_end == NULL) return 0;
strncpy(id_str, id_pos, id_end - id_pos);
*parsed_id = atoi(id_str); // 转为整数
// 2. 检查是否包含LED字段(格式:"LED":true/false)
char *led_pos = strstr(json_str, "\"LED\":");
if (led_pos == NULL) {
*is_led_command = false;
return 1; // 存在ID但无LED字段
}
*is_led_command = true;
// 3. 提取LED状态(true/false)
led_pos += 6; // 跳过"LED":
char *led_end = strchr(led_pos, ',');
if (led_end == NULL) led_end = strchr(led_pos, '}'); // 处理末尾情况
if (led_end == NULL) return 0;
strncpy(led_str, led_pos, led_end - led_pos);
*led_status = (strcmp(led_str, "true") == 0); // 转为布尔值
return 1;
}
// 当串口1收到数据, 系统自动调用此中断函数
void USART1_IRQHandler(void) //串口1接收中断
{
u8 Serial_RxData = 0;
static int j=0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
//判断是否是微信小程序回传信息
if(Temp_Recv[0] == 's' && Temp_Recv[1] == 'e' && Temp_Recv[2] == 't')
{
USART_RxFlag = 1;
Recv_LED_Flag = 1;
memset(Temp_Recv, 0, 3);
memset(USART_RX_BUF, 0, USART_REC_LEN);
count = 0;
}
else if(Temp_Recv[0] == 's' && Temp_Recv[1] == 'e')
{
j=2;
Temp_Recv[j] = Serial_RxData;
}
else if(Temp_Recv[0] == 's')
{
j=1;
Temp_Recv[j] = Serial_RxData;
}
else
{
j=0;
Temp_Recv[j] = Serial_RxData;
}
//抓取内部信息
if(USART_RxFlag == 1)
{
USART_RX_BUF[count++] = Serial_RxData;
if(count == USART_REC_LEN-1)
{
count = 0;
memset(USART_RX_BUF, 0, USART_REC_LEN);
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
#endif
最后贴上esp8266的代码文件
#ifndef __ESP8266_H_
#define __ESP8266_H_
#include "sys.h"
#include "usart.h"
#include <stdbool.h>
void esp8266_Init();
int esp8266_mqtt_reply(int id);
int esp8266_mqtt_send_int(const char *property_name, int value);
int esp8266_mqtt_send_bool(const char *property_name, bool value);
#endif
#include "esp8266.h"
#include "delay.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "Task.h"
// 设备和网络配置
#define product_id "PtAFXPCG49"
#define device_id "DB01"
#define device_key "version=2018-10-31&res=products%2FPtAFXPCG49%2Fdevices%2FDB01&et=1756260513&method=sha1&sign=TcuMb4Vdl%2FQiGc6AkEceJHmt2pI%3D"
#define wifi_name "CMCC-yr24"
#define wifi_password "4fy@brba"
/**
* ESP8266初始化函数
* @param bound 串口波特率
* @return 初始化成功返回1,失败返回0
*/
void esp8266_Init()
{
// 定义各命令的超时时间(毫秒)
const uint32_t AT_TIMEOUT = 1000; // 普通AT命令超时
const uint32_t WIFI_TIMEOUT = 30000; // WiFi连接超时
const uint32_t MQTT_TIMEOUT = 10000; // MQTT连接超时
// 用于构建AT命令的缓冲区
char cmd_buffer[256];
// 步骤1:测试AT通信
while(!wait_for_ok("AT\r\n", AT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤2:软重启ESP8266
while(!wait_for_ok("AT+RST\r\n", AT_TIMEOUT, "ready"));
vTaskDelay(1000); // 等待模块完全重启
// 步骤3:设置WiFi模式为STA
while(!wait_for_ok("AT+CWMODE=1\r\n", AT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤4:启用DHCP
while(!wait_for_ok("AT+CWDHCP=1,1\r\n", AT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤5:连接WiFi
snprintf(cmd_buffer, sizeof(cmd_buffer), "AT+CWJAP=\"%s\",\"%s\"\r\n", wifi_name, wifi_password);
while(!wait_for_ok(cmd_buffer, WIFI_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤6:配置MQTT用户信息
snprintf(cmd_buffer, sizeof(cmd_buffer), "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n",
device_id, product_id, device_key);
while(!wait_for_ok(cmd_buffer, AT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤7:连接MQTT服务器
while(!wait_for_ok("AT+MQTTCONN=0,\"mqtts.heclouds.com\",1883,1\r\n", MQTT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤8:订阅属性回复主题
snprintf(cmd_buffer, sizeof(cmd_buffer), "AT+MQTTSUB=0,\"$sys/%s/%s/thing/property/post/reply\",0\r\n",
product_id, device_id);
while(!wait_for_ok(cmd_buffer, AT_TIMEOUT, "OK"));
vTaskDelay(100);
// 步骤9:订阅属性设置主题
snprintf(cmd_buffer, sizeof(cmd_buffer), "AT+MQTTSUB=0,\"$sys/%s/%s/thing/property/set\",0\r\n",
product_id, device_id);
while(!wait_for_ok(cmd_buffer, AT_TIMEOUT, "OK"));
vTaskDelay(100);
while(!esp8266_mqtt_send_bool("LED", false));
vTaskDelay(100);
}
/**
* 发送MQTT回复消息到OneNET平台
* @param id 平台下发指令的ID值
* @return 发送成功返回1,失败返回0
*/
// 当接收到平台下发的指令ID为36时,回复成功
//esp8266_mqtt_reply(36);
int esp8266_mqtt_reply(int id)
{
// 定义命令缓冲区
char cmd_buffer[256];
// 定义消息内容缓冲区
char msg_buffer[128];
// 构建JSON格式的消息内容(修正转义)
snprintf(msg_buffer, sizeof(msg_buffer),
"{\\\"id\\\":\\\"%d\\\"\\,\\\"code\\\": 200\\,\\\"msg\\\":\\\"success\\\"}", id);
// 构建完整的AT+MQTTPUB命令
snprintf(cmd_buffer, sizeof(cmd_buffer),
"AT+MQTTPUB=0,\"$sys/%s/%s/thing/property/set_reply\",\"%s\",0,0\r\n",
product_id, device_id, msg_buffer);
// 发送命令并等待响应
return wait_for_ok(cmd_buffer, 5000, "OK");
}
/**
* 发送单个传感器值到OneNET平台
* @param property_name 要上报的属性名称,如"Temp"或"Humi"
* @param value 属性值
* @return 发送成功返回1,失败返回0
*/
// 发送温度值16
//esp8266_mqtt_send_int("Temp", 16);
// 发送湿度值66
//esp8266_mqtt_send_int("Humi", 66);
int esp8266_mqtt_send_int(const char *property_name, int value)
{
// 定义命令缓冲区
char cmd_buffer[256];
// 定义消息内容缓冲区
char msg_buffer[128];
// 构建JSON格式的消息内容(修正转义)
snprintf(msg_buffer, sizeof(msg_buffer),
"{\\\"id\\\":\\\"123\\\"\\,\\\"params\\\":{\\\"%s\\\":{\\\"value\\\":%d}}}",
property_name, value);
// 构建完整的AT+MQTTPUB命令
snprintf(cmd_buffer, sizeof(cmd_buffer),
"AT+MQTTPUB=0,\"$sys/%s/%s/thing/property/post\",\"%s\",0,0\r\n",
product_id, device_id, msg_buffer);
// 发送命令并等待响应(注意:MQTT响应通常是"OK"而非"success")
return wait_for_ok(cmd_buffer, 5000, "OK");
}
/**
* 发送布尔类型的传感器值到OneNET平台
* @param property_name 要上报的属性名称,如"LED"
* @param value 属性值(true或false)
* @return 发送成功返回1,失败返回0
*/
// 发送LED开启状态
//esp8266_mqtt_send_bool("LED", true);
// 发送LED关闭状态
//esp8266_mqtt_send_bool("LED", false);
int esp8266_mqtt_send_bool(const char *property_name, bool value)
{
// 定义命令缓冲区
char cmd_buffer[256];
// 定义消息内容缓冲区
char msg_buffer[128];
// 将布尔值转换为JSON格式的字符串
const char *bool_str = value ? "true" : "false";
// 构建JSON格式的消息内容(修正转义)
snprintf(msg_buffer, sizeof(msg_buffer),
"{\\\"id\\\":\\\"123\\\"\\,\\\"params\\\":{\\\"%s\\\":{\\\"value\\\":%s}}}",
property_name, bool_str);
// 构建完整的AT+MQTTPUB命令
snprintf(cmd_buffer, sizeof(cmd_buffer),
"AT+MQTTPUB=0,\"$sys/%s/%s/thing/property/post\",\"%s\",0,0\r\n",
product_id, device_id, msg_buffer);
// 发送命令并等待响应(注意:MQTT响应通常是"OK"而非"success")
return wait_for_ok(cmd_buffer, 5000, "OK");
}
创建不易。希望大家能够点赞、收藏、关注。谢谢大家!!!!!!!!