Проблема с пониманием кода (Arduino и MPU-6050)

Тема в разделе "Arduino & Shields", создана пользователем Harvey_Specter, 27 мар 2017.

  1. Всем доброго дня
    Совсем недавно начал работать с платой MPU-6050. Нашёл на просторах интернета работающую программы для чтения значений. Она работает, но хочется разобраться как. Конкретно не ясна работа функции read_mpu_6050_data(). Помогите пожалуйста разобраться)

    void read_mpu_6050_data(){

    Wire.beginTransmission(0x68);
    Wire.write(0x3B);
    Wire.endTransmission();
    Wire.requestFrom(0x68,14);
    while(Wire.available() < 14);
    acc_x = Wire.read()<<8|Wire.read();
    acc_y = Wire.read()<<8|Wire.read();
    acc_z = Wire.read()<<8|Wire.read();
    temperature = Wire.read()<<8|Wire.read();
    gyro_x = Wire.read()<<8|Wire.read();
    gyro_y = Wire.read()<<8|Wire.read();
    gyro_z = Wire.read()<<8|Wire.read();

    }
     
  2. serg_admin

    serg_admin Гик

    Что не ясно?

    Функция работает напрямую с I2C шиной. Устройство на шине с адресом 0x68.
    Остальное нужно смотреть в описании MPU-6050.
     
  3. serg_admin

    serg_admin Гик

    Код (C++):
    oid read_mpu_6050_data(){

    Wire.beginTransmission(0x68);
    Wire.write(0x3B);
    Wire.endTransmission();
    Wire.requestFrom(0x68,14); // Прочитать 14 байт
    while(Wire.available() < 14); // Ждем окончания чтения
    acc_x = Wire.read()<<8|Wire.read(); // Объеденяем два байта в одно двухбайтное значение
    acc_y = Wire.read()<<8|Wire.read();
    acc_z = Wire.read()<<8|Wire.read();
    temperature = Wire.read()<<8|Wire.read();
    gyro_x = Wire.read()<<8|Wire.read();
    gyro_y = Wire.read()<<8|Wire.read();
    gyro_z = Wire.read()<<8|Wire.read();

    }
     
  4. "Объеденяем два байта в одно двухбайтное значение" Вот эта часть и не ясна. Зачем это делать ? И можно ли это сделать как-то по-другому ?
     
  5. serg_admin

    serg_admin Гик

    Двухбайтное число приходит двумя кусками. Мы его собираем

    Как работает часть:
    Код (C++):
    Wire.read()<<8
    Допустим получен байт
    0x58
    Код (C++):
    0x58<<8 = 0x5800
    Вертикальную палочку можно рассматривать как сложение
    Допустим второй байт 0x9A
    Код (C++):
    0x5800 | 0x9A = 0x589A
    Можно переписать так
    Код (C++):
    acc_x = Wire.read()*256 + Wire.read();
    Но я не думаю, что так понятнее.
     
  6. Огромное спасибо, теперь разобрался. А почему датчик не может отправить двухбайтное число сразу, не деля его на две части ?
     
  7. Unixon

    Unixon Оракул Модератор

    Датчик отправляет 16 бит данных, после приема они складываются в буфер с побайтовой организацией памяти, как наиболее универсальной. Wire.read() возвращает данные из приемного буфера поэлементно, т.е. побайтово. Конечно, можно понаписать всяких Wire.readInt16(), Wire.readFloat() и т.п., чтобы не собирать многобайтные типы вручную, но это влияет только на удобство чтения кода. Внутри программы и то и другое просто копирует байты из одного места в другое.
     
  8. Спасибо. Теперь и с этим разобрался. Да и в datasheet'е наконец то нашел нужную строчку)))
    ADC Word Length Output in two’s complement format 16 bits.
     
  9. Появятся ещё вопросы - задам
     
  10. И вот снова проблемы...
    На этот раз вот в этом кусочке:

    //Place the MPU-6050 spirit level and note the values in the following two lines for calibration
    angle_pitch_acc -= 0.0; //Accelerometer calibration value for pitch
    angle_roll_acc -= 0.0; //Accelerometer calibration value for roll

    Разве что-то должно измениться после этих двух строк ?

    if(set_gyro_angles){ //If the IMU is already started
    angle_pitch = angle_pitch * 0.9996 + angle_pitch_acc * 0.0004; //Correct the drift of the gyro pitch angle with the accelerometer pitch angle
    angle_roll = angle_roll * 0.9996 + angle_roll_acc * 0.0004; //Correct the drift of the gyro roll angle with the accelerometer roll angle
    }
    else{ //At first start
    angle_pitch = angle_pitch_acc; //Set the gyro pitch angle equal to the accelerometer pitch angle
    angle_roll = angle_roll_acc; //Set the gyro roll angle equal to the accelerometer roll angle
    set_gyro_angles = true; //Set the IMU started flag
    }

    Здесь не очень понятна часть после else

    //To dampen the pitch and roll angles a complementary filter is used
    angle_pitch_output = angle_pitch_output * 0.9 + angle_pitch * 0.1; //Take 90% of the output pitch value and add 10% of the raw pitch value
    angle_roll_output = angle_roll_output * 0.9 + angle_roll * 0.1; //Take 90% of the output roll value and add 10% of the raw roll value

    А назначение этих двух строк и вовсе тайна для меня. Переменные с приставкой output встречаются только в этих двух строках. Получается это взаимодействие с переменной оставшейся с прошлого цикла ?

    Заранее спасибо за помощь и простите, если вопросы глупые - я не так давно в этом варюсь)
     
  11. А это программа полностью. Не знаю, поможет это или нет)
    Код (C++):
    //Include LCD and I2C library
    #include <LiquidCrystal_I2C.h>
    #include <Wire.h>

    //Declaring some global variables
    int gyro_x, gyro_y, gyro_z;
    long acc_x, acc_y, acc_z, acc_total_vector;
    int temperature;
    long gyro_x_cal, gyro_y_cal, gyro_z_cal;
    long loop_timer;
    int lcd_loop_counter;
    float angle_pitch, angle_roll;
    int angle_pitch_buffer, angle_roll_buffer;
    boolean set_gyro_angles;
    float angle_roll_acc, angle_pitch_acc;
    float angle_pitch_output, angle_roll_output;

    //Initialize the LCD library
    LiquidCrystal_I2C lcd(0x27,16,2);


    void setup() {
      Wire.begin();                                                      
      //Serial.begin(57600);
      pinMode(13, OUTPUT);

      setup_mpu_6050_registers();                                          //Setup the registers of the MPU-6050 (500dfs / +/-8g) and start the gyro

      digitalWrite(13, HIGH);                

      lcd.begin();      
      lcd.backlight();    
      lcd.clear();

      lcd.setCursor(0,0);      
      lcd.print("  MPU-6050 IMU");      
      lcd.setCursor(0,1);      
      lcd.print("     V1.0");  

      delay(1500);    
      lcd.clear();  

      lcd.setCursor(0,0);  
      lcd.print("Calibrating gyro");
      lcd.setCursor(0,1);
      for (int cal_int = 0; cal_int < 2000 ; cal_int ++){                  //Run this code 2000 times
        if(cal_int % 125 == 0)lcd.print(".");                              
        read_mpu_6050_data();                                              //Read the raw acc and gyro data from the MPU-6050
        gyro_x_cal += gyro_x;                                              //Add the gyro x-axis offset to the gyro_x_cal variable
        gyro_y_cal += gyro_y;                                              //Add the gyro y-axis offset to the gyro_y_cal variable
        gyro_z_cal += gyro_z;                                              //Add the gyro z-axis offset to the gyro_z_cal variable
        delay(3);                                                          //Delay 3us to simulate the 250Hz program loop
      }
      gyro_x_cal /= 2000;                                                  //Divide the gyro_x_cal variable by 2000 to get the avarage offset
      gyro_y_cal /= 2000;                                                  //Divide the gyro_y_cal variable by 2000 to get the avarage offset
      gyro_z_cal /= 2000;                                                  //Divide the gyro_z_cal variable by 2000 to get the avarage offset

      lcd.clear();
      lcd.setCursor(0,0);  
      lcd.print("Pitch:");  
      lcd.setCursor(0,1);
      lcd.print("Roll :");

      digitalWrite(13, LOW);      
      loop_timer = micros();  
    }

    void loop(){

      read_mpu_6050_data();                                                //Read the raw acc and gyro data from the MPU-6050

      gyro_x -= gyro_x_cal;                                                //Subtract the offset calibration value from the raw gyro_x value
      gyro_y -= gyro_y_cal;                                                //Subtract the offset calibration value from the raw gyro_y value
      gyro_z -= gyro_z_cal;                                                //Subtract the offset calibration value from the raw gyro_z value

      //Gyro angle calculations
      //0.0000611 = 1 / (250Hz / 65.5)
      angle_pitch += gyro_x * 0.0000611;                                   //Calculate the traveled pitch angle and add this to the angle_pitch variable
      angle_roll += gyro_y * 0.0000611;                                    //Calculate the traveled roll angle and add this to the angle_roll variable

      //0.000001066 = 0.0000611 * (3.142(PI) / 180degr) The Arduino sin function is in radians
      angle_pitch += angle_roll * sin(gyro_z * 0.000001066);               //If the IMU has yawed transfer the roll angle to the pitch angel
      angle_roll -= angle_pitch * sin(gyro_z * 0.000001066);               //If the IMU has yawed transfer the pitch angle to the roll angel

      //Accelerometer angle calculations
      acc_total_vector = sqrt((acc_x*acc_x)+(acc_y*acc_y)+(acc_z*acc_z));  //Calculate the total accelerometer vector
      //57.296 = 1 / (3.142 / 180) The Arduino asin function is in radians
      angle_pitch_acc = asin((float)acc_y/acc_total_vector)* 57.296;       //Calculate the pitch angle
      angle_roll_acc = asin((float)acc_x/acc_total_vector)* -57.296;       //Calculate the roll angle

      //Place the MPU-6050 spirit level and note the values in the following two lines for calibration
      angle_pitch_acc -= 0.0;                                              //Accelerometer calibration value for pitch
      angle_roll_acc -= 0.0;                                               //Accelerometer calibration value for roll

      if(set_gyro_angles){                                                 //If the IMU is already started
        angle_pitch = angle_pitch * 0.9996 + angle_pitch_acc * 0.0004;     //Correct the drift of the gyro pitch angle with the accelerometer pitch angle
        angle_roll = angle_roll * 0.9996 + angle_roll_acc * 0.0004;        //Correct the drift of the gyro roll angle with the accelerometer roll angle
      }
      else{                                                                //At first start
        angle_pitch = angle_pitch_acc;                                     //Set the gyro pitch angle equal to the accelerometer pitch angle
        angle_roll = angle_roll_acc;                                       //Set the gyro roll angle equal to the accelerometer roll angle
        set_gyro_angles = true;                                            //Set the IMU started flag
      }

      //To dampen the pitch and roll angles a complementary filter is used
      angle_pitch_output = angle_pitch_output * 0.9 + angle_pitch * 0.1;   //Take 90% of the output pitch value and add 10% of the raw pitch value
      angle_roll_output = angle_roll_output * 0.9 + angle_roll * 0.1;      //Take 90% of the output roll value and add 10% of the raw roll value

      write_LCD();                                                         //Write the roll and pitch values to the LCD display

      while(micros() - loop_timer < 4000);                                 //Wait until the loop_timer reaches 4000us (250Hz) before starting the next loop
      loop_timer = micros();                                               //Reset the loop timer
    }


    void read_mpu_6050_data(){  
      Wire.beginTransmission(0x68);  
      Wire.write(0x3B);
      Wire.endTransmission();    
      Wire.requestFrom(0x68,14);  
      while(Wire.available() < 14);
      acc_x = Wire.read()<<8|Wire.read();  
      acc_y = Wire.read()<<8|Wire.read();  
      acc_z = Wire.read()<<8|Wire.read();  
      temperature = Wire.read()<<8|Wire.read();
      gyro_x = Wire.read()<<8|Wire.read();
      gyro_y = Wire.read()<<8|Wire.read();
      gyro_z = Wire.read()<<8|Wire.read();

    }

    void write_LCD(){                                                      //Subroutine for writing the LCD
      //To get a 250Hz program loop (4us) it's only possible to write one character per loop
      //Writing multiple characters is taking to much time
      if(lcd_loop_counter == 14)lcd_loop_counter = 0;                      //Reset the counter after 14 characters
      lcd_loop_counter ++;                                                 //Increase the counter
      if(lcd_loop_counter == 1){
        angle_pitch_buffer = angle_pitch_output * 10;                      //Buffer the pitch angle because it will change
        lcd.setCursor(6,0);                                                //Set the LCD cursor to position to position 0,0
      }
      if(lcd_loop_counter == 2){
        if(angle_pitch_buffer < 0)lcd.print("-");                          //Print - if value is negative
        else lcd.print("+");                                               //Print + if value is negative
      }
      if(lcd_loop_counter == 3)lcd.print(abs(angle_pitch_buffer)/1000);    //Print first number
      if(lcd_loop_counter == 4)lcd.print((abs(angle_pitch_buffer)/100)%10);//Print second number
      if(lcd_loop_counter == 5)lcd.print((abs(angle_pitch_buffer)/10)%10); //Print third number
      if(lcd_loop_counter == 6)lcd.print(".");                             //Print decimal point
      if(lcd_loop_counter == 7)lcd.print(abs(angle_pitch_buffer)%10);      //Print decimal number

      if(lcd_loop_counter == 8){
        angle_roll_buffer = angle_roll_output * 10;
        lcd.setCursor(6,1);
      }
      if(lcd_loop_counter == 9){
        if(angle_roll_buffer < 0)lcd.print("-");                           //Print - if value is negative
        else lcd.print("+");                                               //Print + if value is negative
      }
      if(lcd_loop_counter == 10)lcd.print(abs(angle_roll_buffer)/1000);    //Print first number
      if(lcd_loop_counter == 11)lcd.print((abs(angle_roll_buffer)/100)%10);//Print second number
      if(lcd_loop_counter == 12)lcd.print((abs(angle_roll_buffer)/10)%10); //Print third number
      if(lcd_loop_counter == 13)lcd.print(".");                            //Print decimal point
      if(lcd_loop_counter == 14)lcd.print(abs(angle_roll_buffer)%10);      //Print decimal number
    }

    void setup_mpu_6050_registers(){
      //Activate the MPU-6050
      Wire.beginTransmission(0x68);                                        
      Wire.write(0x6B);                                                    
      Wire.write(0x00);                                                  
      Wire.endTransmission();                                              
      //Configure the accelerometer (+/-8g)
      Wire.beginTransmission(0x68);                                      
      Wire.write(0x1C);                                                    
      Wire.write(0x10);                                                    
      Wire.endTransmission();                                              
      //Configure the gyro (500dps full scale)
      Wire.beginTransmission(0x68);                                        
      Wire.write(0x1B);                                                  
      Wire.write(0x08);                                                    
      Wire.endTransmission();                                              
    }
     
  12. zealot01

    zealot01 Нерд

    По виду кода, это инклинометр на основе гироскопа и акселерометра, комплементарный фильтр короче :) Почитай тут http://www.poprobot.ru/theory/complementary_filter.
     
  13. Что это такое и каков принцип работы комплиментарного фильтра я знаю, мне непонятны только те вопросы, которые я задал до программы...