小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

[智能硬件] 2、三分鐘看懂智能硬件原理

 看見就非常 2015-11-20

 

  

  恭喜大家順利通過測試一!在測試一中我們學(xué)會了如何利用現(xiàn)有模塊HC-05/06進(jìn)行簡單的連線來制作一個藍(lán)牙防丟器,同時學(xué)習(xí)了安卓藍(lán)牙相關(guān)的幾個API并最終制作了一個自己的藍(lán)牙防丟客戶端軟件。可能有些專門來看軟硬結(jié)合的同學(xué)會抱怨“什么呀,感覺就是在開發(fā)安卓App嘛!“。不錯!測試一的目的就是讓大家通過了解硬件原理DIY一個簡單的硬件,并學(xué)習(xí)如何充分利用移動端開發(fā)的特點設(shè)計一款配套的應(yīng)用。除此之外樓主還悄悄地為測試二埋下了伏筆,因為測試二將會涉及利用移動端和藍(lán)牙模塊的通信功能來實現(xiàn)一個遙控小風(fēng)扇!如果沒有前面關(guān)于藍(lán)牙軟硬件知識的鋪墊,直接做這個可能會很吃力。那么現(xiàn)在我們就著手測試二吧![正版請搜索:beautifulzzzz(看樓主博客園官方博客,享高質(zhì)量生活)嘻嘻?。?!]

 

1 預(yù)期效果構(gòu)思

  簡單起見我們實現(xiàn)一個可以通過手機(jī)App遙控的可調(diào)速小風(fēng)扇。如圖1_1左邊手機(jī)應(yīng)用部分主要包括1、2、3三個按鈕和4用于顯示風(fēng)扇速度的文本框;右邊小風(fēng)扇部分主要包括7風(fēng)扇模塊和8用于顯示風(fēng)扇速度的顯示模塊;中間的5、6表示雙方通過藍(lán)牙進(jìn)行無線通信實現(xiàn)遙控功能。

             圖1_1 預(yù)期效果構(gòu)思

 

2 硬件輪廓勾勒

      其實整個硬件部分都是要我們自己DIY的。如圖2_1所示1號為51最小系統(tǒng)模塊,起總控作用;2號為電源模塊,用于向整個系統(tǒng)供電;3號為藍(lán)牙模塊,用于單片機(jī)和智能手機(jī)進(jìn)行藍(lán)牙通信;4號為電機(jī)模塊(包括電機(jī)驅(qū)動電路),用于將電能轉(zhuǎn)換為機(jī)械能提供風(fēng);5號為數(shù)碼管顯示模塊,用于顯示小風(fēng)扇的當(dāng)前轉(zhuǎn)速。

    

              圖 2_1 硬件輪廓勾勒

 

3 硬件整體電路圖設(shè)計

  既然輪廓已經(jīng)勾勒出,接下來要看看我們具體需要哪些元件。首先對于51最小系統(tǒng)模塊(如圖3_1所示)包括晶振電路和89C52單片機(jī)(其實為了簡單筆者偷偷地將復(fù)位電路去掉了,這樣帶來的直接后果是程序燒不進(jìn)去。如果大家也一樣學(xué)著偷懶,不妨把該最小系統(tǒng)的電源引腳和串口引腳用杜邦線連接到你買來的開發(fā)板對應(yīng)的引腳處,同時把開發(fā)板上的單片機(jī)拿掉。這樣就可以利用開發(fā)板上的復(fù)位電路模塊來實現(xiàn)程序的有效燒寫。)

 

             圖 3_1 51最小系統(tǒng)

  對于電源模塊,我們可以使用可充電的5V鋰電池或者用3節(jié)1.5V的普通電池湊合。藍(lán)牙模塊是我們上一章中制作藍(lán)牙防丟器的HC-05或HC-06。這里電機(jī)模塊要特別說明下:如圖3_2需要用一個ULN2003做驅(qū)動,這樣控制信號要從4號引腳輸入以實現(xiàn)對馬達(dá)的控制。另外馬達(dá)可以選擇玩具四驅(qū)車上的那種。

    

            圖 3_2 電機(jī)模塊

  最后顯示模塊采用的是四位八段共陰數(shù)碼管3461AS。如圖3_3每個3461AS有4個數(shù)碼管,每個數(shù)碼管中有8個LED燈。這樣當(dāng)我們想使某一個數(shù)碼管顯示相應(yīng)的數(shù)字時,只要給4路位選信號和8路段選信號相應(yīng)的組合電平就能實現(xiàn)功能。需要另外說明的是:3461AS屬于共陰數(shù)碼管,如圖3_4其中6、8、9、12為位選引腳,3、5、10、1、2、4、7、11為段選引腳。如果我們想讓第二個數(shù)碼管顯示2時,要讓9號引腳置低電平其余位選引腳置高電平,同時要讓11、7、5、1、2置高電平其余段選置低電平。

            圖 3_3 3461AS封裝圖

            圖 3_4 3461AS內(nèi)部電路圖

  因此在實際電路中(如圖3_5)將P0口作段選信號引腳,同時用P2.3、P2.4、P2.5、P2.6作為位選信號引腳,通過單片機(jī)直接驅(qū)動即可。此外R1~R8八個上拉電阻不能忽視,起初筆者沒有注意結(jié)果燒壞了2個3461AS。

  

            圖3_5 顯示模塊實際驅(qū)動電路

  最終我們設(shè)計的電路圖如下,其中RXD和TXD引腳接HC-05或HC-06的TXD和RXD(要交錯相連)。因為HC-05/06是藍(lán)牙串口模塊,也就是說只要單片機(jī)采用串口驅(qū)動程序并且相應(yīng)的引腳連接正確,單片機(jī)-藍(lán)牙模塊通信完全和單片機(jī)-串口設(shè)備通信一樣。所以圖中的串口模塊也就相當(dāng)于我們的藍(lán)牙模塊,唯一需要注意的是單片機(jī)和藍(lán)牙模塊的RXD和TXD是交錯相連。

                            圖 3_6 整體電路圖

 

