一、熟悉開發環境

首先拿到Linkit 7697 + 開發版。

根據聯發科教學網站上面的指引,使用Arduino IDE來開發系統。

基本的Arduino IDE的安裝以及增加支援Linkit 7697的支援都非常簡單,按照「LinkIt 7697 – Arduino IDE 開發指南」的說明應該可以很快的安裝好並且完成執行第一個程式Blink範例練習。

如果沒有辦法正確的讓USR LED每秒中閃爍一次,那麼就按照程序重新操作一次。如果連續操作三次都沒有能夠正確地執行,若不是硬體出現了問題就是你沒那個天分,這個行業不適合你,應該考慮其他的專題或專業。

操作有正確的結果必須是必然的,然後我們需要觀察與思考的是Log裡面傳遞的訊息(下圖黑色部分),並且盡量要去理解這些訊息的內容。

一般人做到這裡可能會停下來,不過我會建議你們在這個階段多做一些測試讓自己更理解這個系統,所以我加上一些程式碼來幫助理解

/*
  Blink
  Turns an LED on for one second, then off for one second, repeatedly.
*/

long count;  // 加入一個計數器
// the setup function runs once when you press reset or power the board
void setup() {
  // initialize serial communication
  // (38400 chosen because it works as well at 8MHz as it does at 16MHz, but
  // it's really up to you depending on your project)
  Serial.begin(38400);
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  Serial.println("initialize digital pin LED_BUILTIN as an output. In setup()");
  count = 0;
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
  Serial.print("turn the LED off, the ");
  Serial.print(++count);
  Serial.println(" times)");
}

打開Arduino IDE的【工具】–> 【序列埠監控視窗】來觀看結果。重新上傳程式碼到7697,同時在觀察結果時按下7697板子上的RST按鈕來觀察程式輸出內容

這裡是為了讓自己熟悉7697上面的按鈕功能,雖然有一些功能在文件裡可以找到,但是對於初學者來說或者很久沒用的人來說,自行設計一些測試是有必要的。

關於硬體方面,網路上也有資料可以找,在設計測試時其實有先看過硬體說明文件。

LinkIt 7697 HDK v1.0 User’s Guide

RST button: Resets the chipset and is connected to the RST pin. While it’s pressed, it will be linked to the
GND.
USR button: A button used for user-defined behavior. It’s connected to P6 pin (GPIO37). When it’s
pressed, it will be linked to the 3V3 source.

LinkIt 7697 腳位的初始狀態

到這裡,對於開發環境以及相關硬體的熟悉告一個段落。

二、使用Grove – IMU 10DOF v2.0

This module is base on MPU-9250 and BMP280, the MPU-9250 is a 9-axis MotionTracking device that combines a 3-axis gyroscope, 3-axis accelerometer, 3-axis magnetometer and a Digital Motion Processor(DMP), and BMP280 is a high precision, ultra-low power digital pressure sensors for consumer applications.

Specifications


  • I2C Grove interface, include GND, VCC, SDA, SCL.
  • MPU-9250 I2C address selectable
  • Low Power Consumption
  • 400kHz Fast Mode I2C for communicating with all registers
  • Digital-output X-, Y-, and Z-Axis angular rate sensors (gyroscopes) with a user-programmable full-scale range of ±250, ±500, ±1000, and ±2000°/sec
  • Digital-output 3-Axis accelerometer with a programmable full scale range of ±2g, ±4g, ±8g and ±16g
  • Digital-output magnetometer with a full scale range of ±4800uT
  • Digital-output barometer with range of 300 ~ 1100hPa(+9000m ~ -500m relating to sea level)
  • Dimensions: 25.43mm x 20.35mm

先把規格查出來,當需要使用的時候再進一步理解。

其中看到accelerometer裡面的規格有一項full scale range,理解的方式就是直接使用accelerometer + full scale range去google,然後就找到

Accelerometer Terminology Guide  和 Accelerometer Specifications – Quick Definitions 這些資料,從裡面可以看到它的定義就是可以正確表示地輸入加速度的範圍。然後我們就可以估算在專案中最大可能的g值會是多少,然後在加速度計初始化的時候加以設定,預設值是±2g,適用於大部份的專案。

