免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
[智能硬件] 3、三分鐘看懂智能硬件原理

  

 

  首先恭喜大家挺過了測試二!為什么說“挺”呢?因為測試二的難度和測試一相比有一個比較大的跳躍:首先測試一僅僅利用現(xiàn)有硬件模塊稍加改造而DIY一個藍牙防丟器,而測試二則要求大家具有從腦袋里的一個想法到一個全新的小設備的實現(xiàn)的全部能力,顯然該過程不是連幾根線那么簡單;其次測試一對藍牙的使用僅限于信號搜索層面,而測試二一下子深入到可靠通信的層面了,其難度可想而知;最后在測試二中客戶端的設計中復雜的狀態(tài)轉(zhuǎn)換過程,以及嵌入式編程時需要對所使用的硬件作細致的分析,都構(gòu)成了對前期基礎沒打牢的同學一種挑戰(zhàn)。不過好消息是大家挺過來了,接下來的模式大致相同。

  現(xiàn)在,讓我們回到本次的正題!可能細心的讀者會發(fā)現(xiàn)上次的遙控小風扇雖然利用了藍牙的通信功能,但是僅限于客戶端向硬件發(fā)送命令(在上次并未使用更嚴謹?shù)膽鹉J?,即客戶端發(fā)送控制命令->硬件響應命令->硬件返回響應完畢命令)。為了彌補上次的缺陷,那么本次會將重點放在數(shù)據(jù)收集方面,樓主想來想去覺得還是一個簡單的記步手環(huán)最適合了。[正版請搜索:beautifulzzzz(看樓主博客園官方博客,享高質(zhì)量生活)]

 

1 智能手環(huán)簡介

  智能手環(huán)是一種穿戴式智能設備。通過該設備,用戶可以記錄日常生活中的鍛煉、睡眠等實時數(shù)據(jù),并將這些數(shù)據(jù)與手機、平板同步,起到通過數(shù)據(jù)指導健康生活的作用。另外,智能手環(huán)還具有社交功能,能夠?qū)㈠憻捛闆r和睡眠質(zhì)量發(fā)送到社交網(wǎng)絡進行分享。

圖 1_1某款智能手環(huán)

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

圖 1_2某款智能手環(huán)核心電路板

 

2 如何實現(xiàn)記步

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

 

3 預期效果構(gòu)思

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

圖 3_1 預期效果圖

 

4 硬件整體設計

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

圖 4_1 硬件電路圖

 

5 MPU6050介紹

  MPU-60X0是全球首例9軸運動處理器。它集成了3軸MEMS陀螺儀,3軸MEMS加速計,以及1個可擴展的數(shù)字運動處理器DMP(Digital Motion Processor)。如圖5_1所示軸向是相對于加速計說的,當芯片水平靜止放置時x軸和y軸的加速度分量幾乎為0,z軸的加速度分量約為當?shù)氐闹亓铀俣?;而旋轉(zhuǎn)極性則是對陀螺儀來說的,本次先不介紹。

圖 5_1 MPU-60X0軸向和旋轉(zhuǎn)的極性(來自MPU6050數(shù)據(jù)手冊)

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

圖 5_2 MPU-60X0典型工作電路(來自MPU6050數(shù)據(jù)手冊)

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

圖 5_3 MPU6050輸出加速計和陀螺儀6軸的原始數(shù)據(jù)

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

圖5_4 實際值計算公式

  最后給大家推薦一款比較容易買到的MPU6050,如圖5_5該模塊將核心芯片和外圍電路集成到一個模塊上并留出八個引腳,本次使用只需用到上面四個即可(具體連接參考圖4_1)。

圖5_5 MPU6050模塊

 

6 一個簡單的記步算法設計

  第二小節(jié)講到當MPU6050隨著運動的佩戴者手臂而做周期性擺動時,其數(shù)據(jù)也是有一定規(guī)律可循的。簡單起見我們只分析合加速度:一個擺臂周期其合加速度會在重力加速度上下波動,如圖6_1只要選取合適的閾值(黑線代表閾值),每次檢測出合加速度大于該閾值則認為是一次擺臂,從而可以實現(xiàn)記步的功能。這里要特別說明下:如果想把你的手環(huán)推向市場,就要通過大量分析擺臂數(shù)據(jù)建立一套更好的記步算法,如果偷懶只用樓主的簡單算法,小心產(chǎn)品推出后被用戶的口水淹死(哈哈)!

圖 6_1 擺臂時合加速度變化圖

 