4 四位八段共陰數(shù)碼管3461AS的驅(qū)動程序設(shè)計

  由上面分析我們知道通過位選信號和段選信號的組合可以實現(xiàn)數(shù)碼管顯示功能。如果采用圖3_6所示電路圖,上面想讓第二個數(shù)碼管顯示2時,則P0等于0x5b(01011101),P2等于0xdf(11011111)。采用同樣的分析方法我們可以計算出讓八段數(shù)碼管顯示從0~F的所有P0對應(yīng)的賦值:0x3f 0x06 0x5b 0x4f 0x66 0x6d 0x7d 0x07 0x7f 0x6f 0x77 0x7c 0x39 0x5e 0x79 0x71,以及單獨選通第1位到第4位P2的所有賦值:0xbf 0xdf 0xef 0xf7。這樣當(dāng)我們想讓第3位顯示9只需要給P0、P2分別賦值0x6f和0xef即可。

  這時大家可能會有這樣的疑惑:“按照上面的說法似乎每次只能讓某一位顯示一個數(shù)字”。其實有這樣的疑惑說明大家學(xué)的比較認(rèn)真,其實生活中很多數(shù)碼管的顯示案例中都是每次只顯示一位的!之所以我們看到的情況是一次顯示多個,就在于數(shù)碼管驅(qū)動程序設(shè)計了!而這其中的秘訣則是采用了高頻刷新(也即動態(tài)掃描)這一技巧。如果大家對動態(tài)掃描沒有感覺,可以想象一下?lián)]舞熒光棒時的樣子——本來只是一根熒光棒,由于揮舞速度比較快而在空中劃出一道美麗的弧線。下面結(jié)合驅(qū)動程序和大家詳細(xì)介紹:

復(fù)制代碼
 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};//分別對應(yīng)相應(yīng)的數(shù)碼管點亮,即位碼
 6 unsigned char TempData[4]; //存儲顯示值的全局變量
 7 
 8 //------------------------------------------------
 9 //4位8段共陰數(shù)碼管顯示函數(shù)