另外就是關於I2C的位址的定義,規格書裡面也有標示出來:

  • MPU-9250 I2C address select Pad, default connected a and b address is 0x68, if connect b and c address is 0x69
  • The MPU9250 always acts as a slave when communicating to the system processor. The LSB of the of the I2C slave address is set by pin 9 (AD0). 預設值是0x68,若要改成0x69則把pin9接到High。

上面的規格裡面也有提到,I2C address selectable,在MPU9250裡面就只用兩個,0x68和0x69,使用pin9來調整。這裡就想到如果在一個應用裡面需要接兩顆MPU-9250在同一個I2C介面上須要如何處理,這邊有些討論串可以參考。(PS: 這裡的Pin9是MPU-9250上面的Pin9,而不是Linkit7697上面的pin9)

最後我們從規格書裡面 MPU-9250 Product Specification 可以看到MPU 9250內建一個數位運動處理引擎DMPTM ,可以用來做手勢偵測,以及一個計步器功能可以讓當主要處理器在睡眠狀態時持續的進行計步的計算。

2.5 MotionProcessing:

• Internal Digital Motion Processing™ (DMP™) engine supports advanced MotionProcessing and low power functions such as gesture recognition using programmable interrupts
• Low-power pedometer functionality allows the host processor to sleep while the DMP maintains the step count.

其他規格書中可能會用到的資訊

  1. MPU-9250 features three 16-bit analog-to-digital converters (ADCs) for digitizing the outputs.
  2. Communication with all registers of the device is performed using either I2C at 400kHz or SPI at 1MHz. For applications requiring faster communications, the sensor and interrupt registers may be read using SPI at 20MHz.

Note: 經過幾天在網路上找IMU 10DOF V2.0和MPU 9250的資料,發現會有版本散亂的問題,像是Grove網站上下載到的MPU-9250 datasheet是1.0版,2014年;然而InveSense網站上已經更新到1.1版,2016年。另外一份資料MPU-9250 Register Map也是一樣,網路上有很多舊版本,但目前最新式1.6版,2015年。

三、IMU 10DOF v2.0程式開發一:範例程式測試

根據教學網頁內容去下載測試程式,網頁上提供下載的程式範例是IMU 9DOF v2.0,但同時也給了IMU 10DOF v2.0的連結,因此下載時要稍微注意一下下載的版本,同時也要注意相關的函式庫設定。

下載好IMU_10DOF_V2_Test這支程式,在沒接上IMU 10DOF V2.0這個模組時還是能夠執行,而輸出結果如下

可以看到,MPU9250 connection failed的訊息,然後後面的程式碼還是繼續執行。然後我在程式執行過程中將IMU 10DOF V2.0模組接上7697,系統就自動偵測到模組,同時讀取到感測器的數值,並將它印出。

我們不應該覺得這樣是理所當然的,一定有硬體或是軟體的支援才有這樣的效果,這會是之後需要弄清楚的細節。基本上的原則就是在學習的過程中不斷的自己找狀況測試系統,然後想辦法理解系統為何如此反應的原因。只有透過這樣的學習才能夠徹底的理解一顆晶片進而理解嵌入式系統設計的方法。

然後我們再看一下這些數字有沒有哪裡不對的,目前只能看到Temperature的部分是0.00deg C。印象中,MPU 9250沒有溫度感測Sensor,所以為何有這項輸出會是我們等一下check程式碼時需要留意的。其他數字目前無法判定是否正確。

首先,我們去看函式庫的API,也就是MPU9250.h和I2CDev.h這兩個檔案。在C語言裡面,標頭檔(.h)就是定義整個程式功能的API (Application Programming Interface,應用程式介面)。所以MPU9250.h裡面就會定義MPU9250這個晶片向使用者開放的所有功能。因此大部分的時候應用程式設計師是不想去看硬體的spec的,功力夠只要看API文件就可以很好的掌控一顆晶片。

至於MPU9250.c檔,裡面是這些功能的實作。如果沒有必要是不需要去專研它。在一些產品中,企業只向用戶開放API (就是.h檔),而實作的部分(.c檔)會當作公司機密保護起來。

MPU9250.h中幾個目前需要的重點整理一下:

1. 裝置位址,目前預設值為0x68

#define MPU9250_ADDRESS_AD0_LOW     0x68 // address pin low (GND), default for InvenSense evaluation board
#define MPU9250_ADDRESS_AD0_HIGH    0x69 // address pin high (VCC)
#define MPU9250_DEFAULT_ADDRESS     MPU9250_ADDRESS_AD0_LOW

