物联网实践教程:使用微信小程序和OneNET平台MQTT实现51/STM32单片机远程智能控制和数据通信【代码篇】

章节

本项目教程总共分为四节
1.创建OneNET新版MQTT设备:为微信小程序与单片机通信打基础(微信小程序通信单片机前置任务)

2.ESP8266-01s入门:烧录AT固件与OneNET MQTT通信教程包含MQTT.fx1.7.1教程(微信小程序通信单片机前置任务)

3.(当前文章)物联网实践教程:微信小程序结合OneNET平台MQTT实现51/STM32单片机智能控制【单片机代码篇】

4.物联网实践教程(终章):微信小程序结合OneNET平台MQTT实现51/STM32单片机智能控制【微信小程序篇】

文章提要


欢迎跟随本教程,我们将一步一步地构建起一个智慧型项目,使您的单片机设备能够通过网络与世界互联。整个项目可以概括为以下四个核心阶段,每一个阶段都有一篇文章去详细说明

  1. 搭建物联网桥梁:首先,我们将在OneNET平台上注册并创建您的MQTT设备。这个过程为我们打下了互联网通信的基础。

  2. 引擎启动:接下来,我们需要为我们的ESP8266模块烧录AT固件,确保它具备连接到MQTT服务器的能力。

  3. 编织数据网络【此篇文章】:有了硬件基础后,是时候通过精心编写的代码让单片机通过ESP8266模块与MQTT服务器进行沟通。

  4. 微信小程序的桥梁:最终,我们将打造一个微信小程序,它不仅可以实时展示单片机传递的数据,还可以发送指令控制单片机。

透过这个分阶段的布局,即使是初学者也能跟随步骤轻松完成整个设定。让我们开始这段富有成就感的旅程吧!


视频演示:

ESP8266视频演示

引言

欢迎阅读本教程,在这里我们将解锁物联网世界的大门。在这个互联网高速发展的时代,物联网已成为连接实体世界与数字世界的重要桥梁。通过传感器、控制器以及网络技术,物联网将日常对象智能化,无缝融入互联网大生态。如果您想在物联网项目中实现设备间的智能通信,那么您来对地方了。
不管您是物联网爱好者,还是嵌入式系统开发的初学者,本教程都将为您提供一条清晰的路径,让您能够轻松搭建智能物联网解决方案的蓝图

在我们共同的探索之旅中,我将逐步引导您构建出能够实现数据实时上传和接收微信小程序指令的系统。虽然这篇文章篇幅较长,但它为您提供的细节将是非常宝贵的,确保您能够按步就班、稳扎稳打地完成设置。文章被划分为多个模块化的章节,您可以直接跳转到您最关心的部分。如果您有任何疑问、建议或反馈,我非常期待与您讨论。

物联网概述

物联网,或称IoT(Internet of Things),指的是通过传感器、软件等技术,连接各种物品与互联网的技术架构,实现物品信息互联、智能化管理与运营。物联网在智能家居、工业自动化、环境监测等领域都有广泛应用。

本文目标

本文将详细介绍如何运用51单片机STM32单片机结合ESP8266 Wi-Fi模块,与OneNET IoT平台进行MQTT通信,实现俩个主要目的:

  1. 数据上传:将单片机采集的数据(例如光线强度或温度信息)上传到OneNET,并通过微信小程序实时展示。
  2. 下发指令:用户通过微信小程序界面发送控制指令(例如控制灯开关),并通过OneNET平台传递给单片机执行。

实践意义

通过本文的学习,不仅能提升您对物联网技术的理解和应用能力,还能够让您对单片机与Wi-Fi模块的搭配使用MQTT协议的运作机制有更深入的掌握。此外,本项目也能够增强您在开发相关微信小程序的实战经验。

项目所需材料清单:

在开始我们的项目之前,请确保您拥有以下所需硬件和软件:

