Arduino菜鳥通俗版解讀系列(6)MPU6050慣性單元

這一講來和大家介紹另一款非常有用的模塊---MPU6050。這是一款集成度比較高的模塊,它的內部集成了多種感測器。根據集成感測器種類的多少,MPU6050分高端,中端,低端幾種不同版本,高端的版本可以輸出更多的感測信息,低端的版本輸出的信息會相對較少。具體的區別如下:

高端版:可以輸出X-Y-Z三個方向的加速度,X-Y-Z三個方向的角速度,X-Y-Z三個方向的偏轉角,溫度,海拔,氣壓,經緯度等等。。。除了集成了這麼多感測信息以外,高端版還自帶卡爾曼濾波,這是非常好的,因為如果沒有卡爾曼濾波,你獲得的信號數據會有很多噪音,沒有辦法直接拿來使用。比如,你要測量某個方向上的偏轉角,如果沒有卡爾曼濾波,那麼你得到的初始數據波動會很大,導致你根本無法確定當前的狀態值,所以你需要先對這些初始數據進行濾波,然後才能得到一串比較穩定的數據。不過由於高端版的MPU6050本身植入了卡爾曼濾波功能,所以它輸出的所有信號數據都是穩定且可直接使用的。

中端版:只輸出X-Y-Z三個方向的加速度,X-Y-Z三個方向的角速度,X-Y-Z三個方向的偏轉角。同時可喜的是,中端版也帶有卡爾曼濾波,所以中端版的數據也是非常穩定的,可以直接拿來使用。

低端版:低端版一般只輸出X-Y-Z方向的加速度信號和角速度信號,並且它不帶有卡爾曼濾波。所以使用起來比較麻煩也很考驗專業功底。假如你不懂卡爾曼濾波的話(其實我也不懂,只知道是一種濾波演算法),那麼光是自己編製濾波程序可能就夠你花上一段時間的。

在版本選擇上我本人用的是中端版,因為考慮到高端版價格比較高。在淘寶上面一般高端的MPU6050要100塊左右,中端的60塊左右,低端版只需要10多塊錢。不過我個人用下來還是覺得一分錢一分貨,不差錢的話還是推薦直接買高端版本的省時省力,不過假如你是那種喜歡搗鼓底層演算法並且想成為一個深度專業人士的話可以用用低端版的。

了解了MPU6050的功能後,我們其實可以隱約感覺到 MPU6050的廣泛應用了:手機,四軸飛行器,飛機,平衡車等等產品內部都有它的身影。比如手機裡面的指南針,氣壓,加速度什麼的,全都是運用這款模塊採集而來的,還有手機橫屏豎屏之間的轉換也都仰仗MPU6050,平衡車之所以不倒也是仰仗MPU6050。既然MPU6050這麼膩害,那是不是很複雜呢?是的。不過你別慌,複雜指的是它的結構原理,對於我們消費者使用來講,一點都不難。當然前提是你買的高端版或者中端版啦,低端版玩不轉別找我。。。下面就來介紹下MPU怎麼使用。

本篇以本人用的一款中端版為例,介紹兩大部分內容:1.MPU6050的結構和簡單的作用原理;2.MPU6050的軟體部分,即驅動程序和內部的數據結構。

圖1

圖2

先看硬體部分,圖1是MPU6050的外形結構圖。可見MPU6050的體積是非常小的,也就是指尖那麼大。圖2是MPU6050的引腳說明。我們可以看到,MPU6050左右兩側各有4個介面,在圖2中這4個介面沒有焊引腳上去,那麼這就需要我們在買來MPU6050模塊後自己焊接金屬引腳上去,這都沒什麼可說的。需要說明一下的是左右兩側的引腳區別,我使用的這款MPU6050模塊它支持兩種串列通信方式:I2C和USART(串口通信,注意是串口通信,不是串列通信)(關於串列通信模式雖然之前已經有兩篇文章進行了講解,不過在下一片我會進行一個總結,因為串列通信花樣繁多,SPI,I2C,USB,USART等等著實讓我自己也犯迷糊了,在最近整合不同模塊來控制四軸飛行器的時候這種模糊讓我走了不少彎路,所以我覺得在下一片有必要將串列通信做一個歸納總結),左側VCC,TX,RX,GND是USART模式用的引腳,右側VCC,SCL,SDA,GND是I2C模式使用的引腳。那麼在選擇上我選擇了USART模式,為什麼?因為根據說明書上所講,I2C模式下, MPU6050不會輸出X-Y-Z三個方向的偏轉角信息,而我恰恰要用這個信息,所以我就採用了USART模式。下面我也就以USART模式為例來介紹了。這個USART模式採用引腳TX,RX來發送和接收數據;大家一定記得,對!就是在第4篇中我們講到過,其實USART就是第四篇中我們講Arduino和電腦之間的串口通信模式。強調一句話:要區分串口通信和串列通信,串口通信在Arduino 上就是指USART模式,USART是串列通信的一種;而我們通常說到串列通信,是和並行通信區別的,它們倆都是一個大類的總稱,串列通信有好幾種:SPI,I2C,USART,USB,UART等。他們之間是有一些區別的,具體的區別在下一篇會講解。