10 //第一個參數(shù)為0表示從第一個數(shù)碼管開始顯示num個數(shù)
11 //提前要顯示的數(shù)要存在TempData中(TempData[0]表示要顯示的第一個數(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 }
復(fù)制代碼

  這里的DuanMa[]和WeiMa[]不再說明,TempData[4]用來存儲要顯示數(shù)據(jù)。在該驅(qū)動中只有一個Display函數(shù),正如第10行提示所述:第一個參數(shù)用來表明從哪一個數(shù)碼管開始顯示數(shù)據(jù),第二個參數(shù)表明一共要顯示多少位數(shù)據(jù)。這樣如果要在數(shù)碼管的后兩位顯示一個兩位數(shù)則可以用Display(2,2)。這里要特別說明下TempData數(shù)組,該數(shù)組用于存放數(shù)碼管要顯示的數(shù)據(jù),千萬不要把該數(shù)組和數(shù)碼管直接對應(yīng)。例如同樣是在數(shù)碼管后兩位顯示一個兩位數(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ù)存儲在TempData的前兩位,然后調(diào)用Display函數(shù)從第3個數(shù)碼管開始顯示2位來實現(xiàn)功能。方案二其實是把要顯示的數(shù)據(jù)存放在TempData的后兩位(前兩位默認(rèn)為0),然后調(diào)用Display函數(shù)從第1個數(shù)碼管開始顯示4位來實現(xiàn)功能。

  對于動態(tài)掃描這里用了一個很巧妙的方法:注意到第15行定義了一個靜態(tài)變量i,其功能在于實現(xiàn)一個周期內(nèi)實現(xiàn)對需要點亮的數(shù)碼管順序點亮。這樣如果Display(0,4)顯示1234,則數(shù)碼管的慢動作則為:第一個數(shù)碼管顯示1、接著第二個數(shù)碼管顯示2、然后第三個數(shù)碼管顯示3……由于刷新頻率很高,所以人眼看上去就是4個數(shù)碼管同時顯示1234的效果。

 

5 PWM實現(xiàn)變速小馬達(dá)

  欲實現(xiàn)直流小馬達(dá)的速度控制這里必須先講解下PWM。所謂PWM是“Pulse Width Modulation”的縮寫,簡稱脈寬調(diào)制。它是利用微處理器的數(shù)字輸出來對模擬電路進(jìn)行控制的一種非常有效的技術(shù)。這里舉個通俗的例子來解釋PWM:假設(shè)你是某公司的老板,手下有個奇葩的員工喜歡周期性的在一個小時內(nèi)干一會休息一會,如果你想多壓榨一下他就會督促讓他在一個周期內(nèi)多干活少休息。同樣的利用微處理器在一個比較短的周期內(nèi)設(shè)置某個引腳輸出高電平比低電平的持續(xù)時間多一點,從宏觀上看則呈現(xiàn)出輸出功率升高的效果,反之輸出功率變低。

          圖 5_1不同占空比的輸出脈沖

 

6 串口驅(qū)動程序設(shè)計

  上面已經(jīng)介紹過單片機(jī)和藍(lán)牙模塊的通信方式是采用串口通信,其重要特別注意的是單片機(jī)和HC-05/06的RXD引腳和TXD引腳要交錯相連。既然HC-05/06采用的是串口通信方式,所以在給單片機(jī)編程時只要按照串口驅(qū)動來設(shè)計就可以了。

復(fù)制代碼
 1 #include"uart.h"              
 2 
 3 #define Length 8
 4 
 5 unsigned char getByte[Length];  //定義臨時變量
 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ā)送一個字節(jié)
27 //------------------------------------------------
28 void SendByte(unsigned char dat)
29 {
30     SBUF = dat;
31     while(!TI);
32     TI = 0;
33 }
34 
35 //------------------------------------------------
36 //發(fā)送一個字符串
37 //------------------------------------------------
38 void SendStr(unsigned char *s)
39 {
40     while(*s!='\0')// \0 表示字符串結(jié)束標(biāo)志,通過檢測是否字符串末尾
41     {
42         SendByte(*s);
43         s++;
44     }
45 }
46 
47 //------------------------------------------------
48 //串口中斷程序
49 //------------------------------------------------
50 void UART_SER (void) interrupt 4 //串行中斷服務(wù)程序
51 {
52     if(RI)                                  //檢測接收完成標(biāo)志位置1
53     {
54         RI=0;                            //清零接收完成標(biāo)志位
55         getByte[point]=SBUF;               //讀取接收到的數(shù)據(jù)
56 
57         if(getByte[point++]==0xAA)        //遇到可能的結(jié)束標(biāo)志則發(fā)送flag
58             flag=1;                        //再主函數(shù)再進(jìn)行判斷是否為有效幀
59 
60         if(point==8)                    //防止數(shù)組越界
61             point=0;
62     }
63 }
復(fù)制代碼

  在該串口驅(qū)動文件里主要包括串口初始化函數(shù)InitUART,用來設(shè)置串口通信的波特率和接收中斷等。接下來分別是發(fā)送一字節(jié)函數(shù)和發(fā)送一個字符串函數(shù)。這里單片機(jī)向串口設(shè)備發(fā)送信息采用直接發(fā)送,即在程序中用到要發(fā)送信息的地方直接調(diào)用發(fā)送函數(shù)發(fā)送;但是數(shù)據(jù)接收則采用中斷的方式,因為在順序執(zhí)行的程序中不容易處理隨時都可能傳輸過來的信息。在中斷函數(shù)中把每次接收來的數(shù)據(jù)保存在getByte數(shù)組中。由于這里采用了數(shù)據(jù)幀,所以包含了對數(shù)據(jù)有效性的驗證,這個將在下面詳細(xì)分析。

 

