ESP8266是我第一個(gè),也是唯一一個(gè)接觸過的無(wú)線網(wǎng)絡(luò)模塊,我非常喜歡!大部分網(wǎng)上的教程都是ARM架構(gòu)單片機(jī)配用ESP8266。我比較摳門,給大家上51版的。
我記得當(dāng)時(shí)和老板承諾可以做成無(wú)線通信的時(shí)候我也不是很確定,心里一直打鼓。好在最后做出來(lái)了,很有成就感!所以今天的代碼案例是我把我之前在公司寫的項(xiàng)目代碼簡(jiǎn)化之后分享給大家的。雖然結(jié)構(gòu)有點(diǎn)惡心,但應(yīng)該還是入得了眼的。
如果有沒看懂的地方,大家評(píng)論區(qū)告訴我。
需求
利用ESP8266芯片,通過無(wú)線網(wǎng)絡(luò)同電腦建立TCP連接。接收電腦傳來(lái)的消息并顯示在LCD上。
每條信息以\結(jié)尾。
每條信息字符數(shù)不超過32。
信息內(nèi)容只能是ASCII表內(nèi)的字符。
清單
硬件
學(xué)習(xí)板
簡(jiǎn)介
由于這次要用到WIFI模塊,沒法用Proteus模擬,所以就上真家伙。
自己焊板子就太麻煩了,所以就買學(xué)習(xí)板(其實(shí)我不會(huì)焊)。
QX-MINI51
這是我入的學(xué)習(xí)板,你也可以入其他的,如果把握不大就跟我入一樣的。
這個(gè)是我在淘寶上小挑了一陣子選的板子:QX-MINI51。尺寸不大不小,想玩的基本都有。配備的單片機(jī)是STC89C52。
淘寶鏈接
電路
下面是QX-MINI51在本節(jié)會(huì)用到的電路圖
STC89C52
使用89C52芯片,和我們之前用的單片機(jī)一樣。
供電&USB串口
CH340芯片是用于普通串口和USB轉(zhuǎn)換的,其上的RXD為串口輸入,TXD為串口輸出。UD+和UD-對(duì)應(yīng)USB數(shù)據(jù)線。
雖說(shuō)是用了USB,其實(shí)和普通串口通信線用法一致,只是人家成品方便我們使用封裝成了USB。這里不深究。
流水燈
流水燈在本節(jié)中的角色是輔助調(diào)試(具體的看后面),這里有用的信息就是讓你知道這8個(gè)流水燈接哪了。
ESP8266
簡(jiǎn)介
主角,簡(jiǎn)而言之就是Wi-Fi模塊,屬于網(wǎng)絡(luò)層以上的設(shè)備。擁有MAC地址和IP地址,支持UDP和TCP。
ESP8266
這款的型號(hào)是ESP8266-01,其他的和QX-MINI51配合使用不太方便。
淘寶鏈接
參數(shù)
型號(hào) 主芯片 無(wú)線標(biāo)準(zhǔn) 工作電壓 安全機(jī)制 支持模式
ESP8266-01 ESP8266 IEEE 802.11b/g/n 3.3V WEP/WPA-PSK/WPA2-PSK STA、AP、STA+AP
STA 模式:ESP8266模塊通過路由器連接互聯(lián)網(wǎng),手機(jī)或電腦通過互聯(lián)網(wǎng)實(shí)現(xiàn)對(duì)設(shè)備的遠(yuǎn)程控制。
AP 模式:ESP8266模塊作為熱點(diǎn),實(shí)現(xiàn)手機(jī)或電腦直接與模塊通信,實(shí)現(xiàn)局域網(wǎng)無(wú)線控制。
STA+AP 模式:兩種模式的共存模式,即可以通過互聯(lián)網(wǎng)控制可實(shí)現(xiàn)無(wú)縫切換,方便操作。
引腳圖
上圖對(duì)8個(gè)針腳進(jìn)行說(shuō)明。
UTXD GND CH_PD GPIO2 GPIO16 GPIO0 VCC URXD
發(fā)送 接地 高電平工作 |||電源 接收
本例只用到這五個(gè)引腳(畫“\”的不用),其他引腳的說(shuō)明資料請(qǐng)自行到淘寶鏈接處下載。
AT指令
操作ESP8266芯片是靠AT指令的,類似之前的操作LCD1602(《51單片機(jī)實(shí)戰(zhàn):液晶顯示器のLCD1602》)。操作LCD1602的指令都是一個(gè)字節(jié)的十六進(jìn)制碼,比較難記和理解。AT指令是字符串形式的指令,一般都是單詞縮寫,對(duì)可笑的人類比較友好。
之前和LCD1602交互是靠并口傳輸,而這次是用串口傳輸(串口傳輸簡(jiǎn)例:《51單片機(jī)實(shí)戰(zhàn):與計(jì)算機(jī)異步串行通信》),所以這次的Wi-Fi通信是建立在串口通信基礎(chǔ)上的。
AT指令集下載鏈接
AT指令使用示例下載鏈接
波特率
注意,這個(gè)模塊的默認(rèn)波特率是115200,本例也是根據(jù)這個(gè)波特率進(jìn)行演示的。若想改變波特率請(qǐng)使用以下語(yǔ)句進(jìn)行修改:
AT+CIOBAUD=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>
如:
AT+CIOBAUD=9600,8,1,0,0
軟件
程序 說(shuō)明 下載
UartAssist 串口調(diào)試助手,用來(lái)給單片機(jī)發(fā)送消息 度娘網(wǎng)盤
NetAssist 網(wǎng)絡(luò)調(diào)試助手,在電腦端建立TCP連接與單片機(jī)的ESP8266進(jìn)行通信 度娘網(wǎng)盤
STC-ISP STC單片機(jī)工具集,很強(qiáng)大,可燒錄程序,可串口調(diào)試等等 度娘網(wǎng)盤
CH340驅(qū)動(dòng) CH340的USB驅(qū)動(dòng),如果沒有這個(gè)驅(qū)動(dòng),你的電腦可能識(shí)別不到單片機(jī)(識(shí)別到CH340就相當(dāng)于識(shí)別到單片機(jī)的串口。) 度娘網(wǎng)盤
51單片機(jī)波特率初值計(jì)算工具 用于計(jì)算在各種波特率和晶振頻率等參數(shù)下計(jì)時(shí)器的初值,屬于輔助工具,省的還得算。 度娘網(wǎng)盤
分析
調(diào)試
在開始用單片機(jī)直接和無(wú)線模塊通信之前,首先要繞過單片機(jī)直接和無(wú)線模塊通信以確定其可以使用和接入默認(rèn)網(wǎng)絡(luò)(接入一個(gè)熱點(diǎn)后,模塊每次斷電后啟動(dòng)都會(huì)自動(dòng)連接該熱點(diǎn)),這樣可以讓單片機(jī)少做很多事情(要知道我們用的是51單片機(jī),硬件資源極其有限,能省則?。?/span>
TXD/RXD反接
首先要繞過單片機(jī)直接給ESP8266下指令,就要用電腦的串口。但ESP-01是針腳接口,所以我們可以利用QX-MINI51上的針腳和USB接口。
回看前面QX-MINI51的主控芯片和USB的電路圖,可以發(fā)現(xiàn),單片機(jī)的串口引腳是被并聯(lián)式的暴露再外的,分別為:P30 - P31和USB,前者為針腳接口,后者為USB接口。且它們的RXD和TXD的數(shù)據(jù)信息是一摸一樣的,注意并不是兩個(gè)獨(dú)立的串口,是同一個(gè)串口。比方說(shuō),單片機(jī)要往出發(fā)送一個(gè)1,P30針腳和USB都會(huì)是往出發(fā)送一個(gè)1。
所以我們利用這個(gè)特點(diǎn),將ESP8266的TXD接到P31(單片機(jī)的TXD),RXD接到P30(單片機(jī)的RXD),這樣就等于讓ESP8266直接和USB打交道,也就是和電腦直接打交道了。
為什么叫反接,我一般管RXD對(duì)TXD叫正接。嗯…
接線圖
強(qiáng)勢(shì)秀一波畫工!
我覺得應(yīng)該可以看得懂吧,ESP8266的引腳說(shuō)明請(qǐng)看前面的引腳圖??醇t色號(hào)碼對(duì)應(yīng)接線,其中4、5、6號(hào)引腳不用。
實(shí)物圖
這個(gè)圖就很難看出線是怎么連的了,所以你要忍受我丑陋的畫工。
接入網(wǎng)絡(luò)
打開串口調(diào)試助手,調(diào)好參數(shù)
串口調(diào)試助手,設(shè)置參數(shù)
其中串口號(hào)你連的哪個(gè)串口就設(shè)置哪個(gè)串口號(hào),我是連的COM3。
另一個(gè)要注意的就是波特率要115200(ESP-01的默認(rèn)波特率,也可以統(tǒng)一改為9600)。
打開串口后,給開發(fā)板上電。你的串口調(diào)試助手會(huì)有信息出來(lái)(文本顯示,不要十六進(jìn)制顯示)。
?諄MEM CHECK FAIL!!!
d{$弬s
Ai-Thinker Technology Co. Ltd.
invalid
顯示的信息類似上面,你可以先給個(gè)測(cè)試命令A(yù)T看看是否可以接受指令
注意!每個(gè)指令后要跟回車再發(fā)送!
如果返回OK則說(shuō)明指令可以被接收并識(shí)別。
如果下面的指令都會(huì)返回ERROR(在沒有給錯(cuò)指令的情況下),可以嘗試AT+RST重啟模塊。
1. 更改模式
指令:AT+CWMODE?
一般情況下,第一次使用會(huì)返回2,也就是AP模式,我們不用它發(fā)熱點(diǎn),所以要改回Station模式(模式1)。
指令:AT+CWMODE?
一般情況下,第一次使用會(huì)返回2,也就是AP模式,我們不用它發(fā)熱點(diǎn),所以要改回Station模式(模式1)。
指令:AT+CWMODE=1
返回:OK則成功
2. 接入熱點(diǎn)(連Wi-Fi)
指令:AT+CWJAP=<SSID>,<Password>
參數(shù):<SSID>處填寫熱點(diǎn)名稱,<Password>處填寫密碼。兩者都要用雙引號(hào)括起來(lái)。
例如:AT+CWJAP="CMCC","123456"
指令:AT+CWJAP=<SSID>,<Password>
參數(shù):<SSID>處填寫熱點(diǎn)名稱,<Password>處填寫密碼。兩者都要用雙引號(hào)括起來(lái)。
例如:AT+CWJAP="CMCC","123456"
返回:WIFI CONNECTED:連接到熱點(diǎn)
返回:WIFI GOT IP:分配到IP,走到這一步,就算已經(jīng)連入到熱點(diǎn)了。
如果忘記SSID了,想看一下可以使用下面的指令,列出廣播的SSID(隱藏的不會(huì)顯示)。
指令:AT+CWLAP
3. 連接TCP服務(wù)器
首先打開NetAssist,設(shè)置TCP Server,然后建立連接(注意防火墻)。注意,Server必須在Client所在內(nèi)網(wǎng)或其外網(wǎng)(我的是在同一個(gè)內(nèi)網(wǎng))。
網(wǎng)絡(luò)調(diào)試助手
首先打開NetAssist,設(shè)置TCP Server,然后建立連接(注意防火墻)。注意,Server必須在Client所在內(nèi)網(wǎng)或其外網(wǎng)(我的是在同一個(gè)內(nèi)網(wǎng))。
網(wǎng)絡(luò)調(diào)試助手
指令:AT+CIPSTART=<Type>,<DomainName>,<Port>
參數(shù):<Type>處寫TCP或UDP,<DomainName>處寫域名,<Port>處寫端口號(hào)。
本例:AT+CIPSTART="TCP","192.168.1.110",1234
返回:
CONNECT
OK
說(shuō)明連接成功。
在NetAssist中,數(shù)據(jù)接收框的下面有一個(gè)連接對(duì)象,點(diǎn)開后發(fā)現(xiàn)除了All Connections之外,多了一個(gè)客戶,就確定客戶連接到服務(wù)器了。
NetAssist
在下方文本框輸入信息后發(fā)送,可在串口調(diào)試助手中看到ESP8266所接收到的信息
UartAssist收到的信息:+IPD,10:Hello 簡(jiǎn)書
到這里就說(shuō)明網(wǎng)絡(luò)連接及建立TCP都可以順利完成,在下面的單片機(jī)操作中就會(huì)變得方便很多。
注意,ESP8266每次斷電后重新上電,最多只會(huì)自動(dòng)連到之前連接的熱點(diǎn),但不會(huì)自動(dòng)連接到TCP服務(wù)器,所以,建立連接要交給單片機(jī)來(lái)做。
問題
波特率
本例要使用的波特率為115200的,不是9600。引申出來(lái)的問題就是:算初值(方法詳見:《51單片機(jī)實(shí)戰(zhàn):與計(jì)算機(jī)異步串行通信》 - 知識(shí)點(diǎn) - 波特率 - 溢出率)。
手算真的很麻煩,因?yàn)樵趨?shù)固定的情況下,算起來(lái)已經(jīng)很煩了,更何況那些參數(shù)可能還會(huì)變得情況。
所以上神器:
初值計(jì)算器
反饋及識(shí)別
我們要做的是讓單片機(jī)在收到ESP8266回饋的WIFI GOT IP后發(fā)出連接TCP服務(wù)器的指令。
發(fā)送指令不用多說(shuō),只要記得在后面跟"換行(CR)"和"新行(NL)"字符就行(這兩個(gè)是不一樣的)。
1. 關(guān)鍵字符配對(duì)
如果是電腦程序的高級(jí)語(yǔ)言編程,這個(gè)問題就不存在了。但是我們給單片機(jī)寫程序就要牢記它的一些點(diǎn)(《扯會(huì)兒?jiǎn)纹瑱C(jī)開發(fā):開始》),比如硬件資源十分緊張。
一個(gè)是因?yàn)樗鎯?chǔ)空間很小,另一個(gè)是因?yàn)榫д裉?,所以我們要想辦法縮減配對(duì)時(shí)間。因?yàn)閱纹瑱C(jī)的使用往往很單一,所以策略都是根據(jù)情況來(lái)設(shè)定的。比如本例,單片機(jī)接收的回饋無(wú)非那么幾種,所以我制定的策略就是關(guān)鍵字符配對(duì)。
比如識(shí)別WIFI GOT IP,我只是別第一個(gè)字符W和第6個(gè)字符G,就說(shuō)明我收到的就是這個(gè)回饋信息。如果收到第一個(gè)是W,只有這條指令的第六個(gè)字符是G,所以就可以確定。如果是出現(xiàn)干擾,可能性也是比較低的,我們的交互是在網(wǎng)絡(luò)層的,下層也可以把出錯(cuò)的報(bào)文擋掉。
總體來(lái)說(shuō)還是蠻可靠的,如果你有更棒的方法,請(qǐng)?jiān)谠u(píng)論區(qū)告訴我,大家一起學(xué)習(xí)進(jìn)步。
2. 利用流水燈
上面就是理論工作,已經(jīng)做得7788了。但實(shí)際經(jīng)驗(yàn)告訴我,如果的代碼哪里也出岔子了在51單片機(jī)開發(fā)中真的比較難發(fā)現(xiàn),他不像高級(jí)程序語(yǔ)言可以報(bào)錯(cuò),可以try - catch。但這個(gè)就別想了,所以我們要自己想辦法,讓他可以反饋我們的程序至少是正常運(yùn)行的,這樣可以節(jié)省很多時(shí)間。
我在這里介紹的我的方法是利用LED,或成組的流水燈(更好)。承租的流水燈一般是8個(gè),可以直接顯示一個(gè)字節(jié)的信息,可能有人問為什么不用LCD?你若會(huì)LCD或者看過《51單片機(jī)實(shí)戰(zhàn):液晶顯示器のLCD1602》就會(huì)發(fā)現(xiàn),LCD本身的開發(fā)就有點(diǎn)復(fù)雜了。你很難保證這個(gè)子程序運(yùn)行正常,更別提用它抓錯(cuò)了。只能是不推薦哈,我覺得不可靠。LED的話就很簡(jiǎn)單了,就是個(gè)關(guān)開,一般不會(huì)出錯(cuò)。
QX-MINI51開發(fā)板上自帶流水燈,你要是入的其他開發(fā)板,一般都是有的。如果沒有,就單買LED回來(lái)(如果不知道去哪買,鏈接)。
我一般會(huì)怎么做呢,先讓流水燈直接顯示串口接收的數(shù)據(jù),為了知道我們至少是能接收到東西的。然后利用上面的關(guān)鍵字符識(shí)別,收到WIFI CONNECTED亮一號(hào)燈,收到WIFI GOT IP亮二號(hào)燈,收到CONNECT OK亮三號(hào)燈。這樣,根據(jù)燈亮滅的情況就能確定哪一步出了問題,然后定位到代碼或者設(shè)置。
還是那句話,如果你有更棒的方法,歡迎在評(píng)論區(qū)交流。
到這里就做完了所有理論上的準(zhǔn)備工作,也就是理論上我們已經(jīng)可以實(shí)現(xiàn)這個(gè)程序了,下面代碼實(shí)現(xiàn)。
代碼
說(shuō)下這次代碼比較新穎的地方,一個(gè)是用到了.c和.h文件組合的模塊形式,另一個(gè)是用到了函數(shù)指針(函數(shù)名也被括起來(lái)的那個(gè))。前者這種寫法是為了將各子功能模塊化,.h文件里的內(nèi)容相當(dāng)于面向?qū)ο缶幊汤锏膒ublic,.c文件一個(gè)是實(shí)現(xiàn).h內(nèi)的函數(shù),另一個(gè)就是隱藏函數(shù)和變量,相當(dāng)于private。后者是用來(lái)充當(dāng)高級(jí)語(yǔ)言中的“事件”,讓函數(shù)調(diào)用變得更加靈活,其具體用法請(qǐng)自己查資料或者看相關(guān)C語(yǔ)言書籍。
總而言之,這次的代碼是模塊化和事件化的,類似面向?qū)ο蟮木幋a風(fēng)格。
準(zhǔn)備
因?yàn)橛糜谕ㄓ崳晕野袮SCII內(nèi)所有的特殊字符都寫到一個(gè)頭文件里。雖然本例只用到其中的幾個(gè),但以后重復(fù)利用這個(gè)頭文件。
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
LCD
這里都是關(guān)于LCD1602顯示器的主要代碼,與文章《51單片機(jī)實(shí)戰(zhàn):液晶顯示器のLCD1602》所講的一樣,這里就不給出注釋了。
lcd1602.h
#ifndef __LCD1602__
#define __LCD1602__
typedef bit BOOL;
void LCD_writeCmd(unsigned char cmd); //寫命令
void LCD_writeData(unsigned char dat); //寫數(shù)據(jù)
void LCD_writeLine(unsigned char *line); //寫行數(shù)據(jù)
void LCD_init(); //初始化
void delay(unsigned int z); //粗略的延時(shí)器
#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); //每次送來(lái)信息都清屏,可以一直刷新顯示送來(lái)的信息。
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);
}
串口通信
這里用于單片機(jī)和ESP8266交互,與文章《51單片機(jī)實(shí)戰(zhàn):與計(jì)算機(jī)異步串行通信》相似。其中的不同點(diǎn)前面已經(jīng)說(shuō)過。
stc52ser.h
#ifndef __STC52_SER__
#define __STC52_SER__
extern void (*SerialPort_Event_ByteReceived)(unsigned char byte); //事件:串口接收到字節(jié)
void SerialPort_Init_Low(); //初始化為11.0592MHz下的9600波特率
void SerialPort_Init_High(); //初始化為22.1184下的115200波特率
void SerialPort_SendByte(unsigned char byte); //發(fā)送一個(gè)字節(jié)
void SerialPort_SendData(unsigned char* bytes); //發(fā)送一組字節(jié)
#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;
}
簡(jiǎn)單說(shuō)一下,這里留了兩個(gè)初始化函數(shù),SerialPort_Init_Low()用于11.0592MHz下的9600波特率,SerialPort_Init_High()用于22.1184MHz下的115200波特率(此例用這個(gè))。這樣寫只是為了以后可以重用(軟工狗的矯情)。
無(wú)線
這里是最主要的代碼,都是關(guān)于ESP8266的,也是作為一個(gè)模塊給主函數(shù)調(diào)用。
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服務(wù)器
extern void (*ESP01_Event_MsgReceived)(unsigned char* head); //事件:已獲得消息,head為消息數(shù)組頭
void ESP01_Init(); //無(wú)線模塊初始化
void ESP01_ConnectToTCPServer(); //連接TCP服務(wù)器
#endif
esp8266.c
#include <reg52.h>
#include "stc52ser.h"
#include "ASCIIS.h"
#include "esp8266.h"
#define BUFFER_MAX_SIZE 99 //緩沖區(qū)大小
unsigned char buffer[BUFFER_MAX_SIZE]; //緩沖區(qū):用于存放從ESP8266接收來(lái)的各種信息
//連接到TCP服務(wù)器的指令:AT+CIPSTART="TCP","192.168.1.110",1234。后面的CR和NL是AT指令的固定結(jié)尾,EOT用于SerialPort_SendData發(fā)送時(shí)識(shí)別結(jié)尾。
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的執(zhí)行步驟計(jì)數(shù)
int writeIndex = 0; //緩沖區(qū)寫索引
void (*ESP01_Event_WifiConnected)();
void (*ESP01_Event_IpGot)();
void (*ESP01_Event_TcpServerConnected)();
void (*ESP01_Event_MsgReceived)(unsigned char* head);
//注意:下面代碼推薦從后往前看,從注釋標(biāo)"1. "處開始。
void prepareForData(unsigned char byte); //因?yàn)榈谒牟胶偷谌綍?huì)相互調(diào)用,所以這里只是做了個(gè)聲明(C語(yǔ)言的矯情點(diǎn))。
//4. 將信息插入到緩沖區(qū)并送給單片機(jī)。
void insertDataIntoBuffer(unsigned char byte){
if(byte == '\\'){
//檢測(cè)到'\'后,將信息送出到單片機(jī)
buffer[writeIndex] = '\0';
(*ESP01_Event_MsgReceived)(buffer);
SerialPort_Event_ByteReceived = &prepareForData; //回到第三步,準(zhǔn)備接收下一條信息
writeIndex = 0;
return;
}
buffer[writeIndex++] = byte;
}
//3. 準(zhǔn)備信息:這里是過度步驟,前面可以觀察到,ESP8266在接收發(fā)來(lái)的信息時(shí)是有個(gè)頭的,這里的作用就是去頭。
void prepareForData(unsigned char byte){
if(byte == ':'){
SerialPort_Event_ByteReceived = &insertDataIntoBuffer;
writeIndex = 0;
}
}
//識(shí)別回饋指令:用于識(shí)別接收到的是WIFI CONNECTED(連上熱點(diǎn))還是WIFI IP GOT(獲得IP)還是CONNECT(連上TCP服務(wù)器)
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服務(wù)器后,進(jìn)入第三步。
}
}
}
//2. 這里開始向緩沖區(qū)存儲(chǔ)信息,用于識(shí)別。
void insertBuffer(unsigned char byte){
if(byte == NL){
//收到尾(NL)后,將緩沖區(qū)的回饋信息送去識(shí)別
parseCmd();
writeIndex = 0;
return;
}
buffer[writeIndex++] = byte;
}
//1. 接收頭:頭是無(wú)用信息,但我們要通過頭里面的一些字符,推算出什么時(shí)候到達(dá)第二步(WIFI CONNECTED)
void headerReceived(unsigned char byte){
if(byte == NL){
//頭內(nèi)有5個(gè)NL,只要數(shù)夠5個(gè),下一個(gè)就是第二步的內(nèi)容了。
if(++counter == 5){
SerialPort_Event_ByteReceived = &insertBuffer; //跳到第二步
counter = 1;
}
}
}
//同.h中的聲明
void ESP01_Init(){
SerialPort_Init_High();
SerialPort_Event_ByteReceived = &headerReceived; //事件注冊(cè)
}
//同.h中的聲明
void ESP01_ConnectToTCPServer(){
SerialPort_SendData(cmd_connectToTCPServer);
}
主函數(shù)
main.c
#include <reg52.h>
#include "lcd1602.h"
#include "esp8266.h"
//連接到Wi-Fi后,亮第一個(gè)燈
void EventHandler_WifiConnected(){
P1 &= 0xFE;
}
//獲得IP后,亮第二個(gè)燈
void EventHandler_IpGot(){
P1 &= 0xFD;
ESP01_ConnectToTCPServer();
}
//連接到TCP服務(wù)器后,亮第三個(gè)燈
void EventHandler_TcpServerConnected(){
P1 &= 0xFB;
}
//將ESP8266送來(lái)的信息,送去LCD顯示。
void EventHandler_MsgReceived(unsigned char* head){
LCD_writeLine(head);
}
//初始化
void init(){
ESP01_Event_WifiConnected = &EventHandler_WifiConnected; //事件注冊(cè)
ESP01_Event_IpGot = &EventHandler_IpGot; //事件注冊(cè)
ESP01_Event_TcpServerConnected = &EventHandler_TcpServerConnected; //事件注冊(cè)
ESP01_Event_MsgReceived = &EventHandler_MsgReceived; //事件注冊(cè)
ESP01_Init();
LCD_init();
}
void main(){
init();
while(1);
}
編譯
編譯前要設(shè)置一下目標(biāo)參數(shù),如下圖。
目標(biāo)設(shè)置
注意這里要改成XDATA,不然編譯通不過的。
效果
開始前請(qǐng)確定在同一個(gè)網(wǎng)絡(luò)下,并且服務(wù)端已開啟。
初始化效果
我只是把單片機(jī)連到移動(dòng)電源上了。
服務(wù)端
為了能讓1602第一行顯示Hello,第二行顯示W(wǎng)orld,中間可以留了11個(gè)空格。
客戶端
結(jié)語(yǔ)
猴!到這里這個(gè)程序就算完成了。這個(gè)比那些用手機(jī)控制開關(guān)燈要復(fù)雜一些。所以你只要掌握了這個(gè)例子,那些都不在話下了