本实验开发板基于:GD32F103

我们首先需要看一下原理图




esp8266做wifi中继 esp8266wi-fi_esp8266做wifi中继


esp8266做wifi中继 esp8266wi-fi_嵌入式软件_02


根据原理图可以看到,ESP8266是通过PA2 PA3这个串口进行通讯,PA13是控制它的复位,

从芯片手册中可以看到PA2PA3是串口1,PA2是串口1的发送,PA3是串口1的接收。


esp8266做wifi中继 esp8266wi-fi_嵌入式软件_03


一、ESP8266简介

本项目使用ESP8266型号为ESP-01S 自带排针WIFI模块

功能特点:基于ESP8266芯片开发,模组继承了透传功能,即买即用,支持串口AT指令,用户通过串口实现网络访问,可广泛应用于智能穿戴,智能家居,家庭安防,遥控器,汽车电子,智慧照明,工业物联网等领域等。


esp8266做wifi中继 esp8266wi-fi_GD32F103_04


二、产品参数


esp8266做wifi中继 esp8266wi-fi_GD32F103_05


模块支持4Mbps高速连传,在WiFi打印机,WiFi串口摄像头,WiFi高速数据采集等大数据量传输应用中,速度更快。

三、电路图


esp8266做wifi中继 esp8266wi-fi_嵌入式软件_06


四、ESP8266开发方式

ESPb266系列一般有三种开发方式:AT指令开发和LUA语言编程以及Arduino 开发。

AT指令开发:厂家出厂时已经预先在ESP8266芯片烧好固件,封装好wifi的协议栈,内部已经实现透传,而用户只需要使用一个USB转TTL的模块或者单片机的串口就能实现与wifi模块的通信,发送AT指令来对WIFI模块进行控制。(和蓝牙透传模块类似)

LUA语言编程:这是一种单独8266编程的方式,可以不依靠单片机和串口调试软件,直接把程序编写到ESP8266内部。

Arduino 开发:这个接触过Arduino的都会比较熟悉。可以直接在Arduinoide的环境下使用Arduino的开发方式进行开发。

五、常用AT指令

AT指令不区分大小写,以回车、转行结尾。


指令名

响应

含义

AT

OK

测试指令

AT+CWMODE=<mode>

OK

设置应用模式(需重启生效)

AT+CWMODE?

+CWMODE:<mode>

获得当前应用模式

AT+CWLAP

+CWLAP:<ecn>,<ssid>,<rssi>

返回当前的AP列表

AT+CWLAP=<ssid>,<pwd>

OK

加入某一AP

AT+CWJAP?

+CWJAP=<ssid>,

返回当前加入的AP

AT+CIPSTART=<type>,<addr>,port

OK

建立TCP/UDP连接

AT+CIPMUX=<mode>

OK

是否启用多连接

AT+CIPSEND=<param>

OK

发送数据

AT+CIPMODE=<mode>

OK

是否进入透传模式


AT+CWMODE=1:STA模式

AT+CWMODE=2:AP模式

AT+CWMODE=3:STA+AP模式

AT+RST:复位

AT+CIPMUX=1:多连接

AT+CIPSERVER=1:建立服务器

AT+CIFSR:查询模块IP端口

AT+CIPSERVER=1,60000:建立服务器的同时设置端口号

AT+CIPMUX=0:单连接

AT+CIPSEND=0,1:向连接序号为0的连接发1个字节

AT+CIPSTATUS:检测连接状态

AT+MQTTUSERCFG:配置用户属性

六、应用模式

ESP8266支撑单AP模式,单STA模式和混合模式(可以在两种模式切换的状态)

AP模式下,ESP8266 模块作为热点,手机或电脑直接与模块连接,实现局域网无线控制。该模式对应TCP传输协议中的服务端(TCP Server)。

STA模式下,WiFi模块为连接到无线网络的终端(站点),可以连接到AP,一般无线网卡工作在STA模式下,该模式对应TCP传输协议中的客户端(TCP Client)

简单来说:AP模式可以将ESP8266作为热点,让其他的设备连接上它;STA模式可以连接上当前环境下的WIFI热点。