7 硬件工程整體介紹

1) 打開Keil uVision2,點擊Project下的Open Project,打開智能小風(fēng)扇.Uv2加載工程。

                圖 7_1 打開工程

2) 待工程加載完畢,大家會在工程窗口中看到圖7_2所示文件結(jié)構(gòu)。其中FUNC組下面包含數(shù)碼管顯示驅(qū)動和串口驅(qū)動文件,INTE組下包含中斷相關(guān)文件,USER組下是最上層應(yīng)用程序文件。

           

                圖 7_2 文件結(jié)構(gòu)

3) 之前采用的思路是從底向上設(shè)計,這次將采用從上向下講解工程。首先看USER組下的main.c文件:

復(fù)制代碼
 1 #include "../FUNC/display_4X8.h"
 2 #include "../FUNC/uart.h"
 3 #include "../INTE/inte.h"
 4 
 5 sbit DCOUT = P1^1;//定義電機(jī)信號輸出端口
 6 //------------------------------------------------
 7 //全局變量
 8 //------------------------------------------------
 9 unsigned char PWM_ON;   //定義速度等級
10 #define CYCLE 10        //周期
11 
12 //變量
13 extern unsigned char code DuanMa[];// 顯示段碼值
14 extern unsigned char TempData[]; //存儲顯示值的全局變量
15 extern unsigned char getByte[];          //定義臨時變量
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);//定時器初始化
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]; //顯示速度等級
38     TempData[3]=DuanMa[0];     
39     PWM_ON=0;
40 
41     InitUART();
42     Init_Timer0();    //初始化定時器0,主要用于數(shù)碼管動態(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]; //顯示速度等級
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 //定時器中斷子程序
78 //------------------------------------------------
79 void Timer0_isr(void) interrupt 1 
80 {
81     static unsigned char count;
82     TH0=(65536-2000)/256;          //重新賦值 2ms
83     TL0=(65536-2000)%256;
84     
85     Display(0,4);                // 調(diào)用數(shù)碼管掃描
86     
87     if (count==PWM_ON) 
88     {
89         DCOUT = 0;               //如果定時等于on的時間,
90         //說明作用時間結(jié)束,輸出低電平
91     }
92     count++;
93     if(count == CYCLE)       //反之低電平時間結(jié)束后返回高電平
94     {
95         count=0;
96         if(PWM_ON!=0)    //如果開啟時間是0 保持原來狀態(tài)
97             DCOUT = 1;          
98     }
99 }
復(fù)制代碼

  整個工程的功能是遠(yuǎn)程安卓設(shè)備連接上該小風(fēng)扇后,通過發(fā)送幀F(xiàn)F EE num AA來無線控制風(fēng)扇轉(zhuǎn)速(其中num值需滿足0≤num≤10,其中FF和AA是幀頭和幀尾用于驗證是否為有效幀)。若小風(fēng)扇風(fēng)速調(diào)節(jié)成功則會返回給遠(yuǎn)程安卓設(shè)備AA EE num+1 FF來表明設(shè)置成功。此外當(dāng)遠(yuǎn)程設(shè)備發(fā)送FF DD num AA時將會獲得AA EE num+1 FF,通過這個命令可以獲取當(dāng)前的轉(zhuǎn)速。

  這里的answer[5]數(shù)組是用來存儲小風(fēng)扇應(yīng)答信息的,data1、data2用來存儲有效幀的中間兩位,PWM_ON是當(dāng)前的轉(zhuǎn)速,CYCLE是一個周期長度。在主函數(shù)的32~39行分別對answer固定部分進(jìn)行初始化、數(shù)碼管顯示數(shù)據(jù)TempData[]初始化、風(fēng)扇速度PWM_ON初始化。第41、42行主要初始化串口和定時器,接著進(jìn)入while主循環(huán)。在主循環(huán)中不斷對收集的數(shù)據(jù)幀進(jìn)行判斷是否為有效幀,如果是有效幀則分析是詢問速度命令還是設(shè)置速度命令,并分情況作出響應(yīng)。在主循環(huán)的最后(67~71)是一些收尾工作:緩沖區(qū)getByte清空、緩沖區(qū)指針point清零、接收標(biāo)志flag重置、以及開中斷。

  第76~99行是定時器中斷子程序,每隔2ms觸發(fā)一次。在其內(nèi)實現(xiàn)了對數(shù)碼管的高頻動態(tài)刷新和PWM。這里PWM是通過一個中間變量count來控制,從而實現(xiàn)在一個CYCLE*2ms的周期內(nèi)前PWM_ON*2ms時間輸出高電平的效果。

 