7 I2C總線介紹

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

圖 7_1I2C總線掛接多個設備圖

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

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

圖 7_2 I2C總線中主從設備

這里要先介紹I2C總線中幾個專有名詞:

l  發(fā)送者:將數(shù)據(jù)發(fā)送到總線的設備
l  接收者:從總線接收數(shù)據(jù)的設備
l  主設備:產(chǎn)生時鐘信號、啟動通信、發(fā)送I2C命令和終止通信的設備
l  從設備:監(jiān)聽總線、能被主設備尋址的設備
l  多主設備:I2C能夠擁有多個主設備,而且每個主設備都能夠發(fā)送命令
l  仲裁:當多個主設備請求使用總線時,決定哪一個主設備可以占用的一個過程
l  同步:同步多個設備時鐘信號的一個過程

上面是從宏觀上對I2C總線介紹了下,接下來將深入細節(jié)研究其通信過程:

n  串行數(shù)據(jù)傳送:

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

圖 7_3 串行數(shù)據(jù)發(fā)送

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

 1 //------------------------------------------------ 2 //向I2C總線發(fā)送一個字節(jié)數(shù)據(jù) 3 //------------------------------------------------ 4 void I2C_SendByte(uchar dat) 5 { 6     uchar i; 7     for (i=0; i<8; i++)         //8位計數(shù)器 8     { 9         dat <<= 1;              //移出數(shù)據(jù)的最高位10         SDA = CY;               //送數(shù)據(jù)口11         SCL = 1;                //拉高時鐘線12         Delay5us();             //延時13         SCL = 0;                //拉低時鐘線14         Delay5us();             //延時15     }16     I2C_RecvACK();17 }

n  開始和結(jié)束條件:

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

圖 7_4 開始和結(jié)束條件

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

 1 //------------------------------------------------ 2 //I2C起始信號 3 //------------------------------------------------ 4 void I2C_Start() 5 { 6     SDA = 1;                    //拉高數(shù)據(jù)線 7     SCL = 1;                    //拉高時鐘線 8     Delay5us();                 //延時 9     SDA = 0;                    //產(chǎn)生下降沿10     Delay5us();                 //延時11     SCL = 0;                    //拉低時鐘線12 }

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

 1 //------------------------------------------------ 2 //I2C停止信號 3 //------------------------------------------------ 4 void I2C_Stop() 5 { 6     SDA = 0;                    //拉低數(shù)據(jù)線 7     SCL = 1;                    //拉高時鐘線 8     Delay5us();                 //延時 9     SDA = 1;                    //產(chǎn)生上升沿10     Delay5us();                 //延時11 }

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

n  I2C數(shù)據(jù)傳送:

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

圖 7_5 數(shù)據(jù)傳送圖

所以在上面向I2C總線發(fā)送一字節(jié)的數(shù)據(jù)的代碼的最后有一個I2C_RecvACK()函數(shù)。(如下)該函數(shù)負責接收接收者發(fā)送過來的應答信號,也即圖7_5中的第9個時鐘脈沖的期間的相應操作。

 1 //------------------------------------------------ 2 //I2C接收應答信號 3 //------------------------------------------------ 4 bit I2C_RecvACK() 5 { 6     SCL = 1;                    //拉高時鐘線 7     Delay5us();                 //延時 8     CY = SDA;                   //讀應答信號 9     SCL = 0;                    //拉低時鐘線10     Delay5us();                 //延時11     return CY;12 }

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

圖 7_6 應答信號

n  I2C的7-bit地址:

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

圖 7_7 一個完整的數(shù)據(jù)讀寫操作

 

 