七、几个相关概念

透传(透明传输):就是指不需要关心WiFi协议是如何传输的,所需要做的是A通过串口发数据,B通过串口收数据,整个过程中A串口和B串口就好像是用导线连接起来了一样。使用者不用关心内部具体实现,模块对于使用者是“透明的”、“似乎不存在的”(因为可无视中间的实现原理)。

如果不开启透传模式,在每次发送数据前都必须先发送指令AT+CIPSEND=<param>。若开启了透传模式,就不需要再每次发数据之前都发指令了,只需要发送一次AT+CIPSEND,之后发送的内容都会当成是数据。如果再次发送命令,需要退出透传模式(发送“+++”退出),否则就会把命令当成是数据发送过去。

八、工作流程

ESP8266一般用于连接当前环境的热点,与服务器建立TCP连接,传输数据,大致流程如下:

AT+CWMODE=1:设置工作模式(STA模式)

RT+RST:模块重启(生效工作模式)

AT+CWJAP=”111”,”111111”:连接当前环境的WIFI热点(热点名,密码)

AT+CIPMUX=0:设置单路连接模式

AT+CIPSTART=”TCP”:”xxx.xxx.xxx.xxx”,”xxxx”:建立TCP连接服务器IP与服务器端口号

AT+CIPMODE:透传模式下

AT+CIPSEND:传输数据

+++:退出透传模式

九、主要代码如下:

ESP8266的初始化和TCP功能函数:

ESP8266.h

void ESP8266_Init(void);
void ESP8266_AT_Test(void);
bool ESP8266_Send_AT_Cmd(char *cmd,char *ack1,char *ack2,u32 time);
void ESP8266_Rst(void);
bool ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode);
bool ESP8266_JoinAP( char * pSSID, char * pPassWord );
bool ESP8266_Enable_MultipleId ( FunctionalState enumEnUnvarnishTx );
bool ESP8266_Link_Server(ENUM_NetPro_TypeDef enumE, char * ip, char * ComNum, ENUM_ID_NO_TypeDef id);
bool ESP8266_SendString(FunctionalState enumEnUnvarnishTx, char * pStr, u32 ulStrLength, ENUM_ID_NO_TypeDef ucId );
bool ESP8266_UnvarnishSend ( void );
void ESP8266_ExitUnvarnishSend ( void );
u8 ESP8266_Get_LinkStatus ( void );
void USART_printf( uint32_t USARTx, char * Data, ... );

ESP8266.c

#include "esp8266.h"
#include "gd32f10x.h"
#include "systick.h"
#include <stdarg.h>
#include "string.h"

extern uint8_t UartRxbuf[512];
extern uint16_t UartRxLen;
extern uint8_t UartRecv_Clear(void);


struct STRUCT_USART_Fram ESP8266_Fram_Record_Struct = { 0 };  //定义了一个数据帧结构体
void ESP8266_Init(void)
{
    ESP8266_RST_Pin_Periph_Clock();//PC时钟
        gpio_init(ESP8266_RST_Pin_Port, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, ESP8266_RST_Pin);//复位配置成输出
      ESP8266_Rst();//PC13复位管脚配置  复位操作:低-延迟-高
}
//对ESP8266模块发送AT指令 AT指令网上资料很多的
// cmd 待发送的指令
// ack1,ack2;期待的响应,为NULL表不需响应,两者为或逻辑关系
// time 等待响应时间
//返回1发送成功, 0失败
bool ESP8266_Send_AT_Cmd(char *cmd,char *ack1,char *ack2,u32 time)
{ 
    UartRecv_Clear(); //重新接收新的数据包
    ESP8266_USART("%s\r\n", cmd);
    if(ack1==0&&ack2==0)     //不需要接收数据
    {
    return true;
    }
    delay_1ms(time);   //延时
        delay_1ms(1000);
        if(Uart_RecvFlag()==1)
        {
            UartRxbuf[UartRxLen]='\0';
        }
   // printf("%s",UartRxbuf);
    if(ack1!=0&&ack2!=0)
    {
        return ( ( bool ) strstr ( UartRxbuf, ack1 ) || 
                         ( bool ) strstr ( UartRxbuf, ack2 ) );
    }
    else if( ack1 != 0 )  //strstr(s1,s2);检测s2是否为s1的一部分,是返回该位置,否则返回false,它强制转换为bool类型了
        return ( ( bool ) strstr ( UartRxbuf, ack1 ) );

    else
        return ( ( bool ) strstr ( UartRxbuf, ack2 ) );

}


