美文网首页C语言&嵌入式物联之美-物联网专业
51单片机实战:物联网初步のESP8266无线网络模块

51单片机实战:物联网初步のESP8266无线网络模块

作者: 兔子泽 | 来源:发表于2017-02-10 16:44 被阅读13674次

    文章框架

    文章框架

    前言

    扯单一周目BOSS出现!请速速讨伐!
    ESP8266是我第一个,也是唯一一个接触过的无线网络模块,我非常喜欢!大部分网上的教程都是ARM架构单片机配用ESP8266。我比较抠门,给大家上51版的。
    我记得当时和老板承诺可以做成无线通信的时候我也不是很确定,心里一直打鼓。好在最后做出来了,很有成就感!所以今天的代码案例是我把我之前在公司写的项目代码简化之后分享给大家的。虽然结构有点恶心,但应该还是入得了眼的。
    如果有没看懂的地方,大家评论区告诉我。

    需求

    利用ESP8266芯片,通过无线网络同电脑建立TCP连接。接收电脑传来的消息并显示在LCD上。

    每条信息以\结尾。
    每条信息字符数不超过32。
    信息内容只能是ASCII表内的字符。


    清单

    硬件

    学习板
    • 简介
      由于这次要用到WIFI模块,没法用Proteus模拟,所以就上真家伙。
      自己焊板子就太麻烦了,所以就买学习板(其实我不会焊)。

      QX-MINI51
      这是我入的学习板,你也可以入其他的,如果把握不大就跟我入一样的。
      这个是我在淘宝上小挑了一阵子选的板子:QX-MINI51。尺寸不大不小,想玩的基本都有。配备的单片机是STC89C52。

      淘宝链接

    • 电路
      下面是QX-MINI51在本节会用到的电路图

      STC89C52
      使用89C52芯片,和我们之前用的单片机一样。
      供电&USB串口
      CH340芯片是用于普通串口和USB转换的,其上的RXD为串口输入,TXD为串口输出。UD+UD-对应USB数据线。
      虽说是用了USB,其实和普通串口通信线用法一致,只是人家成品方便我们使用封装成了USB。这里不深究。
      流水灯
      流水灯在本节中的角色是辅助调试(具体的看后面),这里有用的信息就是让你知道这8个流水灯接哪了。
    ESP8266
    • 简介
      主角,简而言之就是Wi-Fi模块,属于网络层以上的设备。拥有MAC地址和IP地址,支持UDP和TCP。
      ESP8266
      这款的型号是ESP8266-01,其他的和QX-MINI51配合使用不太方便。
      淘宝链接
    • 参数
    型号 主芯片 无线标准 工作电压 安全机制 支持模式
    ESP8266-01 ESP8266 IEEE 802.11b/g/n 3.3V WEP/WPA-PSK/WPA2-PSK STA、AP、STA+AP

    STA 模式:ESP8266模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。
    AP 模式:ESP8266模块作为热点,实现手机或电脑直接与模块通信,实现局域网无线控制。
    STA+AP 模式:两种模式的共存模式,即可以通过互联网控制可实现无缝切换,方便操作。

    引脚图

    上图对8个针脚进行说明。

    UTXD GND CH_PD GPIO2 GPIO16 GPIO0 VCC URXD
    发送 接地 高电平工作 |||电源 接收

    本例只用到这五个引脚(画“\”的不用),其他引脚的说明资料请自行到淘宝链接处下载。

    • AT指令
      操作ESP8266芯片是靠AT指令的,类似之前的操作LCD1602(《51单片机实战:液晶显示器のLCD1602》)。操作LCD1602的指令都是一个字节的十六进制码,比较难记和理解。AT指令是字符串形式的指令,一般都是单词缩写,对可笑的人类比较友好。
      之前和LCD1602交互是靠并口传输,而这次是用串口传输(串口传输简例:《51单片机实战:与计算机异步串行通信》),所以这次的Wi-Fi通信是建立在串口通信基础上的。

      AT指令集下载链接
      AT指令使用示例下载链接

    • 波特率
      注意,这个模块的默认波特率是115200本例也是根据这个波特率进行演示的。若想改变波特率请使用以下语句进行修改:

    AT+CIOBAUD=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>
    

    如:

    AT+CIOBAUD=9600,8,1,0,0  
    

    软件

    程序 说明 下载
    UartAssist 串口调试助手,用来给单片机发送消息 度娘网盘
    NetAssist 网络调试助手,在电脑端建立TCP连接与单片机的ESP8266进行通信 度娘网盘
    STC-ISP STC单片机工具集,很强大,可烧录程序,可串口调试等等 度娘网盘
    CH340驱动 CH340的USB驱动,如果没有这个驱动,你的电脑可能识别不到单片机(识别到CH340就相当于识别到单片机的串口。) 度娘网盘
    51单片机波特率初值计算工具 用于计算在各种波特率和晶振频率等参数下计时器的初值,属于辅助工具,省的还得算。 度娘网盘

    分析

    调试

    在开始用单片机直接和无线模块通信之前,首先要绕过单片机直接和无线模块通信以确定其可以使用和接入默认网络(接入一个热点后,模块每次断电后启动都会自动连接该热点),这样可以让单片机少做很多事情(要知道我们用的是51单片机,硬件资源极其有限,能省则省)。

    • TXD/RXD反接
      首先要绕过单片机直接给ESP8266下指令,就要用电脑的串口。但ESP-01是针脚接口,所以我们可以利用QX-MINI51上的针脚和USB接口。

      回看前面QX-MINI51的主控芯片和USB的电路图,可以发现,单片机的串口引脚是被并联式的暴露再外的,分别为:P30 - P31USB,前者为针脚接口,后者为USB接口。且它们的RXD和TXD的数据信息是一摸一样的,注意并不是两个独立的串口,是同一个串口。比方说,单片机要往出发送一个1,P30针脚和USB都会是往出发送一个1。

      所以我们利用这个特点,将ESP8266的TXD接到P31(单片机的TXD),RXD接到P30(单片机的RXD),这样就等于让ESP8266直接和USB打交道,也就是和电脑直接打交道了。

      为什么叫反接,我一般管RXD对TXD叫正接。嗯…

      接线图
      强势秀一波画工!
      我觉得应该可以看得懂吧,ESP8266的引脚说明请看前面的引脚图。看红色号码对应接线,其中4、5、6号引脚不用。
    实物图

    这个图就很难看出线是怎么连的了,所以你要忍受我丑陋的画工。

    • 接入网络
      打开串口调试助手,调好参数
      串口调试助手,设置参数
      其中串口号你连的哪个串口就设置哪个串口号,我是连的COM3。
      另一个要注意的就是波特率要115200(ESP-01的默认波特率,也可以统一改为9600)。
      打开串口后,给开发板上电。你的串口调试助手会有信息出来(文本显示,不要十六进制显示)。
    ?諄MEM CHECK FAIL!!!
    d{$弬s
    Ai-Thinker Technology Co. Ltd.
    
    invalid
    

    显示的信息类似上面,你可以先给个测试命令AT看看是否可以接受指令

    注意!每个指令后要跟回车再发送!

    如果返回OK则说明指令可以被接收并识别。

    如果下面的指令都会返回ERROR(在没有给错指令的情况下),可以尝试AT+RST重启模块。

    1. 更改模式
    指令:AT+CWMODE?
    一般情况下,第一次使用会返回2,也就是AP模式,我们不用它发热点,所以要改回Station模式(模式1)。

    指令:AT+CWMODE?
    一般情况下,第一次使用会返回2,也就是AP模式,我们不用它发热点,所以要改回Station模式(模式1)。

    指令:AT+CWMODE=1
    返回:OK则成功

    2. 接入热点(连Wi-Fi)
    指令:AT+CWJAP=<SSID>,<Password>
    参数:<SSID>处填写热点名称,<Password>处填写密码。两者都要用双引号括起来。
    例如:AT+CWJAP="CMCC","123456"

    指令:AT+CWJAP=<SSID>,<Password>
    参数:<SSID>处填写热点名称,<Password>处填写密码。两者都要用双引号括起来。
    例如:AT+CWJAP="CMCC","123456"

    返回:WIFI CONNECTED:连接到热点
    返回:WIFI GOT IP:分配到IP,走到这一步,就算已经连入到热点了。

    如果忘记SSID了,想看一下可以使用下面的指令,列出广播的SSID(隐藏的不会显示)。
    指令:AT+CWLAP

    3. 连接TCP服务器
    首先打开NetAssist,设置TCP Server,然后建立连接(注意防火墙)。注意,Server必须在Client所在内网或其外网(我的是在同一个内网)。

    网络调试助手

    首先打开NetAssist,设置TCP Server,然后建立连接(注意防火墙)。注意,Server必须在Client所在内网或其外网(我的是在同一个内网)。


    网络调试助手

    指令:AT+CIPSTART=<Type>,<DomainName>,<Port>
    参数:<Type>处写TCP或UDP,<DomainName>处写域名,<Port>处写端口号。
    本例:AT+CIPSTART="TCP","192.168.1.110",1234
    返回:

    CONNECT
    
    OK
    

    说明连接成功。
    在NetAssist中,数据接收框的下面有一个连接对象,点开后发现除了All Connections之外,多了一个客户,就确定客户连接到服务器了。

    NetAssist
    在下方文本框输入信息后发送,可在串口调试助手中看到ESP8266所接收到的信息
    UartAssist收到的信息:+IPD,10:Hello 简书

    到这里就说明网络连接及建立TCP都可以顺利完成,在下面的单片机操作中就会变得方便很多。

    注意,ESP8266每次断电后重新上电,最多只会自动连到之前连接的热点,但不会自动连接到TCP服务器,所以,建立连接要交给单片机来做。

    问题

    • 波特率
      本例要使用的波特率为115200的,不是9600。引申出来的问题就是:算初值(方法详见:《51单片机实战:与计算机异步串行通信》 - 知识点 - 波特率 - 溢出率)。
      手算真的很麻烦,因为在参数固定的情况下,算起来已经很烦了,更何况那些参数可能还会变得情况。
      所以上神器:

      初值计算器
    • 反馈及识别
      我们要做的是让单片机在收到ESP8266回馈的WIFI GOT IP后发出连接TCP服务器的指令。
      发送指令不用多说,只要记得在后面跟"换行(CR)"和"新行(NL)"字符就行(这两个是不一样的)。
      1. 关键字符配对
      如果是电脑程序的高级语言编程,这个问题就不存在了。但是我们给单片机写程序就要牢记它的一些点(《扯会儿单片机开发:开始》),比如硬件资源十分紧张。
      一个是因为它存储空间很小,另一个是因为晶振太慢,所以我们要想办法缩减配对时间。因为单片机的使用往往很单一,所以策略都是根据情况来设定的。比如本例,单片机接收的回馈无非那么几种,所以我制定的策略就是关键字符配对。

      比如识别WIFI GOT IP,我只是别第一个字符W和第6个字符G,就说明我收到的就是这个回馈信息。如果收到第一个是W,只有这条指令的第六个字符是G,所以就可以确定。如果是出现干扰,可能性也是比较低的,我们的交互是在网络层的,下层也可以把出错的报文挡掉。

      总体来说还是蛮可靠的,如果你有更棒的方法,请在评论区告诉我,大家一起学习进步。

      2. 利用流水灯
      上面就是理论工作,已经做得7788了。但实际经验告诉我,如果的代码哪里也出岔子了在51单片机开发中真的比较难发现,他不像高级程序语言可以报错,可以try - catch。但这个就别想了,所以我们要自己想办法,让他可以反馈我们的程序至少是正常运行的,这样可以节省很多时间。

      我在这里介绍的我的方法是利用LED,或成组的流水灯(更好)。承租的流水灯一般是8个,可以直接显示一个字节的信息,可能有人问为什么不用LCD?你若会LCD或者看过《51单片机实战:液晶显示器のLCD1602》就会发现,LCD本身的开发就有点复杂了。你很难保证这个子程序运行正常,更别提用它抓错了。只能是不推荐哈,我觉得不可靠。LED的话就很简单了,就是个关开,一般不会出错。

      QX-MINI51开发板上自带流水灯,你要是入的其他开发板,一般都是有的。如果没有,就单买LED回来(如果不知道去哪买,链接)。

      我一般会怎么做呢,先让流水灯直接显示串口接收的数据,为了知道我们至少是能接收到东西的。然后利用上面的关键字符识别,收到WIFI CONNECTED亮一号灯,收到WIFI GOT IP亮二号灯,收到CONNECT OK亮三号灯。这样,根据灯亮灭的情况就能确定哪一步出了问题,然后定位到代码或者设置。

      还是那句话,如果你有更棒的方法,欢迎在评论区交流。

    到这里就做完了所有理论上的准备工作,也就是理论上我们已经可以实现这个程序了,下面代码实现。


    代码

    说下这次代码比较新颖的地方,一个是用到了.c.h文件组合的模块形式,另一个是用到了函数指针(函数名也被括起来的那个)。前者这种写法是为了将各子功能模块化,.h文件里的内容相当于面向对象编程里的public.c文件一个是实现.h内的函数,另一个就是隐藏函数和变量,相当于private。后者是用来充当高级语言中的“事件”,让函数调用变得更加灵活,其具体用法请自己查资料或者看相关C语言书籍。

    总而言之,这次的代码是模块化事件化的,类似面向对象的编码风格。

    • 准备
      因为用于通讯,所以我把ASCII内所有的特殊字符都写到一个头文件里。虽然本例只用到其中的几个,但以后重复利用这个头文件。

      ASCII.h

    #ifndef __ASCIIS__
    #define __ASCIIS__
    
    #define NUL 0x00 // NULL
    #define SOH 0x01 // Start of Heading
    #define STX 0x02 // Start of Text
    #define ETX 0x03 // End of Text
    #define EOT 0x04 // End of Transmission
    #define ENQ 0x05 // Enquiry
    #define ACK 0x06 // Acknowledge
    #define BEL 0x07 // Bell
    #define BS 0x08 // Backspace
    #define HT 0x09 // Horizontal Tab
    #define LF 0x0A // Line Feed
    #define NL 0x0A // New Line
    #define VT 0x0B // Vertical Tab
    #define FF 0x0C // Form Feed
    #define NP 0x0C // New Page
    #define CR 0x0D // Carriage Return
    #define SO 0x0E // Shift Out
    #define SI 0x0F // Shift In
    #define DLE 0x10 // Data Link Escape
    #define DC1 0x11 // Device Control 1
    #define DC2 0x12 // Device Control 2
    #define DC3 0x13 // Device Control 3
    #define DC4 0x14 // Device Control 4
    #define NAK 0x15 // Negative Acknowledge
    #define SYN 0x16 // Synchronous Idle
    #define ETB 0x17 // End of Transmission Block
    #define CAN 0x18 // Cancel
    #define EM 0x19 // End of Medium
    #define SUB 0x1A // Substitute
    #define ESC 0x1B // Escape
    #define FS 0x1C // File Separator
    #define GS 0x1D // Group Separator
    #define RS 0x1E // Record Separator
    #define US 0x1F // Unit Separator
    #define SP 0x20 // Space
    
    #endif
    

    lcd1602.h

    #ifndef __LCD1602__
    #define __LCD1602__
    
    typedef bit BOOL;
    
    void LCD_writeCmd(unsigned char cmd);  //写命令
    void LCD_writeData(unsigned char dat);  //写数据
    void LCD_writeLine(unsigned char *line);  //写行数据
    void LCD_init();  //初始化
    void delay(unsigned int z);  //粗略的延时器
    
    #endif
    

    lcd1602.c

    #include <reg52.h>
    #include "lcd1602.h"
    
    #define LCD_CLEAR 0x01
    #define LCD_Display_Mode 0X38
    
    #define DISPLAY_OFF 0x08
    #define DISPLAY_ON_NO_CURSOR 0x0c
    #define DISPLAY_ON_WITH_CURSOR_NO_BLINK 0x0e
    #define DISPLAY_ON_WITH_CURSOR_BLINK 0x0f
    
    #define AUTO_BACK_STEP 0x04
    #define AUTO_NEXT_STEP 0x06
    #define AUTO_DISPLAY_MOVE_LEFT 0x07
    #define AUTO_DISPLAY_MOVE_RIGHT 0x05
    
    #define ALL_MOVE_LEFT 0x18
    #define ALL_MOVE_RIGHT 0x1c
    #define CURSOR_MOVE_LEFT 0x10
    #define CURSOR_MOVE_RIGHT 0x14
    
    #define FIRST_ROW 0x80
    #define SECOND_ROW FIRST_ROW+0x40
    
    sbit enable = P0^5;
    sbit RS = P0^7;
    sbit RW = P0^6;
    
    void delay(unsigned int z)
    {
        unsigned int x,y;
        for(x=z;x>0;x--)
            for(y=220;y>0;y--);
    }
    
    void LCD_writeCmd(unsigned char cmd){
        RS = 0;
        P2 = cmd;
        delay(5);
        enable = 1;
        delay(5);
        enable = 0;
    }
    
    void LCD_writeData(unsigned char dat)
    {
        RS = 1;
        P2 = dat;
        delay(5);
        enable = 1;
        delay(5);
        enable = 0;
    }
    
    void LCD_writeLine(unsigned char *line){
        unsigned char i=0;
        BOOL flag = 0;
        LCD_writeCmd(LCD_CLEAR);  //每次送来信息都清屏,可以一直刷新显示送来的信息。
        LCD_writeCmd(FIRST_ROW);
        while(line[i] != '\0'){
            LCD_writeData(line[i++]);
            if(i>15 && flag == 0){
                LCD_writeCmd(SECOND_ROW);
                flag = 1;
            }
            delay(5);
        }
    }
    
    void LCD_init()
    {
        RW = 0;
        enable = 0;
        LCD_writeCmd(LCD_Display_Mode);
        LCD_writeCmd(DISPLAY_ON_NO_CURSOR);
        LCD_writeCmd(AUTO_NEXT_STEP);
        LCD_writeCmd(LCD_CLEAR);
    }
    

    stc52ser.h

    #ifndef __STC52_SER__
    #define __STC52_SER__
    
    extern void (*SerialPort_Event_ByteReceived)(unsigned char byte);  //事件:串口接收到字节
    
    void SerialPort_Init_Low();  //初始化为11.0592MHz下的9600波特率
    void SerialPort_Init_High();  //初始化为22.1184下的115200波特率
    void SerialPort_SendByte(unsigned char byte);  //发送一个字节
    void SerialPort_SendData(unsigned char* bytes);  //发送一组字节
    
    #endif
    

    stc52ser.c

    #include <reg52.h>
    #include "ASCIIS.h"
    #include "stc52ser.h"
    
    //Byte Received Event
    void (*SerialPort_Event_ByteReceived)(unsigned char byte);
    
    //initialize registers pertinent to serial port
    void SerialPort_Init_Low(){
        //set and run Timer1
        //mode2: 8bit, auto reload initial value
        //9600bps and 11.0592MHz => 0xfd(initial value)
        TMOD = 0x20;
        TH1 = 0xfd;
        TL1 = 0xfd;
        TR1 = 1;
        
        //set serial port configuration and enable receive
        //mode1: asyc 10bit(8 data bit), alterable baud rate
        SM0 = 0;
        SM1 = 1;
        REN = 1;
        
        //set interruption
        //enable all and serial port interruption
        EA = 1;
        ES = 1;
    }
    
    //initialize registers pertinent to serial port for esp8266
    void SerialPort_Init_High(){
        //SMOD = 1
        PCON |= 0x80;
        
        //set and run Timer1
        //mode2: 8bit, auto reload initial value
        //115200bps and 22.1184MHz => 0xfd(initial value)
        TMOD = 0x20;
        TH1 = 0xff;
        TL1 = 0xff;
        TR1 = 1;
        
        //set serial port configuration and enable receive
        //mode1: asyc 10bit(8 data bit), alterable baud rate
        SM0 = 0;
        SM1 = 1;
        REN = 1;
        
        //set interruption
        //enable all and serial port interruption
        EA = 1;
        ES = 1;
    }
    
    //Send a byte
    void SerialPort_SendByte(unsigned char byte){
        ES = 0;
        SBUF = byte;
        while(!TI);
        // transmit interrupt
        TI = 0;
        ES = 1;
    }
    
    //Send a data of byte sequence end by 'EOT'
    void SerialPort_SendData(unsigned char* bytes){
        int i = 0;
        while(bytes[i] != EOT){
            SerialPort_SendByte(bytes[i]);
            i++;
        }
    }
    
    
    //Occured when byte received
    void receivedInterruped() interrupt 4 {
        TR0 = 0;
        (*SerialPort_Event_ByteReceived)(SBUF);
        while(!RI);
        RI = 0;
    }
    

    简单说一下,这里留了两个初始化函数,SerialPort_Init_Low()用于11.0592MHz下的9600波特率,SerialPort_Init_High()用于22.1184MHz下的115200波特率(此例用这个)。这样写只是为了以后可以重用(软工狗的矫情)。

    • 无线
      这里是最主要的代码,都是关于ESP8266的,也是作为一个模块给主函数调用。

    esp8266.h

    #ifndef __ESP8266__
    #define __ESP8266__
    
    extern void (*ESP01_Event_WifiConnected)();    //事件:Wi-Fi已连接
    extern void (*ESP01_Event_IpGot)();    //事件:IP地址已获得
    extern void (*ESP01_Event_TcpServerConnected)();    //事件:已连接到TCP服务器
    extern void (*ESP01_Event_MsgReceived)(unsigned char* head);    //事件:已获得消息,head为消息数组头
    
    void ESP01_Init();  //无线模块初始化
    void ESP01_ConnectToTCPServer();  //连接TCP服务器
    
    #endif
    

    esp8266.c

    #include <reg52.h>
    #include "stc52ser.h"
    #include "ASCIIS.h"
    #include "esp8266.h"
    
    #define BUFFER_MAX_SIZE 99  //缓冲区大小
    unsigned char buffer[BUFFER_MAX_SIZE];  //缓冲区:用于存放从ESP8266接收来的各种信息
    
    //连接到TCP服务器的指令:AT+CIPSTART="TCP","192.168.1.110",1234。后面的CR和NL是AT指令的固定结尾,EOT用于SerialPort_SendData发送时识别结尾。
    code unsigned char cmd_connectToTCPServer[] = {0x41, 0x54, 0x2B, 0x43, 0x49, 0x50, 0x53, 0x54, 0x41, 0x52, 0x54, 0x3D, 0x22, 0x54, 0x43, 0x50, 0x22, 0x2C, 0x22, 0x31, 0x39, 0x32, 0x2E, 0x31, 0x36, 0x38, 0x2E, 0x31, 0x2E, 0x31, 0x31, 0x30, 0x22, 0x2C, 0x31, 0x32, 0x33, 0x34, CR, NL, EOT};
    int counter = 0;    //用于ESP8266的执行步骤计数
    int writeIndex = 0;    //缓冲区写索引
    
    void (*ESP01_Event_WifiConnected)();
    void (*ESP01_Event_IpGot)();
    void (*ESP01_Event_TcpServerConnected)();
    void (*ESP01_Event_MsgReceived)(unsigned char* head);
    
    //注意:下面代码推荐从后往前看,从注释标"1. "处开始。
    
    void prepareForData(unsigned char byte);    //因为第四步和第三步会相互调用,所以这里只是做了个声明(C语言的矫情点)。
    
    //4. 将信息插入到缓冲区并送给单片机。
    void insertDataIntoBuffer(unsigned char byte){
        if(byte == '\\'){
            //检测到'\'后,将信息送出到单片机
            buffer[writeIndex] = '\0';
            (*ESP01_Event_MsgReceived)(buffer);
            SerialPort_Event_ByteReceived = &prepareForData;    //回到第三步,准备接收下一条信息
            writeIndex = 0;
            return;
        }
        buffer[writeIndex++] = byte;
    }
    
    //3. 准备信息:这里是过度步骤,前面可以观察到,ESP8266在接收发来的信息时是有个头的,这里的作用就是去头。
    void prepareForData(unsigned char byte){
        if(byte == ':'){
            SerialPort_Event_ByteReceived = &insertDataIntoBuffer;
            writeIndex = 0;
        }
    }
    
    //识别回馈指令:用于识别接收到的是WIFI CONNECTED(连上热点)还是WIFI IP GOT(获得IP)还是CONNECT(连上TCP服务器)
    void parseCmd(){
        switch(counter){
            case 1:
                if(buffer[0] == 'W' && buffer[5] == 'C'){
                    (*ESP01_Event_WifiConnected)();
                    counter += 1;
                }
                break;
            case 2:
                if(buffer[0] == 'W' && buffer[5] == 'G'){
                    (*ESP01_Event_IpGot)();
                    counter += 1;
                }
                break;
            case 3:
                if(buffer[0] == 'A' && buffer[3] == 'C')
                    counter += 1;
                break;
            case 4:
                if(buffer[0] == 'C' && buffer[3] == 'N' && buffer[6] == 'T'){
                    (*ESP01_Event_TcpServerConnected)();
                    SerialPort_Event_ByteReceived = &prepareForData;  //连接到TCP服务器后,进入第三步。
                }
        }
    }
    
    //2. 这里开始向缓冲区存储信息,用于识别。
    void insertBuffer(unsigned char byte){
        if(byte == NL){
            //收到尾(NL)后,将缓冲区的回馈信息送去识别
            parseCmd();
            writeIndex = 0;
            return;
        }
        buffer[writeIndex++] = byte;
    }
    
    //1. 接收头:头是无用信息,但我们要通过头里面的一些字符,推算出什么时候到达第二步(WIFI CONNECTED)
    void headerReceived(unsigned char byte){
        if(byte == NL){
            //头内有5个NL,只要数够5个,下一个就是第二步的内容了。
            if(++counter == 5){
                SerialPort_Event_ByteReceived = &insertBuffer;  //跳到第二步
                counter = 1;
            }
        }
    }
    
    //同.h中的声明
    void ESP01_Init(){
        SerialPort_Init_High();
        SerialPort_Event_ByteReceived = &headerReceived;  //事件注册
    }
    
    //同.h中的声明
    void ESP01_ConnectToTCPServer(){
        SerialPort_SendData(cmd_connectToTCPServer);
    }
    
    • 主函数

    main.c

    #include <reg52.h>
    #include "lcd1602.h"
    #include "esp8266.h"
    
    //连接到Wi-Fi后,亮第一个灯
    void EventHandler_WifiConnected(){
        P1 &= 0xFE;
    }
    
    //获得IP后,亮第二个灯
    void EventHandler_IpGot(){
        P1 &= 0xFD;
        ESP01_ConnectToTCPServer();
    }
    
    //连接到TCP服务器后,亮第三个灯
    void EventHandler_TcpServerConnected(){
        P1 &= 0xFB;
    }
    
    //将ESP8266送来的信息,送去LCD显示。
    void EventHandler_MsgReceived(unsigned char* head){
        LCD_writeLine(head);
    }
    
    //初始化
    void init(){
        ESP01_Event_WifiConnected = &EventHandler_WifiConnected;  //事件注册
        ESP01_Event_IpGot = &EventHandler_IpGot;  //事件注册
        ESP01_Event_TcpServerConnected = &EventHandler_TcpServerConnected;  //事件注册
        ESP01_Event_MsgReceived = &EventHandler_MsgReceived;  //事件注册
        ESP01_Init();
        LCD_init();
    }
    
    void main(){
        init();
        while(1);
    }
    
    • 编译
      编译前要设置一下目标参数,如下图。
      目标设置
      注意这里要改成XDATA,不然编译通不过的。

    效果

    开始前请确定在同一个网络下,并且服务端已开启。

    初始化效果

    我只是把单片机连到移动电源上了。

    服务端
    为了能让1602第一行显示Hello,第二行显示World,中间可以留了11个空格。 客户端

    结语

    猴!到这里这个程序就算完成了。这个比那些用手机控制开关灯要复杂一些。所以你只要掌握了这个例子,那些都不在话下了。
    扯单一周目BOSS正式刷完,这个系列的文章将会暂告一段落,因为接下来笔者又要去考试了,还有什么噼里啪啦科三学车,烦。消失一小阵子后我会再次诈尸的!

    恭喜你获得一周目BOSS神装:物联网神技!

    相关文章

      网友评论

      • 中移OneMO模组:厉害了,我的小编。了解更多物联网通信模组知识,获取贴身模组专业技术服务。关注中移模组,你想要的都在这里喔!
      • 清风蓝:支持一下
        兔子泽:@清风蓝 谢谢~~
      • d0e51a99cdd0:谢谢博主的文章 思路清晰 解释到位 另外请教个问题 怎么用数组来表示AT指令呢 就像这样
        //连接到TCP服务器的指令:AT+CIPSTART="TCP","192.168.1.110",1234。。。
        code unsigned char cmd_connectToTCPServer[] = {0x41, 0x54, 0x2B, 0x43, 0x49, 0x50, 0x53, 0x54, 0x41,。。。 0x524, CR, NL, EOT};
        兔子泽:@pacification 把命令变成ascii编码,将编码后的所有字符放到数组即可
      • 2948c1cb87b4:楼主您好,我最近在做毕业设计,刚好是和8266有关的,刚好有几个问题想请教您。希望您有空能回答下。如果要用8266把单片机采集到的数据传到电脑上的话,应该把8266设置成哪种工作模式?
      • cqflx105:你好!刚才拜读了您的串口转wifi模块和51单片机的教程,写的非常好!但我按照你的连接,在串口助手上面接收不到任何数据,能否指导一下!
      • YGY___:为什么按照你这个代码来做,不能再1602上显示内容
      • YGY___:请问这个程序可以直接12864替换1602吗?如果可以,能直接汉字传输吗?
        兔子泽:@YGY___ 肯定不能直接替换,相应的模块代码也要替换
      • 010f478ed0d2:为什么我的还是单片机与8266没通信呢,试过很多程序都是这样,不知道是哪里出问题了
        兔子泽:@艾伦耶格尔_4857 那行代码的上面我有写注释,就是一句AT指令AT+CIPSTART="TCP","192.168.1.110",1234
        010f478ed0d2:还有cmd_connectToTCPServer[]这个数组没看懂,里面是什么
      • S_EL:单片机小白,在网上找了很多资料,但是太菜,总是没有看下去的欲望或者消化不起来,感谢LZ大大...给你比个小心心:heart:
        兔子泽:@PANJJUN 例程的作用只是为了帮助理解消化文章里面的点,最好还是要自己花些时间调试一下:smile:
        PANJJUN:为什么main函数里面没有调用TCP连接函数?我在实际操作的时候,把程序下到单片机里面去了(没改过),但在网络串口调试助手建立不了连接,没有连接对象
        兔子泽:@苏恩丽 谢谢你的小心心,也谢谢你能喜欢我的文章:smile:
      • 赵大鹏先生:博主这思路清晰,言简意赅啊。。。。佩服佩服。。
        在下有一点问题,这个8266的波特率是115200,我用的是stc89c51单片机,,他的晶振能到115200吗?如果不幸的话你感觉是调8266的号还是改单片机的比较合适?
      • 16ac185be70b:extern void (*SerialPort_Event_ByteReceived)(unsigned char byte) 这个函数是不是没有定义啊,没见到哪里对这个函数进行定义了
      • 16ac185be70b:博主,最后编译后,ESP8266与单片机的连线顺序要不要改成(TX-RX,RX-TX),你用网络调试助手发送的“Hello world”,是不是电脑通过wifi——路由器——ESP8266——单片机——LCD1602呢
        兔子泽:@糊涂_8552 是的
        PANJJUN:请问您解决了么
      • b7af2ebfce4a:楼主,真的太用心了,通过学习你的编程思路真的让我受益匪浅,我对于单片机也是小白一个,希望在以后的学习道路上与君共勉,真的感谢你,加油。。。
        兔子泽:非常感谢你的认可!以后一起加油哈!
      • e5da866d9364:你好,看了文章,受益匪浅;还有一个不太明白的地方,ESP8266是不能通过中文的SSID连接WIFI的;但是类似“米家”这样的APP,手机连接上wifi,并搜索到设备之后,再输入密码,就能让设备联网,这个不知道是怎么实现的。
        兔子泽:@TinyFree 那就只是编码显示的问题,我也get了😄
        e5da866d9364:@兔子泽 嗯嗯,主要是家里最开始设置了中文WiFi,比较搞不懂。我这几天也试了一下,有点收获:对于中文,手机或者电脑显示的是UTF8,ESP8266使用的SSID使用应该是GBK,转换编码之后虽然是乱码,但是可以正常连上。
        兔子泽:@TinyFree 很不好意思,你问的两点我可能都没有办法回答你。第一个ESP8266识别中文SSID的事情我还没有试过(最近工作比较忙,而且板子也不在身边)。米家那个App我也没有用过,所以我也没办法回答你这个问题😂
      • 6591e24f1094:您好,您写的文章对我帮助很大,首先表示感谢,然后我想请教一个问题,我现在在做stc15+esp8266+ws2812在做一个无线全彩灯,需要发送rgb的颜色 可能会和判断的标识位有重叠,这个有没有什么好的思路进行规避
        兔子泽:@举举举举 �不好意思回复的有点晚了,其实思路应该和我这个例子差不多,主要就是单片机要对esp8266传来的信息进行字符串处理。对于ws2812我不太了解,你担心的是单片机和ws2812的通信吗?
      • 1c509923e44e:你好,楼主。文章写的很不错。我是个单片机小白。最近也正好想研究一下ESP8266。我买的单片机开发版也正好和你一样。我有两个问题想请教一下。1.我先做的事是,单片机上主动把某个开关的开关状态信息通过WIFI模块发到网络上某台WEB服务器。只通过一个ESP8266可以做到么?2.网络上web服务器请求单片机获取单片机上的开关状态,可以做到么? 能简单讲解下么,谢谢。
        兔子泽:@落叶潇潇_317b 讲道理应该是这样的,虽然我不太了解这个市场,但是应该是这样的
        1c509923e44e:@兔子泽 谢谢你的回答,那请问现在市面上的无线远程插座,也都是把插座的开关状态主动传到网上,然后APP再访问外网的服务器查看状态么?
        兔子泽:@落叶潇潇_317b
        1. 可以;
        2. esp8266是网络层设备,http是什么东西他肯定是不懂的。他通信是依赖tcp,主动请求连接esp8266也是可以的(作主机)。但是单片机往往都是连接在子网里,服务器暴露在外网,外网找子网一般是找不到的,倒也有一些特殊手段可以做到。所以建议你重新设计一下请求方式,最好让子网设备主动请求外网设备
      • 0bc16266f3eb:老铁没毛病果断收藏了,我打算用stc15系列做一个没毛病吧老铁。很感谢老铁的分享
        兔子泽:@起航梦醒时分 老铁不用客气~
      • 吃肉长胖:楼主有用过机智云的 MCU和APP代码自动生成工具,来实现联网、MCU开发和APP开发吗
        兔子泽:@吃肉长胖 没有使用过喔
      • 轻风烟雨幻城:博主,请问一下,单片机是怎么让ESP8266连上TCP的啊?这块儿不太懂
        兔子泽:@轻风烟雨幻城 ESP8266是单独运作的,不是单片机让他连上TCP的,他自己就是网络层设备,所以说ESP8266是有能力连接TCP的。单片机只是与ESP8266通信(发送与接收)还有就是为他供电。
      • 10fcc8315a9f:错误提示:
        Build target 'Target 1'
        assembling STARTUP.A51...
        compiling main.c...
        compiling stc52ser.c...
        compiling lcd1602.c...
        compiling esp8266.c...
        linking...
        *** ERROR L104: MULTIPLE PUBLIC DEFINITIONS
        SYMBOL: _SERIALPORT_EVENT_BYTERECEIVED
        MODULE: esp8266.obj (ESP8266)
        *** WARNING L13: RECURSIVE CALL TO SEGMENT
        SEGMENT: ?PR?_PREPAREFORDATA?ESP8266
        CALLER: ?PR?_INSERTDATAINTOBUFFER?ESP8266
        *** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS
        SEGMENT: ?PR?SERIALPORT_INIT_LOW?STC52SER
        Program Size: data=9.1 xdata=125 code=1197
        Target not created
        兔子泽:@韦宏 有重复定义,检查一下代码。这样子的问题还是要尽量学会自己排查。
      • 10fcc8315a9f:你好,我编译不通过怎么回事
      • 90878d82a2ca:请问 在NetAssist连接后,输入AT+CIPSTART="TCP","IP地址",端口点发送后显示我当前没有连接对象,这是什么情况?
        兔子泽:@宝瓶宫时代 烧程序的时候把ESP8266拔掉。烧程序的时候走串口,ESP8266也是通过串口和单片机通信,这样就导致电脑上的烧录程序找不到目标。
        90878d82a2ca:烧写程序的时候如果加上ESP8266的话,就一直卡在正在检测目标单片机 ... ,请问这怎么解决,谢谢
        兔子泽:你不会是在NetAssist里输入AT指令了吧?
      • 哈哈羊:有要做软件开发和网站建设的朋友们欢迎咨询我,qq849440483
      • 眼淚_b310:楼主 为啥主程序中 没看见你调用连接TCP服务器的函数呢:flushed:
        兔子泽:@眼淚_b310 哦哦哦,不好意思,说错了。8266是客户端,电脑那边是主机。连接到TCP服务器的代码在esp8266.c中,最后一个函数void ESP01_ConnectToTCPServer()
        眼淚_b310:@兔子泽 这个流程不是单片机发送AT指令给8266 然后8266才能连接到网络调试助手嘛
        兔子泽:@眼淚_b310 因为在这次通信中,单片机是服务器
      • ad2fea644aa4:很巧合跟博主用的是同一款开发板,且在同一家店铺买的ESP8266-01。然后还想请教一下就是:
        1,可以用11.0592Mhz+9600波特率来工作吗?为什么要用115200的波特率。
        2,在店铺还送了一个CH340的USB转串口的小模块,不知道是做什么用的,只是前期的一个测试功能吗?
        3,博主说的报错用Led显示是极好的。但是加printf打印一句错误信息可以吗?(这个只是个人的一个补充而已啦。。。没啥重要的)
        谢谢:relaxed:
        ad2fea644aa4:@兔子泽 所以如果是无线通信方式,就不用到CH340模块了对吧~.~
        ad2fea644aa4:@兔子泽 好的哈~!~~~~~谢谢,比较忙才看到回复:smile:
        兔子泽:1. 可以9600,需要通过AT指令与ESP8266说一下(更改串口模式:AT+CIOBAUD =<baudrate>, <databits>,<stopbits>, <parity>,<flow control>)这样就用不着22.1184的晶振了。这个确实是我文章的问题,谢谢指出!
        2. CH340就是我上面的图片(标记“供电&USB串口”,第四张)中所示的那个模块。串口(详见我的另一篇文章:《51单片机实战:与计算机异步串行通信》)就是指的单片机上的RXD和TXD,让它变成用USB的方式通信,就需要这个芯片(这个芯片已经嵌上去了)。
        3. 首先呢,printf是面向标准输出设备的打印函数。这个案例里面是没有标准输出设备的(LCD1602也不是),所以不能。
      • ad2fea644aa4:博主,你很强,思路清晰,通俗易懂,语言风趣。可是这个社交平台我也是找了好久才挖到您的文章。。。。。
        ad2fea644aa4:@兔子泽 :smile:
        兔子泽:谢谢夸奖!文章对你有帮助,那定是极好的呀!
      • lewinxu:感谢作者,函数指针这个思路很棒,在我尝试过程中那些事件函数并没有被调用,请问会是哪里出错了呢?谢谢请点
        PANJJUN:我也是这样,请问怎么解决的
        兔子泽:确保做好三大块:事件调用(函数指针被调用),事件绑定(函数指针指向确定的函数块),事件处理(函数指针所指的函数块已实现)
        兔子泽:你用的是学习板吗?你可以把上面的LED当做断点调试一下,先看看你的函数指针所在代码块有没有被调用,如果被调用了,再去看看绑定的事件函数有没有被调用,如果没有,可能是绑定上出了问题。不好意思很晚才回复你。
      • JoshuaEsther:看了这么这个清晰!还是相当不错的!没有那么的专业术语!赞一个!
        兔子泽:谢谢!对你有帮助那定是极好哒!
      • Luat物联网通信模块:可以关注下Luat开源项目,基于Air200 GPRS模块(价格才12块多),基于Lua开发应用软件,超级容易上手。
        兔子泽:@Luat开源物联网 好的,我之后会去关注一下
      • 零号星期风:我想问下你这里的程序是烧到ESP82266还是51单片机啊?你的手机端是怎么弄的
        兔子泽:@零号星期风 烧到51单片机,这个帖子只说单片机这边的通信程序,手机或电脑的这里不说。电脑的话你可以下载文章里面的netassist,我给了链接。手机的话,比如iPhone,可以下TCPConsole。
      • 谢mingmin:ESP可以直接用lua开发啊
        兔子泽:@谢mingmin 好!
        谢mingmin: @兔子泽 😅可以看看我的文章
        兔子泽:ESP知道,lua知道,但合在一起就不太明白了:joy:

      本文标题:51单片机实战:物联网初步のESP8266无线网络模块

      本文链接:https://www.haomeiwen.com/subject/nnlmittx.html