外设驱动库开发笔记27:ESP8266无线通讯驱动
我们的物联网产品所使用的平台都支持无线通讯,而且无线通讯本身更的成本较低,受到大家的欢迎。在本篇文章中,我们将详细讨论并实现ESP8266无线通讯模块的驱动。
1、功能概述
ESP8266是由乐鑫公司出品的一款物联网芯片,因为价格较低,性能稳定等收到很大关注。
该芯片可工作于三种WIFI模式下,分别是:station模式,AP模式以及混合模式,通过AT指令进行控制,显影的指令格式为:AT+CWMODE=<mode>。mode的取值决定设定的模式:
当mode为1时,ESP8266工作于station 模式:ESP8266 模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。
当mode为2时,ESP8266工作于softAP 模式:ESP8266 模块作为热点,手机或电脑直接与模块连接,实现局域网无线控制。
当mode为3时,ESP8266工作于softAP + station 模式:两种模式的共存模式,即可以通过互联网控制可实现无缝切换,方便操作。
ESP8266拥有2种传输模式,即正常模式和透传模式。而传输模式的配置也是采用AT指令,具体格式为:AT+CIPMODE=<mode>。其中mode 取值0时,为普通传输模式;而mode 取值1时,为透传模式,仅支持TCP单连接和 UDP固定通信对端的情况。在正常模式下,每次发送数据前都必须先发送指令AT+CIPSEND=<param>。而在透传模式下,我们就不需要在每次发送数据前都发送指令AT+CIPSEND=<param>了,只需要发送一次AT+CIPSEND,之后发送的所有内容全部当成是数据了。但这又存在一个问题,我们想要发送命令该如何呢?那么就需要发送数据"+++"来退出透传模式。
ESP8266有几种不同的使用方式,最为常见的就是使用AT指令进行操作。ESP8266的AT指令分为基础AT指令、WiFi功能AT指令和TCP/IP相关AT指令3个方面。这些指令从使用功能上讲可分为4类:
按照相应的格式发送不同的AT指令就可以实现ESP8266的数据通讯了。
2、驱动设计与实现
ESP8266无线通讯模块是常用的通讯模块,我们已经描述了其功能及通讯方式,接下来我们将设计并实现其驱动程序。
2.1、对象定义
在使用一个对象之前我们需要获得一个对象。同样的我们想要ESP8266无线通讯模块就需要先定义ESP8266无线通讯模块的对象。
2.1.1、对象的抽象
我们要得到ESP8266无线通讯模块对象,需要先分析其基本特性。一般来说,一个对象至少包含两方面的特性:属性与操作。接下来我们就来从这两个方面思考一下ESP8266无线通讯模块的对象。
先来考虑属性,作为属性肯定是用于标识或记录对象特征的东西。我们来考虑ESP8266无线通讯模块对象属性。我们考虑到ESP8266的WIFI模式以及数据传输模式决定了其工作方式,在使用过程中有时我们也需要了解这两个模式的配置是什么,所以我们将其作为对象的属性已记录这两个模式配置。我们每一个ESP8266对象都需要接收数据,所以要有一个接受缓存区,我们定义了一个结构体变量来作为对象接收缓冲区。
接着我们还需要考虑ESP8266无线通讯模块对象的操作问题。我们想要使用ESP8266对象实现我们的功能,就需要发送命令或数据以及接收数据。串口接收数据我们一般使用中断方式,所以定义了缓冲区,不再需要特定的操作。串口发送消息需要实现,但这依赖于具体的硬件平台,所以我们将其作为对象的操作。此外,我们使用串口通讯时,需要控制时序就离不开延时函数,而延时操作一般都依赖于具体的软硬件平台,所以我们将延时函数作为对象的一个操作。
根据上述我们对ESP8266无线通讯模块的分析,我们可以定义ESP8266无线通讯模块的对象类型如下:
1 /*定义ESP8266对象*/ 2 typedef struct Esp8266Object { 3 Esp8266CWModeType cwMode; //WIFI模式 4 Esp8266CIPModeType cipMode; //传输模式,正常或透传 5 struct EspRxBuffer{ 6 uint8_t queue[Esp8266RxBufferLength]; //数据存储队列 7 uint8_t lengthRecieving; //正在接收的数据长度 8 uint8_t lengthRecieved; //已经接收的数据长度 9 }rxBuffer; 10 void (*SendData)(uint8_t *sData,uint16_t sSize);//数据发送函数指针 11 void (*Delayms)(volatile uint32_t nTime); //延时操作指针 12 }Esp8266ObjectObject;
2.1.2、对象初始化
我们知道,一个对象仅作声明是不能使用的,我们需要先对其进行初始化,所以这里我们来考虑ESP8266无线通讯模块对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此我们设计ESP8266无线通讯模块对象的初始化函数如下:
1 /*ESP8266对象初始化*/ 2 void Esp8266Initialization(Esp8266ObjectObject *esp, //ESP8266对象 3 Esp8266CWModeType cwMode, //WIFI模式 4 Esp8266CIPModeType cipMode, //传输模式,正常或透传 5 char *wifiName, //WIFI名称 6 char *wifiPassword, //WIFI密码 7 ESP8266SendDataType send, //发送函数指针 8 ESP8266DelaymsType delayms //毫秒延时函数 9 ) 10 { 11 char cwjap[50]; 12 char cwsap[50]; 13 14 if((esp==NULL)||(send==NULL)||(delayms==NULL)) 15 { 16 return; 17 } 18 esp->SendData=send; 19 esp->Delayms=delayms; 20 21 esp->cwMode=cwMode; 22 esp->cipMode=cipMode; 23 24 esp->rxBuffer.lengthRecieved=0; 25 ClearReciveBuffer(esp); 26 27 //设置工作模式 1:station模式 2:AP模式 3:兼容 AP+station模式 28 if(Esp8266SendCommmand(esp,cwModeCmd[esp->cwMode],"OK",50)==Esp8266_TxFial) 29 { 30 return; 31 } 32 33 //让Wifi模块重启的命令 34 35 if(Esp8266SendCommmand(esp,"AT+RST","OK",20)==Esp8266_TxFial) 36 { 37 return; 38 } 39 40 esp->Delayms(3000); //延时3S等待重启成功 41 42 if(esp->cwMode==Esp8266_StationMode) 43 { 44 sprintf(cwjap,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",wifiName,wifiPassword); 45 46 //让模块连接上自己的路由 47 if(Esp8266SendCommmand(esp,cwjap,"OK",600)==Esp8266_TxFial) 48 { 49 return; 50 } 51 52 if(esp->cipMode==Esp8266_TransMode) 53 { 54 if(Esp8266EnterTrans(esp)==Esp8266_TxFial) 55 { 56 return; 57 } 58 } 59 else 60 { 61 //=0:单路连接模式 =1:多路连接模式 62 if(Esp8266SendCommmand(esp,"AT+CIPMUX=0\r\n","OK",20)==Esp8266_TxFial) 63 { 64 return; 65 } 66 } 67 } 68 else if(esp->cwMode==Esp8266_SoftAPMode) 69 { 70 sprintf(cwsap,"AT+CWSAP_CUR=\"%s\",\"%s\"\r\n",wifiName,wifiPassword); 71 72 //设置模块的WIFI名和密码 73 if(Esp8266SendCommmand(esp,cwsap,"OK",600)==Esp8266_TxFial) 74 { 75 return; 76 } 77 } 78 else if(esp->cwMode==Esp8266_MixedMode) 79 { 80 //尚未使用,有待添加 81 } 82 }
2.2、对象操作
我们已经完成了ESP8266无线通讯模块对象类型的定义和对象初始化函数的设计。但我们的主要目标是获取对象的信息,接下来我们还要实现面向ESP8266无线通讯模块的各类操作。
对于ESP8266来说,发送命令主要是AT命令,这是与发送数据完全不同的操作,所以我们设计了一个专用于命令发送的操作函数。
1 /*ESP8266发送命令*/ 2 static Esp8266TxStatusType Esp8266SendCommmand(Esp8266ObjectObject *esp,char *cmd,char *ack,uint16_t timeOut) 3 { 4 esp->SendData((unsigned char *)cmd, strlen((const char *)cmd)); //写命令到网络设备 5 6 if(ack&&timeOut) 7 { 8 while(timeOut--) //等待超时 9 { 10 if(ChecRecieveFinished(esp) == Esp8266_RxFinish) //如果数据接收完成 11 { 12 if(strstr((const char *)esp->rxBuffer.queue,ack) != NULL) //如果检索到关键词 13 { 14 ClearReciveBuffer(esp); 15 16 return Esp8266_RxSucceed; 17 } 18 } 19 20 esp->Delayms(10); 21 } 22 } 23 24 return Esp8266_TxFial; 25 }
而ESP8266在发送数据时,因发送模式的不同会有一定区别。在透传模式下只需要发送数据就好了。而在普通模式下,需要先发送AT命令再发送发送数据。所以我们可设计数据发送函数如下:
1 /*ESP8266发送数据*/ 2 void Esp8266SendData(Esp8266ObjectObject *esp,uint8_t *sData,uint16_t sSize) 3 { 4 if(esp->cipMode==Esp8266_TransMode) 5 { 6 esp->SendData(sData,sSize); 7 } 8 else 9 { 10 char cmd[32]; 11 12 esp->Delayms(50); 13 ClearReciveBuffer(esp); 14 sprintf(cmd,"AT+CIPSEND=%d\r\n",sSize); 15 16 if(Esp8266SendCommmand(esp,cmd, ">", 1)==Esp8266_RxSucceed) //收到‘>’时可以发送数据 17 { 18 esp->SendData(sData,sSize); 19 } 20 } 21 }
3、驱动的使用
我们已经设计并实现了ESP8266无线通讯模块的驱动程序。接下来我们将设计一个简单的应用以验证驱动的设计是否符合要求。
3.1、声明并初始化对象
使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的ESP8266无线通讯模块对象类型声明一个ESP8266无线通讯模块对象变量,具体操作格式如下:
Esp8266ObjectObject esp;
声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:
Esp8266ObjectObject *esp, //ESP8266对象
Esp8266CWModeType cwMode, //WIFI模式
Esp8266CIPModeType cipMode, //传输模式,正常或透传
char *wifiName, //WIFI名称
char *wifiPassword, //WIFI密码
ESP8266SendDataType send, //发送函数指针
ESP8266DelaymsType delayms //毫秒延时函数
对于这些参数,对象变量我们已经定义了。而WIFI模式与传输模式均为枚举,根据实际情况选择就好了。同样WIFI名称和WIFI密码更具实际使用情况输入,注意时字符串就可以了。最主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
1 /*定义ESP8266数据发送指针类型*/ 2 typedef void (*ESP8266SendDataType)(uint8_t *sData,uint16_t sSize); 3 4 /*延时操作指针*/ 5 typedef void (*ESP8266DelaymsType)(volatile uint32_t nTime);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。实际上我们主要需要关注的是串口发送函数。具体函数定义如下:
1 /*串口数据发送*/ 2 static void SendDataForEsp8266(uint8_t *txData,uint16_t length) 3 { 4 HAL_UART_Transmit(&esp8266huart,txData,length,1000); 5 }
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
1 /*ESP8266对象初始化*/ 2 Esp8266Initialization(&esp, //ESP8266对象 3 Esp8266_StationMode, //WIFI模式 4 Esp8266_TransMode, //传输模式,正常或透传 5 wifiName, //WIFI名称 6 wifiPassword, //WIFI密码 7 SendDataForEsp8266, //发送函数指针 8 HAL_Delay //毫秒延时函数 9 );
3.2、基于对象进行操作
我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。我们在驱动中已经将获取数据并转换为转换值的比例值,接下来我们使用这一驱动开发我们的应用实例。
1 /*ESP8266数据通讯*/ 2 void Esp8266DataCommunication(void) 3 { 4 uint8_t sData[16]={0x10,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}; 5 6 Esp8266SendData(&esp,sData,16); 7 }
4、应用总结
在这一篇中我们设计并实现了ESP8266无线模块的驱动,并基于次驱动程序设计了一个简单的验证应用。测试结果是符合我们的预期的,说明我们设计的驱动没有问题。
在使用驱动程序时需要注意,这一驱动只是实现了ESP8266的基本功能,所以要想实现更复杂的功能是可以在驱动基础上扩展的。后续我们也会根据使用的需要进一步扩充驱动。当然这个驱动是基于AT指令来实现操作的,扩充这个驱动程序的功能也需要使用AT指令来实现。
本驱动程序在设计时,考虑使用串口中断来接收数据,所以我们为对象设计了一个接收数据缓存结构。在设计应用时需在串口中断服务函数中向缓存种添加数据。
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】