8 客戶端軟件構(gòu)成模塊

1) 打開Eclipse點擊File菜單欄下的Import按鈕準(zhǔn)備導(dǎo)入second_test工程(如圖8_1所示)。

圖 8_1 導(dǎo)入工程

2) 接著在彈出的Select窗口中選擇Android文件夾下的Existing Android Code Into Workspace點擊next(如圖8_2所示)。

              圖 8_2 選擇導(dǎo)入類型

3) 接著在彈出的框中點擊右上角的Browse按鈕,找到要導(dǎo)入的second_test所在路徑,并且需要勾選Copy projects into workspace(如圖8_3所示)。

              圖 8_3 選擇工程

4) 最終效果如圖8_4所示在src文件夾下有兩個包:其中上面一個是和藍(lán)牙相關(guān)的類(從下到上依次為藍(lán)牙設(shè)備搜索相關(guān)類、藍(lán)牙通信連接相關(guān)類和藍(lán)牙通信相關(guān)類),另一個包是UI相關(guān)類(上一章已經(jīng)講過ui_main.xml負(fù)責(zé)顯示,UI_Main.java負(fù)責(zé)顯示背后的邏輯實現(xiàn))。如果讀者導(dǎo)入過程中出現(xiàn)錯誤,也可以采用上一章的方法新建一個工程,然后把src下的文件、layout下的文件和AndroidManifest.xml文件做相應(yīng)的新建或修改。

                    圖 8_4 工程文件結(jié)構(gòu)

 

9 藍(lán)牙通信三劍客詳解

  從圖8_4大家可以看出整個工程最重要的部分在于bluetooth包下的藍(lán)牙相關(guān)的三個類,它們封裝并對外提供藍(lán)牙設(shè)備搜索、建立藍(lán)牙連接以及數(shù)據(jù)傳輸?shù)幕舅{(lán)牙功能。這樣在UI_Main.java中只要做簡單的調(diào)用即可實現(xiàn)比較繁瑣的藍(lán)牙通信功能,下面將針對它們做詳細(xì)的介紹。

1)BlueToothSearch主要負(fù)責(zé)藍(lán)牙設(shè)備搜索。仔細(xì)的讀者可能會發(fā)現(xiàn)它與上一章中的Func_BT.java很類似。如下的構(gòu)造函數(shù)除了去掉了表示信號強(qiáng)弱的RSSI向量去掉和在16行實例化并啟動一個BTStateThread的線程外基本沒變。

復(fù)制代碼
 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 }
復(fù)制代碼

  openBT函數(shù)和上一章的略有不同:上一章中打開藍(lán)牙設(shè)備函數(shù)的目的是確保本地藍(lán)牙設(shè)備打開的情況下進(jìn)行藍(lán)牙搜索,所以上一章中的函數(shù)體內(nèi)還包含了else語句,同時用onActivityResult進(jìn)行監(jiān)聽用戶是否授權(quán);本章的openBT函數(shù)僅僅是用來在本地藍(lán)牙設(shè)備沒有開啟時發(fā)送一個Intent請求,接著就撒手不管了。

復(fù)制代碼
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 }
復(fù)制代碼

    這里的doDIscovery函數(shù)并未做修改,仍然是取消正在進(jìn)行的搜索過程并啟動新的搜索。

1 public void doDiscovery() {
2     if (mBtAdapter.isDiscovering()) {
3         mBtAdapter.cancelDiscovery();
4     }
5     mBtAdapter.startDiscovery();
6 }

    當(dāng)上面啟動藍(lán)牙搜索后,在此過程中所搜到的藍(lán)牙設(shè)備將可以在下面的BroadcastReceiver獲得。這里每次發(fā)現(xiàn)一個藍(lán)牙設(shè)備時會獲取該設(shè)備的名稱和地址并放入相應(yīng)的向量中,在最后搜索結(jié)束時會通過handler將該消息傳遞給UI_Main.java。

復(fù)制代碼
 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_FINISHED