2. 內部暫存器位址,數量比較多,如果用手算大約113個位址,然而使用一隻名為i2c_scanner的程式碼掃描,會回傳126個位址。因為I2C的位址只有7個位元,所以126個位置基本上就是全滿了,而126的輸出是因為程式撰寫的關係 for (address = 1; address <127; address++),這個疑問因為現在並沒有很迫切需要處理,留待以後看看是哪裡的問題。

3. 與三軸感測相關的函式,其實其他的也很重要,基本上就是瀏覽一下所有API提供的函式

// ACCEL_CONFIG register
bool getAccelXSelfTest();
void setAccelXSelfTest(bool enabled);
bool getAccelYSelfTest();
void setAccelYSelfTest(bool enabled);
bool getAccelZSelfTest();
void setAccelZSelfTest(bool enabled);
uint8_t getFullScaleAccelRange();
void setFullScaleAccelRange(uint8_t range);
uint8_t getDHPFMode();
void setDHPFMode(uint8_t mode);

// ACCEL_*OUT_* registers
void getMotion9(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz, int16_t* mx, int16_t* my, int16_t* mz);
void getMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz);
void getAcceleration(int16_t* x, int16_t* y, int16_t* z);
int16_t getAccelerationX();
int16_t getAccelerationY();
int16_t getAccelerationZ();

 

四、IMU 10DOF v2.0程式開發二:使用自己的專案與程式測試

要理解程式功能看範例程式碼是OK的,但是當要測試時,範例程式碼的內容太多了,會影響判斷。另外,我們最後會需要一個獨立的系統,專案系統和程式範例是有差距的,這邊我們直接開新專案來處理

直接選【檔案】–>【新增】,建立新的草稿碼,然後儲存檔案,這時候請留意一下儲存的目錄位置,可以幫自己的專案開一個目錄存放。

然後將IMU_10DOF_V2_Test裡面需要測試的檔案拷貝過來,以最小拷貝為原則,現階段需要測試或實做的才拷貝。

第一次寫出來的程式碼如下,我把註解都刪光光讓程式碼簡潔一些:

#define _DEBUG   // <-- 自定義的巨集

#include "Wire.h"

#include "MPU9250.h"
#include "BMP280.h"

MPU9250 accelgyro;
BMP280 bmp280;

void setup() {
    Wire.begin();

    #ifdef _DEBUG    // <-- 一個很簡單的方式,當有定義這個巨集的時候才將程式碼納入編譯,用來區分debug版本和release版本。
      Serial.begin(38400);
    #endif

    // initialize device
    #ifdef _DEBUG
      Serial.println("Initializing I2C devices...");
    #endif
    accelgyro.initialize();
    bmp280.init();

    // verify connection
    bool check_connection = accelgyro.testConnection();
    #ifdef _DEBUG
      Serial.println("Testing device connections...");
      if (check_connection)
        Serial.println("MPU9250 connection successful");
      else
        Serial.println("MPU9250 connection failed");
    #endif

    delay(1000);  
}

void loop() {
  // put your main code here, to run repeatedly:

}

這邊測試的重點是,學習如何建立自己的專案,撰寫debug版本和release版本的程式碼。然後確認函式庫的使用沒有問題,如果發現編譯有錯誤大概都是函式庫的位置有問題,沒有正確的import或是放置在適當的目錄下。我們稍後還會學習如果需要修改函式庫內容時如何區分新的函式庫和舊的函式庫。

輸出結果

Initializing I2C devices...
Testing device connections...
MPU9250 connection failed

檢查細節,22行呼叫了MPU9250的初始化,去看一下他做了什麼事。這邊提醒一下,在第8行是變數宣告,所以accelgyro這個變數是一個MPU9250的物件,檢查initialize()的方法應該在MPU9250.c裡面找。

/** Power on and prepare for general usage.
 * This will activate the device and take it out of sleep mode (which must be done
 * after start-up). This function also sets both the accelerometer and the gyroscope
 * to their most sensitive settings, namely +/- 2g and +/- 250 degrees/sec, and sets
 * the clock source to use the X Gyro for reference, which is slightly better than
 * the default internal clock source.
 */