介紹完MPU6050的引腳,下面看看MPU6050和Arduino怎麼連接。正如前面所講,我們採用了USART模式來讓MPU6050和Arduino通信,所以我們採用左側的四個引腳。如圖3所示,VCC接Arduino的3.5V或者5V引腳,因為MPU6050的供電電壓位3~6V,所以Arduino上面3.3V或者5V兩個引腳都可以用來給MPU6050供電;GND當然和Arduino的 GND相連了;MPU6050的TX要和Arduino的RX相連,因為TX是輸出信號端,RX是輸入信號端,由於MPU6050要將它測量到的數據傳送給Arduino使用,所以要將MPU6050的輸出端和Arduino的輸入端連接;同理,Arduino的TX端要和 MPU6050的RX端鏈接,這是因為有時候Arduino需要回傳一些信息給MPU6050,不過在圖3中可以看到我沒有把Arduino的TX端和MPU6050的RX端相連,這個我要解釋一下。是這樣:由於在我的四軸無人機上,我不需要Arduino回傳信息給MPU6050,相反,我需要Arduino隨時隨地將MPU6050傳送給它的數據反饋到電腦上的串口監控窗口,以便於我隨時監測數據是否正確,但是Arduino UNO只有一個硬串口(TX,RX為一組硬串口),假如全被MPU6050佔用了就不能再和電腦連接,所以我不將Arduino的TX接到MPU6050的RX上。相反,我會通過USB線,將Arduino上的TX介面和電腦連接,那麼這樣一來我就可以通過Arduino上的TX介面把數據傳到電腦上了,這樣我就可以在電腦上監視MPU6050的測量數據。連接原理見圖4,圖4是我使用的正式接法,MPU6050的數據通過TX--RXD傳給Arduino,Arduino通過TXD將數據傳給USB介面,USB介面連接到電腦上把數據發送到串口監控窗口上。

圖3

圖4

上面講解的就是MPU6050和Arduino的連接方式,下面簡單介紹一下MPU6050的一些作用原理。MPU6050最常用的數據信息就是加速度和角速度,通過這兩者又能解算出X-Y-Z的偏轉角。圖4中右上角可以看到有一個小坐標系,根據這個小坐標系可知,將MPU6050按照圖4所示擺放好以後,水平向右為X軸正方向,向上為Y軸正方向,沿紙面向外為Z軸正方向。下面分別介紹下角速度和加速度的相關知識。

角速度---角速度符號定義規則:從原點往某一軸正向看去,順時針為正,逆時針為負。偏轉角的符號定義同角速度是一致的。角速度的單位是度/秒,這裡的度是指角度不是指弧度。

加速度---加速度的單位很簡單,正負號就按照X-Y-Z軸正方向而定。對於加速度需要講解的是它的測量原理。如圖5所示,我們可以假想有一個球在MPU6050裡面,它可以在X,Y,Z三個軸的方向上自由滑動;當MPU6050往X軸正向加速時,假想球會向X軸負向偏移,事實上MPU6050的加速度數值就是按照這個假想球的偏移量坐標取反得到的。也就是說當假想球往X軸負向偏移10個單位時,MPU6050測出來的加速度就應該是X正方向10個單位。來了解到這一個原理後,我們可以想像一下這麼一個場景:當MPU6050處於圖4中那樣的水平靜止狀態時,這個假想球會由於重力的作用向下偏移,也就是向Z軸負向偏移,那麼此時MPU6050將會有一個Z軸正向的讀數,且這個讀數就是9.8米/ s^2。所以我們要知道這樣一個事實,在如圖4所示的水平靜止狀態下,MPU6050並不是讀數為0,而是有一個Z向且數值為9.8米/ s^2的讀數,代表了此時MPU6050所受到的重力加速度。更進一步可以知道,當MPU6050處於傾斜靜止狀態時,假想球將不沿著Z軸偏移,此時MPU6050所測出來的Z方向加速度將不再是9.8米/ s^2,如圖6所示,此時的Z向加速度將是9.8 x cos(Z軸傾斜角),當Z軸水平時即傾斜角達到90度時,Z向加速度讀數將變為0。

