MPU6050 Проблема с NodeMCU

Я использую MPU6050 как часть IMU моего дрона, и я написал эту программу для Arduino Nano, чтобы получить ориентацию модуля MPU6050 в 3D-пространстве. И это прекрасно работает, но когда я загрузил этот точный код в NodeMCU, вывод начал только увеличиваться, т.е. всякий раз, когда происходит какое-либо изменение ориентации модуля, вывод вместо того, чтобы показывать конкретную ориентацию, увеличивается и на. Не могли бы вы помочь мне с интерфейсом MPU6050 с моим NodeMCU. Я предоставляю программу внизу.

#include <Wire.h>
const int MPU = 0x68; // I2C-адрес MPU6050

char status;                // Объявление всех необходимых переменных
double T, P;
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY, gyroAngleZ;
float roll_sensor, pitch_sensor, yaw_sensor;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY, GyroErrorZ;
float elapsedTime, currentTime, previousTime;
int c = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();                      // Инициализация связи
  Wire.beginTransmission(MPU);       // Начать связь с MPU6050 // MPU=0x68
  Wire.write(0x6B);                  // Обращение к регистру 6B
  Wire.write(0x00);                  // Сделать сброс - поместить 0 в регистр 6B
  Wire.endTransmission(true);        // конец передачи
  calculate_IMU_error();  // Вызовите эту функцию, если вам нужно получить значения ошибок IMU для вашего модуля
  delay(20);
}
void loop() {
  // === Чтение данных ускорителя === //
  Wire.beginTransmission(MPU);
  Wire.write(0x3B); // Начать с регистра 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 14, true); //Чтение всего 6 регистров, значение каждой оси хранится в 2 регистрах

  //Для диапазона +-2g нам нужно разделить необработанные значения на 16384, согласно таблице данных
  AccX = (Wire.read() << 8 | Wire.read()) / 16384.0; // Значение по оси X
  AccY = (Wire.read() << 8 | Wire.read()) / 16384.0; // Значение по оси Y
  AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0; // значение по оси Z

  // Расчет roll_sensor и pitch_sensor по данным акселерометра
  accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * (180 / PI)) - AccErrorX;
  accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * (180 / PI)) - AccErrorY;

  // === Чтение данных гироскопа === //
  previousTime = currentTime;        // Предыдущее время сохраняется перед чтением фактического времени
  currentTime = millis();            // Текущее время фактическое время чтения
  elapsedTime = (currentTime - previousTime) / 1000; // Делим на 1000, чтобы получить секунды
  Wire.beginTransmission(MPU);
  Wire.write(0x43); // Адрес первого регистра данных гироскопа 0x43
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 14, true); //Чтение всего 4 регистра, значение каждой оси хранится в 2 регистрах

  GyroX = (Wire.read() << 8 | Wire.read()) / 131.0; // Для диапазона 250 град/с мы должны сначала разделить необработанное значение на 131,0, согласно техническому описанию
  GyroY = (Wire.read() << 8 | Wire.read()) / 131.0;
  GyroZ = (Wire.read() << 8 | Wire.read()) / 131.0;

  // Исправьте выходные данные с вычисленными значениями ошибки
  GyroX = GyroX - GyroErrorX;
  GyroY = GyroY - GyroErrorY;
  GyroZ = GyroZ - GyroErrorZ;

  // В настоящее время необработанные значения выражены в градусах в секунду, град/с, поэтому нам нужно умножить на сендоны (с), чтобы получить угол в градусах
  gyroAngleX = gyroAngleX + (GyroX * elapsedTime); // град/с * с = град
  gyroAngleY = gyroAngleY + (GyroY * elapsedTime);
  yaw_sensor =  yaw_sensor + (GyroZ * elapsedTime);

  // Дополнительный фильтр - объединить значения акселерометра и угла гироскопа
  roll_sensor = (0.94 * gyroAngleX) + (0.06 * accAngleX);
  pitch_sensor = (0.94 * gyroAngleY) + (0.06 * accAngleY);
  

  // Печатаем значения на последовательном мониторе
  Serial.print("                   roll_GY: "); Serial.print(roll_sensor);
  Serial.print("         pitch_GY: "); Serial.print(pitch_sensor);
  Serial.print("         yaw_GY: "); Serial.print(yaw_sensor);
  Serial.println();
  
  
}
void calculate_IMU_error() {
  // Мы можем вызвать эту функцию в разделе настройки, чтобы рассчитать погрешность данных акселерометра и гироскопа. Отсюда мы получим значения ошибок, используемые в приведенных выше уравнениях, напечатанных на последовательном мониторе.
  // Обратите внимание, что мы должны разместить IMU горизонтально, чтобы получить правильные значения, чтобы мы могли затем получить правильные значения
  // Чтение значений акселерометра 300 раз
  while (c < 300) {
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);
    AccX = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
    AccY = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
    AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
    // Суммируем все показания
    AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * (180 / PI)));
    AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * (180 / PI)));
    c++ ;
  }
  // Делим сумму на 200, чтобы получить значение ошибки
  AccErrorX = AccErrorX / 300;
  AccErrorY = AccErrorY / 300;
  c = 0;
  // Чтение значений гироскопа 300 раз
  while (c < 300) {
    Wire.beginTransmission(MPU);
    Wire.write(0x43);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);

    GyroX = Wire.read() << 8 | Wire.read();
    GyroY = Wire.read() << 8 | Wire.read();
    GyroZ = Wire.read() << 8 | Wire.read();

    // Суммируем все показания
    GyroErrorX = GyroErrorX + (GyroX / 131.0);
    GyroErrorY = GyroErrorY + (GyroY / 131.0);
    GyroErrorZ = GyroErrorZ + (GyroZ / 131.0);
    c++;
  }

  // Делим сумму на 300, чтобы получить значение ошибки
  GyroErrorX = GyroErrorX / 300;
  GyroErrorY = GyroErrorY / 300;
  GyroErrorZ = GyroErrorZ / 300;
}