//复位重启
void ESP8266_Rst(void)
{
    ESP8266_RST_Pin_SetL;
    delay_1ms(500); 
    ESP8266_RST_Pin_SetH;
}


//发送恢复出厂默认设置指令将模块恢复成出厂设置
void ESP8266_AT_Test(void)
{
    char count=0;
    delay_1ms(1000); 
    while(count < 10)
    {
        if(ESP8266_Send_AT_Cmd("AT+RESTORE","OK",NULL,500)) 
        {
           // printf("OK\r\n");
            return;
        }
        ++ count;
    }
}


//选择ESP8266的工作模式
// enumMode 模式类型
//成功返回true,失败返回false
bool ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode)
{
    switch ( enumMode )
    {
        case STA:
            return ESP8266_Send_AT_Cmd ( "AT+CWMODE=1", "OK", "no change", 2500 ); 

        case AP:
            return ESP8266_Send_AT_Cmd ( "AT+CWMODE=2", "OK", "no change", 2500 ); 

        case STA_AP:
            return ESP8266_Send_AT_Cmd ( "AT+CWMODE=3", "OK", "no change", 2500 ); 

        default:
          return false;
    }       
}


//ESP8266连接外部的WIFI
//pSSID WiFi帐号
//pPassWord WiFi密码
//设置成功返回true 反之false
bool ESP8266_JoinAP( char * pSSID, char * pPassWord)
{
    char cCmd [120];
    
    sprintf ( cCmd, "AT+CWJAP=\"%s\",\"%s\"", pSSID, pPassWord );
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 5000 );
}

//ESP8266 透传使能
//enumEnUnvarnishTx  是否多连接,bool类型
//设置成功返回true,反之false
bool ESP8266_Enable_MultipleId (FunctionalState enumEnUnvarnishTx )
{
    char cStr [20];

    sprintf ( cStr, "AT+CIPMUX=%d", ( enumEnUnvarnishTx ? 1 : 0 ) );

    return ESP8266_Send_AT_Cmd ( cStr, "OK", 0, 500 );

}


//ESP8266 连接服务器
//enumE  网络类型
//ip ,服务器IP
//ComNum  服务器端口
//id,连接号,确保通信不受外界干扰
//设置成功返回true,反之fasle
bool ESP8266_Link_Server(ENUM_NetPro_TypeDef enumE, char * ip, char * ComNum, ENUM_ID_NO_TypeDef id)
{
    char cStr [100] = { 0 }, cCmd [120];

    switch (  enumE )
    {
        case enumTCP:
          sprintf ( cStr, "\"%s\",\"%s\",%s", "TCP", ip, ComNum );
          break;

        case enumUDP:
          sprintf ( cStr, "\"%s\",\"%s\",%s", "UDP", ip, ComNum );
          break;

        default:
            break;
    }

    if ( id < 5 )
        sprintf ( cCmd, "AT+CIPSTART=%d,%s", id, cStr);

    else
        sprintf ( cCmd, "AT+CIPSTART=%s", cStr );

    return ESP8266_Send_AT_Cmd ( cCmd, "OK", "ALREAY CONNECT", 4000 );

}


//透传使能
//设置成功返回true, 反之false
bool ESP8266_UnvarnishSend ( void )
{
    if (!ESP8266_Send_AT_Cmd ( "AT+CIPMODE=1", "OK", 0, 500 ))//进入透传模式
        return false;

    return 
            ESP8266_Send_AT_Cmd( "AT+CIPSEND", "OK", ">", 500 );//然后开始发送数据

}