圖5

圖6

好了,講到這裡關於MPU6050的硬體部分需要了解的知識基本講完了。接下來就是軟體部分了,軟體部分又分為兩部分:數據結構和驅動程序。

首先要講數據結構,因為只有在了解了MPU6050內部數據結構以後,才能夠編寫驅動程序來讀取數據。什麼是數據結構呢?數據結構這個詞我們經常看到,這裡我就用我自己的個人語言來解釋一下,可能不嚴謹不過差不多應該能讓你明白個大概。所謂數據結構,就是這些數據是以什麼樣的方式存儲和組織在一起的。舉個通俗的例子,比如我們現在要統計一個班級里小朋友的健康程度,那麼通常我們會搜集小朋友們的身高,體重,年紀,姓名,性別等等,那麼這些數據在搜集完畢以後肯定需要整理出來吧?比如我們要給當地教育局領導進行彙報什麼的,那麼我們不可能拿著一堆寫滿數據的草稿甩給領導,說您看著得嘞!這領導絕對立刻馬上炒你魷魚。那麼該怎麼做呢?是個人都懂,要製作一張表格,把數據清清楚楚統計到表格里,為啥要這樣做呢?為的是能夠讓領導輕鬆地從表格中獲取到他們想要的信息。那麼好,其實製作表格這個過程也就是我們把數據結構化的過程,而結構化的目的也是想讓使用者能夠讀取這些數據並獲得信息,試想一堆亂七八糟的草稿怎麼能夠讓使用者提取出有效信息呢?所以這個表格就叫數據結構,按照表格排布的數據就是結構化的數據。另外表格形式可以各種各樣,所以數據結構也有很多種,但是一個不變的宗旨就是:只有結構化的數據,才具有可讀性,才能從中挖掘出信息和價值。就像我們工作中一樣,只有整理成表格或者報告的數據,才讓人讀得懂。這也是為什麼目前很火的「大數據」,它有大量的數據清洗工作需要做,所謂數據清洗,無非也就是把有用的數據挑出來,並結構化的過程。

好,接下來我們看一下MPU6050的數據結構是怎麼樣。見圖7,看過第4篇文章的朋友應該會對圖7非常熟悉,圖中所示就是MPU6050一幀數據的數據結構。MPU6050傳輸的一幀數據有11個位元組,這11個位元組組成了一個數組 Re_buff,Re_buff是數組名;有的朋友可能會迷糊,怎麼又是幀又是數組的,其實這些都只是對於數據結構的稱呼而已,換句話說圖7中一幀數據是由一個名叫Re_buff的數組構成的,這個數組可存放11個位元組,然後MPU6050一次發送這樣的一幀數據。下面具體講講其中細節,數組中的第0位數據為包頭,其實就是第4篇文章中講過的幀頭,它的作用是用來識別一幀數據開始位置的,也就是說一旦Arduino讀到0x55這個信息時,它就知道接下來是一幀新的數據發過來了;數組中的第1位數據是一個識別位,通過這個數據Arduino可以獲知這一幀數據是加速度,角速度還是偏轉角,圖7中0x51代表著一幀數據是加速度數據,如果是0x52則代表是角速度數據,如果是0x53則代表是偏轉角數據;數組中的第2到第9位數據依次是X軸加速度,Y軸加速度,Z軸加速度,溫度數據,這幾位是我們真正關心的數據,也是我們真正要從MPU6050中取出來的數據;數組的最後一位即第10位是「校驗和」,它的作用是檢查數據發送是否完整,同時也作為這一幀數據的幀尾,當Arduino讀到「校驗和」這一位後,它就知道這一幀數據傳輸結束了。這就是MPU6050的數據結構;角速度,偏轉角和加速度的數據結構是一致的。

圖7