void MPU9250::initialize() {
    setClockSource(MPU9250_CLOCK_PLL_XGYRO);
    setFullScaleGyroRange(MPU9250_GYRO_FS_250);
    setFullScaleAccelRange(MPU9250_ACCEL_FS_2);
    setSleepEnabled(false); // thanks to Jack Elston for pointing this one out!
}

這裡設定四個參數,對於三軸來說,設定full scale range為±2g。目前先使用預設值。

26行檢查連線是否建立,這裡的程式碼回傳False,所以需要進一步檢查。

/** Verify the I2C connection.
 * Make sure the device is connected and responds as expected.
 * @return True if connection is valid, false otherwise
 */
bool MPU9250::testConnection() {
    return getDeviceID() == 0x71;
}

然後

// WHO_AM_I register

/** Get Device ID.
 * This register is used to verify the identity of the device (0b110100, 0x34).
 * @return Device ID (6 bits only! should be 0x34)
 * @see MPU9250_RA_WHO_AM_I
 * @see MPU9250_WHO_AM_I_BIT
 * @see MPU9250_WHO_AM_I_LENGTH
 */
uint8_t MPU9250::getDeviceID() {
    I2Cdev::readBits(devAddr, MPU9250_RA_WHO_AM_I, MPU9250_WHO_AM_I_BIT, MPU9250_WHO_AM_I_LENGTH, buffer);
    return buffer[0];
}
/** Read multiple bits from an 8-bit device register.
 * @param devAddr I2C slave device address
 * @param regAddr Register regAddr to read from
 * @param bitStart First bit position to read (0-7)
 * @param length Number of bits to read (not more than 8)
 * @param data Container for right-aligned value (i.e. '101' read from any bitStart position will equal 0x05)
 * @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
 * @return Status of read operation (true = success)
 */
int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout) {
    // 01101001 read byte
    // 76543210 bit numbers
    //    xxx   args: bitStart=4, length=3
    //    010   masked
    //   -> 010 shifted
    uint8_t count, b;
    if ((count = readByte(devAddr, regAddr, &b, timeout)) != 0) {
        uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
        b &= mask;
        b >>= (bitStart - length + 1);
        *data = b;
    }
    return count;
}

在I2Cdev::readBits()這裡需要輸入一個devAddr,定義在MPU9250的建構子裡面

/** Default constructor, uses default I2C address.
 * @see MPU9250_DEFAULT_ADDRESS
 */
MPU9250::MPU9250() {
    devAddr = MPU9250_DEFAULT_ADDRESS;
}

這個位址在前面有提過是0x68。然後一路追

int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout) {
    return readBytes(devAddr, regAddr, 1, data, timeout);
}
/** Read multiple bytes from an 8-bit device register.
 * @param devAddr I2C slave device address
 * @param regAddr First register regAddr to read from
 * @param length Number of bytes to read
 * @param data Buffer to store read data in
 * @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
 * @return Number of bytes read (-1 indicates failure)
 */
