最近在做一款单片机系统,使用的是AVR128单片机作为上位机,其中使用ESP8266接入互联网,大体的想法是,上位机通过串口向esp8266发送AT指令,来达到请求服务器接口的目的,服务器上的接口是使用PHP写的简单的HTTP接口。
最初的方法
esp8266的AT指令返回的信息非常的不规范,没有统一的格式,所以对回传的判断是有一定的困难的。刚开始使用的是最简单的方法通过充足的延时和指令的重复发送来确保每一个AT指令的正确执行。
- 上位机在初始化的时候会重启esp8266,这时候延时5秒的时间(用来esp8266的重启和自动连接好已连接过的热点)
- 发送TCP连接指令(AT+CIPSTART=”TCP”,”www.icharm.me”,80),这时候延时2秒(可以缩短为一秒,视服务器情况)
- 发送进入透传模式指令(AT+CIPMODE=1),延时500ms
- 发送进入透传发送模式指令(AT+CIPSEND),延时500ms
- 然后发送请求接口的数据(GET http://www.icharm.me/index.php?ac=100002)延时1s等待服务器的返回
如果上面的每一条指令都能正确的执行的话,肯定是可以触发服务器的接口的。但是经过测试发现,这种方法并不靠谱,经常会出现问题。
有关ESP8266怎么连接服务器的可以参考:
检查回传
因为上面的方法稳定性太差,所以哀差闷开始分析esp8266回传信息,试图找到一种通用的规则来判断AT指令的成功执行。
哀差闷的设想是保证每次开机后都顺利的进入透传发送模式,所以怎么保证进入了透传发送的模式,只有检查每一条AT指令的回传。确保每一条指令的成功执行。
首先需要知道的是:每发送一个AT指令,ESP8266会返回一下你发送的指令,接着紧接回车换行(0x0D 0x0A),再返回指令执行的信息,再紧接回车换行(0x0D 0x0A),最后返回指令执行的情况(OK或者ERROR),最后再紧接一个回车换行。
接下来分析下AT+CIPSTART=”TCP”,”www.icharm.me”,80 这条AT指令的回传信息。发送这条指令的时候esp8266有四种回传情况:(/n/r 代表回车换行)
- 未连接wifi,这时候会返回:no ip /n/r ERROR /n/r 等信息
- 已连接wifi,但没有网络访问权限 ,这时候会返回 CONNECT CLOSE /n/r ERROR /n/r
- 已经和服务器建立连接,这时候会返回 ALREADY CONNECTED /n/r ERROR /n/r
- 成功和服务器建立连接, 返回OK
再来分析下AT+CIPMODE=1 指令的回传情况,这个比较简单,成功返回OK, 失败返回ERROR,重复设置也会返回OK,所以这条指令可以通过判断是否为OK或ERROR。
指令AT+CIPSEND 的返回信息比较简单,成功返回 > 失败返回ERROR。
从上面可以知道,判断返回是否为OK或者ERROR并不能达到预期的目标,而且在单片机程序中,串口接受缓冲数组长度有限,会出现覆盖掉前面的一部分信息的情况,甚至出现ERROR这单词的前一部分在数组的末端,后一部分在数组的前端的情况。
所以哀差闷想了一个办法,先从串口中断接收函数入手,弄两个接收缓冲数组A,B,首先对接收到的字符进行判断如果为0x0d(即回车)时,则舍弃,同时从A数组中取出0x0d前面的一个字符存入B,如果为0x0a则直接放弃, 否则将存入数组A。
这样数组B中存放即为每一个回车的前一个字符。后面将通过判断数组B中的字符来判断,从面上面的情况中,可以总结出成功的情况有三种:
- 数组B最后一个字符为K (即OK)
- 数组B最后一个字符为R (即ERROR),但数组B倒数第二个字符为D,这种情况代表ALREADY CONNECTED /n/r ERROR
- 数组B最后一个字符为 >
除了上面的三种情况,其他的都视为失败。
代码分析
可能经过上面的说明,还是不太明白,可以参考一下哀差闷的代码。但因为单片机的不同,代码会不同,请参考着看:
中断接收函数:
1 //***********************************************************************
2 // USART1中断接收字符串
3 //***********************************************************************
4 #pragma interrupt_handler uart1_rx_isr:iv_USART1_RXC
5 void uart1_rx_isr(void)
6 {
7 uchar temp = UDR1; //从数据缓冲器中接收数据 放入temp中
8 if(temp == 0x0d ){ //将每一个回车符前面的一个字符串记录下来,用作判断的标志
9 if(RxBufWr_wifi == 0){ //RxCharBuf_wifi即为数组A, wifi_flag即为数组B, RxBufWr_wifi为数组A的写入指针, flagWr为数组B的写入指针
10 wifi_flag[flagWr] = RxCharBuf_wifi[15];
11 }else{
12 wifi_flag[flagWr] = RxCharBuf_wifi[RxBufWr_wifi-1];
13 }
14 flagWr++;
15 flagWr &= 0x0f; //数组长度为16,当写入指针为达到16时 自动归零
16 if(flagWr != 0)
17 wifi_flag[flagWr] = 0x00; //让接收的字符串求出的长度为正确的
18 return;
19 }
20 if( temp == 0x0a)
21 return;
22 RxCharBuf_wifi[RxBufWr_wifi] = temp;
23 RxBufWr_wifi++;
24 RxBufWr_wifi &= 0x0f; //16->0
25 if(RxBufWr_wifi != 0)
26 RxCharBuf_wifi[RxBufWr_wifi] = 0x00; //让接收的字符串求出的长度为正确的(加个0x00结尾)
27 }
回传判断函数:
1 //***********************************************************************
2 // wifi AT指令返回检查
3 //***********************************************************************
4 int wifi_checkReturn(uint flag){ //这个flag为你要检查数组B的倒数第几个字符,如果为上面分析的那三个字符的话,则检查成功 返回1,否则返回0
5 uint length;
6 while(flagWr == RxBufRd_wifi) delay_nms(10); //发送AT指令前要手动将三个读写指针归0(flagWr=RxBufWr_wifi=RxBufRd_wifi=0),如果读写指针不相等则说明有数据回传过来,反则延时等待
7 length = strlen(wifi_flag);
8 if(length < flag){
9 return 0;
10 }
11 if(wifi_flag[length-flag] == 'K')
12 return 1;
13 if(wifi_flag[length-flag] == 0x3E) //判断是否为 >
14 return 1;
15 if(wifi_flag[length-flag] == 'D')
16 return 1;
17 return 0;
18 }
接下来给一个使用的函数:
连接服务器函数,这个函数确保ESP8266通电后进入透传发送模式
1 //***********************************************************************
2 // 连接服务器
3 //***********************************************************************
4 void wifi_ConnectServer(void)
5 {
6 DisplayCgrom(0x88, "连接服务器中。。"); //在LCD显示信息
7 delay_nms(2);
8 do{
9 wifi_TCPConnect(); //这个函数即为发送AT+CIPSTART="TCP","www.icharm.me",80 的函数
10 if(wifi_checkReturn(1) == 1) //如果数组B的最后一个字符检查通过的话 则退出循环
11 break;
12 if(wifi_checkReturn(2) == 1) //如果数组B的倒数第二个字符检查通过的 则退出循环
13 break;
14 }while(1);
15 do{
16 wifi_CIPMODE(); //这个函数发送指令AT+CIPMODE=1
17 }while(wifi_checkReturn(1) == 0);
18 do{
19 wifi_CIPSEND(); //这个函数发送指令AT+CIPSEND
20 }while(wifi_checkReturn(1) == 0);
21 DisplayCgrom(0x88, "服务器连接成功");
22
23 }
上面的那个回传判断函数还有待优化,如果最后一个字符为R的话自动判断倒数第二个字符是否为D 这样更好,可以节省flag变量