圖8

通過上面的講解,大家應該對MPU6050的數據結構有了一定了解,下面就可以介紹驅動程序了,簡單地講就是怎麼讓Arduino和MPU6050通信,進而讀取出MPU6050的數據。見圖8,整個程序分為四大部分:定義變數,setup主程序,loop主程序,serialEvent程序。這裡有兩點要說明:

1.為什麼圖8中程序里沒有頭文件?看過上一篇NRF24L01的朋友可能會問這個問題。在NRF24L01一篇中,我們講到過各種模塊都可以在網上找到別人寫好的庫文件,在下載這些庫文件以後,我們可以很方便地操控這些模塊;這些庫文件通常會通過#include ******這個命令作為頭文件加入到程序里,NRF24L01一篇中就是如此。那麼本篇中圖8的程序里怎麼沒有調用相關的頭文件呢?是這樣,前面我們說過MPU6050由兩套通信模式:USART和I2C,這兩套模式你可以自己任意選擇,那麼在這裡我選擇了USART模式。對於USART模式是不需要上網尋找別人的庫來用的,可以直接進行數據的讀取;而如果你選用了I2C模式的話,那麼你就需要上網尋找別人的庫文件,來幫助你操控MPU6050模塊了。

2.為什麼MPU6050多出來一個serialEvent()函數?serialEvent()函數在Arduino裡面是一個偽中斷函數,關於中斷和偽中斷我在後續會專門開一篇來講解,這裡我就先簡單介紹一下serialEvent()在這裡的作用,我們知道loop()函數是一個不斷循環的函數,而serialEvent()函數一般是在兩個loop()函數之間被調用的,也就是說當loop()函數運行完一次後,在下一次循環運行前serialEvent()函數會被調用;但是還有一個條件,那就是串口寄存器中要有信息輸入。總結一下來說,serialEvent()函數被調用需要同時滿足兩個條件:1.在上一個loop()函數運行完畢,下一個loop()函數還沒開始前;2.串口寄存器中要有信息傳入。好像還是有點繞是吧?好的,那我用更通俗的說法來講一下到底serialEvent()函數是怎麼運行的:首先,假設我們正在運行loop()函數,當loop()函數運行完畢一次以後,這時候Arduino會去查看一下它的串口寄存器(關於串口寄存器在第4篇中有講解),假如串口寄存器中沒有數據傳入的話,那麼Arduino就會直接開始下一個loop()函數的運行,但是如果Arduino發現串口寄存器中有數據傳入的話,它就會暫時不開始下一次loop()函數運行而去運行serialEvent()函數,在serialEvent()函數運行完畢後再開始下一次的loop()函數運行。所以在圖8的程序中,serialEvent()函數的作用就是監視串口寄存器是否有數據傳入,這裡講的數據其實就是MPU6050傳過來的數據,一旦發現有數據傳入,那麼Arduino就會在兩次loop()函數的運行中間間隔期間,通過調用serialEvent()函數來讀取MPU6050傳入的數據。這就是serialEvent()在圖8程序中的作用。此外,正由於serialEvent()函數是在兩次loop()函數運行間隔期間才運行的,而不是隨時隨地可以運行的,所以serialEvent()被稱為偽中斷,而真正的中斷時隨時可跳轉運行的。

下面針對圖8中的4大部分分別講解一下其功能:

定義變數---這部分很好理解,就是定義一些後面需要用到的變數;

setup()函數---這部分也是老樣子,定義Arduino的一些設置,不過在這裡我們只需要定義一個波特率,由於MPU6050默認的波特率是115200,所以我們也需要將Arduino的波特率定義為115200。這樣Arduino才能和MPU6050正常通話。多說幾句話,同步串列通信一般不需要設定波特率,因為它們有時鐘線來協調發送端和接收端,非同步串列通信沒有時鐘線所以一般需要設定波特率來協調收發,且發送端和接收端的波特率要一致,MPU6050的USART就是一種非同步串列通信,而SPI和I2C時同步串列通信,所以MPU6050一定要設定波特率並且要按照MPU6050模塊的設定來決定Arduino程序中的波特率,不能隨意選擇。細心的朋友可能會問:「你說SPI是同步串列通信,不需要設定波特率,那麼上一篇的NRF24L01就是SPI,為什麼上一篇中依然設定了波特率呢?」是這樣:「在我的這幾篇文章中,大家會發現不論是什麼模塊,我都會用Arduino的串口監控窗口來監控數據傳輸,所以上一篇中NRF24L01中的波特率是我設定來將NRF24L01的數據通過Arduino上傳給電腦時用的,由於Arduino和電腦的通信是USART模式,所以需要設定波特率,更進一步說在上一篇中如果我不打算通過串口監控窗口監視數據而僅僅讓發送和接收兩片NRF24L01之間相互通信的話就不需要設定波特率。MPU6050不一樣,不管我是否想通過電腦來監控,Arduino和MPU6050之間的通信都必須要設定波特率,並且必須是115200(MPU6050的默認波特率,也可以切換成9600),因為我用的也是USART模式,當然假如你在使用MPU6050的時候採用了I2C模式來通信,那麼此時就和NRF24L01一樣不需要設定波特率了。

