最近在做一款單片機系統,使用的是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變量