第三章 Freertos物联网实战esp8266模块

发布于:2025-07-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

        在本次物联网温湿度检测中,采用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");
}

        创建不易。希望大家能够点赞、收藏、关注。谢谢大家!!!!!!!!


网站公告

今日签到

点亮在社区的每一天
去签到