早在1994 年愛立信公司就創立了藍牙技術,並制定了基本的技術規範,原意是創造一種設備間通訊的標准化協議,以解決設備間通訊不兼容的情況,規範公布後得到大量移動設備制造商的支持,並於1999年成立藍牙技術聯盟(Bluetooth Special Interest Group),該聯盟制定並維護藍牙無線規範,並對設備制造廠商提供Bluetooth認證與授權。
當前影響最廣的版本應該是藍牙4.0,本標准中增加了 Bluetooth Smart 和Bluetooth SmartReady 標准。特別是Bluetooth Smart版本,作為低功耗藍牙(Bluetooth Low Energy,簡稱BLE),相對歷史版本有質的飛躍,主要表現在成本低,功耗低,峰值電流極小並可以非常快速的建立連接,使用一粒紐扣電池就可以連續工作數年之久。相較於Zigbee和傳統藍牙,協議標准化和低功耗的優勢讓BLE在智能家居和穿戴設備上的優勢一目了然,如圖1所示。
圖1 常用無線協議比較
特別是2011年開始蘋果iOS原生支持BLE之後,BLE得到大量iOS周邊廠商和智能設備廠商的響應,基於BLE技術開發的智能硬件遍地開花,如:智能體重秤,智能手環,智能燈控,智能水杯,智能馬桶,智能鬧鐘……
項目背景
近兩年各種智能家居的產品層出不窮,而且眾多“科技公司”“互聯網企業”“資本投資公司”紛紛加入戰團,看到做手機的 A 公司出了一個智能淨化器,做空調的 B 公司出了一個智能手機,做手表的 C 公司出了一個智能手環,做馬桶的 D 公司坐不住了,在馬桶裡面塞進去了一個溫控+通訊模塊,就誕生了智能馬桶…智能設備市場好熱鬧啊。那麼智能產業迅速發展的現在,我們在智能家居上還能有什麼創意可以挑釁?靈感真的都源於生活,小熊有一天開門收快遞,然後無情的風把萬惡的門拍死了,只剩小熊穿著睡衣舉著手機拿著包裹在風中凌亂,開鎖小哥在小熊女友擔保外加3張毛爺爺之後才高抬貴手開了門,懊惱啊…拍大腿的瞬間,當時小熊就萌發了一個念頭,要是門鎖換成智能控制的話,是不是鑰匙就不再困擾我們了!!!只需要打開手機 APP,就可以操縱智能門鎖開門,或者像有強迫症的熊二,每次鎖完門都要回頭再看一次門有沒有鎖好,是不是有了門鎖的手機APP,就可以隨時看自家門鎖的開關狀態了!
想法出來了,回頭就開始一步步落實。經過對場景設想,最終選用藍牙+以太網網關的方案,正是因為藍牙短距離+傳輸速度快的優勢,其中以太網網關部分使用W5500 硬件協議棧芯片實現。為了更加體現門鎖的智能化,小熊設想了幾個場景:其一是小熊出門收快遞,門被無意中關上了,可以直接用手機開門;其二小熊不在家,小熊女朋友需要進門洗衣服做飯,小熊可以遠程給女朋友開門;其三是熊二被女朋友趕出來了,需要臨時借宿,小熊可以給熊二手機授予限時的開門權限(如2周)。看起來是不是很強大!圖2就是小熊今天要為大家介紹的藍牙智能門鎖的實現示意圖。
圖2 藍牙智能門鎖示意圖
本方案的組成主要分為三個部分:BLE門鎖機構,BLE網關設計,門鎖管理服務器。因為門鎖管理服務器主要為數據庫管理以及APP調用等內容,本文不做過多闡述,本文將主要講述BLE門鎖和BLE的以太網網關的實現等部分內容。
關於BLE的實現,我們選用的是目前市場上最常見的TI CC2541,CC2541是BLE單芯片解決方案,包含一個工業級的8051內核以及RF收發器,集成TI的BLE低功耗協議棧並擁有相對完善的低功耗外設;而以太網部分用的硬件協議棧芯片W5500,W5500 芯片使用硬件邏輯門電路實現TCP/IP協議棧的傳輸層及網絡層(如:TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE等協議),並集成了數據鏈路層,物理層,以及 32K 字節片上 RAM 作為數據收發緩存。不言而喻,W5500 非常適合CC2541這種8051內核,而且片上資源不是很豐富的MCU,一切就這麼愉快的決定了。
為了實現BLE通訊,我們需要使用兩個 CC2541 模塊,一個作為 Central,另一個作為 Peripheral;他們之間實現BLE通信,其中 Peripheral 作為門鎖機構的控制,而 Central 則驅動 W5500 作為 TCP Client 實現網絡通信,從而在網絡端查詢門鎖狀態以及實現網絡控制開鎖的功能。圖3列出了需要准備的硬件設備。
圖3 所需硬件設備
准備工作:
1. 安裝編譯環境 IAR Embedded Workbench for 8051 8.10 Evaluation
2. 安裝協議棧 BLE-CC254x-1.3.2
3. 安裝 CC-Debugger 模塊調試下載器驅動
BLE 以太網網關部分:
BLE 以太網網關部分結構如圖4:
圖4 BLE 以太網網關接線圖
需要在 CC2541 的 Central 模式中集成 W5500 的驅動以及 Socket 處理部分,由於 W5500 的函數驅動庫是分層次書寫的,我們只需將 SPI 通信的硬件抽像層的函數重新編寫即可。以下為 CC2541 的 SPI1 的初始化配置函數和數據收發函數的程序,以及復位管腳的控制程序:
01 void WIZ_SPI_Init(void)
02 {
03 PERCFG |= 0×02; // PERCFG.U1CFG = 1
04 P1SEL |= 0xE0; // P1_7, P1_6, and P1_5 are
05 peripherals
06
07 P1SEL &= ~0×10; // P1_4 is GPIO (SSN)
08 P1DIR |= 0×10; // SSN is set as output
09
10 // Set baud rate to max (system clock frequency / 8)
11 U1CSR = 0; // SPI Master Mode
12 // Configure phase, polarity, and bit order
13 U1GCR = 15;
14 U1BAUD = 255;
15 U1UCR = 0×80;
16 U1GCR |= BV(5);
17
18 P1SEL &= ~0×08; // P1_3 is GPIO (SSN)
19 P1DIR |= 0×08; // P1_3 is set as output
20 }
21
22 void WIZ_RST(uint8 val)
23 {
24 if (val == LOW)
25 {
26 P1_3=0;
27 }
28 else if (val == HIGH)
29 {
30 P1_3=1;
31 }
32 }
33 // Connected to Data Flash
34 void WIZ_CS(uint8 val)
35 {
36 if (val == LOW)
37 {
38 P1_4=0;
39 }
40 else if (val == HIGH)
41 {
42 P1_4=1;
43 }
44 }
需要注意的是,CC2541 的 LCD 驅動的部分引腳與SPI1的幾個引腳是復用的,需要將和 LCD 有關的編譯項去掉,避免發生衝突,導致 SPI 不可用。具體方法為在工程選項的編譯子項裡,將 ”HAL_LCD=TRUE”更改為“HAL_LCD=FALSE”。
程序重寫完畢後,打開TI官方 BLE 的例程 SerialApp3,此例程包含 Central 和Peripheral 兩部分,原始例程可以實現串口數據透傳,基於此例進行集成相對起來會比較簡單。將 W5500 的驅動程序包添加到工程中,如圖 5 所示。
圖5 將 W5500 部分集成並初始化
其中Ethernrt_Init函數主要負責W5500的接口和參數的初始化:
01 /*該函數將會在任務函數的初始化函數中調用*/
02 void Ethernrt_Init( uint8 taskID )
03 {
04 WIZ_SPI_Init(); //初始化CC2541的SPI1作為 W5500 的通訊接口
05 Reset_W5500(); //復位W5500
06 set_default(); //設置初始化參數如MAC,IP地址等參數
07 set_network(); //使初始化的IP參數生效
08
09 //記錄任務函數的taskID,備用
10 sendMsgTo_TaskID = taskID;
11 }
TI 的官方 BLE 例程中有比較完備的協議棧程序,提供了完備的應用函數供用戶調用,用戶可以在應用層添加自己的任務和事件;更改應用層數據之前我們必須了解一下 BLE 的應答機制以及 CC2541 的 OSAL 也就是系統抽像層的任務調用機制,首先TI已經做了大量工作,我們無需重寫 BLE 協議,而只需要根據自身的需求更改相應任務參數即可,在 SimpleBLECentral_Init 函數中將 Central 的相關參數初始化,比如:
GAPCentralRole_SetParameter:設置 Central 可以掃描到的最大從端設備數目;
GATT_InitClient:初始化 GATT_Client,一般情況下 Central 做為 Client,Peripheral 作為 Server,Central 通過 GATT_WriteCharValue 和GATT_ReadCharValue 來和 Peripheral 進行通訊;
GATT_RegisterForInd:注冊當前任務為 GATT 的 notify 和 indicatee 的接收端,當 Peripheral 通過 notify 發來數據時,當前任務函數會收到數據;
osal_set_event:該函數會啟動任務函數 START_DEVICE_EVT以及SBP_PERIODIC_EVT,由此轉入系統任務的運行。
而我們的目的是在當前的任務處理機制中增加 W5500 的通訊連接維持以及通訊數據處理的工作,TI 的意圖是減少每項任務的處理時間,從而獲得系統的快速響應,否則單項任務處理時間過長的話就會因為任務延遲而造成 BLE 連接中斷。基於此原理,應盡量減少 W5500 每個任務循環所占用的時間,從而獲得盡快的任務響應,所以應該減少原始的程序中任務等待和delay時間,我在將 W5500 的通訊任務函數 do_tcpclient() 放進主循環之後也對本函數進行了一些瘦身處理。
01 void osal_start_system( void )
02 {
03 #if !defined ( ZBIT ) && !defined ( UBIT )
04 for (;;) // Forever Loop
05 #endif
06 {
07 #ifdef BLECentral
08 do_tcpclient();
09 #endif
10
11 osal_run_system();
12 }
13 }
01 void do_tcpclient(void)
02 {
03 uint8 s=0;
04 uint16 anyport=3000; /*定義一個任意端口並初始化*/
05
06 switch (getSn_SR(s))
07 {
08 case SOCK_CLOSED: //查詢Socket狀態是closed狀態時,初始化當前Socket
09 socket(s,Sn_MR_TCP,anyport++,Sn_MR_ND);
10 break;
11
12 case SOCK_INIT: //查詢Socket狀態是初始化狀態時,打開此Socket
13 connect(s,pc_ip,pc_port);
14 break;
15
16 case SOCK_ESTABLISHED: //查詢Socket狀態是連接已建立狀態時,進行數據通信
17
18 break;
19
20 case SOCK_CLOSE_WAIT: //查詢Socket狀態是等待關閉狀態時,關閉此Socket
21 close(s);
22 break;
23 }
24 }
大家可能注意到了,在 SOCK_ESTABLISHED 狀態下,沒有任何實際處理,那是因為我們為了減少任務處理時間,將 Socket 數據處理任務放在了前文提到的SBP_PERIODIC_EVT 中,這個任務由 SimpleBLECentral_ProcessEvent 調用,並在任務的末尾進行重新委托,以 SBP_PERIODIC_EVT_PERIOD 為間隔循環調用,從而實現定時查詢 Socket 狀態,發送和接收 W5500 的緩存中數據。
01 if ( events & SBP_PERIODIC_EVT )
02 {
03 if ( SBP_PERIODIC_EVT_PERIOD )
04 {
05 if ((getSn_SR(0))== SOCK_ESTABLISHED)
06 {
07 if (getSn_IR(0) & Sn_IR_CON)
08 {
09 setSn_IR(0, Sn_IR_CON);
10 }
11 len=getSn_RX_RSR(0);
12 if (len>0)
13 {
14 recv(0,buffer,len); //接收Socket數據
15 buffer[len]=0×00;
16 SerialPrintString(buffer); //串口打印接收到Socket的數據
17 //printf(“%srn”,pub_buf);
18 w_send(0,buffer,len); //向Socket發送接收到的數據,方便調試
19 sbpGattWriteString(buffer,len);//將接收的數據向Peripheral發送
20 }
21 }
22 osal_start_timerEx( simpleBLETaskId, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );
23 }
24 return (events ^ SBP_PERIODIC_EVT);
25 }
至此,W5500 的集成已經完成,下一步是處理任務數據,在原始例程中,已經有指令處理部分,比如 AT+SCAN 是搜索 Peripheral,AT+CON1 是與搜索到的第一個從端建立鏈接,這部分我們無需處理,只需要把門鎖的控制指令按標准數據發送就可以。
BLE門鎖部分:
在完成 BLE 以太網網關之後,我們下一步進行門鎖部分的工作,作為Peripheral,程序上和 Central 大同小異,Central 搜索 Peripheral,並通過GATT_WriteCharValue 和 GATT_ReadCharValue 查詢和調用Peripheral上具體的服務;我們要做的是,在系統抽像層的任務調用機制中增加步進電機控制機制,以及在接收到 Central 發送的門鎖的控制指令後執行相應的開鎖和閉鎖動作,另外還可以增加一個干簧管用作磁控開關用來判斷門的閉合狀態,如圖 6 所示。
圖 6 BLE 門鎖步進電機傳動部分
本文選用的 5V 供電的 5 線 4 相步進電機,步進電機的原始例程是通過Delay 函數來實現延遲的,而在 TI 的這種盡量減少每個任務處理時間的機制下,我們需要更改為定時器 Timer3 觸發來執行步進電機的控制,更改部分如圖7所示。
圖7 定時器改進部分代碼
01 void Moter_GPIO_init(void)
02 {
03 P1DIR |= BV(0) | BV(1) | BV(2)| BV(3); //P1.0定義為輸出口
04 P1SEL &= ~(BV(0) | BV(1) | BV(2)| BV(3)); //P1.0定義為一般GPIO
05
06 T3CTL |= BV(3); //開啟溢出中斷
07 T3CTL |= BV(5) | BV(6) | BV(7) ;//128分頻
08 //T3CTL &= ~(BV(0) | BV(1)); //0×00~0xff,重復
09 T3CC0 = 0x4f;
10 T3CTL |= BV(0)| BV(1);
11
12 T3CTL |= BV(4); //啟動timer3
13 T3IE =1; //開啟T3中斷控制
14 EA=1;
15 }
在 SimpleBLEPeripheral_Init 函數中進行了步進電機控制 IO 的初始化Moter_GPIO_init(); 並在其中將 Timer3 進行了初始化;選取 P1.0,P1.1,P1.2,P1.3,分別作為步進電機四相 ABCD ;如果按照 A-B-C-D 的順序就是正轉,也就是開門,相應 D-C-B-A 就是反轉,也就是閉門操作;
Timer3 的中斷觸發執行如下步驟
01 #pragma vector = T3_VECTOR
02 __interrupt void Timer3_ISR(void)
03 {
04 IRCON = 0×00;
05 /*這裡實現每隔0.5秒翻轉一次,128分頻,由於timer3為8為,從0×00~0xff,
06 128/16M *Number=0.5s,所以Number=62500次
07 運行255次,count*255=62500,所以,count=245次*/
08 if (xxcount++>10)
09 {
10 xxcount=0;
11 if (Motor_AB_flag > 0 && Motor_BA_flag == 0)
12 {
13 MotorCW();
14 Motor_AB_flag++;
15 if (Motor_AB_flag >= 1024)
16 {
17 MotorStop();
18 Motor_AB_flag = 0;
19 sbpSerialAppSendNoti(“unlockrn”,8);
20 }
21
22 }
23 if (Motor_BA_flag > 0 && Motor_AB_flag == 0)
24 {
25 MotorCCW();
26 Motor_BA_flag++;
27 if (Motor_BA_flag >= 1024)
28 {
29 MotorStop();
30 Motor_BA_flag = 0;
31 sbpSerialAppSendNoti(“lockrn”,6);
32 }
33 }
34 }
35 }
可以看到在中斷觸發中根據 Motor_AB_flag 和 Motor_BA_flag 的狀態來執行開鎖和閉鎖動作,當執行 1024 個周期後停止動作並將相應的標志位置 0;而Motor_AB_flag 和 Motor_BA_flag 的狀態這時根據 Central 發送過來的數據進行相關解析之後進行的,在Peripheral的初始化函數 SimpleBLEPeripheral_Init 中注冊了一個接收數據的回調函數 simpleProfileChangeCB。
當Central對Peripheral 發送數據更改 PROFILE 服務的 CHAR1 字段時,下面的步驟被觸發
01 case SIMPLEPROFILE_CHAR1:
02 //SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue );
03 SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, newValueBuf );
04 if (newValueBuf[0]== ‘a’ && newValueBuf[1]== ‘d’ && newValueBuf[2]== ‘m’ && newValueBuf[3]== ‘i’ && newValueBuf[4]== ‘n’)
05 {
06 if (newValueBuf[5] == ‘?’)
07 {
08 BLE_REPLY();
09 }
10 else
11 {
12 if (newValueBuf[5]==’=’&&newValueBuf[6]== ‘A’ && newValueBuf[7]== ‘B’)
13 {
14 Motor_AB_flag = 1;
15 }
16 else if(newValueBuf[5]==’=’&&newValueBuf[6]==’B’&& newValueBuf[7]== ‘A’)
17 {
18 Motor_BA_flag = 1;
19 }
20 else
21 {
22 SetRGB(newValueBuf[5], newValueBuf[6], newValueBuf[7]);
23 sbpSerialAppWrite (newValueBuf, 20);
24 #if (defined HAL_LCD) && (HAL_LCD == TRUE)
25 HalLcdWriteString((char*)newValueBuf, HAL_LCD_LINE_4 );
26 #endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
27 }
28 }
29 }
30
31 break;
為了安全的需要,我們定義了一個密碼段,這個密碼段可以進一步改進為 SSL 加密或者 RSA 算法加密,本文以 admin 為例,查詢狀態指令為”admin?”,Peripheral 根據當前”lock”或”unlock” 狀態以及磁控開關回復當前狀態;開鎖指令目前為”admin=AB”;閉鎖為”admin=BA”。
系統測試:
系統測試部分我們可以按照手機 APP(BLE)控制門鎖和網絡控制門鎖兩部分進行:
1.使用手機的藍牙和 BLE 門鎖通訊
為了方便測試 BLE,需要在手機上安裝一個 APP“LightBlue – Bluetooth Low Energy”,
圖8 APP LightBlue – Bluetooth Low Energy
可以測試 BLE 的數據收發等工作,我們在 apple store 上安裝此 app 之後,就可以搜索到 BLE 門鎖“Smart Lock”,如圖9所示。
在選擇 “Smart Lock” 並對UUID :FFF0的服務Characteristic1字段寫入HEX字符的admin=AB“0x61646D696E3D4142”,如圖10所示;之後就可以聽到“Smart Lock”傳來的齒輪摩擦聲,成功。
1.使用 BLE 以太網網關和 BLE 門鎖通訊
網關通訊測試我分為兩部分進行:
1)通過 BLE 以太網網關的串口指令和 ”Smart Lock” 配對並控制門鎖開關,如圖11
圖 11 通過串口指令與”Smart Lock”配對
我們一共發送了三個指令:
AT+SCAN //搜索從端設備
AT+CON1 //與搜索到的第一個設備建立連接
admin=AB //前面兩個是發送給網關的指令,而 admin=AB 會作為數據發送給從端從端在接收到本數據之後控制電機轉動,到達指令周期之後停轉並復 “unlock”給網關,控制周期完成。
1.通過 socket 發送數據給 ”Smart Lock” 控制門鎖開關
BLE 門鎖 ”Smart Lock” 在上電之後作為 TCP Cllient 去和 Server 建立通訊連接並等待接收數據,當通訊連接建立之後,我們通過 Socket 測試工具 “Pdu Receiver”發送控制指令給 “W5500+BLE” 網關,網關會把數據轉發給 ”Smart Lock”,從而控制步進電機按周期轉動,如圖12所示。
圖12 通過測試工具Pdu Receiver進行門鎖控制
最後至此,已經實現了藍牙 BLE 網關對門鎖的智能控制,希望能給大家提供一個DIY 的思路。那麼,在智能家居的大潮中,藍牙 BLE 技術的應用已被越來越多廠商關注並采用,在這裡借助 W5500 的硬件協議棧,給大家提示一個更便捷的聯網方式,進一步減輕智能網關開發的難度,真正做到制作簡便、智能易用!
本文已刊登至《无线电》六月刊