int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout) {
    #ifdef I2CDEV_SERIAL_DEBUG
        Serial.print("I2C (0x");
        Serial.print(devAddr, HEX);
        Serial.print(") reading ");
        Serial.print(length, DEC);
        Serial.print(" bytes from 0x");
        Serial.print(regAddr, HEX);
        Serial.print("...");
    #endif

    int8_t count = 0;
    uint32_t t1 = millis();

    #if (I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE)

        #if (ARDUINO < 100)
            // Arduino v00xx (before v1.0), Wire library

            // I2C/TWI subsystem uses internal buffer that breaks with large data requests
            // so if user requests more than BUFFER_LENGTH bytes, we have to do it in
            // smaller chunks instead of all at once
            for (uint8_t k = 0; k < length; k += min(length, BUFFER_LENGTH)) {
                Wire.beginTransmission(devAddr);
                Wire.send(regAddr);
                Wire.endTransmission();
                Wire.beginTransmission(devAddr);
                Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));

                for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
                    data[count] = Wire.receive();
                    #ifdef I2CDEV_SERIAL_DEBUG
                        Serial.print(data[count], HEX);
                        if (count + 1 < length) Serial.print(" ");
                    #endif
                }

                Wire.endTransmission();
            }
        #elif (ARDUINO == 100)
            // Arduino v1.0.0, Wire library
            // Adds standardized write() and read() stream methods instead of send() and receive()

            // I2C/TWI subsystem uses internal buffer that breaks with large data requests
            // so if user requests more than BUFFER_LENGTH bytes, we have to do it in
            // smaller chunks instead of all at once
            for (uint8_t k = 0; k < length; k += min(length, BUFFER_LENGTH)) {
                Wire.beginTransmission(devAddr);
                Wire.write(regAddr);
                Wire.endTransmission();
                Wire.beginTransmission(devAddr);
                Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
        
                for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
                    data[count] = Wire.read();
                    #ifdef I2CDEV_SERIAL_DEBUG
                        Serial.print(data[count], HEX);
                        if (count + 1 < length) Serial.print(" ");
                    #endif
                }
        
                Wire.endTransmission();
            }
        #elif (ARDUINO > 100)
            // Arduino v1.0.1+, Wire library
            // Adds official support for repeated start condition, yay!

            // I2C/TWI subsystem uses internal buffer that breaks with large data requests
            // so if user requests more than BUFFER_LENGTH bytes, we have to do it in
            // smaller chunks instead of all at once
            for (uint8_t k = 0; k < length; k += min(length, BUFFER_LENGTH)) {
                Wire.beginTransmission(devAddr);
                Wire.write(regAddr);
                Wire.endTransmission();
                Wire.beginTransmission(devAddr);
                Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
        
                for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
                    data[count] = Wire.read();
                    #ifdef I2CDEV_SERIAL_DEBUG
                        Serial.print(data[count], HEX);
                        if (count + 1 < length) Serial.print(" ");
                    #endif
                }
        
                Wire.endTransmission();
            }
        #endif

    #elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
        // Fastwire library (STILL UNDER DEVELOPMENT, NON-FUNCTIONAL!)

        // no loop required for fastwire
        uint8_t status = Fastwire::readBuf(devAddr, regAddr, data, length);
        if (status == 0) {
            count = length; // success
        } else {
            count = -1; // error
        }

    #endif

    // check for timeout
    if (timeout > 0 && millis() - t1 >= timeout && count < length) count = -1; // timeout

    #ifdef I2CDEV_SERIAL_DEBUG
        Serial.print(". Done (");
        Serial.print(count, DEC);
        Serial.println(" read).");
    #endif

    return count;
}

這隻程式I2Cdev::readBytes算是主要的執行部分,有幾個點可以觀察,首先他有設定debug mode在第10行,所以等一下我們可以把他打開來檢查程式執行。另外,他針對不同Arduino版本有不同的code,所以我們需要先確認下Linkit7697符合哪一個版本,這可以透過程式碼來幫助。

我們把I2Cdev.h裡面的(line 68)//#define I2CDEV_SERIAL_DEBUG的註解拿掉,另外在每一個function block裡面加上自己的測試碼,類似

#ifdef I2CDEV_SERIAL_DEBUG
    Serial.println("ARDUINO < 100");
#endif

編譯後執行,結果

知道最後這隻程式碼從”I2C (0x68) reading 1 bytes from 0x68…”開始,被執行了9次,其中有5次是執行的程式區塊是ARDUINO > 100這一塊。

整理跟WHO_AM_I相關的定義

#define MPU9250_RA_WHO_AM_I         0x75
#define MPU9250_WHO_AM_I_BIT        6
#define MPU9250_WHO_AM_I_LENGTH     8

對應到getDeviceID()裡面的readBits(),解讀出來的意思是從devAddr這個I2C的裝置裡面的MPU9250_RA_WHO_AM_I (0x75)這個暫存器裡面,從第6個bit開始,讀出8個bits。

其實目前一切看起來都還好,要不是最終顯示出來的結果是”MPU9250 connection failed”,可以省略掉程式碼追蹤。但是反過來說,程式碼追蹤是任何一個程式設計師必須要會的技能。

所以繼續往下:看readBits()的17到21行。先理解一下它的回傳值count其實沒有意義,在這裡沒有被使用。真正的回傳值是data,這是一個call by address的呼叫。

第17行使用readByt()讀出一個byte放到b裡面,這邊用的是call by reference呼叫方式。

18行建立一個遮罩,<<位移運算子1 << length指的是1向左位移8位。位移裡面又分成算數位移和邏輯位移,在這邊是算術位移。

如果看文件不能理解,就寫程式來驗證

