第三章 Freertos物联网实战esp8266模块
在本次物联网温湿度检测中,采用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;//判断是否接收到了okvoid 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.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_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;//PA10GPIO_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;//抢占优先级0NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1NVIC_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); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断USART_Cmd(USART1, ENABLE); //使能串口1USART_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:软重启ESP8266while(!wait_for_ok("AT+RST\r\n", AT_TIMEOUT, "ready"));vTaskDelay(1000); // 等待模块完全重启// 步骤3:设置WiFi模式为STAwhile(!wait_for_ok("AT+CWMODE=1\r\n", AT_TIMEOUT, "OK"));vTaskDelay(100);// 步骤4:启用DHCPwhile(!wait_for_ok("AT+CWDHCP=1,1\r\n", AT_TIMEOUT, "OK"));vTaskDelay(100);// 步骤5:连接WiFisnprintf(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");
}
创建不易。希望大家能够点赞、收藏、关注。谢谢大家!!!!!!!!