8 MPU6050驅(qū)動設計

  至此,我們基本上已經(jīng)將I2C的知識學完了,下面將結(jié)合MPU6050的驅(qū)動進一步講解其原理(該部分的代碼參見工程的mpu6050.c部分)。我們首先來看一下它的頭文件mpu6050.h:從第6到25行上來就是一大串內(nèi)部地址的定義,對于初學者可能一頭霧水!如果樓主再引入寄存器等數(shù)字電路的知識可能又要說幾頁了,于是這里準備只用一個簡單的例子闡述下這些地址的作用。

 1 #include"i2c.h" 2  3 //----------------------------------------- 4 // 定義MPU6050內(nèi)部地址 5 //----------------------------------------- 6 #define    SMPLRT_DIV        0x19    //陀螺儀采樣率,典型值:0x07(125Hz) 7 #define    CONFIG            0x1A    //低通濾波頻率,典型值:0x06(5Hz) 8 #define    GYRO_CONFIG        0x1B    //陀螺儀自檢及測量范圍,典型值:0x18(不自檢,2000deg/s) 9 #define    ACCEL_CONFIG    0x1C    //加速計自檢、測量范圍及高通濾波頻率,典型值:0x01(不自檢,2G,5Hz)10 #define    ACCEL_XOUT_H    0x3B11 #define    ACCEL_XOUT_L    0x3C12 #define    ACCEL_YOUT_H    0x3D13 #define    ACCEL_YOUT_L    0x3E14 #define    ACCEL_ZOUT_H    0x3F15 #define    ACCEL_ZOUT_L    0x4016 #define    TEMP_OUT_H        0x4117 #define    TEMP_OUT_L        0x4218 #define    GYRO_XOUT_H        0x4319 #define    GYRO_XOUT_L        0x44    20 #define    GYRO_YOUT_H        0x4521 #define    GYRO_YOUT_L        0x4622 #define    GYRO_ZOUT_H        0x4723 #define    GYRO_ZOUT_L        0x4824 #define    PWR_MGMT_1        0x6B    //電源管理,典型值:0x00(正常啟用)25 #define    WHO_AM_I        0x75    //IIC地址寄存器(默認數(shù)值0x68,只讀)26 #define    SlaveAddress    0xD0    //IIC寫入時的地址字節(jié)數(shù)據(jù),+1為讀取27 28 //-----------------------------------------29 // 通過I2C和MPU6050通信的函數(shù)30 //-----------------------------------------31 void  Single_WriteI2C(uchar REG_Address,uchar REG_data);//向I2C設備寫入一個字節(jié)數(shù)據(jù)32 uchar Single_ReadI2C(uchar REG_Address);                //從I2C設備讀取一個字節(jié)數(shù)據(jù)33 void  InitMPU6050();                                    //初始化MPU605034 int   GetData(uchar REG_Address);                         //合成數(shù)據(jù)

 

 

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

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

 1 //------------------------------------------------ 2 //初始化MPU6050 3 //------------------------------------------------ 4 void InitMPU6050() 5 { 6     Single_WriteI2C(PWR_MGMT_1, 0x00);    //解除休眠狀態(tài) 7     Single_WriteI2C(SMPLRT_DIV, 0x07); 8     Single_WriteI2C(CONFIG, 0x06); 9     Single_WriteI2C(GYRO_CONFIG, 0x18);10     Single_WriteI2C(ACCEL_CONFIG, 0x01);11 }

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

 1 //------------------------------------------------ 2 //合成數(shù)據(jù) 3 //------------------------------------------------ 4 int GetData(uchar REG_Address) 5 { 6     uchar H,L; 7     H=Single_ReadI2C(REG_Address); 8     L=Single_ReadI2C(REG_Address+1); 9     return (H<<8)+L;                       //合成數(shù)據(jù)10 }

  最后要說明下:關于MPU6050內(nèi)部的“小抽屜”的地址和功能需要閱讀其官方的MPU6050寄存器手冊。(注意是寄存器手冊!)

 

9 硬件工程整體介紹

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

圖 9_1 打開工程

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

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

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

 1 //------------------------------------------------ 2 //主函數(shù) 3 //------------------------------------------------ 4 void main (void) 5 { 6     delay(500);            //上電延時     7     InitUART();            //初始化串口 8     InitMPU6050();        //初始化MPU6050 9 10     while (1)             //主循環(huán)11     {12         SendByte('#');                //起始標志13         SendData(GetData(0x3B));    //X軸加速度14         SendData(GetData(0x3D));    //Y軸加速度15         SendData(GetData(0x3F));    //Z軸加速度16         SendByte('$');                 //標志17         delay(20);18     }19 }

  其中調(diào)用了串口驅(qū)動中的void InitUART(void)串口初始化函數(shù)、 void SendByte(unsigned char dat)串口發(fā)送一字節(jié)函數(shù)和 void SendStr(unsigned char *s)串口發(fā)送一個字符串函數(shù),以及調(diào)用了mpu6050驅(qū)動中的void InitMPU6050()初始化函數(shù)和int GetData(uchar REG_Address)獲取6軸數(shù)據(jù)函數(shù)。

