1 、智能手環簡介
智能手環是一種穿戴式智能設備。通過該設備,用戶可以記錄日常生活中的鍛煉、睡眠等實時數據,并將這些數據與手機、平板同步,起到通過數據指導健康生活的作用。另外,智能手環還具有社交功能,能夠將鍛煉情況和睡眠質量發送到社交網絡進行分享。
?
圖 1_1某款智能手環
一個智能手環最小系統一般包括:可充電的電源模塊、控制模塊(圖1_2中左邊芯片)、藍牙模塊(右邊芯片)、存儲模塊和加速計模塊(上面芯片)。其中加速計是為了獲得佩戴者在運動或睡眠過程中的加速度數據,通過分析這些數據則能夠判斷佩戴者的運動情況和睡眠質量;存儲模塊主要負責將實時數據暫存,接著在適當的時刻借助藍牙模塊將數據同步到手機端。方便起見本次要自制的記步手環將不采用存儲器暫存,而是將數據實時地傳送到手機端。同時為了便于大家對記步算法的理解,客戶端將采用一個折線圖的形式實時展示記步手環收集的數據。
?
圖 1_2某款智能手環核心電路板
2 、如何實現記步
看了上面的分析大家可能會疑惑——僅僅用一個加速計怎么能實現記步和睡眠質量檢測呢?其實確實可以!因為加速計可以實時獲取自身的XY三個軸向的加速度。當其靜止時合加速度會在重力加速度附近波動;當佩戴者處于深度睡眠過程中時,其合加速度將呈現出長時間的穩定于重力加速度附近;當其隨著運動的佩戴者手臂而做周期性擺動時,其數據也是有一定規律可循的。這樣,設計時只要通過分析從加速計獲的數據就能實現對運動或睡眠質量的記錄。
3、 預期效果構思
上面已經提到:為了方便,我們并未采用存儲器實現記步手環的離線記錄,而是實時地將數據發送到客戶端由一個可視化的折線圖動態繪制結果。如圖3_1所示系統中記步手環部分包含單片機模塊、藍牙模塊、加速計模塊和電源模塊,這樣通過單片機的協調可以實現將加速計模塊的數據通過藍牙實時地傳送給客戶端程序。在客戶端部分則負責將收集到的實時數據以折線圖的形式動態地展示出來,此外客戶端中也加入一個滑動條來控制記步閾值來真正讓大家明白其設計思想(真正商業化的智能手環多數采用的是先將有效數據保存在手環的小型存儲器中,上位機周期性地將數據收集并同步到服務器端)。
圖 3_1 預期效果圖
4 、硬件整體設計
如圖4_1,相比于上一個無線小風扇該硬件構成反而比較簡單:藍牙模塊依然采用我們比較熟悉的HC-06模塊,對于加速度的測量采用四周飛行器上常采用的MPU6050模塊。該模塊不僅含有加速計的功能,還具有陀螺儀的功能,其在汽車防側翻、相機云臺穩定、機器人平衡、空中鼠標、姿態識別等眾多領域都有應用,這里我們只是利用了它的加速計功能。此外要注意:圖4_1所示的單片機模塊的電源引腳被隱藏了,在真正設計連接時一定不要忽略這兩個引腳!
圖 4_1 硬件電路圖
5 、MPU6050介紹
MPU-60X0是全球首例9軸運動處理器。它集成了3軸MEMS陀螺儀,3軸MEMS加速計,以及1個可擴展的數字運動處理器DMP(Digital Motion Processor)。如圖5_1所示軸向是相對于加速計說的,當芯片水平靜止放置時x軸和y軸的加速度分量幾乎為0,z軸的加速度分量約為當地的重力加速度;而旋轉極性則是對陀螺儀來說的,本次先不介紹。
圖 5_1 MPU-60X0軸向和旋轉的極性(來自MPU6050數據手冊)
為何上面說9軸信號呢?因為MPU-60X0可用I2C接口連接一個第三方的數字傳感器,比如磁力計。擴展之后就可以通過其I2C或SPI接口輸出一個9軸的信號。也可以通過其I2C接口連接非慣性的數字傳感器,比如壓力傳感器。(為什么特別提磁力計和壓力傳感器呢?因為在飛控方面,利用陀螺儀和加速計可以計算飛行器的傾角,從而調節飛行器平衡。但是只是調節平衡對方向沒有概念也不能執行復雜任務,因此需要配備磁力計(也即電子羅盤傳感器)。此外,由于飛行器在不同高度作業時,其周圍的重力加速度也不同,這樣會影響傾角的準確性,因此通過氣壓計計算所處高度然后計算實時加速度達到精確控制的效果。)
圖 5_2 MPU-60X0典型工作電路(來自MPU6050數據手冊)
MPU-60X0對陀螺儀和加速計分別用了三個16位的ADC,將其測量的模擬量轉化為可輸出的數字量。為了精確跟蹤快速和慢速運動,傳感器的測量范圍是可控的,陀螺儀可測范圍為±250,±500,±1000,±2000°/秒(dps),加速計可測范圍為±2,±4,±8,±16g(重力加速度)。如圖5_3是直接從16位ADC中讀出的6軸的數據(從左到右依次為加速計X軸數據、Y軸數據、Z軸數據、陀螺儀X極數據、Y極數據、Z極數據):
圖 5_3 MPU6050輸出加速計和陀螺儀6軸的原始數據
但是這里的輸出值并不是真正的加速度和角速度的值,上面說過,MPU是一個16位AD量程可程控的設備,這里設置的加速度傳感器的測量量程為正負2g(這里的g為重力加速度),陀螺儀的量程為正負2000°/s。所以要用下面的公式進行轉化:
圖5_4 實際值計算公式
最后給大家推薦一款比較容易買到的MPU6050,如圖5_5該模塊將核心芯片和外圍電路集成到一個模塊上并留出八個引腳,本次使用只需用到上面四個即可(具體連接參考圖4_1)。
?
圖5_5 MPU6050模塊
6 、一個簡單的記步算法設計
第二小節講到當MPU6050隨著運動的佩戴者手臂而做周期性擺動時,其數據也是有一定規律可循的。簡單起見我們只分析合加速度:一個擺臂周期其合加速度會在重力加速度上下波動,如圖6_1只要選取合適的閾值(黑線代表閾值),每次檢測出合加速度大于該閾值則認為是一次擺臂,從而可以實現記步的功能。這里要特別說明下:如果想把你的手環推向市場,就要通過大量分析擺臂數據建立一套更好的記步算法,如果偷懶只用樓主的簡單算法,小心產品推出后被用戶的口水淹死(哈哈)!
圖 6_1 擺臂時合加速度變化圖
7 、I2C總線介紹
上次我們在使用藍牙串口模塊時使用過串口通信,由于51系列單片機將串口通信很多細節都封裝到芯片內部,所以我們即使設計了串口驅動模塊,也并沒有真正了解串口通信的核心思想。其實串口協議的出現是為了構成一個總線線路,這樣單片機只要使用比較少的引腳就能和比較多的設備進行通信了,這里要用到的I2C總線也具有相同的效果但又有些不同。
圖 7_1I2C總線掛接多個設備圖
I2C(Inter-Integrated Circuit)總線是由PHILIPS公司開發的兩線式串行總線,用于連接微控制器及其外圍設備。是微電子通信控制領域廣泛采用的一種總線標準。它是同步通信的一種特殊形式,具有接口線少,控制方式簡單,器件封裝形式小,通信速率較高等優點。如圖7_1采用I2C總線后CPU只要使用2個引腳便可和多個設備進行通信(其實每個采用I2C通信方式的設備都具有唯一的地址碼,這樣在總線中便能夠被唯一識別),從而大大減少了引腳的使用。
在I2C總線中使用的兩線為時鐘線SCL和數據線SDA。所有的I2C主從設備都是只被這兩根線連接起來的。每一個設備既可以作為發送方,也可以作為接收方,或者既可以作為發送發也可以作為接收方。在總線中的主設備一般起產生時鐘信號和初始化通信的作用,從設備則負責響應主設備發出的命令。為了在總線上區分每一個設備,每一個從設備必須有一個唯一的地址。主設備一般不需要地址(一般為微處理器),因為從設備不能發送命令給主設備。
圖 7_2 I2C總線中主從設備
這里要先介紹I2C總線中幾個專有名詞:
l 發送者:將數據發送到總線的設備
l 接收者:從總線接收數據的設備
l 主設備:產生時鐘信號、啟動通信、發送I2C命令和終止通信的設備
l 從設備:監聽總線、能被主設備尋址的設備
l 多主設備:I2C能夠擁有多個主設備,而且每個主設備都能夠發送命令
l 仲裁:當多個主設備請求使用總線時,決定哪一個主設備可以占用的一個過程
l 同步:同步多個設備時鐘信號的一個過程
上面是從宏觀上對I2C總線介紹了下,接下來將深入細節研究其通信過程:
n 串行數據傳送:
在總線備用時SDA和SCL都必須保持高電平狀態,只有關閉I2C總線時才能使SCL鉗位在低電平。在I2C總線數據傳輸時,在時鐘線高電平期間,數據線上必須保持有穩定的邏輯電平(也就是說在數據傳輸期間只有時鐘線低電平期間,才允許數據線上的電平發生變化)。
圖 7_3 串行數據發送
因此在如圖7_3中對于每一個時鐘脈沖期間一比特的數據將會被傳送,SDA只能在時鐘信號為低電平時才能改變。下面是代碼中發送一字節的函數:在循環體內每次將dat內的最高位移出到CY中,進而賦值給SDA(這時SCL為低,SDA可改變)。接著拉高SCL并保持5us,最后再拉低SCL實現一個時鐘脈沖將dat中最高位送出。依此循環8次實現將dat全部傳出。
//------------------------------------------------
//向I2C總線發送一個字節數據
//------------------------------------------------
void I2C_SendByte(uchar dat)
{
uchar i;
for (i=0; i《8; i++) //8位計數器
{
dat 《《= 1; //移出數據的最高位
SDA = CY; //送數據口
SCL = 1; //拉高時鐘線
Delay5us(); //延時
SCL = 0; //拉低時鐘線
Delay5us(); //延時
}
I2C_RecvACK();
}
n 開始和結束條件:
命令不會沒有任何預兆直接發送的,每一個I2C命令的發送總是開始于開始條件并結束于終止條件。這里所謂的開始條件和終止條件起始也是由SCL和SDA組合形成的(如圖7_4)。
圖 7_4 開始和結束條件
如果時鐘線保持高電平期間,數據線出現由高到低的電平變化,則會啟動I2C總線,此時為I2C的起始信號:
//------------------------------------------------
//I2C起始信號
//------------------------------------------------
void I2C_Start()
{
SDA = 1; //拉高數據線
SCL = 1; //拉高時鐘線
Delay5us(); //延時
SDA = 0; //產生下降沿
Delay5us(); //延時
SCL = 0; //拉低時鐘線
}
若在時鐘線保持高電平期間,數據線出現由低到高的電平變化,則會停止I2C總線的數據傳輸,此時為I2C的終止信號:
//------------------------------------------------
//I2C停止信號
//------------------------------------------------
void I2C_Stop()
{
SDA = 0; //拉低數據線
SCL = 1; //拉高時鐘線
Delay5us(); //延時
SDA = 1; //產生上升沿
Delay5us(); //延時
}
開始條件之后I2C總線被認為是忙狀態,只有當停止信號之后其他主設備才能使用該總線。此外,當開始條件之后主設備能夠多次發出開始信號。這些開始信號和第一次發出的開始信號類似,他們后面經常會跟從設備的地址。這樣可以方便實現在I2C總線忙期間,當前占線的主設備可以和不同的從設備進行通信。
n I2C數據傳送:
I2C總線上傳送的每一個字節均為8位,但是每啟動一次I2C總線,其后的數據傳送字節數是沒有限制的。同時每傳送一字節的數據后面都要跟隨一個接收者回應的應答位(低電平為應答信號,高電平為非應答信號),當全部數據發送完畢后主設備發送終止信號。
圖 7_5 數據傳送圖
所以在上面向I2C總線發送一字節的數據的代碼的最后有一個I2C_RecvACK()函數。(如下)該函數負責接收接收者發送過來的應答信號,也即圖7_5中的第9個時鐘脈沖的期間的相應操作。
//------------------------------------------------
//I2C接收應答信號
//------------------------------------------------
bit I2C_RecvACK()
{
SCL = 1; //拉高時鐘線
Delay5us(); //延時
CY = SDA; //讀應答信號
SCL = 0; //拉低時鐘線
Delay5us(); //延時
return CY;
}
要特別說明下:所有的數據位包括應答位都需要主設備產生時鐘脈沖。如果從設備沒有應答意味著將沒有更多的數據要傳送或者設備沒有準備好傳送。這時,主設備要么產生停止信號,要么重新發出開始條件。
圖 7_6 應答信號
n I2C的7-bit地址:
上面說過每一個從設備都應該具有唯一的地址,這樣主設備才能準確的尋址到每一個設備,而這些地址被統一規定為7比特。但是上面講過I2C總線傳輸數據都是8比特傳送,地址7比特豈不是少一位!其實緊跟地址還有一位用來表示是讀操作還是寫操作的標志位。如果該位為0表示主設備將要向從設備寫數據,否則表示主設備將要從從設備讀數據。在這8比特被發送后主設備能夠持續地進行讀或者寫。如果主設備想和其他從設備進行通信,只要再次發送一個新的開始信號就可以而不必發送終止信號。
圖 7_7 一個完整的數據讀寫操作
8 、MPU6050驅動設計
至此,我們基本上已經將I2C的知識學完了,下面將結合MPU6050的驅動進一步講解其原理(該部分的代碼參見工程的mpu6050.c部分)。我們首先來看一下它的頭文件mpu6050.h:從第6到25行上來就是一大串內部地址的定義,對于初學者可能一頭霧水!如果樓主再引入寄存器等數字電路的知識可能又要說幾頁了,于是這里準備只用一個簡單的例子闡述下這些地址的作用。
#include“i2c.h”
//-----------------------------------------
// 定義MPU6050內部地址
//-----------------------------------------
#define SMPLRT_DIV 0x19 //陀螺儀采樣率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通濾波頻率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺儀自檢及測量范圍,典型值:0x18(不自檢,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速計自檢、測量范圍及高通濾波頻率,典型值:0x01(不自檢,2G,5Hz)
#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 //電源管理,典型值:0x00(正常啟用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默認數值0x68,只讀)
#define SlaveAddress 0xD0 //IIC寫入時的地址字節數據,+1為讀取
//-----------------------------------------
// 通過I2C和MPU6050通信的函數
//-----------------------------------------
void Single_WriteI2C(uchar REG_Address,uchar REG_data);//向I2C設備寫入一個字節數據
uchar Single_ReadI2C(uchar REG_Address); //從I2C設備讀取一個字節數據
void InitMPU6050(); //初始化MPU6050
int GetData(uchar REG_Address); //合成數據
上面講到在I2C總線中主設備可以通過固定的7-bit地址尋找到相應的從設備(這里的7-bit地址為第26行的SlaveAddress,想必大家也能夠理解后面注釋的意義了吧~不加1表示緊跟著地址的一位為0,表示向該設備寫數據;加1則表示緊跟著的一位為1,表示主設備從從設備讀數據)。雖然采用這種方式能夠準確找到從設備,但是從設備里面又有比較多的寄存器。這就好比你知道了某個要找的東西在具體的某個大柜子里,但是來到大柜子前又發現有許多小抽屜。這里的7-bit地址就好像指明了哪個柜子,而從第6到25行的內部地址就像柜子上的抽屜編號,而不一樣之處是位于mpu6050內的“小抽屜”一部分存放著其采集的實時數據,另一部分等著外部放一些數據來設置其采樣屬性。
這樣,如上面的第6行的SMPLRT_DIV(0x19)是用來設置陀螺儀采樣率的寄存器地址,只要向該地址所指的寄存器寫入相應的值則可以設置陀螺儀采樣率。因此下面MPU6050初始化函數就是調用封裝的I2C寫函數向相應的小抽屜內寫屬性數據,設置MPU6050采樣屬性。
//------------------------------------------------
//初始化MPU6050
//------------------------------------------------
void InitMPU6050()
{
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是用來存放最新的陀螺儀X極的數值,因為采用16位ADC所以這里需要用兩個寄存器。所以下面合成數據函數負責連續讀取REG_Address開始的兩字節數據組成一個16位數據。當函數的參數為ACCEL_XOUT_H時,則獲取的是實時的陀螺儀X極的數值,同樣地可以獲得實時的6軸數據。
//------------------------------------------------
//合成數據
//------------------------------------------------
int GetData(uchar REG_Address)
{
uchar H,L;
H=Single_ReadI2C(REG_Address);
L=Single_ReadI2C(REG_Address+1);
return (H《《8)+L; //合成數據
}
評論
查看更多