void setup() {
  int a,b,i;
  Serial.begin(38400);
  for (i=1; i<11; ++i) {
    a = 1 << i;
    b = 1023 >> i;
    Serial.print("i = ");
    Serial.println(i);
    Serial.print("    a = ");
    Serial.println(a);
    Serial.print("    b = ");
    Serial.println(b);
  }
    delay(1000);  
}

void loop() {
  // put your main code here, to run repeatedly:
}

輸出結果

可以算出mask = (( 1 << 8) – 1) << (6 – 8 + 1) = 127 = 1111111(2)

b &= mask;就是把超過第6個bit以上的清零。(ps: LSB是第0個bit)

b >>= (bitstart-length+1);把剛才位移的部分再移回來。

再回頭看I2Cdev::readBytes()的輸出

I2C (0x68) reading 1 bytes from 0x68...

因為前面定義的#define MPU9250_RA_WHO_AM_I 0x75,所以應該是由ox75開始讀,這裡變成從0x68開始讀,覺得有一點疑惑,所以在每一隻程式裡面再加上偵錯碼來檢查。因為程式碼改得有點多了,所以建議將MPU9250.h這幾個用到的Library變更成為自己的專用函式庫,更名為MY_MPU9250.h等。

然後就發現好玩的東西,原來剛才看到的9次執行I2Cdev::readBytes()其實只有最後一次是在呼叫MPU9250::DeviceID()之後,前面的8次都是系統其他程序的呼叫結果,而且讀到的結果的確是0x71。程式執行結果如下圖,其中”In MPU9250::DeviceID()”是加到MPU9250::DeviceID()裡面的偵錯碼印出的結果。

繼續加入偵測碼回朔看看哪一個環節出錯。

加入的偵錯碼

int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout) {
    uint8_t count, b;
    if ((count = readByte(devAddr, regAddr, &b, timeout)) != 0) {
        #ifdef I2CDEV_SERIAL_DEBUG
            Serial.println("\nI2Cdev::readBits()");
            Serial.print("b (before) = 0x");
            Serial.print(b, HEX);            
        #endif
        uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
        b &= mask;
        b >>= (bitStart - length + 1);
        *data = b;
        #ifdef I2CDEV_SERIAL_DEBUG
            Serial.print("; b (after) = 0x");
            Serial.println(b, HEX);            
        #endif
    }
    return count;
}

輸出結果

現在可以確定是I2Cdev::readBits()把結果修改成為0,目前不懂它的原因,有可能是Linkit7697和Arduino設計上的差異,因為也沒有很重要所以也不想要深究了。

知道原因之後就可以修改程式碼去跳過I2Cdev::readBits(),所以修改MPU9250::DeviceID()

uint8_t MPU9250::getDeviceID() {
    #ifdef I2CDEV_SERIAL_DEBUG
        Serial.println("\nIn MPU9250::getDeviceID()");
        Serial.print("devAddr = ");
        Serial.print(devAddr);
        Serial.print("; MPU9250_RA_WHO_AM_I = ");
        Serial.print(MPU9250_RA_WHO_AM_I);
        Serial.print("; MPU9250_WHO_AM_I_BIT = ");
        Serial.print(MPU9250_WHO_AM_I_BIT);
        Serial.print("; MPU9250_WHO_AM_I_LENGTH = ");
        Serial.println(MPU9250_WHO_AM_I_LENGTH);
    #endif
    // 把舊的註解掉
    //I2Cdev::readBits(devAddr, MPU9250_RA_WHO_AM_I, MPU9250_WHO_AM_I_BIT, MPU9250_WHO_AM_I_LENGTH, buffer);
    // 加上我們修改的
    uint8_t b;
    I2Cdev::readByte(devAddr, MPU9250_RA_WHO_AM_I, &b);
    *buffer = b;
    return buffer[0];
}

輸出結果,可以看到”MPU9250 connection successful”,到這裡結束這段程式碼追蹤。

 

This Post Has 2 Comments

  1. Eric

    您好,我想請問,如果說我想要把 i2cdev 的 clock 變成 400k 請問該怎麼做呢?謝謝,目前角速度的部分會突然數值都完全不更新,上網看好像是 FIFO 滿的關係,因此想讓讀取在快一點!

    1. hhliu

      我用的是MPU9250,他的default值就是400kHz (I2C傳輸速率,不是裝置時脈)。

發佈留言