//ESP8266发送字符串
//enumEnUnvarnishTx是否使能透传模式
//pStr字符串
//ulStrLength字符串长度
//ucId 连接号
//设置成功返回true, 反之false
bool ESP8266_SendString(FunctionalState enumEnUnvarnishTx, char * pStr, u32 ulStrLength, ENUM_ID_NO_TypeDef ucId )
{
    char cStr [20];
    bool bRet = false;


    if ( enumEnUnvarnishTx )
    {
        ESP8266_USART ( "%s", pStr );

        bRet = true;

    }

    else
    {
        if ( ucId < 5 )
            sprintf ( cStr, "AT+CIPSEND=%d,%d", ucId, ulStrLength + 2 );

        else
            sprintf ( cStr, "AT+CIPSEND=%d", ulStrLength + 2 );

        ESP8266_Send_AT_Cmd ( cStr, "> ", 0, 1000 );

        bRet = ESP8266_Send_AT_Cmd ( pStr, "SEND OK", 0, 1000 );
  }

    return bRet;

}


//ESP8266退出透传模式
void ESP8266_ExitUnvarnishSend ( void )
{
    delay_1ms(1000);
    ESP8266_USART( "+++" );
    delay_1ms( 500 );    
}


//ESP8266 检测连接状态
//返回0:获取状态失败
//返回2:获得ip
//返回3:建立连接 
//返回4:失去连接 
u8 ESP8266_Get_LinkStatus ( void )
{
    if (ESP8266_Send_AT_Cmd( "AT+CIPSTATUS", "OK", 0, 500 ) )
    {
        if ( strstr ( ESP8266_Fram_Record_Struct .Data_RX_BUF, "STATUS:2\r\n" ) )
            return 2;

        else if ( strstr ( ESP8266_Fram_Record_Struct .Data_RX_BUF, "STATUS:3\r\n" ) )
            return 3;

        else if ( strstr ( ESP8266_Fram_Record_Struct .Data_RX_BUF, "STATUS:4\r\n" ) )
            return 4;       

    }

    return 0;
}

static char *itoa( int value, char *string, int radix )
{
    int     i, d;
    int     flag = 0;
    char    *ptr = string;

    /* This implementation only works for decimal numbers. */
    if (radix != 10)
    {
        *ptr = 0;
        return string;
    }

    if (!value)
    {
        *ptr++ = 0x30;
        *ptr = 0;
        return string;
    }

    /* if this is a negative value insert the minus sign. */
    if (value < 0)
    {
        *ptr++ = '-';

        /* Make the value positive. */
        value *= -1;

    }

    for (i = 10000; i > 0; i /= 10)
    {
        d = value / i;

        if (d || flag)
        {
            *ptr++ = (char)(d + 0x30);
            value -= (d * i);
            flag = 1;
        }
    }

    /* Null terminate the string. */
    *ptr = 0;

    return string;

} /* NCL_Itoa */


void USART_printf ( uint32_t  USARTx, char * Data, ... )
{
    const char *s;
    int d;   
    char buf[16];
    unsigned char TempData;


    va_list ap;
    va_start(ap, Data);

    while ( * Data != 0 )     // 判断数据是否到达结束符
    {                                         
        if ( * Data == 0x5c )  //'\'
        {                                     
            switch ( *++Data )
            {
                case 'r':                                     //回车符
                                TempData=0x0d;
                                usart_data_transmit(USARTx, TempData);
                                while (RESET == usart_flag_get(USARTx, USART_FLAG_TC));//发送完成判断
                Data ++;
                break;

                case 'n':                                     //换行符
                                TempData=0x0a;
                                usart_data_transmit(USARTx, TempData);
                                while (RESET == usart_flag_get(USARTx, USART_FLAG_TC));//发送完成判断                        
                Data ++;
                break;

                default:
                Data ++;
                break;
            }            
        }

        else if ( * Data == '%')
        {                                     
            switch ( *++Data )
            {               
                case 's':                                         //字符串
                s = va_arg(ap, const char *);
                for ( ; *s; s++) 
                {
                                        TempData=*s;
                                        usart_data_transmit(USARTx, TempData);
                                      while (RESET == usart_flag_get(USARTx, USART_FLAG_TC));//发送完成判断
                }
                Data++;
                break;

                case 'd':           
                    //十进制
                d = va_arg(ap, int);
                itoa(d, buf, 10);
                for (s = buf; *s; s++) 
                {
                       TempData=*s;
                                      usart_data_transmit(USARTx, TempData);
                                      while (RESET == usart_flag_get(USARTx, USART_FLAG_TC));//发送完成判断
                }
                     Data++;
                     break;
                default:
                     Data++;
                     break;
            }        
        }
        else 
                {
                                        TempData=*Data++;
                                    usart_data_transmit(USARTx, TempData);
                                        while (RESET == usart_flag_get(USARTx, USART_FLAG_TC));//发送完成判断                    
                }

    }
}

