恭喜大家順利通過測(cè)試一!在測(cè)試一中我們學(xué)會(huì)了如何利用現(xiàn)有模塊HC-05/06進(jìn)行簡(jiǎn)單的連線來制作一個(gè)藍(lán)牙防丟器,同時(shí)學(xué)習(xí)了安卓藍(lán)牙相關(guān)的幾個(gè)API并最終制作了一個(gè)自己的藍(lán)牙防丟客戶端軟件。可能有些專門來看軟硬結(jié)合的同學(xué)會(huì)抱怨“什么呀,感覺就是在開發(fā)安卓App嘛!“。不錯(cuò)!測(cè)試一的目的就是讓大家通過了解硬件原理DIY一個(gè)簡(jiǎn)單的硬件,并學(xué)習(xí)如何充分利用移動(dòng)端開發(fā)的特點(diǎn)設(shè)計(jì)一款配套的應(yīng)用。除此之外樓主還悄悄地為測(cè)試二埋下了伏筆,因?yàn)闇y(cè)試二將會(huì)涉及利用移動(dòng)端和藍(lán)牙模塊的通信功能來實(shí)現(xiàn)一個(gè)遙控小風(fēng)扇!如果沒有前面關(guān)于藍(lán)牙軟硬件知識(shí)的鋪墊,直接做這個(gè)可能會(huì)很吃力。那么現(xiàn)在我們就著手測(cè)試二吧![正版請(qǐng)搜索:beautifulzzzz(看樓主博客園官方博客,享高質(zhì)量生活)嘻嘻?。?!]
1 預(yù)期效果構(gòu)思
簡(jiǎn)單起見我們實(shí)現(xiàn)一個(gè)可以通過手機(jī)App遙控的可調(diào)速小風(fēng)扇。如圖1_1左邊手機(jī)應(yīng)用部分主要包括1、2、3三個(gè)按鈕和4用于顯示風(fēng)扇速度的文本框;右邊小風(fēng)扇部分主要包括7風(fēng)扇模塊和8用于顯示風(fēng)扇速度的顯示模塊;中間的5、6表示雙方通過藍(lán)牙進(jìn)行無線通信實(shí)現(xiàn)遙控功能。
圖1_1 預(yù)期效果構(gòu)思
2 硬件輪廓勾勒
其實(shí)整個(gè)硬件部分都是要我們自己DIY的。如圖2_1所示1號(hào)為51最小系統(tǒng)模塊,起總控作用;2號(hào)為電源模塊,用于向整個(gè)系統(tǒng)供電;3號(hào)為藍(lán)牙模塊,用于單片機(jī)和智能手機(jī)進(jìn)行藍(lán)牙通信;4號(hào)為電機(jī)模塊(包括電機(jī)驅(qū)動(dòng)電路),用于將電能轉(zhuǎn)換為機(jī)械能提供風(fēng);5號(hào)為數(shù)碼管顯示模塊,用于顯示小風(fēng)扇的當(dāng)前轉(zhuǎn)速。
圖 2_1 硬件輪廓勾勒
3 硬件整體電路圖設(shè)計(jì)
既然輪廓已經(jīng)勾勒出,接下來要看看我們具體需要哪些元件。首先對(duì)于51最小系統(tǒng)模塊(如圖3_1所示)包括晶振電路和89C52單片機(jī)(其實(shí)為了簡(jiǎn)單筆者偷偷地將復(fù)位電路去掉了,這樣帶來的直接后果是程序燒不進(jìn)去。如果大家也一樣學(xué)著偷懶,不妨把該最小系統(tǒng)的電源引腳和串口引腳用杜邦線連接到你買來的開發(fā)板對(duì)應(yīng)的引腳處,同時(shí)把開發(fā)板上的單片機(jī)拿掉。這樣就可以利用開發(fā)板上的復(fù)位電路模塊來實(shí)現(xiàn)程序的有效燒寫。)
圖 3_1 51最小系統(tǒng)
對(duì)于電源模塊,我們可以使用可充電的5V鋰電池或者用3節(jié)1.5V的普通電池湊合。藍(lán)牙模塊是我們上一章中制作藍(lán)牙防丟器的HC-05或HC-06。這里電機(jī)模塊要特別說明下:如圖3_2需要用一個(gè)ULN2003做驅(qū)動(dòng),這樣控制信號(hào)要從4號(hào)引腳輸入以實(shí)現(xiàn)對(duì)馬達(dá)的控制。另外馬達(dá)可以選擇玩具四驅(qū)車上的那種。
圖 3_2 電機(jī)模塊
最后顯示模塊采用的是四位八段共陰數(shù)碼管3461AS。如圖3_3每個(gè)3461AS有4個(gè)數(shù)碼管,每個(gè)數(shù)碼管中有8個(gè)LED燈。這樣當(dāng)我們想使某一個(gè)數(shù)碼管顯示相應(yīng)的數(shù)字時(shí),只要給4路位選信號(hào)和8路段選信號(hào)相應(yīng)的組合電平就能實(shí)現(xiàn)功能。需要另外說明的是:3461AS屬于共陰數(shù)碼管,如圖3_4其中6、8、9、12為位選引腳,3、5、10、1、2、4、7、11為段選引腳。如果我們想讓第二個(gè)數(shù)碼管顯示2時(shí),要讓9號(hào)引腳置低電平其余位選引腳置高電平,同時(shí)要讓11、7、5、1、2置高電平其余段選置低電平。
圖 3_3 3461AS封裝圖
圖 3_4 3461AS內(nèi)部電路圖
因此在實(shí)際電路中(如圖3_5)將P0口作段選信號(hào)引腳,同時(shí)用P2.3、P2.4、P2.5、P2.6作為位選信號(hào)引腳,通過單片機(jī)直接驅(qū)動(dòng)即可。此外R1~R8八個(gè)上拉電阻不能忽視,起初筆者沒有注意結(jié)果燒壞了2個(gè)3461AS。
圖3_5 顯示模塊實(shí)際驅(qū)動(dòng)電路
最終我們?cè)O(shè)計(jì)的電路圖如下,其中RXD和TXD引腳接HC-05或HC-06的TXD和RXD(要交錯(cuò)相連)。因?yàn)镠C-05/06是藍(lán)牙串口模塊,也就是說只要單片機(jī)采用串口驅(qū)動(dòng)程序并且相應(yīng)的引腳連接正確,單片機(jī)-藍(lán)牙模塊通信完全和單片機(jī)-串口設(shè)備通信一樣。所以圖中的串口模塊也就相當(dāng)于我們的藍(lán)牙模塊,唯一需要注意的是單片機(jī)和藍(lán)牙模塊的RXD和TXD是交錯(cuò)相連。
圖 3_6 整體電路圖
4 四位八段共陰數(shù)碼管3461AS的驅(qū)動(dòng)程序設(shè)計(jì)
由上面分析我們知道通過位選信號(hào)和段選信號(hào)的組合可以實(shí)現(xiàn)數(shù)碼管顯示功能。如果采用圖3_6所示電路圖,上面想讓第二個(gè)數(shù)碼管顯示2時(shí),則P0等于0x5b(01011101),P2等于0xdf(11011111)。采用同樣的分析方法我們可以計(jì)算出讓八段數(shù)碼管顯示從0~F的所有P0對(duì)應(yīng)的賦值:0x3f 0x06 0x5b 0x4f 0x66 0x6d 0x7d 0x07 0x7f 0x6f 0x77 0x7c 0x39 0x5e 0x79 0x71,以及單獨(dú)選通第1位到第4位P2的所有賦值:0xbf 0xdf 0xef 0xf7。這樣當(dāng)我們想讓第3位顯示9只需要給P0、P2分別賦值0x6f和0xef即可。
這時(shí)大家可能會(huì)有這樣的疑惑:“按照上面的說法似乎每次只能讓某一位顯示一個(gè)數(shù)字”。其實(shí)有這樣的疑惑說明大家學(xué)的比較認(rèn)真,其實(shí)生活中很多數(shù)碼管的顯示案例中都是每次只顯示一位的!之所以我們看到的情況是一次顯示多個(gè),就在于數(shù)碼管驅(qū)動(dòng)程序設(shè)計(jì)了!而這其中的秘訣則是采用了高頻刷新(也即動(dòng)態(tài)掃描)這一技巧。如果大家對(duì)動(dòng)態(tài)掃描沒有感覺,可以想象一下?lián)]舞熒光棒時(shí)的樣子——本來只是一根熒光棒,由于揮舞速度比較快而在空中劃出一道美麗的弧線。下面結(jié)合驅(qū)動(dòng)程序和大家詳細(xì)介紹:
1 #include"display_4X8.h" 2 3 unsigned char code DuanMa[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 4 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};// 顯示段碼值0~F 5 unsigned char code WeiMa[]={0xbf,0xdf,0xef,0xf7};//分別對(duì)應(yīng)相應(yīng)的數(shù)碼管點(diǎn)亮,即位碼 6 unsigned char TempData[4]; //存儲(chǔ)顯示值的全局變量 7 8 //------------------------------------------------ 9 //4位8段共陰數(shù)碼管顯示函數(shù)10 //第一個(gè)參數(shù)為0表示從第一個(gè)數(shù)碼管開始顯示num個(gè)數(shù)11 //提前要顯示的數(shù)要存在TempData中(TempData[0]表示要顯示的第一個(gè)數(shù))12 //------------------------------------------------13 void Display(unsigned char FirstBit,unsigned char Num)14 {15 static unsigned char i=0;16 17 DataPort=0x00; //清空數(shù)據(jù),防止有交替重影18 DataControl=0x00;19 20 DataPort=TempData[i]; //取顯示數(shù)據(jù),段碼21 DataControl=WeiMa[i+FirstBit];22 23 i++;24 if(i==Num)25 i=0;26 }
這里的DuanMa[]和WeiMa[]不再說明,TempData[4]用來存儲(chǔ)要顯示數(shù)據(jù)。在該驅(qū)動(dòng)中只有一個(gè)Display函數(shù),正如第10行提示所述:第一個(gè)參數(shù)用來表明從哪一個(gè)數(shù)碼管開始顯示數(shù)據(jù),第二個(gè)參數(shù)表明一共要顯示多少位數(shù)據(jù)。這樣如果要在數(shù)碼管的后兩位顯示一個(gè)兩位數(shù)則可以用Display(2,2)。這里要特別說明下TempData數(shù)組,該數(shù)組用于存放數(shù)碼管要顯示的數(shù)據(jù),千萬不要把該數(shù)組和數(shù)碼管直接對(duì)應(yīng)。例如同樣是在數(shù)碼管后兩位顯示一個(gè)兩位數(shù)num可以采用下列兩種方案:
① TempData[0]=DuanMa[num/10];
TempData[1]=DuanMa[num%10];
Display(2,2);
② TempData[2]=DuanMa[num/10];
TempData[3]=DuanMa[num%10];
Display(0,4);
其中方案一直接把要顯示的兩位數(shù)據(jù)存儲(chǔ)在TempData的前兩位,然后調(diào)用Display函數(shù)從第3個(gè)數(shù)碼管開始顯示2位來實(shí)現(xiàn)功能。方案二其實(shí)是把要顯示的數(shù)據(jù)存放在TempData的后兩位(前兩位默認(rèn)為0),然后調(diào)用Display函數(shù)從第1個(gè)數(shù)碼管開始顯示4位來實(shí)現(xiàn)功能。
對(duì)于動(dòng)態(tài)掃描這里用了一個(gè)很巧妙的方法:注意到第15行定義了一個(gè)靜態(tài)變量i,其功能在于實(shí)現(xiàn)一個(gè)周期內(nèi)實(shí)現(xiàn)對(duì)需要點(diǎn)亮的數(shù)碼管順序點(diǎn)亮。這樣如果Display(0,4)顯示1234,則數(shù)碼管的慢動(dòng)作則為:第一個(gè)數(shù)碼管顯示1、接著第二個(gè)數(shù)碼管顯示2、然后第三個(gè)數(shù)碼管顯示3……由于刷新頻率很高,所以人眼看上去就是4個(gè)數(shù)碼管同時(shí)顯示1234的效果。
5 PWM實(shí)現(xiàn)變速小馬達(dá)
欲實(shí)現(xiàn)直流小馬達(dá)的速度控制這里必須先講解下PWM。所謂PWM是“Pulse Width Modulation”的縮寫,簡(jiǎn)稱脈寬調(diào)制。它是利用微處理器的數(shù)字輸出來對(duì)模擬電路進(jìn)行控制的一種非常有效的技術(shù)。這里舉個(gè)通俗的例子來解釋PWM:假設(shè)你是某公司的老板,手下有個(gè)奇葩的員工喜歡周期性的在一個(gè)小時(shí)內(nèi)干一會(huì)休息一會(huì),如果你想多壓榨一下他就會(huì)督促讓他在一個(gè)周期內(nèi)多干活少休息。同樣的利用微處理器在一個(gè)比較短的周期內(nèi)設(shè)置某個(gè)引腳輸出高電平比低電平的持續(xù)時(shí)間多一點(diǎn),從宏觀上看則呈現(xiàn)出輸出功率升高的效果,反之輸出功率變低。
圖 5_1不同占空比的輸出脈沖
6 串口驅(qū)動(dòng)程序設(shè)計(jì)
上面已經(jīng)介紹過單片機(jī)和藍(lán)牙模塊的通信方式是采用串口通信,其重要特別注意的是單片機(jī)和HC-05/06的RXD引腳和TXD引腳要交錯(cuò)相連。既然HC-05/06采用的是串口通信方式,所以在給單片機(jī)編程時(shí)只要按照串口驅(qū)動(dòng)來設(shè)計(jì)就可以了。
1 #include"uart.h" 2 3 #define Length 8 4 5 unsigned char getByte[Length]; //定義臨時(shí)變量 6 unsigned char flag; //接收標(biāo)記 7 unsigned char point; //指針 8 9 //------------------------------------------------10 //串口初始化11 //------------------------------------------------12 void InitUART (void)13 {14 flag=0;15 point=0;16 SCON = 0x50; // SCON: 模式 1, 8-bit UART, 使能接收 17 TMOD |= 0x20; // TMOD: timer 1, mode 2, 8-bit 重裝18 TH1 = 0xFD; // TH1: 重裝值 9600 波特率 晶振 11.0592MHz 19 TL1 = 0xFD; 20 TR1 = 1; // TR1: timer 1 打開 21 EA = 1; //打開總中斷22 ES = 1; //打開串口中斷23 } 24 25 //------------------------------------------------26 //發(fā)送一個(gè)字節(jié)27 //------------------------------------------------28 void SendByte(unsigned char dat)29 {30 SBUF = dat;31 while(!TI);32 TI = 0;33 }34 35 //------------------------------------------------36 //發(fā)送一個(gè)字符串37 //------------------------------------------------38 void SendStr(unsigned char *s)39 {40 while(*s!='\0')// \0 表示字符串結(jié)束標(biāo)志,通過檢測(cè)是否字符串末尾41 {42 SendByte(*s);43 s++;44 }45 }46 47 //------------------------------------------------48 //串口中斷程序49 //------------------------------------------------50 void UART_SER (void) interrupt 4 //串行中斷服務(wù)程序51 {52 if(RI) //檢測(cè)接收完成標(biāo)志位置153 {54 RI=0; //清零接收完成標(biāo)志位55 getByte[point]=SBUF; //讀取接收到的數(shù)據(jù)56 57 if(getByte[point++]==0xAA) //遇到可能的結(jié)束標(biāo)志則發(fā)送flag58 flag=1; //再主函數(shù)再進(jìn)行判斷是否為有效幀59 60 if(point==8) //防止數(shù)組越界61 point=0;62 }63 }
在該串口驅(qū)動(dòng)文件里主要包括串口初始化函數(shù)InitUART,用來設(shè)置串口通信的波特率和接收中斷等。接下來分別是發(fā)送一字節(jié)函數(shù)和發(fā)送一個(gè)字符串函數(shù)。這里單片機(jī)向串口設(shè)備發(fā)送信息采用直接發(fā)送,即在程序中用到要發(fā)送信息的地方直接調(diào)用發(fā)送函數(shù)發(fā)送;但是數(shù)據(jù)接收則采用中斷的方式,因?yàn)樵陧樞驁?zhí)行的程序中不容易處理隨時(shí)都可能傳輸過來的信息。在中斷函數(shù)中把每次接收來的數(shù)據(jù)保存在getByte數(shù)組中。由于這里采用了數(shù)據(jù)幀,所以包含了對(duì)數(shù)據(jù)有效性的驗(yàn)證,這個(gè)將在下面詳細(xì)分析。
7 硬件工程整體介紹
1) 打開Keil uVision2,點(diǎn)擊Project下的Open Project,打開智能小風(fēng)扇.Uv2加載工程。
圖 7_1 打開工程
2) 待工程加載完畢,大家會(huì)在工程窗口中看到圖7_2所示文件結(jié)構(gòu)。其中FUNC組下面包含數(shù)碼管顯示驅(qū)動(dòng)和串口驅(qū)動(dòng)文件,INTE組下包含中斷相關(guān)文件,USER組下是最上層應(yīng)用程序文件。
圖 7_2 文件結(jié)構(gòu)
3) 之前采用的思路是從底向上設(shè)計(jì),這次將采用從上向下講解工程。首先看USER組下的main.c文件:
1 #include "../FUNC/display_4X8.h" 2 #include "../FUNC/uart.h" 3 #include "../INTE/inte.h" 4 5 sbit DCOUT = P1^1;//定義電機(jī)信號(hào)輸出端口 6 //------------------------------------------------ 7 //全局變量 8 //------------------------------------------------ 9 unsigned char PWM_ON; //定義速度等級(jí)10 #define CYCLE 10 //周期11 12 //變量13 extern unsigned char code DuanMa[];// 顯示段碼值14 extern unsigned char TempData[]; //存儲(chǔ)顯示值的全局變量15 extern unsigned char getByte[]; //定義臨時(shí)變量16 extern unsigned char flag; //接收標(biāo)記17 extern unsigned char point; //指針18 19 //函數(shù)20 extern void Display(unsigned char FirstBit,unsigned char Num);//數(shù)碼管顯示函數(shù)21 extern void Init_Timer0(void);//定時(shí)器初始化22 extern void InitUART(void);23 extern void SendStr(unsigned char *s);24 extern void SendByte(unsigned char dat);25 26 //------------------------------------------------27 //主函數(shù)28 //------------------------------------------------29 void main (void)30 {31 //發(fā)來的FF EE num AA 或 FF DD num AA返回 AA和FF互換位置32 unsigned char answer[5];33 unsigned char k,data1,data2;34 answer[0]=0xAA;35 answer[3]=0xFF;36 answer[4]='\0';37 TempData[2]=DuanMa[0]; //顯示速度等級(jí)38 TempData[3]=DuanMa[0]; 39 PWM_ON=0;40 41 InitUART();42 Init_Timer0(); //初始化定時(shí)器0,主要用于數(shù)碼管動(dòng)態(tài)掃描43 44 while (1) //主循環(huán)45 {46 if(flag==1 && point>3 && getByte[point-4]==0xFF)47 {48 ES = 0; //關(guān)串口中斷49 50 answer[1]=0xFF;51 data1=getByte[point-3];52 data2=getByte[point-2];53 if(data1==0xEE){54 if(0<=data2 && data2<=10){55 PWM_ON=data2;56 TempData[2]=DuanMa[PWM_ON/10]; //顯示速度等級(jí)57 TempData[3]=DuanMa[PWM_ON%10]; 58 answer[1]=0xEE;59 answer[2]=data2+1;60 }61 }else if(data1==0xDD){62 answer[1]=0xDD;63 answer[2]=PWM_ON+1;64 }65 SendStr(answer); //應(yīng)答66 67 for(k=0;k<8;k++) //清空getByte中數(shù)據(jù)68 getByte[k]=0;69 point=0; //point歸零70 flag=0; //重置flag標(biāo)志71 ES=1; //打開串口中斷72 }73 }74 }75 76 //------------------------------------------------77 //定時(shí)器中斷子程序78 //------------------------------------------------79 void Timer0_isr(void) interrupt 1 80 {81 static unsigned char count;82 TH0=(65536-2000)/256; //重新賦值 2ms83 TL0=(65536-2000)%256;84 85 Display(0,4); // 調(diào)用數(shù)碼管掃描86 87 if (count==PWM_ON) 88 {89 DCOUT = 0; //如果定時(shí)等于on的時(shí)間,90 //說明作用時(shí)間結(jié)束,輸出低電平91 }92 count++;93 if(count == CYCLE) //反之低電平時(shí)間結(jié)束后返回高電平94 {95 count=0;96 if(PWM_ON!=0) //如果開啟時(shí)間是0 保持原來狀態(tài)97 DCOUT = 1; 98 }99 }
整個(gè)工程的功能是遠(yuǎn)程安卓設(shè)備連接上該小風(fēng)扇后,通過發(fā)送幀F(xiàn)F EE num AA來無線控制風(fēng)扇轉(zhuǎn)速(其中num值需滿足0≤num≤10,其中FF和AA是幀頭和幀尾用于驗(yàn)證是否為有效幀)。若小風(fēng)扇風(fēng)速調(diào)節(jié)成功則會(huì)返回給遠(yuǎn)程安卓設(shè)備AA EE num+1 FF來表明設(shè)置成功。此外當(dāng)遠(yuǎn)程設(shè)備發(fā)送FF DD num AA時(shí)將會(huì)獲得AA EE num+1 FF,通過這個(gè)命令可以獲取當(dāng)前的轉(zhuǎn)速。
這里的answer[5]數(shù)組是用來存儲(chǔ)小風(fēng)扇應(yīng)答信息的,data1、data2用來存儲(chǔ)有效幀的中間兩位,PWM_ON是當(dāng)前的轉(zhuǎn)速,CYCLE是一個(gè)周期長(zhǎng)度。在主函數(shù)的32~39行分別對(duì)answer固定部分進(jìn)行初始化、數(shù)碼管顯示數(shù)據(jù)TempData[]初始化、風(fēng)扇速度PWM_ON初始化。第41、42行主要初始化串口和定時(shí)器,接著進(jìn)入while主循環(huán)。在主循環(huán)中不斷對(duì)收集的數(shù)據(jù)幀進(jìn)行判斷是否為有效幀,如果是有效幀則分析是詢問速度命令還是設(shè)置速度命令,并分情況作出響應(yīng)。在主循環(huán)的最后(67~71)是一些收尾工作:緩沖區(qū)getByte清空、緩沖區(qū)指針point清零、接收標(biāo)志flag重置、以及開中斷。
第76~99行是定時(shí)器中斷子程序,每隔2ms觸發(fā)一次。在其內(nèi)實(shí)現(xiàn)了對(duì)數(shù)碼管的高頻動(dòng)態(tài)刷新和PWM。這里PWM是通過一個(gè)中間變量count來控制,從而實(shí)現(xiàn)在一個(gè)CYCLE*2ms的周期內(nèi)前PWM_ON*2ms時(shí)間輸出高電平的效果。
8 客戶端軟件構(gòu)成模塊
1) 打開Eclipse點(diǎn)擊File菜單欄下的Import按鈕準(zhǔn)備導(dǎo)入second_test工程(如圖8_1所示)。
圖 8_1 導(dǎo)入工程
2) 接著在彈出的Select窗口中選擇Android文件夾下的Existing Android Code Into Workspace點(diǎn)擊next(如圖8_2所示)。
圖 8_2 選擇導(dǎo)入類型
3) 接著在彈出的框中點(diǎn)擊右上角的Browse按鈕,找到要導(dǎo)入的second_test所在路徑,并且需要勾選Copy projects into workspace(如圖8_3所示)。
圖 8_3 選擇工程
4) 最終效果如圖8_4所示在src文件夾下有兩個(gè)包:其中上面一個(gè)是和藍(lán)牙相關(guān)的類(從下到上依次為藍(lán)牙設(shè)備搜索相關(guān)類、藍(lán)牙通信連接相關(guān)類和藍(lán)牙通信相關(guān)類),另一個(gè)包是UI相關(guān)類(上一章已經(jīng)講過ui_main.xml負(fù)責(zé)顯示,UI_Main.java負(fù)責(zé)顯示背后的邏輯實(shí)現(xiàn))。如果讀者導(dǎo)入過程中出現(xiàn)錯(cuò)誤,也可以采用上一章的方法新建一個(gè)工程,然后把src下的文件、layout下的文件和AndroidManifest.xml文件做相應(yīng)的新建或修改。
圖 8_4 工程文件結(jié)構(gòu)
9 藍(lán)牙通信三劍客詳解
從圖8_4大家可以看出整個(gè)工程最重要的部分在于bluetooth包下的藍(lán)牙相關(guān)的三個(gè)類,它們封裝并對(duì)外提供藍(lán)牙設(shè)備搜索、建立藍(lán)牙連接以及數(shù)據(jù)傳輸?shù)幕舅{(lán)牙功能。這樣在UI_Main.java中只要做簡(jiǎn)單的調(diào)用即可實(shí)現(xiàn)比較繁瑣的藍(lán)牙通信功能,下面將針對(duì)它們做詳細(xì)的介紹。
1)BlueToothSearch主要負(fù)責(zé)藍(lán)牙設(shè)備搜索。仔細(xì)的讀者可能會(huì)發(fā)現(xiàn)它與上一章中的Func_BT.java很類似。如下的構(gòu)造函數(shù)除了去掉了表示信號(hào)強(qiáng)弱的RSSI向量去掉和在16行實(shí)例化并啟動(dòng)一個(gè)BTStateThread的線程外基本沒變。
1 public BlueToothSearch(Activity activity, Handler mHandler) { 2 this.mHandler = mHandler; 3 this.activity = activity; 4 5 mNameVector = new Vector<String>();// 向量 6 mAddrVector = new Vector<String>(); 7 8 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 9 activity.registerReceiver(mReceiver, filter);10 filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);11 activity.registerReceiver(mReceiver, filter);12 activity.registerReceiver(mReceiver, filter);13 14 mBtAdapter = BluetoothAdapter.getDefaultAdapter();15 16 new BTStateThread().start();//藍(lán)牙狀態(tài)監(jiān)聽17 }
openBT函數(shù)和上一章的略有不同:上一章中打開藍(lán)牙設(shè)備函數(shù)的目的是確保本地藍(lán)牙設(shè)備打開的情況下進(jìn)行藍(lán)牙搜索,所以上一章中的函數(shù)體內(nèi)還包含了else語句,同時(shí)用onActivityResult進(jìn)行監(jiān)聽用戶是否授權(quán);本章的openBT函數(shù)僅僅是用來在本地藍(lán)牙設(shè)備沒有開啟時(shí)發(fā)送一個(gè)Intent請(qǐng)求,接著就撒手不管了。
1 public void openBT() {2 // 如果沒有打開則打開3 if (!mBtAdapter.isEnabled()) {4 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);5 activity.startActivityForResult(intent, ENABLE_BLUETOOTH);6 }7 }
這里的doDIscovery函數(shù)并未做修改,仍然是取消正在進(jìn)行的搜索過程并啟動(dòng)新的搜索。
1 public void doDiscovery() {2 if (mBtAdapter.isDiscovering()) {3 mBtAdapter.cancelDiscovery();4 }5 mBtAdapter.startDiscovery();6 }
當(dāng)上面啟動(dòng)藍(lán)牙搜索后,在此過程中所搜到的藍(lán)牙設(shè)備將可以在下面的BroadcastReceiver獲得。這里每次發(fā)現(xiàn)一個(gè)藍(lán)牙設(shè)備時(shí)會(huì)獲取該設(shè)備的名稱和地址并放入相應(yīng)的向量中,在最后搜索結(jié)束時(shí)會(huì)通過handler將該消息傳遞給UI_Main.java。
1 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 2 @Override 3 public void onReceive(Context context, Intent intent) { 4 String action = intent.getAction(); 5 if (BluetoothDevice.ACTION_FOUND.equals(action)) { 6 BluetoothDevice device = intent 7 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 8 mNameVector.add(device.getName()); 9 mAddrVector.add(device.getAddress());10 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED11 .equals(action)) {12 // 藍(lán)牙搜索完畢發(fā)送0x01msg13 Message msg = new Message();14 msg.what = 0x01;15 mHandler.sendMessage(msg);16 }17 }18 };
在構(gòu)造函數(shù)的第16行有個(gè)new BTStateThread().start()語句,其主要功能是周期性檢測(cè)本地藍(lán)牙設(shè)備狀態(tài)(如下的BTStateThread類)。此外在run函數(shù)內(nèi)還加入了一旦本地藍(lán)牙狀態(tài)改變則發(fā)送0x10Handler消息,用來及時(shí)地通知UI_Main.java當(dāng)前的本地藍(lán)牙設(shè)備的狀態(tài)。
1 class BTStateThread extends Thread { 2 public void run() { 3 boolean oldBTState; 4 while (true) { 5 try { 6 Thread.sleep(1000); 7 oldBTState=BTState; 8 BTState = mBtAdapter.isEnabled(); 9 if(oldBTState!=BTState){//一旦藍(lán)牙狀態(tài)改變就發(fā)送消息10 // 藍(lán)牙狀態(tài)改變發(fā)送0x10消息11 Message msg = new Message();12 msg.what = 0x10;13 mHandler.sendMessage(msg);14 }15 } catch (InterruptedException e) {}16 }17 }18 }
2) BlueToothConnect主要負(fù)責(zé)建立本地和遠(yuǎn)程藍(lán)牙的Bluetooth Socket連接。由于我們?cè)贐lueToothSearch中已經(jīng)獲得了周邊藍(lán)牙設(shè)備的名稱和地址,所以(代碼中第3行)這里直接調(diào)用getRemoteDevice函數(shù)右地址直接獲得遠(yuǎn)程藍(lán)牙設(shè)備。接著(代碼中第5行)通過調(diào)用代表目標(biāo)遠(yuǎn)程服務(wù)設(shè)備的BluetoothDevice對(duì)象的createRfcommSocketToServiceRecord方法創(chuàng)建客戶端Bluetooth Socket。
1 public void setDevice(String Addr){2 mBtAdapter = BluetoothAdapter.getDefaultAdapter();3 mmDevice = mBtAdapter.getRemoteDevice(Addr);4 try {5 mmSocket = mmDevice.createRfcommSocketToServiceRecord(MY_UUID);6 } catch (IOException e) {7 }8 }
上面的setDevice函數(shù)僅僅通過傳入的地址獲得了Bluetooth Socket,接下來需要調(diào)用connect來啟動(dòng)連接。(如下面代碼所示)啟動(dòng)連接是放在一個(gè)獨(dú)立的線程里的,一旦連接建立完畢則通過Handler將該消息通知給activity。
1 public void run() { 2 setName("ConnectThread"); 3 try { 4 mmSocket.connect(); 5 } catch (IOException e) { 6 try { 7 mmSocket.close(); 8 } catch (IOException e2) { 9 10 }11 return;12 }13 //藍(lán)牙連接完畢發(fā)送0x02msg14 Message msg=new Message();15 msg.what = 0x02;16 mHandler.sendMessage(msg);17 }
此外要特別說明下cancel()函數(shù),該函數(shù)體內(nèi)執(zhí)行關(guān)閉藍(lán)牙連接的函數(shù)。因?yàn)樵诤芏鄷r(shí)候,比如讀寫文件、網(wǎng)絡(luò)socket等,由于建立連接后沒有關(guān)閉連接會(huì)導(dǎo)致一些意外的錯(cuò)誤。
1 public void cancel() {2 try {3 mmSocket.close();4 } catch (IOException e) {5 }6 }
3) BlueToothCommunicate主要負(fù)責(zé)數(shù)據(jù)傳輸。上面已經(jīng)解決了連接建立問題,這樣當(dāng)連接一旦建立,客戶端和服務(wù)器設(shè)備上都會(huì)有Bluetooth Socket。自此之后兩者之間沒有太大的區(qū)別,可以使用這兩種設(shè)備上的Bluetooth Socket來發(fā)送和接收消息(這里因?yàn)镠C-05/06已經(jīng)把藍(lán)牙通信協(xié)議固件化了,所以大家可能不能很好的理解上面一段話的精妙之處,如果大家自己嘗試開發(fā)一個(gè)手機(jī)和手機(jī)的藍(lán)牙聊天室或者藍(lán)牙對(duì)戰(zhàn)游戲就能明白我的意思了)。下面是其構(gòu)造函數(shù),和BlueToothConnect類似負(fù)責(zé)將Activity的Handler傳入。
1 public BlueToothCommunicate(Handler mHandler) {2 this.mHandler = mHandler; 3 state=true;4 }
這里的setSocket主要是根據(jù)BlueToothConnect建立的BluetoothSocket來獲取標(biāo)準(zhǔn)輸入輸出流。這樣當(dāng)本地設(shè)備想向遠(yuǎn)程設(shè)備發(fā)送消息時(shí),只要調(diào)用標(biāo)準(zhǔn)輸出流的write函數(shù)即可實(shí)現(xiàn);當(dāng)本地設(shè)備想讀取遠(yuǎn)程設(shè)備發(fā)送過來的消息時(shí),只要調(diào)用標(biāo)準(zhǔn)輸入流的read函數(shù)即可實(shí)現(xiàn)。
1 public void setSocket(BluetoothSocket socket){ 2 mmSocket = socket; 3 InputStream tmpIn = null; 4 OutputStream tmpOut = null; 5 // 獲取輸入輸出流 6 try { 7 tmpIn = socket.getInputStream(); 8 tmpOut = socket.getOutputStream(); 9 } catch (IOException e) {10 }11 mmInStream = tmpIn;12 mmOutStream = tmpOut;13 }
和硬件部分藍(lán)牙數(shù)據(jù)傳輸類似:對(duì)于本地設(shè)備向遠(yuǎn)程設(shè)備發(fā)消息是本地程序可控的,即本地程序控制發(fā)送消息的時(shí)間點(diǎn),因此這里僅僅把發(fā)送數(shù)據(jù)封裝成一個(gè)write函數(shù),一旦程序需要發(fā)送消息直接調(diào)用即可;但是對(duì)于遠(yuǎn)端設(shè)備向本地發(fā)送過來的消息本地是不可控的,即本地程序不清楚該消息會(huì)在什么時(shí)候出現(xiàn),在硬件中我們采用了中斷的方式解決的問題,而在這里我們采用一個(gè)獨(dú)立的輪訓(xùn)線程來處理的,這樣一旦有有效信息傳送過來就能夠做出及時(shí)的響應(yīng)(例如可以在有效信息過來時(shí)采用Handler將該消息傳送給Activity,本代碼中沒有做進(jìn)一步優(yōu)化)。
1 // 利用線程一直收數(shù)據(jù) 2 public void run() { 3 byte[] buffer = new byte[1024]; 4 int bytes; 5 // 循環(huán)一直接收 6 while (state) { 7 try { 8 // bytes是返回讀取的字符數(shù)量,其中數(shù)據(jù)存在buffer中 9 bytes = mmInStream.read(buffer);10 String readMessage = new String(buffer, 0, bytes);11 Log.i("beautifulzzzz", "read: " + bytes + " mes: "12 + readMessage);13 } catch (IOException e) {14 break;15 }16 }17 }18 19 // 發(fā)送就直接發(fā)送,沒有用線程20 public void write(byte[] buffer) throws IOException {21 mmOutStream.write(buffer);22 }
同樣的這里也需要一個(gè)用來關(guān)閉BluetoothSocket和標(biāo)準(zhǔn)輸入輸出流的cancel函數(shù)。
1 public void cancel(){2 try {3 state=false;//讓死循環(huán)停止4 mmSocket.close();5 mmInStream.close();6 mmOutStream.close();7 } catch (IOException e) {8 }9 }
10 客戶端軟件整體邏輯梳理
欲較好地梳理整個(gè)安卓工程,一般都是從Activity的onCreate函數(shù)開始的,此外通過結(jié)合對(duì)應(yīng)的XML文件能夠更快地理解。下面便是ui_main.xml所對(duì)應(yīng)的UI_Main.java中的onCreate函數(shù):該函數(shù)中最占篇幅的莫過于三個(gè)按鈕監(jiān)聽了。
如代碼所示第54~86行為對(duì)應(yīng)XML中加減按鈕的監(jiān)聽,不難看出在mButton2和mButton3中核心是調(diào)用mBlueToothCommunicate.write(buffer)函數(shù)將數(shù)據(jù)幀buffer發(fā)送給遠(yuǎn)程藍(lán)牙設(shè)備。這里要幫大家回憶一下我們硬件設(shè)計(jì)時(shí)規(guī)定的控制命令幀的格式了:(請(qǐng)轉(zhuǎn)到第七節(jié)最后幾段)遠(yuǎn)程設(shè)備通過發(fā)送幀F(xiàn)F EE num AA來無線控制風(fēng)扇轉(zhuǎn)速(其中num值需滿足0≤num≤10,其中FF和AA是幀頭和幀尾用于驗(yàn)證是否為有效幀)。所以在下面代碼中的7~11行是對(duì)控制命令幀的設(shè)置(這里初始化buffer[2]=0x00,即初始速度為0)。因此,大家也不難理解在加減按鈕監(jiān)聽中的對(duì)buffer[2]范圍的限制以及buffer[2]++和buffer[2]--的用意了。
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 setContentView(R.layout.ui_main); 5 6 //控制命令幀格式(首尾為校驗(yàn),第二:0xEE為設(shè)置速度,0xDD為獲取速度,第三:速度值) 7 buffer=new byte[4]; 8 buffer[0]=(byte) 0xFF; 9 buffer[1]=(byte) 0xEE;10 buffer[2]=(byte) 0x00;11 buffer[3]=(byte) 0xAA;12 13 //實(shí)例化藍(lán)牙三劍客(搜索、連接、通信)14 //myHandler是用來反饋信息的15 mBlueToothSearch=new BlueToothSearch(this, myHandler);16 mBlueToothConnect=new BlueToothConnect(myHandler);17 mBlueToothCommunicate=new BlueToothCommunicate(myHandler);18 19 mTextView = (TextView)findViewById(R.id.textView1);20 21 mButton1 = (Button) findViewById(R.id.button_start);22 if(mBlueToothSearch.getBT()==true) mButton1.setText("連接我的小風(fēng)扇");23 else mButton1.setText("打開藍(lán)牙設(shè)備");24 mButton1.setOnClickListener(new OnClickListener() {25 @Override26 public void onClick(View v) {27 if(mButton1.getText().equals("打開藍(lán)牙設(shè)備")){28 mBlueToothSearch.clearVector();29 mBlueToothSearch.openBT();30 mButton1.setText("連接我的小風(fēng)扇");31 }else if(mButton1.getText().equals("連接我的小風(fēng)扇")){32 mBlueToothSearch.clearVector();33 mBlueToothSearch.doDiscovery();34 35 mProgressDialog = ProgressDialog.show(UI_Main.this,"進(jìn)入搜索藍(lán)牙設(shè)備階段...", "稍等一下~", true); 36 }else{37 if(mBlueToothConnect!=null){38 mBlueToothConnect.cancel();39 mBlueToothConnect=null;40 mBlueToothConnect=new BlueToothConnect(myHandler);41 }42 if(mBlueToothCommunicate!=null){43 mBlueToothCommunicate.cancel();44 mBlueToothCommunicate=null;45 mBlueToothCommunicate=new BlueToothCommunicate(myHandler);46 }47 mButton1.setText("連接我的小風(fēng)扇");48 mButton2.setEnabled(false);49 mButton3.setEnabled(false);50 }51 }52 });53 54 mButton2=(Button) findViewById(R.id.button_add);55 mButton2.setEnabled(false);56 mButton2.setOnClickListener(new OnClickListener() {57 @Override58 public void onClick(View v) {59 if(buffer[2]<(byte) 0x0A){60 buffer[2]++;61 try {62 mBlueToothCommunicate.write(buffer);63 mTextView.setText(new Integer(buffer[2]).toString());64 } catch (IOException e) {65 e.printStackTrace();66 }67 }68 }69 });70 71 mButton3=(Button) findViewById(R.id.button_cut);72 mButton3.setEnabled(false);73 mButton3.setOnClickListener(new OnClickListener() {74 @Override75 public void onClick(View v) {76 if(buffer[2]>(byte) 0x00){77 buffer[2]--;78 try {79 mBlueToothCommunicate.write(buffer);80 mTextView.setText(new Integer(buffer[2]).toString());81 } catch (IOException e) {82 e.printStackTrace();83 }84 }85 }86 });87 }
其實(shí)有一點(diǎn)大家可能注意到了:加減按鈕初始化時(shí)是被setEnabled(false)的!因?yàn)檎{(diào)用藍(lán)牙的write函數(shù)已經(jīng)是藍(lán)牙搜索、建立連接之后的事情了,而在初始化時(shí)我們是不能輕易開放這兩個(gè)按鈕中的write功能的。所以在此之前我們必須保證連接已經(jīng)建立完畢,這就要引出稍微復(fù)雜的mButton1按鈕監(jiān)聽了。
注意到上面代碼的第22、23兩行,首先調(diào)用mBlueToothSearch的getBT()行數(shù)判斷用戶當(dāng)前藍(lán)牙設(shè)備是否打開,如果打開則mButton1的功能直接可設(shè)置為“連接我的小風(fēng)扇”,否則mButton1要設(shè)置為“打開藍(lán)牙設(shè)備”。從mButton1的監(jiān)聽中可以看出其主要有三個(gè)功能:①當(dāng)本地藍(lán)牙設(shè)備沒有打開時(shí),負(fù)責(zé)調(diào)用mBlueToothSearch.openBT()函數(shù)打開本地藍(lán)牙設(shè)備,并進(jìn)入連接小風(fēng)扇的功能;②當(dāng)本地藍(lán)牙打開并且還未連接遠(yuǎn)程小風(fēng)扇時(shí),負(fù)責(zé)調(diào)用mBlueToothSearch.doDiscovery()函數(shù)開始搜索周邊藍(lán)牙設(shè)備,并啟動(dòng)一個(gè)ProgressDialog告訴用戶稍等;③當(dāng)連接好了之后需要斷開連接時(shí),負(fù)責(zé)調(diào)用藍(lán)牙建立連接和藍(lán)牙通信相關(guān)函數(shù)取消相關(guān)操作并讓加減按鈕失效。
圖 10_1 mButton1功能轉(zhuǎn)換圖
從圖10_1中可以看出有一個(gè)過程筆者打了個(gè)問號(hào),即從點(diǎn)擊mButton1執(zhí)行連接小風(fēng)扇如何變成可控制階段狀態(tài)的中間過程被我偷偷跳過了。上面第②點(diǎn)中講到當(dāng)本地藍(lán)牙打開并且還未連接遠(yuǎn)程小風(fēng)扇時(shí),點(diǎn)擊按鈕會(huì)執(zhí)行mBlueToothSearch.doDiscovery()函數(shù),然后似乎就沒有狀態(tài)變換了。其實(shí)一切的一切都指向了Activity中的myHandler!
1 // 消息句柄(線程里無法進(jìn)行界面更新,所以要把消息從線程里發(fā)送出來在消息句柄里進(jìn)行處理) 2 public Handler myHandler = new Handler() { 3 @Override 4 public void handleMessage(Message msg) { 5 switch(msg.what){ 6 case 0x00: 7 break;//出現(xiàn)異?;?yàn)樗阉鞯皆O(shè)備 8 case 0x01: 9 mProgressDialog.setTitle("進(jìn)入嘗試連接藍(lán)牙設(shè)備階段...");10 //當(dāng)搜索完畢自動(dòng)查找是否是我們的設(shè)備然后嘗試連接11 boolean isFind=false;12 for(int i=0;i<mBlueToothSearch.mNameVector.size();i++){13 if(mBlueToothSearch.mNameVector.get(i).equals("HC-06")){14 Log.i("beautifulzzzz",mBlueToothSearch.mNameVector.get(i));15 mBlueToothConnect.setDevice(mBlueToothSearch.mAddrVector.get(i));16 mBlueToothConnect.start();17 isFind=true;18 break;19 }20 }21 if(isFind!=true)mProgressDialog.dismiss();//等待窗口關(guān)閉22 break;//搜索完畢23 case 0x02:24 mProgressDialog.setTitle("進(jìn)入啟動(dòng)通信階段...");25 //將上一步獲得的socket傳給藍(lán)牙通信線程并啟動(dòng)線程監(jiān)聽數(shù)據(jù)26 mBlueToothCommunicate.setSocket(mBlueToothConnect.mmSocket);27 mBlueToothCommunicate.start();28 29 mProgressDialog.dismiss();//等待窗口關(guān)閉30 mButton1.setText("斷開我的小風(fēng)扇");31 mButton2.setEnabled(true);32 mButton3.setEnabled(true);33 break;//連接完畢34 case 0x03:break;35 case 0x04:break;36 case 0x10:37 if(mBlueToothSearch.getBT()==true 38 && mButton1.getText().equals("打開藍(lán)牙設(shè)備")){ 39 mButton1.setText("連接我的小風(fēng)扇");40 }else if(mBlueToothSearch.getBT()==false){41 if(mBlueToothConnect!=null){42 mBlueToothConnect.cancel();43 mBlueToothConnect=null;44 mBlueToothConnect=new BlueToothConnect(myHandler);45 }46 if(mBlueToothCommunicate!=null){47 mBlueToothCommunicate.cancel();48 mBlueToothCommunicate=null;49 mBlueToothCommunicate=new BlueToothCommunicate(myHandler);50 }51 mButton1.setText("打開藍(lán)牙設(shè)備");52 mButton2.setEnabled(false);53 mButton3.setEnabled(false);54 }55 break;//藍(lán)牙狀態(tài)改變56 default:break;57 }58 }59 };
這時(shí)大家可能會(huì)恍然大悟(想想上一節(jié)講的藍(lán)牙通信三劍客每個(gè)構(gòu)造函數(shù)中的Handler,以及時(shí)不時(shí)地在它們的成員函數(shù)內(nèi)部出現(xiàn)的發(fā)送Handler消息):原來mBlueToothSearch.doDiscovery()執(zhí)行將會(huì)啟動(dòng)藍(lán)牙搜索,在其搜索過程中搜索的設(shè)備名和設(shè)備地址分別存儲(chǔ)在BlueToothSearch的公有成員變量mNameVector和mAddrVector中,然后在本次搜索結(jié)束后會(huì)向Activity發(fā)送一個(gè)類型為0x01的Handler消息,而該消息會(huì)被Activity中的handleMessage接收到:
圖 10_2 Handler消息之0x01
經(jīng)過上面一個(gè)過程最終位于Activity中的handleMessage接收到0x01消息,請(qǐng)看上面代碼的第8~22行:在case 0x01中遍歷所有找到的藍(lán)牙設(shè)備是否有name為“HC-06”的藍(lán)牙設(shè)備(因?yàn)槲矣玫乃{(lán)牙模塊HC-06出廠默認(rèn)的name就是“HC-06”,此外大家可以參看HC-06的AT指令自行設(shè)置其名字)。當(dāng)找到名為“HC-06”的設(shè)備時(shí)(第15、16兩行)將會(huì)把該設(shè)備的地址傳給mBlueToothConnect來獲得遠(yuǎn)程藍(lán)牙設(shè)備,繼而獲得Bluetooth Socket,然后執(zhí)行獨(dú)立線程進(jìn)行啟動(dòng)連接(大家可以結(jié)合上一節(jié)的BlueToothConnect理解)。當(dāng)然也不排除找不到設(shè)備的情況,第21行如果找不到想要的藍(lán)牙設(shè)備則把mProgressDialog等待窗口關(guān)閉。有一點(diǎn)要和大家說一下:這里是為了演示方便而采用name來確定藍(lán)牙設(shè)備,而name會(huì)出現(xiàn)相同的情況,真正應(yīng)用的時(shí)候一定要注意這一點(diǎn)的!
圖 10_3 Handler消息之0x02
上面講到當(dāng)handleMessage收到0x01消息后,首先找到名為“HC-06”的藍(lán)牙設(shè)備地址,然后執(zhí)行圖10_3所示①的操作獲取BluetoothSocket,接著執(zhí)行②操作啟動(dòng)線程。這樣等到RUN函數(shù)內(nèi)藍(lán)牙通信連接建立完畢后會(huì)向Activity發(fā)送0x02消息,又重新交給Activity來處理。
請(qǐng)看代碼的第23~33行:在case 0x02中的第26、27兩行,首先調(diào)用mBlueToothCommunicate的setSocket方法來將將上一步獲得的socket傳給藍(lán)牙通信線程并啟動(dòng)線程監(jiān)聽數(shù)據(jù),這樣就能實(shí)施藍(lán)牙無線通信了。所以在接下來的29~32行內(nèi)關(guān)閉了等待窗口并使能加減按鈕,使系統(tǒng)運(yùn)行的狀態(tài)轉(zhuǎn)換到圖10_1中的可控階段。
圖 10_4 進(jìn)入可控制狀態(tài)
至此,大家把圖10_2、10_3、10_4的圖連起來,然后再換掉圖10_1的帶問號(hào)的部分就是整個(gè)程序的基本狀態(tài)轉(zhuǎn)換圖。此外,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)在Activity中還有0x10這條消息,其實(shí)該消息的發(fā)送者來自BlueToothSearch中的BTStateThread線程。在上一章中提到該線程起監(jiān)視本地藍(lán)牙設(shè)備狀態(tài)的作用,一旦本地藍(lán)牙設(shè)備的狀態(tài)被改變,則會(huì)發(fā)出0x10的消息。這樣在我們的Activity中一旦發(fā)現(xiàn)有0x10這個(gè)消息則改變相應(yīng)的狀態(tài),來提高程序的可靠性(否則中途關(guān)掉藍(lán)牙可能導(dǎo)致整個(gè)狀態(tài)機(jī)紊亂)。
11 最終成果檢查
怎么樣,上一章玩硬件沒有盡興的同學(xué)這回有感覺了嗎?這個(gè)看似簡(jiǎn)單的小風(fēng)扇是不是還有點(diǎn)含金量?哈哈哈,給自己評(píng)價(jià)一下吧:
及格分70分,對(duì)自己要狠一點(diǎn)哦,否則后面有你受的!哈哈哈?。?!
[搜索:beautifulzzzz(看樓主博客園官方博客,享高質(zhì)量生活)嘻嘻?。?!]
[如果有需要制作藍(lán)牙防丟器或藍(lán)牙室內(nèi)定位的可以聯(lián)系我哦~]
如果您覺得不錯(cuò),別忘點(diǎn)個(gè)贊讓更多的小伙伴看到\(^o^)/~
聯(lián)系客服