11                 .equals(action)) {
12             // 藍(lán)牙搜索完畢發(fā)送0x01msg
13             Message msg = new Message();
14             msg.what = 0x01;
15             mHandler.sendMessage(msg);
16         }
17     }
18 };
復(fù)制代碼

  在構(gòu)造函數(shù)的第16行有個new BTStateThread().start()語句,其主要功能是周期性檢測本地藍(lán)牙設(shè)備狀態(tài)(如下的BTStateThread類)。此外在run函數(shù)內(nèi)還加入了一旦本地藍(lán)牙狀態(tài)改變則發(fā)送0x10Handler消息,用來及時地通知UI_Main.java當(dāng)前的本地藍(lán)牙設(shè)備的狀態(tài)。

復(fù)制代碼
 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 }
復(fù)制代碼

2)  BlueToothConnect主要負(fù)責(zé)建立本地和遠(yuǎn)程藍(lán)牙的Bluetooth Socket連接。由于我們在BlueToothSearch中已經(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對象的createRfcommSocketToServiceRecord方法創(chuàng)建客戶端Bluetooth Socket。

復(fù)制代碼
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 }
復(fù)制代碼

  上面的setDevice函數(shù)僅僅通過傳入的地址獲得了Bluetooth Socket,接下來需要調(diào)用connect來啟動連接。(如下面代碼所示)啟動連接是放在一個獨立的線程里的,一旦連接建立完畢則通過Handler將該消息通知給activity。

復(fù)制代碼
 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ā)送0x02msg
14     Message msg=new Message();
15     msg.what = 0x02;
16     mHandler.sendMessage(msg);
17 }
復(fù)制代碼

  此外要特別說明下cancel()函數(shù),該函數(shù)體內(nèi)執(zhí)行關(guān)閉藍(lán)牙連接的函數(shù)。因為在很多時候,比如讀寫文件、網(wǎng)絡(luò)socket等,由于建立連接后沒有關(guān)閉連接會導(dǎo)致一些意外的錯誤。

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è)備上都會有Bluetooth Socket。自此之后兩者之間沒有太大的區(qū)別,可以使用這兩種設(shè)備上的Bluetooth Socket來發(fā)送和接收消息(這里因為HC-05/06已經(jīng)把藍(lán)牙通信協(xié)議固件化了,所以大家可能不能很好的理解上面一段話的精妙之處,如果大家自己嘗試開發(fā)一個手機(jī)和手機(jī)的藍(lán)牙聊天室或者藍(lán)牙對戰(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ā)送消息時,只要調(diào)用標(biāo)準(zhǔn)輸出流的write函數(shù)即可實現(xiàn);當(dāng)本地設(shè)備想讀取遠(yuǎn)程設(shè)備發(fā)送過來的消息時,只要調(diào)用標(biāo)準(zhǔn)輸入流的read函數(shù)即可實現(xiàn)。

復(fù)制代碼
 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 }
復(fù)制代碼

  和硬件部分藍(lán)牙數(shù)據(jù)傳輸類似:對于本地設(shè)備向遠(yuǎn)程設(shè)備發(fā)消息是本地程序可控的,即本地程序控制發(fā)送消息的時間點,因此這里僅僅把發(fā)送數(shù)據(jù)封裝成一個write函數(shù),一旦程序需要發(fā)送消息直接調(diào)用即可;但是對于遠(yuǎn)端設(shè)備向本地發(fā)送過來的消息本地是不可控的,即本地程序不清楚該消息會在什么時候出現(xiàn),在硬件中我們采用了中斷的方式解決的問題,而在這里我們采用一個獨立的輪訓(xùn)線程來處理的,這樣一旦有有效信息傳送過來就能夠做出及時的響應(yīng)(例如可以在有效信息過來時采用Handler將該消息傳送給Activity,本代碼中沒有做進(jìn)一步優(yōu)化)。

復(fù)制代碼
 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 }
復(fù)制代碼

  同樣的這里也需要一個用來關(guān)閉BluetoothSocket和標(biāo)準(zhǔn)輸入輸出流的cancel函數(shù)。

復(fù)制代碼
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 }
復(fù)制代碼

 