//下面为ESP8266MQTT功能指令

/*
*MQTT配置用户属性
*LinkID 连接ID,目前只支持0
*scheme 连接方式,这里选择MQTT over TCP,这里设置为1
*client_id MQTTclientID 用于标志client身份
*username 用于登录 MQTT 服务器 的 username
*password 用于登录 MQTT 服务器 的 password
*cert_key_ID 证书 ID, 目前支持一套 cert 证书, 参数为 0
*CA_ID 目前支持一套 CA 证书, 参数为 0
*path 资源路径,这里设置为""
*设置成功返回true 反之false
*/
bool ESP8266_MQTTUSERCFG( char * pClient_Id, char * pUserName,char * PassWord)
{
    char cCmd [120];
    sprintf ( cCmd, "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"", pClient_Id,pUserName,PassWord );
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 500 );
}


/*
*连接指定的MQTT服务器
*LinkID 连接ID,目前只支持0
*IP:MQTT服务器上对应的IP地址
*ComNum MQTT服务器上对应的端口号,一般为1883
*设置成功返回true 反之false
*/
bool ESP8266_MQTTCONN( char * Ip, int  Num)
{
    char cCmd [120];
    sprintf ( cCmd,"AT+MQTTCONN=0,\"%s\",%d,0", Ip,Num);
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 500 );
}

/*
*订阅指定连接的 MQTT 主题, 可重复多次订阅不同 topic
*LinkID 连接ID,目前只支持0
*Topic 订阅的主题名字,这里设置为Topic
*Qos值:一般为0,这里设置为1
*设置成功返回true 反之false
*/
bool ESP8266_MQTTSUB(char * Topic)
{
    char cCmd [120];
    sprintf ( cCmd, "AT+MQTTSUB=0,\"%s\",1",Topic );
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 500 );
}


/*
*在LinkID上通过 topic 发布数据 data, 其中 data 为字符串消息
*LinkID 连接ID,目前只支持0
*Topic 订阅的主题名字,这里设置为Topic
*data:字符串信息
*设置成功返回true 反之false
*/
bool ESP8266_MQTTPUB( char * Topic,char *temp)
{
    char cCmd [120];
    sprintf (cCmd, "AT+MQTTPUB=0,\"%s\",\"%s\",1,0", Topic ,temp);
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 1000 );
}

/*
*关闭 MQTT Client 为 LinkID 的连接, 并释放内部占用的资源
*LinkID 连接ID,目前只支持0
*Topic 订阅的主题名字,这里设置为Topic
*data:字符串信息
*设置成功返回true 反之false
*/
bool ESP8266_MQTTCLEAN(void)
{
    char cCmd [120];
    sprintf ( cCmd, "AT+MQTTCLEAN=0");
    return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 500 );
}

//ESP8266发送字符串
//enumEnUnvarnishTx是否使能透传模式
//pStr字符串
//ulStrLength字符串长度
//ucId 连接号
//设置成功返回true, 反之false
bool MQTT_SendString(char * pTopic,char *temp2)
{
    
    bool bRet = false;
    ESP8266_MQTTPUB(pTopic,temp2);
      delay_1ms(1000);
    bRet = true;
    return bRet;

}

实现效果如下:


esp8266做wifi中继 esp8266wi-fi_嵌入式软件_07