1 //外部函數(shù)2 extern void InitUART(void);3 extern void SendByte(unsigned char dat);4 extern void SendStr(unsigned char *s);5 extern void InitMPU6050();6 extern int  GetData(uchar REG_Address);

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

 1 //----------------------------------------- 2 //整數(shù)轉(zhuǎn)字符串 3 //----------------------------------------- 4 void enCode(uchar *s,int temp_data) 5 { 6     if(temp_data<0) 7     { 8         temp_data=-temp_data; 9         *s='-';10     }11     else *s=' ';12     *++s =temp_data/10000+0x30;13     temp_data=temp_data%10000;      //取余運算14     *++s =temp_data/1000+0x30;15     temp_data=temp_data%1000;       //取余運算16     *++s =temp_data/100+0x30;17     temp_data=temp_data%100;        //取余運算18     *++s =temp_data/10+0x30;19     temp_data=temp_data%10;         //取余運算20     *++s =temp_data+0x30;21     *++s ='\0';                        //字符串結(jié)束標志     22 }23 24 //-----------------------------------------25 //編碼+發(fā)送到串口26 //-----------------------------------------27 void SendData(int value)28 { 29     enCode(temp, value);            //轉(zhuǎn)換數(shù)據(jù)顯示30     SendStr(temp);31 }

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

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

 

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

10.1、打開Eclipse點擊File菜單欄下的Import按鈕準備導入second_test工程(如圖10_1所示)。

圖 10_1 導入工程

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

圖 10_2 選擇導入類型

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

圖 10_3 選擇工程

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

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

 

11 軟件最終效果預覽

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

n  第一幅圖:是初始打開界面,如果本地藍牙沒有打開最左邊的按鈕將會顯示“打開藍牙設備”;
n  第二幅圖:是點擊“連接我的小手環(huán)”后進入藍牙搜索階段;
n  第三幅圖:是自動搜索到記步手環(huán)后進入的連接藍牙階段;
n  第四幅圖:是連接完成后,應用把從手環(huán)收集的實時數(shù)據(jù)(X\Y\Z軸加速度以及合加速度)繪制出;
n  第五幅圖:是通過滑動條調(diào)大記步閾值,并選擇CheckBox只顯示合加速度值的實時折線;
n  第六幅圖:是放大折線圖,并點擊某個點顯示具體信息圖。

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

圖 11_1 軟件最終效果預覽圖(從左到右從上到下編號1-6)

 

12 一個高效處理數(shù)據(jù)的數(shù)據(jù)池設計

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

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

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

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

圖 12_1 從點擊連接小手環(huán)到完成藍牙連接全過程流程圖

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

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

圖 12_2 自動更新的環(huán)形棧

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

  具體在程序中UI_Main.java的onCreate函數(shù)中聲明并實例化一個大小為20000的數(shù)據(jù)池mDataPool = new DataPool(20000)。接著在BlueToothCommunicate的輪詢接收數(shù)據(jù)的線程中(也即圖12_1的最后一環(huán)節(jié)的read中)對于每次新收到的數(shù)據(jù)調(diào)用mDataPool的push_back(buffer, bytes)函數(shù)將其存儲在數(shù)據(jù)池中。當每次需要取最新數(shù)據(jù)時只要先調(diào)用mDataPool的ask()函數(shù),接著便可直接通過訪問DataPool的公有成員X\Y\Z獲取最新三軸加速度的值了。

 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             UI_Main.mDataPool.push_back(buffer, bytes);14         } catch (IOException e) {15             break;16         }17     }18 }

 

13  一個開源的折線圖繪制方案

  在第10節(jié)客戶端軟件構(gòu)成模塊中曾提到本項目中采用了開源圖表繪制引擎AChartEngine。它是一個安卓系統(tǒng)上制作圖表的框架,支持折線圖、面積圖、分區(qū)圖、對比圖、散點圖、柱狀圖、餅圖等(如圖13_1所示)。

圖13_1 AChartEngine繪制的圖標Demo

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

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