10 客戶端軟件整體邏輯梳理

  欲較好地梳理整個安卓工程,一般都是從Activity的onCreate函數(shù)開始的,此外通過結(jié)合對應(yīng)的XML文件能夠更快地理解。下面便是ui_main.xml所對應(yīng)的UI_Main.java中的onCreate函數(shù):該函數(shù)中最占篇幅的莫過于三個按鈕監(jiān)聽了。

  如代碼所示第54~86行為對應(yīng)XML中加減按鈕的監(jiān)聽,不難看出在mButton2和mButton3中核心是調(diào)用mBlueToothCommunicate.write(buffer)函數(shù)將數(shù)據(jù)幀buffer發(fā)送給遠(yuǎn)程藍(lán)牙設(shè)備。這里要幫大家回憶一下我們硬件設(shè)計時規(guī)定的控制命令幀的格式了:(請轉(zhuǎn)到第七節(jié)最后幾段)遠(yuǎn)程設(shè)備通過發(fā)送幀F(xiàn)F EE num AA來無線控制風(fēng)扇轉(zhuǎn)速(其中num值需滿足0≤num≤10,其中FF和AA是幀頭和幀尾用于驗證是否為有效幀)。所以在下面代碼中的7~11行是對控制命令幀的設(shè)置(這里初始化buffer[2]=0x00,即初始速度為0)。因此,大家也不難理解在加減按鈕監(jiān)聽中的對buffer[2]范圍的限制以及buffer[2]++和buffer[2]--的用意了。

復(fù)制代碼
 1 @Override
 2 protected void onCreate(Bundle savedInstanceState) {
 3     super.onCreate(savedInstanceState);
 4     setContentView(R.layout.ui_main);
 5 
 6     //控制命令幀格式(首尾為校驗,第二: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     //實例化藍(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         @Override
26         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         @Override
58         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         @Override
75         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 }
復(fù)制代碼

  其實有一點大家可能注意到了:加減按鈕初始化時是被setEnabled(false)的!因為調(diào)用藍(lán)牙的write函數(shù)已經(jīng)是藍(lán)牙搜索、建立連接之后的事情了,而在初始化時我們是不能輕易開放這兩個按鈕中的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)聽中可以看出其主要有三個功能:①當(dāng)本地藍(lán)牙設(shè)備沒有打開時,負(fù)責(zé)調(diào)用mBlueToothSearch.openBT()函數(shù)打開本地藍(lán)牙設(shè)備,并進(jìn)入連接小風(fēng)扇的功能;②當(dāng)本地藍(lán)牙打開并且還未連接遠(yuǎn)程小風(fēng)扇時,負(fù)責(zé)調(diào)用mBlueToothSearch.doDiscovery()函數(shù)開始搜索周邊藍(lán)牙設(shè)備,并啟動一個ProgressDialog告訴用戶稍等;③當(dāng)連接好了之后需要斷開連接時,負(fù)責(zé)調(diào)用藍(lán)牙建立連接和藍(lán)牙通信相關(guān)函數(shù)取消相關(guān)操作并讓加減按鈕失效。

                  圖 10_1 mButton1功能轉(zhuǎn)換圖

  從圖10_1中可以看出有一個過程筆者打了個問號,即從點擊mButton1執(zhí)行連接小風(fēng)扇如何變成可控制階段狀態(tài)的中間過程被我偷偷跳過了。上面第②點中講到當(dāng)本地藍(lán)牙打開并且還未連接遠(yuǎn)程小風(fēng)扇時,點擊按鈕會執(zhí)行mBlueToothSearch.doDiscovery()函數(shù),然后似乎就沒有狀態(tài)變換了。其實一切的一切都指向了Activity中的myHandler!

復(fù)制代碼
 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)異?;驗樗阉鞯皆O(shè)備
 8         case 0x01:
 9             mProgressDialog.setTitle("進(jìn)入嘗試連接藍(lán)牙設(shè)備階段...");
