<ruby id="66w77"><video id="66w77"></video></ruby>
<ruby id="66w77"><option id="66w77"><thead id="66w77"></thead></option></ruby>
  • <ruby id="66w77"><table id="66w77"></table></ruby>

  • <ruby id="66w77"><table id="66w77"></table></ruby>
    <strong id="66w77"></strong>

    智能手環(huán)硬件軟件功能模塊設計_預期效果方案構思

    2020-09-02 09:05:36分類(lèi):智能穿戴方案7617

      你的一天在做什么,你的身體呈現什么狀態(tài)?現在的智能穿戴產(chǎn)品就能夠給出答案,其中智能手環(huán)是最受大家認可的一款智能穿戴設備,它不僅可以記錄生活的鍛煉、睡眠等實(shí)時(shí)數據,還具有社交功能,能夠將鍛煉情況和身體健康狀況等發(fā)送到社交網(wǎng)絡(luò )進(jìn)行分享。為我們健康的生活起到指導的作用。


    智能手環(huán)

      一個(gè)智能手環(huán)最小系統一般包括:可充電的電源模塊、控制模塊(下圖中左邊芯片)、藍牙模塊(右邊芯片)、存儲模塊加速計模塊(上面芯片)。其中加速計是為了獲得佩戴者在運動(dòng)或睡眠過(guò)程中的加速度數據,通過(guò)分析這些數據則能夠判斷佩戴者的運動(dòng)情況和睡眠質(zhì)量;存儲模塊主要負責將實(shí)時(shí)數據暫存,接著(zhù)在適當的時(shí)刻借助藍牙模塊將數據同步到手機端。方便起見(jiàn)本次要自制的記步手環(huán)將不采用存儲器暫存,而是將數據實(shí)時(shí)地傳送到手機端。同時(shí)為了便于大家對記步算法的理解,客戶(hù)端將采用一個(gè)折線(xiàn)圖的形式實(shí)時(shí)展示記步手環(huán)收集的數據。


    智能手環(huán)電路板


      2. 如何實(shí)現記步

      看了上面的分析大家可能會(huì )疑惑——僅僅用一個(gè)加速計怎么能實(shí)現記步和睡眠質(zhì)量檢測呢?其實(shí)確實(shí)可以!因為加速計可以實(shí)時(shí)獲取自身的XYZ三個(gè)軸向的加速度。當其靜止時(shí)合加速度會(huì )在重力加速度附近波動(dòng);當佩戴者處于深度睡眠過(guò)程中時(shí),其合加速度將呈現出長(cháng)時(shí)間的穩定于重力加速度附近;當其隨著(zhù)運動(dòng)的佩戴者手臂而做周期性擺動(dòng)時(shí),其數據也是有一定規律可循的。這樣,設計時(shí)只要通過(guò)分析從加速計獲的數據就能實(shí)現對運動(dòng)或睡眠質(zhì)量的記錄。
     


      3.預期效果構思

      上面已經(jīng)提到:為了方便,我們并未采用存儲器實(shí)現記步手環(huán)的離線(xiàn)記錄,而是實(shí)時(shí)地將數據發(fā)送到客戶(hù)端由一個(gè)可視化的折線(xiàn)圖動(dòng)態(tài)繪制結果。如下圖所示系統中記步手環(huán)部分包含單片機模塊、藍牙模塊、加速計模塊電源模塊,這樣通過(guò)單片機的協(xié)調可以實(shí)現將加速計模塊的數據通過(guò)藍牙實(shí)時(shí)地傳送給客戶(hù)端程序。在客戶(hù)端部分則負責將收集到的實(shí)時(shí)數據以折線(xiàn)圖的形式動(dòng)態(tài)地展示出來(lái),此外客戶(hù)端中也加入一個(gè)滑動(dòng)條來(lái)控制記步閾值來(lái)真正讓大家明白其設計思想(真正商業(yè)化的智能手環(huán)多數采用的是先將有效數據保存在手環(huán)的小型存儲器中,上位機周期性地將數據收集并同步到服務(wù)器端)。


    預期效果構思圖
     


      4.硬件整體設計

      如下圖,相比于上一個(gè)無(wú)線(xiàn)小風(fēng)扇該硬件構成反而比較簡(jiǎn)單:藍牙模塊依然采用我們比較熟悉的HC-06模塊,對于加速度的測量采用四周飛行器上常采用的MPU6050模塊。該模塊不僅含有加速計的功能,還具有陀螺儀的功能,其在汽車(chē)防側翻、相機云臺穩定、機器人平衡、空中鼠標、姿態(tài)識別等眾多領(lǐng)域都有應用,這里我們只是利用了它的加速計功能。此外要注意:下圖所示的單片機模塊的電源引腳被隱藏了,在真正設計連接時(shí)一定不要忽略這兩個(gè)引腳!


    硬件整體設計
     


      介紹

      MPU-60X0是全球首例9軸運動(dòng)處理器。它集成了3軸MEMS陀螺儀,3軸MEMS加速計,以及1個(gè)可擴展的數字運動(dòng)處理器DMP(Digital Motion Processor)。如下圖所示軸向是相對于加速計說(shuō)的,當芯片水平靜止放置時(shí)x軸和y軸的加速度分量幾乎為0,z軸的加速度分量約為當地的重力加速度;而旋轉極性則是對陀螺儀來(lái)說(shuō)的。


    數字運動(dòng)處理器

      為何上面說(shuō)9軸信號呢?因為 MPU-60X0 可用 I2C 接口連接一個(gè)第三方的數字傳感器,比如磁力計。擴展之后就可以通過(guò)其I2C或SPI接口輸出一個(gè)9軸的信號。也可以通過(guò)其I2C接口連接非慣性的數字傳感器,比如壓力傳感器。(為什么特別提磁力計和壓力傳感器呢?因為在飛控方面,利用陀螺儀和加速計可以計算飛行器的傾角,從而調節飛行器平衡。但是只是調節平衡對方向沒(méi)有概念也不能執行復雜任務(wù),因此需要配備磁力計(也即電子羅盤(pán)傳感器)。此外,由于飛行器在不同高度作業(yè)時(shí),其周?chē)闹亓铀俣纫膊煌?,這樣會(huì )影響傾角的準確性,因此通過(guò)氣壓計計算所處高度然后計算實(shí)時(shí)加速度達到精確控制的效果。)


    數字傳感器

      MPU-60X0對陀螺儀和加速計分別用了三個(gè)16位的ADC,將其測量的模擬量轉化為可輸出的數字量。為了精確跟蹤快速和慢速運動(dòng),傳感器的測量范圍是可控的,陀螺儀可測范圍為±250,±500,±1000,±2000°/秒(dps),加速計可測范圍為±2,±4,±8,±16g(重力加速度)。如圖是直接從16位ADC中讀出的6軸的數據(從左到右依次為加速計X軸數據、Y軸數據、Z軸數據、陀螺儀X極數據、Y極數據、Z極數據):

      但是這里的輸出值并不是真正的加速度和角速度的值,上面說(shuō)過(guò),MPU是一個(gè)16位AD量程可程控的設備,這里設置的加速度傳感器的測量量程為正負2g(這里的g為重力加速度),陀螺儀的量程為正負2000°/s。所以要用下面的公式進(jìn)行轉化:


    陀螺儀的量程
     


      6.一個(gè)簡(jiǎn)單的記步算法設計

      當MPU6050隨著(zhù)運動(dòng)的佩戴者手臂而做周期性擺動(dòng)時(shí),其數據也是有一定規律可循的。簡(jiǎn)單起見(jiàn)我們只分析合加速度:一個(gè)擺臂周期其合加速度會(huì )在重力加速度上下波動(dòng),如圖只要選取合適的閾值(黑線(xiàn)代表閾值),每次檢測出合加速度大于該閾值則認為是一次擺臂,從而可以實(shí)現記步的功能。這里要特別說(shuō)明下:如果想把你的手環(huán)推向市場(chǎng),就要通過(guò)大量分析擺臂數據建立一套更好的記步算法。


    計步算法設計
     


      7. I2C總線(xiàn)介紹

      由于51系列單片機將串口通信很多細節都封裝到芯片內部,所以我們即使設計了串口驅動(dòng)模塊,也并沒(méi)有真正了解串口通信的核心思想。其實(shí)串口協(xié)議的出現是為了構成一個(gè)總線(xiàn)線(xiàn)路,這樣單片機只要使用比較少的引腳就能和比較多的設備進(jìn)行通信了,這里要用到的I2C總線(xiàn)也具有相同的效果但又有些不同。


    i2c總線(xiàn)介紹

      I2C(Inter-Integrated Circuit)總線(xiàn)是由PHILIPS公司開(kāi)發(fā)的兩線(xiàn)式串行總線(xiàn),用于連接微控制器及其外圍設備。是微電子通信控制領(lǐng)域廣泛采用的一種總線(xiàn)標準。它是同步通信的一種特殊形式,具有接口線(xiàn)少,控制方式簡(jiǎn)單,器件封裝形式小,通信速率較高等優(yōu)點(diǎn)。如上圖采用I2C總線(xiàn)后CPU只要使用2個(gè)引腳便可和多個(gè)設備進(jìn)行通信(其實(shí)每個(gè)采用I2C通信方式的設備都具有唯一的地址碼,這樣在總線(xiàn)中便能夠被唯一識別),從而大大減少了引腳的使用。

      在I2C總線(xiàn)中使用的兩線(xiàn)為時(shí)鐘線(xiàn)SCL和數據線(xiàn)SDA。所有的I2C主從設備都是只被這兩根線(xiàn)連接起來(lái)的。每一個(gè)設備既可以作為發(fā)送方,也可以作為接收方,或者既可以作為發(fā)送發(fā)也可以作為接收方。在總線(xiàn)中的主設備一般起產(chǎn)生時(shí)鐘信號和初始化通信的作用,從設備則負責響應主設備發(fā)出的命令。為了在總線(xiàn)上區分每一個(gè)設備,每一個(gè)從設備必須有一個(gè)唯一的地址。主設備一般不需要地址(一般為微處理器),因為從設備不能發(fā)送命令給主設備。


    總線(xiàn)中主從設備

      這里要先介紹I2C總線(xiàn)中幾個(gè)專(zhuān)有名詞:

      l 發(fā)送者:將數據發(fā)送到總線(xiàn)的設備

      l 接收者:從總線(xiàn)接收數據的設備

      l 主設備:產(chǎn)生時(shí)鐘信號、啟動(dòng)通信、發(fā)送I2C命令和終止通信的設備

      l 從設備:監聽(tīng)總線(xiàn)、能被主設備尋址的設備

      l 多主設備:I2C能夠擁有多個(gè)主設備,而且每個(gè)主設備都能夠發(fā)送命令

      l 仲裁:當多個(gè)主設備請求使用總線(xiàn)時(shí),決定哪一個(gè)主設備可以占用的一個(gè)過(guò)程

      l 同步:同步多個(gè)設備時(shí)鐘信號的一個(gè)過(guò)程

      上面是從宏觀(guān)上對I2C總線(xiàn)介紹了下,接下來(lái)將深入細節研究其通信過(guò)程:

      串行數據傳送:

      在總線(xiàn)備用時(shí)SDA和SCL都必須保持高電平狀態(tài),只有關(guān)閉I2C總線(xiàn)時(shí)才能使SCL鉗位在低電平。在I2C總線(xiàn)數據傳輸時(shí),在時(shí)鐘線(xiàn)高電平期間,數據線(xiàn)上必須保持有穩定的邏輯電平(也就是說(shuō)在數據傳輸期間只有時(shí)鐘線(xiàn)低電平期間,才允許數據線(xiàn)上的電平發(fā)生變化)。


    串行數據發(fā)送

      因此在如上圖中對于每一個(gè)時(shí)鐘脈沖期間一比特的數據將會(huì )被傳送,SDA只能在時(shí)鐘信號為低電平時(shí)才能改變。下面是代碼中發(fā)送一字節的函數:在循環(huán)體內每次將dat內的最高位移出到CY中,進(jìn)而賦值給SDA(這時(shí)SCL為低,SDA可改變)。接著(zhù)拉高SCL并保持5us,最后再拉低SCL實(shí)現一個(gè)時(shí)鐘脈沖將dat中最高位送出。依此循環(huán)8次實(shí)現將dat全部傳出。

      voidI2C_SendByte(uchar dat){

      uchar i;for(i=0; i<8; i++){

      dat <<=1;

      SDA = CY;

      SCL =1;Delay5us();

      SCL =0;Delay5us();}I2C_RecvACK();}

      開(kāi)始和結束條件

      命令不會(huì )沒(méi)有任何預兆直接發(fā)送的,每一個(gè)I2C命令的發(fā)送總是開(kāi)始于開(kāi)始條件并結束于終止條件。這里所謂的開(kāi)始條件和終止條件起始也是由SCL和SDA組合形成的(如下圖)。


    開(kāi)始和結束條件

      如果時(shí)鐘線(xiàn)保持高電平期間,數據線(xiàn)出現由高到低的電平變化,則會(huì )啟動(dòng)I2C總線(xiàn),此時(shí)為I2C的起始信號:

      voidI2C_Start(){

      SDA =1;

      SCL =1;Delay5us();

      SDA =0;Delay5us();

      SCL =0;}

      若在時(shí)鐘線(xiàn)保持高電平期間,數據線(xiàn)出現由低到高的電平變化,則會(huì )停止I2C總線(xiàn)的數據傳輸,此時(shí)為I2C的終止信號:

      voidI2C_Stop(){

      SDA =0;

      SCL =1;Delay5us();

      SDA =1;Delay5us();}

      開(kāi)始條件之后I2C總線(xiàn)被認為是忙狀態(tài),只有當停止信號之后其他主設備才能使用該總線(xiàn)。此外,當開(kāi)始條件之后主設備能夠多次發(fā)出開(kāi)始信號。這些開(kāi)始信號和第一次發(fā)出的開(kāi)始信號類(lèi)似,他們后面經(jīng)常會(huì )跟從設備的地址。這樣可以方便實(shí)現在I2C總線(xiàn)忙期間,當前占線(xiàn)的主設備可以和不同的從設備進(jìn)行通信。

      7.3 I2C數據傳送

      I2C總線(xiàn)上傳送的每一個(gè)字節均為8位,但是每啟動(dòng)一次I2C總線(xiàn),其后的數據傳送字節數是沒(méi)有限制的。同時(shí)每傳送一字節的數據后面都要跟隨一個(gè)接收者回應的應答位(低電平為應答信號,高電平為非應答信號),當全部數據發(fā)送完畢后主設備發(fā)送終止信號。



    數據傳送圖

      所以在上面向I2C總線(xiàn)發(fā)送一字節的數據的代碼的最后有一個(gè)I2C_RecvACK()函數。(如下)該函數負責接收接收者發(fā)送過(guò)來(lái)的應答信號,也即上圖中的第9個(gè)時(shí)鐘脈沖的期間的相應操作。

      bit I2C_RecvACK(){

      SCL =1;Delay5us();

      CY = SDA;

      SCL =0;Delay5us();return CY;}

      注:所有的數據位包括應答位都需要主設備產(chǎn)生時(shí)鐘脈沖。如果從設備沒(méi)有應答意味著(zhù)將沒(méi)有更多的數據要傳送或者設備沒(méi)有準備好傳送。這時(shí),主設備要么產(chǎn)生停止信號,要么重新發(fā)出開(kāi)始條件。


    應答信號

      7.4 I2C的7-bit地址

      每一個(gè)從設備都應該具有唯一的地址,這樣主設備才能準確的尋址到每一個(gè)設備,而這些地址被統一規定為7比特。但是上面講過(guò)I2C總線(xiàn)傳輸數據都是8比特傳送,地址7比特豈不是少一位!其實(shí)緊跟地址還有一位用來(lái)表示是讀操作還是寫(xiě)操作的標志位。如果該位為0表示主設備將要向從設備寫(xiě)數據,否則表示主設備將要從從設備讀數據。在這8比特被發(fā)送后主設備能夠持續地進(jìn)行讀或者寫(xiě)。如果主設備想和其他從設備進(jìn)行通信,只要再次發(fā)送一個(gè)新的開(kāi)始信號就可以而不必發(fā)送終止信號。


    一個(gè)完整的數據讀寫(xiě)操作
     


      8. MPU6050驅動(dòng)設計

      下面將結合MPU6050的驅動(dòng)進(jìn)一步講解其原理(該部分的代碼參見(jiàn)工程的部分)。我們首先來(lái)看一下它的頭文件:從第6到25行上來(lái)就是一大串內部地址的定義,對于初學(xué)者可能一頭霧水!如果樓主再引入寄存器等數字電路的知識可能又要說(shuō)幾頁(yè)了,于是這里準備只用一個(gè)簡(jiǎn)單的例子闡述下這些地址的作用。

      #include""#define SMPLRT_DIV 0x19 #define CONFIG 0x1A #define GYRO_CONFIG 0x1B #define ACCEL_CONFIG 0x1C #define ACCEL_XOUT_H 0x3B#define ACCEL_XOUT_L 0x3C#define ACCEL_YOUT_H 0x3D#define ACCEL_YOUT_L 0x3E#define ACCEL_ZOUT_H 0x3F#define ACCEL_ZOUT_L 0x40#define TEMP_OUT_H 0x41#define TEMP_OUT_L 0x42#define GYRO_XOUT_H 0x43#define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45#define GYRO_YOUT_L 0x46#define GYRO_ZOUT_H 0x47#define GYRO_ZOUT_L 0x48#define PWR_MGMT_1 0x6B #define WHO_AM_I 0x75 #define SlaveAddress 0xD0 voidSingle_WriteI2C(uchar REG_Address,uchar REG_data);

      uchar Single_ReadI2C(uchar REG_Address);voidInitMPU6050();intGetData(uchar REG_Address);

      上面講到在I2C總線(xiàn)中主設備可以通過(guò)固定的7-bit地址尋找到相應的從設備(這里的7-bit地址為第26行的SlaveAddress,想必大家也能夠理解后面注釋的意義了吧~不加1表示緊跟著(zhù)地址的一位為0,表示向該設備寫(xiě)數據;加1則表示緊跟著(zhù)的一位為1,表示主設備從從設備讀數據)。雖然采用這種方式能夠準確找到從設備,但是從設備里面又有比較多的寄存器。這就好比你知道了某個(gè)要找的東西在具體的某個(gè)大柜子里,但是來(lái)到大柜子前又發(fā)現有許多小抽屜。這里的7-bit地址就好像指明了哪個(gè)柜子,而從第6到25行的內部地址就像柜子上的抽屜編號,而不一樣之處是位于mpu6050內的“小抽屜”一部分存放著(zhù)其采集的實(shí)時(shí)數據,另一部分等著(zhù)外部放一些數據來(lái)設置其采樣屬性。

      這樣,如上面的第6行的SMPLRT_DIV(0x19)是用來(lái)設置陀螺儀采樣率的寄存器地址,只要向該地址所指的寄存器寫(xiě)入相應的值則可以設置陀螺儀采樣率。因此下面MPU6050初始化函數就是調用封裝的I2C寫(xiě)函數向相應的小抽屜內寫(xiě)屬性數據,設置MPU6050采樣屬性。

      voidInitMPU6050(){Single_WriteI2C(PWR_MGMT_1,0x00);Single_WriteI2C(SMPLRT_DIV,0x07);Single_WriteI2C(CONFIG,0x06);Single_WriteI2C(GYRO_CONFIG,0x18);Single_WriteI2C(ACCEL_CONFIG,0x01);}

      再如第10~11行的ACCEL_XOUT_H、ACCEL_XOUT_L是用來(lái)存放最新的陀螺儀X極的數值,因為采用16位ADC所以這里需要用兩個(gè)寄存器。所以下面合成數據函數負責連續讀取REG_Address開(kāi)始的兩字節數據組成一個(gè)16位數據。當函數的參數為ACCEL_XOUT_H時(shí),則獲取的是實(shí)時(shí)的陀螺儀X極的數值,同樣地可以獲得實(shí)時(shí)的6軸數據。

      intGetData(uchar REG_Address){

      uchar H,L;

      H=Single_ReadI2C(REG_Address);

      L=Single_ReadI2C(REG_Address+1);return(H<<8)+L;}

      **注:**關(guān)于MPU6050內部的“小抽屜”的地址和功能需要閱讀其官方的MPU6050寄存器手冊。
     


      9.硬件工程整體介紹

      9.1、打開(kāi)Keil uVision2,點(diǎn)擊Project下的Open Project,打開(kāi)記步手環(huán).Uv2加載工程。


    打開(kāi)工程

      9.2、待工程加載完畢,大家會(huì )在工程窗口中看到圖9_2所示文件結構。其中FUNC組下面包含數i2c驅動(dòng)、mpu6050和串口驅動(dòng)文件, USER組下是最上層應用程序文件。


    文件結構

      9.3、上一章已經(jīng)把講解了,前幾節也把和mpu6050,c介紹了。這里直接從對整個(gè)工程的流程進(jìn)行分析:主函數中先初始化串口和MPU6050,接著(zhù)進(jìn)入無(wú)限循環(huán)。循環(huán)中每隔一定的時(shí)間發(fā)送一幀的數據——該幀以‘#’開(kāi)始以‘$’結束,中間依次是X軸加速度值、Y軸加速度值和Z軸加速度值。

      void main (void){delay(500);InitUART();InitMPU6050();while(1){SendByte('#');SendData(GetData(0x3B));SendData(GetData(0x3D));SendData(GetData(0x3F));SendByte('$');delay(20);}}

      其中調用了串口驅動(dòng)中的void InitUART(void)串口初始化函數、 void SendByte(unsigned char dat)串口發(fā)送一字節函數和 void SendStr(unsigned char *s)串口發(fā)送一個(gè)字符串函數,以及調用了mpu6050驅動(dòng)中的void InitMPU6050()初始化函數和int GetData(uchar REG_Address)獲取6軸數據函數。

      externvoidInitUART(void);externvoidSendByte(unsignedchar dat);externvoidSendStr(unsignedchar*s);externvoidInitMPU6050();externintGetData(uchar REG_Address);

      這里唯一要特別說(shuō)明的函數是:void SendData(int value)函數。我們知道直接調用MPU6050的函數int GetData(uchar REG_Address)返回的是int類(lèi)型的數據,而串口每次只能發(fā)送一個(gè)8bit的數據,于是這里的SendData則是負責將該int類(lèi)型的數值轉換為串口容易發(fā)送的數據再進(jìn)行發(fā)送。

      voidenCode(uchar *s,int temp_data){if(temp_data<0){

      temp_data=-temp_data;*s='-';}else*s=' ';*++s =temp_data/10000+0x30;

      temp_data=temp_data%10000;*++s =temp_data/1000+0x30;

      temp_data=temp_data%1000;*++s =temp_data/100+0x30;

      temp_data=temp_data%100;*++s =temp_data/10+0x30;

      temp_data=temp_data%10;*++s =temp_data+0x30;*++s ='';}voidSendData(int value){enCode(temp, value);SendStr(temp);}

      上面的enCode函數是將輸入的int類(lèi)型的數據轉換為第一位為符號(正用空格代替,負用負號代替),后5位為數值的字符串,即使不足五位數前面也要填充0。這樣便不難理解SendData的功能:將value編碼并通過(guò)串口發(fā)送。

      這樣整個(gè)工程的作用則是周期性讀取MPU6050三軸的加速度并用下面的幀格式通過(guò)藍牙發(fā)送出去:


     


      10.客戶(hù)端軟件構成模塊

      10.1、打開(kāi)Eclipse點(diǎn)擊File菜單欄下的Import按鈕準備導入second_test工程(如圖10_1所示)。
    客戶(hù)端軟件導入工程

      10.2、接著(zhù)在彈出的Select窗口中選擇Android文件夾下的Existing Android Code Into Workspace點(diǎn)擊next(如圖10_2所示)。
    選擇導入類(lèi)型

      10.3、接著(zhù)在彈出的框中點(diǎn)擊右上角的Browse按鈕,找到要導入的third_test所在路徑,并且需要勾選Copy projects into workspace(如圖10_3所示)。

    選擇工程

      10.4、最終效果如圖10_4所示在src文件夾下有四個(gè)包:其中第一個(gè)是和藍牙相關(guān)的類(lèi)(從下到上依次為藍牙設備搜索相關(guān)類(lèi)、藍牙通信連接相關(guān)類(lèi)和藍牙通信相關(guān)類(lèi));第二個(gè)是繪制折線(xiàn)圖表相關(guān)的類(lèi)(這里采用開(kāi)源圖表繪制引擎achartengine,所以在libs里要添加相應的包);第三個(gè)是數據池相關(guān)的類(lèi),用于實(shí)現藍牙數據實(shí)時(shí)高速處理;另一個(gè)包是UI相關(guān)類(lèi),也是整個(gè)工程最核心的部分。如果讀者導入過(guò)程中出現錯誤,也可以采用第三章的方法新建一個(gè)工程,然后把src下的文件、layout下的文件和文件做相應的新建或修改,同時(shí)還要注意引入libs的包以及values里的。
    工程文件結構


     


      11.軟件最終效果預覽

      上面是從模塊構成的角度介紹工程的主要文件,為了更好的方便分析其內部邏輯,筆者準備先帶領(lǐng)大家預覽下本次應用的最終效果(如圖11_1所示):

      n 第一幅圖:是初始打開(kāi)界面,如果本地藍牙沒(méi)有打開(kāi)最左邊的按鈕將會(huì )顯示“打開(kāi)藍牙設備”;

      n 第二幅圖:是點(diǎn)擊“連接我的小手環(huán)”后進(jìn)入藍牙搜索階段;

      n 第三幅圖:是自動(dòng)搜索到記步手環(huán)后進(jìn)入的連接藍牙階段;

      n 第四幅圖:是連接完成后,應用把從手環(huán)收集的實(shí)時(shí)數據(XYZ軸加速度以及合加速度)繪制出;

      n 第五幅圖:是通過(guò)滑動(dòng)條調大記步閾值,并選擇CheckBox只顯示合加速度值的實(shí)時(shí)折線(xiàn);

      n 第六幅圖:是放大折線(xiàn)圖,并點(diǎn)擊某個(gè)點(diǎn)顯示具體信息圖。

      其中前三個(gè)階段和上一章中的小風(fēng)扇的控制很類(lèi)似,都是點(diǎn)擊連接到進(jìn)入搜索再到進(jìn)行連接。只不過(guò)一個(gè)是連接后通過(guò)應用向硬件發(fā)送命令幀來(lái)控制小風(fēng)扇轉速;一個(gè)是不斷從記步手環(huán)讀取實(shí)時(shí)的XYZ三軸的加速度,計算合加速度同時(shí)記步,并且將數據實(shí)時(shí)以折線(xiàn)圖的形式展示出來(lái)。


     


      12.一個(gè)高效處理數據的數據池設計

      當提到為什么需要高效處理的數據池時(shí),其實(shí)要從藍牙搜索講起。由于上一章的最后對藍牙搜索、連接、通信的三個(gè)過(guò)程做了詳細的講解,本次則只從整體上進(jìn)行梳理一下。

      如圖12_1,當點(diǎn)擊連接小手環(huán)按鈕后則執行藍牙搜索類(lèi)的doDiscovery()函數進(jìn)行搜索藍牙設備,在其搜索過(guò)程中搜索的設備名和設備地址分別存儲在BlueToothSearch的公有成員變量mNameVector和mAddrVector中,然后在本次搜索結束后會(huì )向Activity發(fā)送一個(gè)類(lèi)型為0x01的Handler消息,而該消息會(huì )被Activity中的handleMessage接收到。

      當Activity中的handleMessage接收類(lèi)型為0x01的消息后,程序會(huì )遍歷本次藍牙搜索到的周邊設備的名稱(chēng)找到符合我們的手環(huán)的藍牙設備。然后調用藍牙連接的setDevice()函數獲取遠程藍牙通信socket,接著(zhù)在handleMessage內再觸發(fā)藍牙連接的線(xiàn)程進(jìn)行藍牙連接。當藍牙連接完畢,則會(huì )發(fā)送0x02類(lèi)型的消息反饋給Activity中的handleMessage。

      同樣的當Activity中的handleMessage接收類(lèi)型為0x02的消息后,程序會(huì )調用藍牙通信類(lèi)的setSocket()函數來(lái)獲取標準輸入輸出流。此后,如果想從軟件向硬件發(fā)送消息則直接可以調用藍牙通信類(lèi)的write()函數,而接收數據則是采用啟動(dòng)一個(gè)接收線(xiàn)程來(lái)實(shí)現實(shí)時(shí)接收的。

    從點(diǎn)擊連接小手環(huán)到完成藍牙連接全過(guò)程流程圖

      現在我們的思維已經(jīng)跟著(zhù)轉到了上圖中最后一個(gè)無(wú)限輪詢(xún)收數據階段。同時(shí)我們知道從小手環(huán)發(fā)來(lái)的數據是比較高速的(硬件工程中寫(xiě)的是每次發(fā)送完畢delay(20),應該算是比較短的時(shí)間了)。那么問(wèn)題就來(lái)了:如果我們不能及時(shí)地將手環(huán)傳來(lái)的數據進(jìn)行處理,很有可能導致大量的數據滯留在緩沖區。這樣進(jìn)一步會(huì )導致每次獲得的數據都不是最新的數據,而表現出動(dòng)態(tài)繪制折線(xiàn)圖滯后糟糕的效果。

      綜上由于下位機10ms發(fā)送一次20byte的數據,上位機一方面要做好接收工作,保證數據不擁擠在串口接收緩沖區;另一方面也要實(shí)時(shí)獲取當前從串口讀到的最新數據。如果采用傳統多線(xiàn)程+鎖的機制是可以的,但是當多線(xiàn)程中加入鎖勢必會(huì )影響程序執行效率,通過(guò)綜合分析該問(wèn)題筆者最終抽象出一個(gè)特殊的數據模型——自動(dòng)更新的環(huán)形棧:

    自動(dòng)更新的環(huán)形棧

      如圖12_2所謂自動(dòng)更新的環(huán)形棧本質(zhì)上是一個(gè)基于環(huán)形數組的特殊數據結構。圖中環(huán)形代表數據池,也是一個(gè)環(huán)形數組(普通數組,采用一定技巧將首尾連接),p_write指示當前數據插入位置,每次插入一個(gè)數據p_write順時(shí)針移動(dòng)一格,從而實(shí)現新數據覆蓋老數據的自動(dòng)更新功能。而這里最精妙的地方在于每次取數據的方式:從p_write所指的位置逆時(shí)針取40個(gè)數據(因為有效幀包含的數據長(cháng)度為20,一次取40保證至少有一個(gè)有效幀),然后從這40個(gè)數據中找出有效信息,賦值給公有成員X,Y,Z。這樣通過(guò)適當調節環(huán)的容量,保證取數據時(shí)該段數據不被覆蓋的前提下,又能根據p_write指示獲取最新的下位機發(fā)來(lái)的有效幀,將存和取有效地分離從而完美達到了我們的需求。

      具體在程序中的onCreate函數中聲明并實(shí)例化一個(gè)大小為20000的數據池mDataPool = new DataPool(20000)。接著(zhù)在BlueToothCommunicate的輪詢(xún)接收數據的線(xiàn)程中(也即圖12_1的最后一環(huán)節的read中)對于每次新收到的數據調用mDataPool的push_back(buffer, bytes)函數將其存儲在數據池中。當每次需要取最新數據時(shí)只要先調用mDataPool的ask()函數,接著(zhù)便可直接通過(guò)訪(fǎng)問(wèn)DataPool的公有成員XYZ獲取最新三軸加速度的值了。

      publicvoidrun(){byte[] buffer =newbyte[1024];int bytes;while(state){try{

      bytes = mmInStream.read(buffer);

      String readMessage =newString(buffer,0, bytes);

      Log.i("beautifulzzzz","read: "+ bytes +" mes: "+ readMessage);

      UI_Main.mDataPool.push_back(buffer, bytes);}catch(IOException e){break;}}}
     


      13.一個(gè)開(kāi)源的折線(xiàn)圖繪制方案

      在第10節客戶(hù)端軟件構成模塊中曾提到本項目中采用了開(kāi)源圖表繪制引擎AChartEngine。它是一個(gè)安卓系統上制作圖表的框架,支持折線(xiàn)圖、面積圖、分區圖、對比圖、散點(diǎn)圖、柱狀圖、餅圖等(如下圖所示)。

    折現圖繪制

      此外其所有支持的圖表類(lèi)型,都可以包含多個(gè)系列,都支持水平(默認)或垂直方式展示圖表。并且支持許多其他的自定義功能。所有圖表都可以建立為一個(gè)view,也可以建立為一個(gè)用于啟動(dòng)activity的intent(顯然上面前兩幅圖是采用view的形式,其他幾個(gè)是采用intent啟動(dòng)的)。

      一般突然提到某某開(kāi)源包或者調用別的接口初學(xué)者可能會(huì )頭大,而且這里更讓多數人頭痛的是筆者竟突然亮出了這么多炫酷的UI,豈不是更加難以使用!于是可能會(huì )有很多人準備自己DIY折線(xiàn)圖了。然而事實(shí)卻是這個(gè)開(kāi)源的框架用起來(lái)十分方便:大家可以把所有的chart都想象成由兩層組成,一部分是Renderer(如XYMultipleSeriesRenderer,用于對圖表樣框架樣式的說(shuō)明),另一部分是Dataset(如XYMultipleSeriesDataset,用于對視圖數值的處理)。所以在類(lèi)的開(kāi)始就定義并聲明這兩種類(lèi)型的私有成員:【第一步:數據層和顯示層定義并實(shí)例化】

      private XYMultipleSeriesDataset mDataset =newXYMultipleSeriesDataset();private XYMultipleSeriesRenderer mRenderer =newXYMultipleSeriesRenderer();

      因為mRenderer用于對圖表框架樣式的說(shuō)明,所以在setChartSettings函數里調用了多個(gè)其成員函數用來(lái)對圖表整體樣式屬性進(jìn)行設置。例如7、8兩行是設置X軸和Y軸的標題,9到12行設置初始X軸和Y軸所表示的范圍,22到24行用來(lái)設置放大縮小的控件和屬性(就像地圖控件里的放大縮小按鈕)。這樣下層的X軸、Y軸等就都設置好了?!镜诙剑涸O置顯示層顯示樣式】

      publicvoidsetChartSettings(String xTitle, String yTitle,double xMin,double xMax,double yMin,double yMax,int axesColor,int labelsColor){

      mRenderer.setXTitle(xTitle);

      mRenderer.setYTitle(yTitle);

      mRenderer.setXAxisMin(xMin);

      mRenderer.setXAxisMax(xMax);

      mRenderer.setYAxisMin(yMin);

      mRenderer.setYAxisMax(yMax);

      mRenderer.setAxesColor(axesColor);

      mRenderer.setLabelsColor(labelsColor);

      mRenderer.setShowGrid(true);

      mRenderer.setGridColor(Color.GRAY);

      mRenderer.setXLabels(16);

      mRenderer.setYLabels(20);

      mRenderer.setYLabelsAlign(Align.RIGHT);

      mRenderer.setPointSize((float)2);

      mRenderer.setShowLegend(true);

      mRenderer.setZoomEnabled(true,false);

      mRenderer.setPanEnabled(true,false);}

      當表格框架設置好之后,接下來(lái)就是向框架內填充折線(xiàn),并且在此過(guò)程中把每一條折線(xiàn)的數據層放入總的數據層中。如下setLineSettings函數循環(huán)4次,每次首先實(shí)例化一個(gè)標題為titles[i]的坐標序列,然后將該序列放入總的數據層mDataset中。同樣的每次實(shí)例化一個(gè)XYSeriesRenderer(因為每個(gè)折線(xiàn)也有自己的樣式),并將其加入總的圖標層mRenderer中。這樣就能夠將4條分別表示X軸加速度、Y軸加速度、Z軸加速度和合加速度的折線(xiàn)圖設置好?!镜谌剑涸O置4個(gè)折線(xiàn)數據序列并加入數據層,設置4個(gè)折線(xiàn)層并加入顯示層】

      publicvoidsetLineSettings(){for(int i =0; i < titles.length; i++){

      mCurrentSeries[i]=newXYSeries(titles[i]);

      mDataset.addSeries(mCurrentSeries[i]);

      renderer[i]=newXYSeriesRenderer();

      mRenderer.addSeriesRenderer(renderer[i]);

      renderer[i].setPointStyle(styles[i]);

      renderer[i].setColor(colors[i]);

      renderer[i].setFillPoints(true);

      renderer[i].setDisplayChartValues(false);

      renderer[i].setDisplayChartValuesDistance(10);}}

      此時(shí)mDataset里存放著(zhù)當前要顯示的折線(xiàn)的所有XYSeries,每個(gè)折線(xiàn)XY序列存放在mCurrentSeries[i]中,如果想在該折線(xiàn)圖上增加一個(gè)數據只要調用mCurrentSeries[i].add(x, y)即可;如果想顯示或隱藏某個(gè)折線(xiàn)圖只要調用圖表層的mRenderer和數據層mDataset移出對應的折線(xiàn)和折線(xiàn)序列即可?!咎崆耙徊?5):如何往對應的折線(xiàn)中增加數據,以及如何顯示隱藏某條折線(xiàn)】

      publicvoidshowLine(int i){

      mDataset.addSeries(mCurrentSeries[i]);

      mRenderer.addSeriesRenderer(renderer[i]);}publicvoidhideLine(int i){

      mDataset.removeSeries(mCurrentSeries[i]);

      mRenderer.removeSeriesRenderer(renderer[i]);}publicvoidaddData(int i,double x,double y){

      mCurrentSeries[i].add(x, y);}

      上面說(shuō)過(guò)所有圖表都可以建立為一個(gè)view,也可以建立為一個(gè)用于啟動(dòng)activity的intent。這里由于我們需要在中添加其他控件,所以采用view的方式新建圖表。如下setChartViewSetting函數負責當圖表沒(méi)有建立時(shí)分別實(shí)例化layout和mChartView,并將新建的mChartView加入中圖表所在的layout中,這樣我們就可以看到基本的圖表了。此外,第10行是給圖表加的點(diǎn)擊監聽(tīng),用于顯示點(diǎn)擊點(diǎn)的詳細信息(圖11_1軟件最終效果的第6張圖)?!镜谒牟剑簩祿雍惋@示層合成為圖表加入UI中】

      publicvoidsetChartViewSetting(final Activity activity){if(mChartView == null){

      LinearLayout layout =(LinearLayout) activity

      .findViewById(R.id.chart);

      mChartView = ChartFactory.getLineChartView(activity, mDataset,

      mRenderer);

      mRenderer.setClickEnabled(true);

      mRenderer.setSelectableBuffer(10);

      mChartView.setOnClickListener(newView.OnClickListener(){publicvoidonClick(View v){

      ……(略)}});

      layout.addView(mChartView,newLayoutParams(

      LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));}else{

      mChartView.repaint();}}
     


      14 整體邏輯梳理

      其實(shí)仔細觀(guān)察的讀者會(huì )發(fā)現:本次的和上次的大致相同。前一階段都是點(diǎn)擊按鈕來(lái)連接遠程藍牙設備。而不同之處在于上一章是通過(guò)加減按鈕向小風(fēng)扇發(fā)送速度控制命令來(lái)控制速度,這一章是不斷讀取手環(huán)的實(shí)時(shí)數據并用折線(xiàn)圖繪制出來(lái)。整體業(yè)務(wù)邏輯還是在控件的點(diǎn)擊事件和handleMessage之間有序進(jìn)行,下面將著(zhù)重說(shuō)明數據的實(shí)時(shí)顯示及一些用于優(yōu)化操作的細節。

      在onCreate中首先實(shí)例化藍牙三劍客,接著(zhù)實(shí)例化數據池和折線(xiàn)圖表,然后調用折線(xiàn)圖類(lèi)的成員函數對折線(xiàn)圖做前期設置,最后啟動(dòng)ChartThread線(xiàn)程。

      mBlueToothSearch =newBlueToothSearch(this, myHandler);

      mBlueToothConnect =newBlueToothConnect(myHandler);

      mBlueToothCommunicate =newBlueToothCommunicate(myHandler);

      mDataPool =newDataPool(20000);

      mChartLine =newChartLine();

      mChartLine.setChartSettings("Time","",0,100,-20000,20000, Color.WHITE, Color.WHITE);

      mChartLine.setLineSettings();

      ChartThread.start();

      在此之后便是對連接手環(huán)按鈕做的相關(guān)設置,這里和上一章中的連接風(fēng)扇幾乎一樣,關(guān)鍵在于理解藍牙三劍客通過(guò)線(xiàn)程啟動(dòng)并通過(guò)handler將消息反饋的機制。

    點(diǎn)擊連接設備到通信建立

      這樣當點(diǎn)擊連接手環(huán)的按鈕之后,然后在handler的溝通下上位機和下位機最終實(shí)現可通信。此時(shí)下位機一旦有數據傳送上來(lái),上位機便快速的將其放入數據池內。那么程序是在成么時(shí)候取數據并更新UI的呢?秘密就在于()!

      private Thread ChartThread =newThread(){publicvoidrun(){while(true){try{sleep(100);

      Message msg =newMessage();

      msg.what =0x04;

      myHandler.sendMessage(msg);}catch(InterruptedException e){}}}};

      從上面的可以看出ChartThread主要負責周期性發(fā)送類(lèi)別為0x04的消息,而在handleMessage的case 0x04中則是負責獲取實(shí)時(shí)數據并更新UI的。之所以這樣繞個(gè)彎是因為UI更新一旦放在ChartThread中就會(huì )導致程序運行異常。這里的數據獲取和更新也比較容易理解:首先調用數據池的ask函數從p_write向后找40個(gè)數據尋找并解析有效幀,如果成功則最新的XYZ三軸的加速度已經(jīng)保存在mDataPool的公有成員XYZ中。下面第3行是計算合加速度(減去16000是為了方便顯示),接著(zhù)6到9行負責分別將三軸加速度及其合速度值加入折線(xiàn)圖。第10到13行便是我們簡(jiǎn)單的記步算法了,即當合加速值超過(guò)設定的記步閾值時(shí)記步數加一。第15、16行是控制折線(xiàn)圖滾動(dòng)到最新的位置并刷新ChartView。

      case0x04:if(mDataPool.ask()==true){int all =(int) Math.sqrt(mDataPool.X * mDataPool.X

      + mDataPool.Y * mDataPool.Y + mDataPool.Z

      * mDataPool.Z)-16000;

      mChartLine.addData(0, mTime, mDataPool.X);

      mChartLine.addData(1, mTime, mDataPool.Y);

      mChartLine.addData(2, mTime, mDataPool.Z);

      mChartLine.addData(3, mTime, all);if(all > mUpperLimit){

      mNum++;

      mTextView2.setText("當前記步數為: "+ mNum);}

      mTime +=1;

      mChartLine.letChartMove(mTime);

      mChartLine.mChartView.repaint();}break;

      綜上,當建立藍牙通信后,整個(gè)應用程序中主要有三個(gè)線(xiàn)程:

     ?、儆糜诓粩嘧x取串口數據并將其存入數據池的數據線(xiàn)程;

     ?、谟糜谥芷谛园l(fā)送0x04消息的信號線(xiàn)程;

     ?、垭[蔽而重要的主線(xiàn)程(UI更新等操作)。

      如圖14_2所示:一方面數據線(xiàn)程不斷讀取數據存入數據池,另一方面信號線(xiàn)程周期性發(fā)送0x04消息觸發(fā)handleMessage的case 0x04執行ask讀數據函數,當成功解析到有效數據時(shí)會(huì )在主線(xiàn)程中記步并更新UI。
    三線(xiàn)程運作

      此外,還有一些其他的控件用于提高交互性,如表14_1所示:開(kāi)始/停止按鈕用于控制折線(xiàn)圖是否動(dòng)態(tài)滾動(dòng),當停止折線(xiàn)圖動(dòng)態(tài)滾動(dòng)時(shí)折線(xiàn)圖的數據增加并未被中止,此時(shí)可以方便用戶(hù)拖動(dòng)折線(xiàn)圖查看歷史或觀(guān)察細節。四個(gè)CheckBox用于控制顯示哪一個(gè)折線(xiàn)圖,這樣便于單獨分析。滾動(dòng)條是用來(lái)動(dòng)態(tài)設置記步閾值的,這樣便于大家深入理解我們的簡(jiǎn)單的記步算法。
     


    原文出處:beautifulzzzz
    侵刪

     

    上一篇:下一篇:
    92视频在线精品国自产拍_乱色熟女综合一区二区_国产精品毛片久久久久久久_久天啪天天久久99久孕妇
    <ruby id="66w77"><video id="66w77"></video></ruby>
    <ruby id="66w77"><option id="66w77"><thead id="66w77"></thead></option></ruby>
  • <ruby id="66w77"><table id="66w77"></table></ruby>

  • <ruby id="66w77"><table id="66w77"></table></ruby>
    <strong id="66w77"></strong>