1 private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset();2 private XYMultipleSeriesRenderer mRenderer = new XYMultipleSeriesRenderer();

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

 1 public void setChartSettings(String xTitle, String yTitle, double xMin, 2         double xMax, double yMin, double yMax, int axesColor, 3         int labelsColor) { 4     // 有關對圖表的渲染可參看api文檔 5     mRenderer.setXTitle(xTitle);// 名字 6     mRenderer.setYTitle(yTitle); 7     mRenderer.setXAxisMin(xMin);// 最小最大值 8     mRenderer.setXAxisMax(xMax); 9     mRenderer.setYAxisMin(yMin);10     mRenderer.setYAxisMax(yMax);11     mRenderer.setAxesColor(axesColor);// 坐標軸顏色12     mRenderer.setLabelsColor(labelsColor);// 標號顏色13     mRenderer.setShowGrid(true);// 顯示網(wǎng)格14     mRenderer.setGridColor(Color.GRAY);15     mRenderer.setXLabels(16);16     mRenderer.setYLabels(20);17     mRenderer.setYLabelsAlign(Align.RIGHT);// 設置標簽居Y軸的方向18     mRenderer.setPointSize((float) 2);19     mRenderer.setShowLegend(true);// 下面的標注20     // mRenderer.setZoomButtonsVisible(true);// 放大縮小按鈕21     mRenderer.setZoomEnabled(true, false);// 設置縮放,這邊是橫向可以縮放,豎向不能縮放22     mRenderer.setPanEnabled(true, false);// 設置滑動,這邊是橫向可以滑動,豎向不可滑動23 }

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

public void setLineSettings() {    for (int i = 0; i < titles.length; i++) {        // create a new series of data        mCurrentSeries[i] = new XYSeries(titles[i]);        mDataset.addSeries(mCurrentSeries[i]);        // create a new renderer for the new series        renderer[i] = new XYSeriesRenderer();        mRenderer.addSeriesRenderer(renderer[i]);        // set some renderer properties        renderer[i].setPointStyle(styles[i]);        renderer[i].setColor(colors[i]);        renderer[i].setFillPoints(true);// 實心還是空心        renderer[i].setDisplayChartValues(false);// 不顯示值        renderer[i].setDisplayChartValuesDistance(10);    }}

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

 1 // 顯示第i個折線圖 2 public void showLine(int i) { 3     mDataset.addSeries(mCurrentSeries[i]); 4     mRenderer.addSeriesRenderer(renderer[i]); 5 } 6  7 // 隱藏第i個折線圖 8 public void hideLine(int i) { 9     mDataset.removeSeries(mCurrentSeries[i]);10     mRenderer.removeSeriesRenderer(renderer[i]);11 }12 13 // 向第i個折線圖中添加(x,y)數(shù)據(jù)14 public void addData(int i, double x, double y) {15     mCurrentSeries[i].add(x, y);16 }

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

 1 public void setChartViewSetting(final Activity activity) { 2     if (mChartView == null) { 3         LinearLayout layout = (LinearLayout) activity 4                 .findViewById(R.id.chart); 5         mChartView = ChartFactory.getLineChartView(activity, mDataset, 6                 mRenderer); 7         // enable the chart click events 8         mRenderer.setClickEnabled(true); 9         mRenderer.setSelectableBuffer(10);10         mChartView.setOnClickListener(new View.OnClickListener() {11             public void onClick(View v) {12                 ……(略)13             }14         });15         layout.addView(mChartView, new LayoutParams(16                 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));17     } else {18         mChartView.repaint();19     }20 }

 

14 整體邏輯梳理 

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

  在onCreate中首先實例化藍牙三劍客,接著實例化數(shù)據(jù)池和折線圖表,然后調(diào)用折線圖類的成員函數(shù)對折線圖做前期設置,最后啟動ChartThread線程。

 1 // 實例化藍牙三劍客(搜索、連接、通信) 2 // myHandler是用來反饋信息的 3 mBlueToothSearch = new BlueToothSearch(this, myHandler); 4 mBlueToothConnect = new BlueToothConnect(myHandler); 5 mBlueToothCommunicate = new BlueToothCommunicate(myHandler); 6  7 mDataPool = new DataPool(20000); 8  9 mChartLine = new ChartLine();10 // 設置圖標顯示的基本屬性11 mChartLine.setChartSettings("Time", "", 0, 100, -20000, 20000,12         Color.WHITE, Color.WHITE);13 // 設置四個折線圖的屬性14 mChartLine.setLineSettings();15 16 ChartThread.start();// 啟動圖標更新線程

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

圖 14_1 點擊連接設備到通信建立

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

 1 private Thread ChartThread = new Thread() { 2     public void run() { 3         while (true) { 4             try { 5                 sleep(100); 6                 // 周期性發(fā)送更新Chart的消息(因為UI不能放在這個里面更新) 7                 Message msg = new Message(); 8                 msg.what = 0x04; 9                 myHandler.sendMessage(msg);10             } catch (InterruptedException e) {11             }12         }13     }14 };

 

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

 1 case 0x04: 2     if (mDataPool.ask() == true) { 3         int all = (int) Math.sqrt(mDataPool.X * mDataPool.X 4                 + mDataPool.Y * mDataPool.Y + mDataPool.Z 5                 * mDataPool.Z) - 16000; 6         mChartLine.addData(0, mTime, mDataPool.X); 7         mChartLine.addData(1, mTime, mDataPool.Y); 8         mChartLine.addData(2, mTime, mDataPool.Z); 9         mChartLine.addData(3, mTime, all);10         if (all > mUpperLimit) {// 記步-和加速度超過設定上限則記步11             mNum++;12             mTextView2.setText("當前記步數(shù)為: " + mNum);13         }14         mTime += 1;15         mChartLine.letChartMove(mTime);// 控制圖形滾動16         mChartLine.mChartView.repaint();17     }18     break;

  綜上,當建立藍牙通信后,整個應用程序中主要有三個線程:①用于不斷讀取串口數(shù)據(jù)并將其存入數(shù)據(jù)池的數(shù)據(jù)線程;②用于周期性發(fā)送0x04消息的信號線程;③隱蔽而重要的主線程(UI更新等操作)。如圖14_2所示:一方面數(shù)據(jù)線程不斷讀取數(shù)據(jù)存入數(shù)據(jù)池,另一方面信號線程周期性發(fā)送0x04消息觸發(fā)handleMessage的case 0x04執(zhí)行ask讀數(shù)據(jù)函數(shù),當成功解析到有效數(shù)據(jù)時會在主線程中記步并更新UI。

圖 14_2 三線程運作

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

表 14_1 其他用于優(yōu)化交互的控件

 

15 最終成果檢查

  如果說遙控小風扇是硬件上要費工夫的一個測試,那么本章的記步手環(huán)無疑需要在軟件上費很大工夫的一個測試。這里所說的軟件不僅包括引進一個開源圖表繪制框架、延續(xù)了上一章中的藍牙通信三劍客、嘗試了稍微繁瑣的布局和多控件UI,還包括多線程中狀態(tài)轉(zhuǎn)換控制和為高速實時而設計的數(shù)據(jù)池數(shù)據(jù)結(jié)構(gòu)。如果再廣一點,還有I2C通信協(xié)議的驅(qū)動設計和數(shù)據(jù)幀的設計等。

  大家也不要被這幾篇測試的難度嚇倒,因為無數(shù)的事實證明備戰(zhàn)的強度要比真正的戰(zhàn)斗要艱難的多(哈哈,否則怎么會有勢如破竹的戰(zhàn)績呢?)。下面讓我們看看自己本次備戰(zhàn)的成績?nèi)绾巍?/p>

知道一個簡單記步手環(huán)的構(gòu)成模塊(+ 10分)
 懂得如何利用加速計的數(shù)據(jù)實現(xiàn)簡單的記步和睡眠質(zhì)量檢查(+ 10分)
 掌握MPU6050的功能并能夠讀懂MPU6050輸出數(shù)據(jù)的意義(+ 10分)
 能根據(jù)講解讀懂I2C驅(qū)動代碼的意義(+ 20分)
 理解MPU6050的驅(qū)動設計(+ 20分)
 成功設計出記步手環(huán)硬件部分(+ 40分)
 回顧藍牙三劍客(+ 5分)
 理解數(shù)據(jù)池數(shù)據(jù)結(jié)構(gòu)的由來原因和設計思想(+ 20分)
 知道利用開源資源豐富自己的項目(+ 5分)
 讀懂利用AChartEngine設計的折線圖繪制類(+ 20分)
 腦袋里走通了整個客戶端軟件的狀態(tài)轉(zhuǎn)換圖(+ 30分)
 加入睡眠質(zhì)量檢測功能(+20分)
 加入存儲器實現(xiàn)記步手環(huán)離線功能(+ 30分)……

——及格線70分,良好線150分,優(yōu)秀200分。自己還滿意吧?

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

[如果您也喜歡智能硬件的東西,可以交個朋友~]

[如果您胸懷大志,能團結(jié)各路豪杰的亂世英雄,也可以留下一下信息~]

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

 

 

 

 

 

 

 

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
用開源硬件打造屬于你自己智能手環(huán)
第一屆UEAL| 計步器
便攜有個性,功能強大,可配戴在手指上的鍵盤鼠標
MPU6050傳感器—姿態(tài)檢測
【干貨】六款智能手表完美設計方案
重力加速度陀螺儀傳感器MPU-6050
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服