10             //當(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)入啟動通信階段...");
25             //將上一步獲得的socket傳給藍(lán)牙通信線程并啟動線程監(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 };
復(fù)制代碼

  這時大家可能會恍然大悟(想想上一節(jié)講的藍(lán)牙通信三劍客每個構(gòu)造函數(shù)中的Handler,以及時不時地在它們的成員函數(shù)內(nèi)部出現(xiàn)的發(fā)送Handler消息):原來mBlueToothSearch.doDiscovery()執(zhí)行將會啟動藍(lán)牙搜索,在其搜索過程中搜索的設(shè)備名和設(shè)備地址分別存儲在BlueToothSearch的公有成員變量mNameVector和mAddrVector中,然后在本次搜索結(jié)束后會向Activity發(fā)送一個類型為0x01的Handler消息,而該消息會被Activity中的handleMessage接收到:

                    圖 10_2 Handler消息之0x01

  經(jīng)過上面一個過程最終位于Activity中的handleMessage接收到0x01消息,請看上面代碼的第8~22行:在case 0x01中遍歷所有找到的藍(lán)牙設(shè)備是否有name為“HC-06”的藍(lán)牙設(shè)備(因為我用的藍(lán)牙模塊HC-06出廠默認(rèn)的name就是“HC-06”,此外大家可以參看HC-06的AT指令自行設(shè)置其名字)。當(dāng)找到名為“HC-06”的設(shè)備時(第15、16兩行)將會把該設(shè)備的地址傳給mBlueToothConnect來獲得遠(yuǎn)程藍(lán)牙設(shè)備,繼而獲得Bluetooth Socket,然后執(zhí)行獨立線程進(jìn)行啟動連接(大家可以結(jié)合上一節(jié)的BlueToothConnect理解)。當(dāng)然也不排除找不到設(shè)備的情況,第21行如果找不到想要的藍(lán)牙設(shè)備則把mProgressDialog等待窗口關(guān)閉。有一點要和大家說一下:這里是為了演示方便而采用name來確定藍(lán)牙設(shè)備,而name會出現(xiàn)相同的情況,真正應(yīng)用的時候一定要注意這一點的!

                     圖 10_3 Handler消息之0x02

  上面講到當(dāng)handleMessage收到0x01消息后,首先找到名為“HC-06”的藍(lán)牙設(shè)備地址,然后執(zhí)行圖10_3所示①的操作獲取BluetoothSocket,接著執(zhí)行②操作啟動線程。這樣等到RUN函數(shù)內(nèi)藍(lán)牙通信連接建立完畢后會向Activity發(fā)送0x02消息,又重新交給Activity來處理。

  請看代碼的第23~33行:在case 0x02中的第26、27兩行,首先調(diào)用mBlueToothCommunicate的setSocket方法來將將上一步獲得的socket傳給藍(lán)牙通信線程并啟動線程監(jiān)聽數(shù)據(jù),這樣就能實施藍(lán)牙無線通信了。所以在接下來的29~32行內(nèi)關(guān)閉了等待窗口并使能加減按鈕,使系統(tǒng)運行的狀態(tài)轉(zhuǎn)換到圖10_1中的可控階段。

                    圖 10_4 進(jìn)入可控制狀態(tài)

  至此,大家把圖10_2、10_3、10_4的圖連起來,然后再換掉圖10_1的帶問號的部分就是整個程序的基本狀態(tài)轉(zhuǎn)換圖。此外,細(xì)心的讀者可能會發(fā)現(xiàn)在Activity中還有0x10這條消息,其實該消息的發(fā)送者來自BlueToothSearch中的BTStateThread線程。在上一章中提到該線程起監(jiān)視本地藍(lán)牙設(shè)備狀態(tài)的作用,一旦本地藍(lán)牙設(shè)備的狀態(tài)被改變,則會發(fā)出0x10的消息。這樣在我們的Activity中一旦發(fā)現(xiàn)有0x10這個消息則改變相應(yīng)的狀態(tài),來提高程序的可靠性(否則中途關(guān)掉藍(lán)牙可能導(dǎo)致整個狀態(tài)機(jī)紊亂)。

 

11 最終成果檢查

怎么樣,上一章玩硬件沒有盡興的同學(xué)這回有感覺了嗎?這個看似簡單的小風(fēng)扇是不是還有點含金量?哈哈哈,給自己評價一下吧:

  • 自己焊制出51最小系統(tǒng)并成功給它燒個小程序(+ 20分)
  • 明白直流電機(jī)電路設(shè)計并理解了PWM的51編程(+ 10分)
  • 理解了3461AS的原理,并成功設(shè)計出自己的數(shù)碼管驅(qū)動(+ 20分)
  • 實現(xiàn)了51串口通信,能對電腦說hello嗎(+ 10分)
  • 大致明白安卓藍(lán)牙相關(guān)API并理解本章介紹的藍(lán)牙三劍客(+ 30分)
  • 腦袋里走通了整個客戶端軟件的狀態(tài)轉(zhuǎn)換圖(+ 30分)
  • 成功DIY出無線小風(fēng)扇系統(tǒng)(+30)
  • 在無線小風(fēng)扇的基礎(chǔ)上設(shè)計出無線小臺燈(+40)
  • 獲得了超過10個人的贊揚(+20)
  • ……

及格分70分,對自己要狠一點哦,否則后面有你受的!哈哈哈?。?!

[搜索:beautifulzzzz(看樓主博客園官方博客,享高質(zhì)量生活)嘻嘻!??!]

[如果有需要制作藍(lán)牙防丟器或藍(lán)牙室內(nèi)定位的可以聯(lián)系我哦~]

如果您覺得不錯,別忘點個贊讓更多的小伙伴看到\(^o^)/~ 

 

 

 

 

 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多