硬件:

  • 51单片机STC89C52RC / 32单片机
  • ESP8266-01s WIFI模块
  • 软件及服务:

  • OneNET新版MQTT服务
  • ESP8266固件烧入软件
  • STC-ISP单片机烧录软件
  • ESP8266-01s固件
  • XCOM串口助手
  • 所有相关材料和软件已经在压缩包中准备好,您可以在文章底部找到下载链接。

    更换头文件

    打开STC—ISP

    打开Keil5

    可以选择原本的#include <REGX52.H>,也可以更换#include <STC89C5xRC.H>

    代码讲解

    代码主要实现功能

    根据提供的代码,可以概括出以下功能:

    1. 初始化ESP8266无线模块
      利用ESP8266_Clear()函数清空缓冲区,然后通过ESP8266_Init()函数来初始化ESP8266模块。此函数发送系列AT命令来配置UART参数,重启ESP8266,设置Wi-Fi工作模式,连接到指定Wi-Fi网络,并配置MQTT连接。

    2. UART通信处理
      UART_Routine()函数用作UART接收中断的处理程序,用于读取串口数据并存储到缓冲区中。代码中定义了中断服务程序,当接收到数据时,会存储到缓冲区并进行后续处理。

    3. 发送数据至服务器
      ESP8266_SendData()函数用于将温度、湿度、ADC等传感器数据格式化为字符串,并发送到服务器。通过调用UART库函数来串行发送数据。

    4. ESP8266指令接收与处理
      使用ESP8266_ReceiveCmd()函数来处理接收到的命令。此函数从缓冲中提取出JSON格式的时间字符串,并根据特定的标志翻转P2端口的状态。

    5. JSON数据提取
      extractJsonValue()函数用于提取JSON格式数据中的键值对。如果找到相应的键,则将值写入提供的缓冲区。

    6. ESP8266 AT指令交互
      使用ESP8266_SendCmd()函数来发送AT指令并等待响应。函数通过UART发送指令,并等待接收ESP8266的响应。

    7. ESP8266缓冲区管理
      ESP8266_Clear()函数负责清除ESP8266的缓冲区数据,以及重置接收计数器。ESP8266_WaitRecive()函数用于监测数据是否接收完毕或者是否超时。

    8. 主循环及硬件驱动
      main函数中初始化各个模块如LCD, UART, ESP8266,并进入一个循环,不断地从DS18B20温度传感器读取数据并通过ESP8266模块发送到服务器。

    9. 读取DS18B20温度传感器
      DS18B20.c文件中的DS18B20_ConvertT()用于发起温度转换,DS18B20_ReadT()用于读取温度值。

    10. 读取XPT2046可控电阻和光敏电阻的ADC值
      XPT2046.c文件中的XPT2046_ReadAD()负责读取通过XPT2046芯片的各个通道的模拟值,并转换为数字量。

    通过这些功能,代码主要实现了温度数据和ADC值的采集,并通过ESP8266模块发送到服务器。同时还处理了从服务器接收到的命令。

    ESP8266联网代码

    在上一篇文章中详细讲述了ESP8266如何通过AT指令连接网络,这一篇文章则通过代码来实现发送AT指令来连接网络

    下面是对每个函数做出详细的解读

    以下是对于ESP8266模块中各函数详细功能的讲解:

    ESP8266_Clear()

    void ESP8266_Clear()
    

    该函数用于清空ESP8266的接收缓冲区。它通过将整个esp8266_buf数组填充为0,并将接收字节计数器esp8266_cnt置为0实现。

    ESP8266_WaitRecive()

    unsigned char ESP8266_WaitRecive(void)
    

    该函数用于等待ESP8266接收数据完成或超时。如果缓冲区为空,则返回REV_WAIT,等待数据接收;如果检测到缓冲区计数器esp8266_cnt停止增加,则认为数据接收完成,返回REV_OK

    ESP8266_SendCmd()

    unsigned char ESP8266_SendCmd(char *cmd, char *res)
    

    此函数用于向ESP8266发送AT命令并等待响应。函数首先发送命令字符串cmd,然后进入等待循环,如果在超时时间(200*50ms)内收到了期望的响应res,则返回0;否则超时后返回1。

    extractJsonValue()

    void extractJsonValue(const char* buffer, const char* key, char* value, size_t valueMaxLen)
    

    这个函数的作用是从JSON字符串中提取出指定键的值。它会查找key,然后分析出对应的值,将其拷贝到value缓存区中。

    ESP8266_ReceiveCmd()

    void ESP8266_ReceiveCmd(void)
    

    此函数处理从ESP8266接收到的命令。它将解析缓冲区中的时间值,并根据特定的条件来控制硬件的状态。解析完毕后,会通过调用ESP8266_Clear()清空缓冲区,并重置标志位Flag

    ESP8266_Init()

    void ESP8266_Init(void)
    

    该函数负责ESP8266模块的初始化。它会连续发送多个AT命令以配置UART波特率、Wi-Fi模式、连接到指定的Wi-Fi热点和设置MQTT服务器。成功的标志是能够连续收到对应的正确响应。

    ESP8266_SendData()

    void ESP8266_SendData(float temp, float rhro, float adcx)
    

    这个函数向ESP8266发送温度temp、可调电阻rhro和ADC值adcx。它首先清空缓冲区,然后将浮点数格式化为字符串并发送MQTT发布命令。当发送完整个数据后,检查是否收到了期望的响应或者是否有标志位设置。

    UART_Routine()

    void UART_Routine(void) interrupt 4
    

    UART_Routine()是UART接收的中断服务程序,当接收到一个字节时将会被调用。它负责将接收的字节存入esp8266_buf缓冲区,并更新相应的计数器。如果缓冲区已满或者收到指定的字符序列,该函数将会根据情况设置标志位Flag或者重置缓冲区计数器。

    下面是详细的代码:

    下方的中断有个写法是因为下发数据时串口接收的内容开头:+MQTTSUBRECV:
    所以判定
    else if (esp8266_buf[0]=='+' && esp8266_buf[5]=='S') { Flag = 1; // 根据接收到的特定字符序列设置标志位 esp8266_cnt = 0; }

    #include <STC89C5xRC.H>
    #include "esp8266.h"
    #include "Delay.h"
    #include "UART.h"
    #include <string.h>
    #include <stdio.h>
    #include "LCD1602.h"
    #define MAX_ESP8266_BUFFER 47 // 定义ESP8266缓冲区的最大大小,用以节省RAM
    
    // ESP8266 AT命令字符串定义用于配置Wi-Fi连接和MQTT连接
    #define ESP8266_WIFI_INFO        "AT+CWJAP=\"ESP\",\"123456789\"\r\n"         // 配置Wi-Fi热点信息
    #define ESP8266_ONENET_INFO      "AT+MQTTCONN=0,\"mqtts.heclouds.com\",1883,1\r\n"  // 配置OneNet MQTT服务器连接信息
    #define ESP8266_USERCFG_INFO     "AT+MQTTUSERCFG=0,1,\"test\",\"05Dy8U26rg\",\"version=2018-10-31&res=products%2F05Dy8U26rg%2Fdevices%2Ftest&et=2017757596000&method=md5&sign=QdBLLnRoBevwqKx7TUjQdQ%3D%3D\",0,0,\"\"\r\n" // 用户账号配置信息
    #define ESP8266_PUB_TOPIC        "$sys/05Dy8U26rg/test/thing/property/post"    // MQTT发布主题
    
    unsigned char esp8266_buf[MAX_ESP8266_BUFFER]; // ESP8266缓冲区数组
    unsigned short esp8266_cnt = 0, esp8266_cntPre = 0; // 接收计数器和前一状态计数器
    bit Flag = 0;
    /**
      * @brief  清空ESP8266缓冲区
      * @param  无
      * @retval 无
      */
    void ESP8266_Clear()
    {
        memset(esp8266_buf, 0, sizeof(esp8266_buf)); // 将缓冲区内存清零
        esp8266_cnt = 0; // 重置接收计数器
    }
    
    /**
      * @brief  等待ESP8266接收数据完毕或等待超时
      * @param  无
      * @retval unsigned char 返回接收状态: REV_WAIT=等待接收/REV_OK=接收完成
      */
    unsigned char ESP8266_WaitRecive(void)
    {
        if(esp8266_cnt == 0) {
            return REV_WAIT; // 如果接收计数器为0,表示还未接收到数据,返回等待状态
        }
    
        if(esp8266_cnt == esp8266_cntPre) {
            esp8266_cnt = 0; // 接收完成后重置接收计数器
            return REV_OK; // 如果接收计数器与前一状态相同,表示接收完毕,返回接收完成状态
        }
    
        esp8266_cntPre = esp8266_cnt; // 更新前一状态计数器
        return REV_WAIT; // 否则返回等待状态
    }
    
    /**
      * @brief  发送AT命令给ESP8266并等待响应
      * @param  *cmd 待发送的AT命令字符串
      * @param  *res 期望得到的响应字符串
      * @retval unsigned char 返回结果: 0=成功接收到期望响应/1=超时或未得到期望响应
      */
    unsigned char ESP8266_SendCmd(char *cmd, char *res)
    {
        unsigned char timeOut = 200; // 超时计数器
    
        UART_SendString((unsigned char *)cmd); // 发送AT命令字符串
        while(timeOut--) // 启动超时计数
        {
            if(ESP8266_WaitRecive() == REV_OK) // 检测是否接收到数据
            {
                if(strstr((const char *)esp8266_buf, res) != NULL) // 检测接收缓冲区中是否含有期望的响应字符串
                {
    				if(Flag == 0)
    					ESP8266_Clear(); // 清空缓冲区
                    return 0; // 如果含有期望的响应,返回成功标志0
                }
            }
            Delay(50); // 等待一定时间后再次检测
        }
    
        return 1; // 如果超时,返回1表示超时或失败
    }
    
    /**
      * @brief  从JSON字符串中提取时间值
      * @param  *buffer 包含JSON的字符串
      * @param  *time 用于存储提取到的时间的缓冲区
      * @param  timeMaxLen 时间缓冲区的最大长度
      * @retval 无
      */
    void extractJsonValue(const char* buffer, const char* key, char* value, size_t valueMaxLen) {
        const char *jsonStart, *jsonEnd, *keyStart, *keyValueStart, *keyValueEnd;
        size_t keyValueLength;
        size_t keyLength = strlen(key);
    
        char keyPattern[15]; // 预留足够的空间存储 key 和查找格式串
        sprintf(keyPattern, "\"%s\":\"", key); // 构建查找模式串,即 "key":
    
        // 查找JSON字符串的开头
        jsonStart = strchr(buffer, '{');
        if (jsonStart == NULL) return;
    
        // 查找JSON字符串的结尾
        jsonEnd = strrchr(jsonStart, '}');
        if (jsonEnd == NULL) return;
    
        // 查找键
        keyStart = strstr(jsonStart, keyPattern);
        if (keyStart == NULL) return;
    
        // 定位至键值的开始位置
        keyValueStart = keyStart + strlen(keyPattern);
    
        // 查找键值的结束双引号,确定键值的结束位置
        keyValueEnd = strchr(keyValueStart, '\"');
        if (keyValueEnd == NULL || keyValueEnd > jsonEnd) return;
    
        // 根据键值的起始和结束位置,计算键值的长度
        keyValueLength = keyValueEnd - keyValueStart;
    
        // 拷贝键值到value缓冲区中
        if (keyValueLength < valueMaxLen) {
            strncpy(value, keyValueStart, keyValueLength);
            value[keyValueLength] = '\0'; // 添加字符串终止字符\0
        } else {
            // 如果提供的缓冲区不够大,那么可以根据实际情况处理,这里简单地截断字符串
            strncpy(value, keyValueStart, valueMaxLen - 1);
            value[valueMaxLen - 1] = '\0'; // 添加字符串终止字符\0
        }
    }
    
    /**
      * @brief  接收处理函数,用于处理接收到的命令
      * @param  无
      * @retval 无
      */
    void ESP8266_ReceiveCmd(void)
    {
        char timeBuffer[16];
        Delay(50); // 等待一段时间,确保数据完全接收
        extractJsonValue(esp8266_buf,"Time",timeBuffer, sizeof(timeBuffer)); // 从缓冲区提取时间值
    
        if (strstr((const char *)timeBuffer, "LDE:0") != NULL) {
            P2 = ~P2; // 如果时间数据包含特定字符串,则翻转P2端口的状态
        }
        Flag = 0; // 重置标志位
        ESP8266_Clear(); // 清空接收缓冲区
    }
    
    /**
      * @brief  初始化ESP8266模块
      * @param  无
      * @retval 无
      */
    void ESP8266_Init(void)
    {
    	LCD_ShowString(1,1,"Connecting...");
        ESP8266_Clear(); // 清空ESP8266缓冲区
    	LCD_ShowString(2,1,"1%");
        // 以下是一系列发送AT命令并等待响应的操作,用于配置ESP8266
        // 设置UART波特率等参数
        while (ESP8266_SendCmd("AT+UART=9600,8,1,0,0\r\n", "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"12%");
        // 确认AT命令可以正确发送并得到响应
        while (ESP8266_SendCmd("AT\r\n", "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"24%");
        // 重启ESP8266
        while (ESP8266_SendCmd("AT+RST\r\n", ""))
            Delay(1500);
    	LCD_ShowString(2,1,"37%");
        // 设置Wi-Fi模式
        while (ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"56%");
        // 打开DHCP
        while (ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"62%");
        // 配置Wi-Fi连接
        while (ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
            Delay(100);
    	LCD_ShowString(2,1,"76%");
        // 设置MQTT登录用户信息
        while (ESP8266_SendCmd(ESP8266_USERCFG_INFO, "OK"))
            Delay(5500);
    	LCD_ShowString(2,1,"82%");
        // 发起向指定MQTT服务器的连接
        while (ESP8266_SendCmd(ESP8266_ONENET_INFO, "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"93%");
        // 订阅指定Topic
        while (ESP8266_SendCmd("AT+MQTTSUB=0,\"$sys/05Dy8U26rg/test/thing/property/set\",0\r\n", "OK"))
            Delay(100);
    	LCD_ShowString(2,1,"100%");
    	LCD_ShowString(2,7,"Success!");
    	Delay(1500);
    	LCD_ShowString(2,1,"               ");
    	LCD_ShowString(1,1,"               ");
    }
    
    /**
      * @brief  向ESP8266发送温度、可调电阻和ADC数据
      * @param  temp 温度值
      * @param  rhro 可调电阻
      * @param  adcx ADC值
      * @retval 无
      */
    void ESP8266_SendData(float temp, float rhro, float adcx)
    {
        char sbuf_temp[6], sbuf_rhro[6], sbuf_adcx[6]; // 定义字符串缓冲区以存储浮点数转换后的字符串
    
        ESP8266_Clear(); // 清空缓冲区
        while (1)
        {
            // 发送温度湿度ADC数据的AT命令
            UART_SendString("AT+MQTTPUB=0,\"$sys/05Dy8U26rg/test/thing/property/post\",\"{\\\"id\\\":\\\"123\\\"\\,\\\"params\\\":");
    
            UART_SendString("{\\\"temp\\\":{\\\"value\\\":");
            sprintf(sbuf_temp, "%.1f", temp); // 将温度转换为字符串
            UART_SendString(sbuf_temp);
            UART_SendString("}\\,");
    
            UART_SendString("\\\"humi\\\":{\\\"value\\\":");
            sprintf(sbuf_rhro, "%.1f", rhro); // 将可调电阻转换为字符串
            UART_SendString(sbuf_rhro);
            UART_SendString("}\\,");
    
            UART_SendString("\\\"adcx\\\":{\\\"value\\\":");
            sprintf(sbuf_adcx, "%.1f", adcx); // 将ADC值转换为字符串
            UART_SendString(sbuf_adcx);
            if (ESP8266_SendCmd("}}}\",0,0\r\n", "OK") == 0 || Flag == 1) break; // 如果发送成功或Flag标志被置1,则跳出循环
    
            memset(sbuf_temp, 0, sizeof(sbuf_temp)); // 清空温度缓冲区
            memset(sbuf_adcx, 0, sizeof(sbuf_adcx)); // 清空ADC缓冲区
            memset(sbuf_rhro, 0, sizeof(sbuf_rhro)); // 清空湿度缓冲区
        }
    }
    
    /**
      * @brief  UART中断服务函数,用于处理UART接收中断
      * @param  无
      * @retval 无
      */
    void UART_Routine(void) interrupt 4
    {
        unsigned char receivedChar;
        if (RI) // 如果接收中断标志位被置位
        {
            RI = 0; // 清除接收中断标志位
            receivedChar = SBUF; // 从串行缓冲寄存器SBUF中读取接收到的字符
            if (esp8266_cnt < MAX_ESP8266_BUFFER) // 如果缓冲区未满
            {
                esp8266_buf[esp8266_cnt++] = receivedChar; // 将接收到的字符放入缓冲区
            }
             else if (esp8266_buf[0]=='+' && esp8266_buf[5]=='S')
            {
                Flag = 1; // 根据接收到的特定字符序列设置标志位
    			esp8266_cnt = 0;
            }
            else
            {
                esp8266_cnt = 0; // 如果缓冲区已满,重置缓冲区计数器
            }
        }
    }
    
    

    ESP8266.h

    #ifndef __ESP8266_H__
    #define __ESP8266_H__ 
    #define REV_OK		0	
    #define REV_WAIT	1	
    
    void ESP8266_Clear(void);
    
    unsigned char ESP8266_WaitRecive(void);
    
    unsigned char ESP8266_SendCmd(char *cmd, char *res);
    
    void ESP8266_SendData(float temp, float humi, float adcx);
    
    void ESP8266_Init(void);
    void ESP8266_Receiver(void);
    void JSON_ESP8266(unsigned char *esp8266_buf);
    void ESP8266_ReceiveCmd(void);
    #endif
    
    

    主函数

    #include <STC89C5xRC.H>
    #include "UART.h"
    #include "ESP8266.h"
    #include "Delay.h" 
    #include "DS18B20.h"
    #include "LCD1602.h"
    #include "XPT2046.h"
    extern bit Flag;
    unsigned char adcx;
    unsigned char rhro;
    float temp;
    void main()
    {
    	LCD_Init();
    	UART_Init();
    	Delay(500);
    	
    	ESP8266_Init();
    	LCD_ShowString(1,1,"temp");
    	LCD_ShowString(1,7,"adcx");
    	LCD_ShowString(1,13,"rhro");
    	DS18B20_ConvertT();
    	DS18B20_ReadT();
    	while(1)
    	{	
    		DS18B20_ConvertT();
    		adcx = XPT2046_ReadAD(XPT2046_VBAT);
    		rhro = XPT2046_ReadAD(XPT2046_XP);
    		temp = DS18B20_ReadT();
    		if(Flag != 1) ESP8266_SendData(temp,rhro,adcx);	
    		else ESP8266_ReceiveCmd();
    		LCD_ShowNum(2,1,(unsigned int)temp,2);
    		LCD_ShowString(2,3,".");
    		LCD_ShowNum(2,4,(temp - (unsigned int)temp) * 10,1);
    		LCD_ShowNum(2,7,adcx,3);
    		LCD_ShowNum(2,13,rhro,3);
    		Delay(1500);
    	}
    }
    

    温度,光照,可控电阻的代码就不再这里赘述了,代码都放在了代码包里,可自行下载

    资源包下载

    资源包下载

    https://pan.baidu.com/s/15iw7kzn4GMUKP3g8809PUA?pwd=jq91

    结语

    紧跟本文,您将能够以实际动手操作的方式,探究物联网的奇妙,打造出一个真正能够与微信生态系统结合的智能设备控制方案。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 物联网实践教程:使用微信小程序和OneNET平台MQTT实现51/STM32单片机远程智能控制和数据通信【代码篇】

    发表评论