loop()函數---其作用就是不斷地將Re_buff[]數組中的信息換算成加速度,角速度或者偏轉角。其中分為兩大步驟:首先判斷幀頭,即if (Re_buf[0] == 0x55)這句話的作用,它的意思是如果Re_buff[0]這一位的數據是0x55的話,那麼說明接下來是一幀新的數據,這就是幀頭的作用前面我們講過的;其次是按條件進行數據計算,即switch(Re_buff[1])...case(0x51)...case(0x52)...case(0x53)...這一組判斷語句,它的意思是對於Re_buff[1] 這一位的數據進行分類討論,假如Re_buff[1]的數據是0x51的話,說明後面的這一幀數據是加速度那麼就運行case(0x51)後面的語句,假如是0x52則是角速度那麼就運行case(0x52)後面的語句,假如是0x53則是偏轉角那麼就運行(0x53)後面的語句;這些也在本文最開頭涉及過。以加速度數據為例,case(0x51)語句後面的計算方式是:

a[0] = (short(Re_buf [3] << 8 | Re_buf [2])) / 32768.0 * 16;

a[1] = (short(Re_buf [5] << 8 | Re_buf [4])) / 32768.0 * 16;

a[2] = (short(Re_buf [7] << 8 | Re_buf [6])) / 32768.0 * 16;

T = (short(Re_buf [9] << 8 | Re_buf [8])) / 340.0 + 36.25;

break;

這幾句話表達的是如何計算出a[0],a[1],a[2]和T這四個數據,a[0]代表的就是X方向加速度,a[1]代表的就是Y方向加速度,a[2]代表的就是Z方向的加速度,它們的計算公式也就是等號後面寫的那些式子。這些式子怎麼來的呢?其實都是MPU6050的說明手冊上提供的,只要你從網上買了一款模塊,一般賣家都會提供相關說明資料的下載,所以你大可不必擔心。最後的那句break意思是跳出當前switch判斷語句,繼續運行後面的程序。你可以看到每一個case分支的最後都有一個break語句,所以switch...case1...case2...case3隻會運行一個case,一旦運行完就會跳出switch繼續運行後面的Serial.print()函數。後面那一大堆的Serial.print()都是用來顯示數據用的,它會將加速度,角速度,偏轉角數據都傳遞到Arduino的串口監控窗口上,讓你能夠實時監控數據的正確性。

serialEvent()---這個函數按照前面講的,在同時滿足兩個條件時會被調用:1.兩次loop()函數循環的中間間隔時期;2.串口寄存器內有數據傳入。所以其實它實現的是這樣一個功能:一旦MPU6050將數據傳入Arduino的串口寄存器中時,Arduino就會通過調用serialEvent()來讀取這些數據,並將這些數據存入Re_buff數組中,然後在接下來的loop循環中由switch...case...case...case對Re_buff數據中的數據進行處理。

好了,這一講到此為止,其中有一些地方如果展開太多則會跑題所以可能不夠細緻,在下一講和下下一講我會專門講解這些不清晰的地方,下一講打算對串列通信的概念進行總結,因為在寫文章的過程中我注意到這其中有很多容易混淆的概念,如:串列通信,串口通信,USART,SPI,USB,I2C;他們之間的關係,區別等等等。下下一講打算講一下中斷和偽中斷,主要也是對本篇中serialEvent()函數的補充講解,因為對於忽然冒出來的serialEvent()函數,可能不少朋友會比較疑惑。


推薦閱讀:

TAG:Arduino | 計算機科學 | 科技 |