, 👍1

Обсуждение

«выход начал только увеличиваться» - что именно это означает?, @chrisl

О, я прошу прощения за то, что был неконкретен раньше, и я отредактировал это!, @Pritam Sarkar

gyroAngleX никогда не сбрасывается, только добавляется., @dandavis

Нет, все в порядке, так как в зависимости от знака GyroX, GyroY, GyroZ он дает ожидаемые цифры., @Pritam Sarkar


1 ответ


Лучший ответ:

0

Я вижу, что вы читаете 16-битные целые числа из IMU следующим образом:

// не работает на 32-битных системах
void loop() {
    ...
    AccX = (Wire.read() << 8 | Wire.read()) / 16384.0; // Значение по оси X
    AccY = (Wire.read() << 8 | Wire.read()) / 16384.0; // Значение по оси Y
    AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0; // значение по оси Z
    ...
    GyroX = (Wire.read() << 8 | Wire.read()) / 131.0; // ...
    GyroY = (Wire.read() << 8 | Wire.read()) / 131.0;
    GyroZ = (Wire.read() << 8 | Wire.read()) / 131.0;
    ....
}

Хотя слишком часто я вынужден использовать "#ifdef" настроить специфичный для платформы код для каждой платформы, в этом случае я почти уверен, что возможно написать кросс-платформенный совместимый код, который будет работать на обеих платформах; возможно, что-то вроде этого:

// предупреждение: не проверено, но думаю должно работать
int16_t wire_read_16(){
    int16_t value = (Wire.read() << 8 | Wire.read());
    return value;
}

void loop() {
    ...
    AccX = (wire_read_16()) * (1.0f / 16384.0f); // Значение по оси X
    AccY = (wire_read_16()) * (1.0f / 16384.0f); // Значение по оси Y
    AccZ = (wire_read_16()) * (1.0f / 16384.0f); // значение по оси Z
    ...
    GyroX = (wire_read_16()) * (1.0f / 131.0f); // Для диапазона 250 град/с мы должны сначала разделить необработанное значение на 131,0, согласно техническому описанию
    GyroY = (wire_read_16()) * (1.0f / 131.0f);
    GyroZ = (wire_read_16()) * (1.0f / 131.0f);
    ....
}

Я вижу это У onehorse, по-видимому, точно такая же проблема в "определения целочисленных типов в Teensy 3.1"

Похоже, вы наткнулись на особенность языка программирования C, где "int" варьируется от системы к системе. (См. Идо Гендель, "Проблема с целыми числами"). "инт" в некоторых системах (таких как ардуино на базе ATmega) это 16-битное целое число. "инт" в некоторых других системах (таких как ардуино на базе ARM, возможно, включая NodeMCU) это 32-битное целое число. ( inttypes и интегральные типы, определенные Arduino ; "Справочник по Arduino: int" ) В других системах "int"; множество других размеров.

Например, когда ваш IMU пытается отправить вам "-2", он отправляет 0xFFFE. Когда nano помещает это в свой «int»; которое представляет собой 16-битное целое число, оно затем правильно интерпретирует его как значение "-2". Я подозреваю, что происходит то, что когда другая система помещает это значение в свой "int" которое представляет собой 32-битное целое число, которое каким-то образом оказывается содержащим 0x0000FFFE, что означает +65534, но что должно произойти, так это "расширение подписи" так что 32-битное целое число в конечном итоге содержит 0xFFFFFFFE, что означает "-2". Поместив это значение в промежуточное значение "int16_t"; переменная, мы явно сообщаем компилятору, что это 16-битное целое число со знаком, поэтому позже, когда этот компилятор вставляет его в 32-битное целое число, он правильно расширяет знак.

Компилятор C (правильно) обрабатывает целое, разделенное на число с плавающей запятой, путем преобразования обоих значений в числа с плавающей запятой (и правильного расширения знака), а затем деления. То же самое с целым числом, разделенным на двойное число.

,

да! Я решил это, приведя необработанные данные MPU6050 к int16_t как AccelX = (((int16_t)Wire.read()<<8) | Wire.read()), но снова преобладает одна проблема, а именно millis(), которая применима для Arduino Nano, но ее проблематичное взаимодействие с Nodemcu до сих пор мне непонятно, не могли бы вы предложить способ измерения времени выполнения в NodeMCU! Большое спасибо, кстати!, @Pritam Sarkar

https://arduinoprosto.ru/q/56712/nodemcu-millis-simple-counter-how-long-is-led-on отвечает на ваш вопрос? Если нет, не могли бы вы опубликовать новый новый вопрос?, @David Cary