IMU-сенсор 10 DOF v1 для Raspberry Pi Pico: инструкция, примеры использования и документация

Используйте IMU-сенсор 10 DOF v1 для определения положения вашего девайса в пространстве. Модуль выполнен в родном форм-факторе для платформы Raspberry Pi Pico.

IMU-модуль 10 DOF включает в себя:

  • Акселерометр для определения величины ускорения свободного падения по осям X, Y, Z.
  • Гироскоп для определения угловой скорости вокруг собственных осей X, Y, Z.
  • Магнитометр для определения углов между собственными осями сенсора X, Y, Z и силовыми линиями магнитного поля Земли.
  • Барометр для определения атмосферного давления, высоты над уровнем моря и температуры.

Подключение и настройка

На роль контроллера для работы с IMU-модулем рассмотрим платформу Raspberry Pi Pico.

Что понадобится

Инструкция по сборке

  1. Установите Raspberry Pi Pico сверху на контактные колодки платы с IMU-сенсора. Для правильности коммуникации воспользуйтесь меткой USB-порта на плате дисплея.
  2. Подключите полученный сет дисплеем вверх к компьютеру по USB. Для коммуникации используйте кабель micro-USB.

Примеры работы

Отобразим демонстрацию работы IMU-сенсора. Для работы примеров скачайте и установите библиотеку amperka_pico_imu_10dof.

Вывод показаний барометра

Для начала выведем в консоль показания барометра.

barometer_demo.py
# Импортируем необходимые модули
import time
from amperka_pico_imu_10dof import lps22hb
 
# Печатаем тестовую строку в консоль
print("Pressure Sensor Test Program")
# Создаём объект для работы с барометром
barometer = lps22hb.LPS22HB()
 
# Выводим атмосферное давление, температуру и высоту над уровнем моря
while True:
    pressure = barometer.read_pressure_millimeters_hg()
    temperature = barometer.read_temperature_c()
    altitude = barometer.read_altitude()
    print(f"Pressure {round(pressure,2)} mmHg")
    print(f"Temperature {round(temperature,2)} °C")
    print(f"Altitude {round(altitude,2)} m\n")
    time.sleep(0.1)

Вывод показаний акселерометра

Далее, выведем в консоль показания акселерометра: величины ускорения свободного падения по осям X, Y и Z.

accelerometer_demo.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("Accelerometer Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
# Выводим ускорения свободного падения относительно осей X, Y и Z в «сырых значениях»
# Выводим ускорения свободного падения относительно осей X, Y и Z в G 
# Выводим ускорения свободного падения относительно осей X, Y и Z в м/с²
while True:
    accelerometer = imu.read_accelerometer()
    accelerometer_g = imu.read_accelerometer_g()
    accelerometer_a = imu.read_accelerometer_a()
    print(f"Accelerometer (Raw):\t{accelerometer}")
    print(f"Accelerometer (G):\t{accelerometer_g}")
    print(f"Accelerometer (A):\t{accelerometer_a}")
    print()
    time.sleep(0.1)

Вывод показаний гироскопа

В продолжении, выведем в консоль показания гироскопа: угловую скорость вокруг собственных осей X, Y, Z.

gyroscope_demo.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("IMU-sensor Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
# Выводим угловую скорость относительно осей X, Y и Z в «сырых значениях»
# Выводим угловую скорость относительно осей X, Y и Z в градусах в секунду
# Выводим угловую скорость относительно осей X, Y и Z в радианах
 
while True:
    gyroscope = imu.read_gyroscope()
    gyroscope_deg = imu.read_gyroscope_deg()
    gyroscope_rad = imu.read_gyroscope_rad()
    print(f"Gyroscope (Raw):\t{gyroscope}")
    print(f"Gyroscope (Deg):\t{gyroscope_deg}")
    print(f"Gyroscope (Rad):\t{gyroscope_rad}")
    print()
    time.sleep(0.1)

Вывод показаний магнитометра

Следующим этапом, выведем в консоль показания магнитометра: значения напряженности магнитного поля по осям X, Y, Z.

magnetometer_demo.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("IMU-sensor Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
# Выводим значения напряженности магнитного поля по осям X, Y, Z в «сырых значениях»
# Выводим значения напряженности магнитного поля по осям X, Y, Z в «мкТс»
while True:
    magnetometer = imu.read_magnetometer()
    magnetometer_uT = imu.read_magnetometer_uT()
    print(f"Magnetometer (RAW):\t{magnetometer}")
    print(f"Magnetometer (uT):\t{magnetometer_uT}")
    print()
    time.sleep(0.1)

Вывод показаний инерциального датчика

Выведем показания сразу трёх сенсоров:

  • акселерометра: ускорения свободного падения по осям X, Y и Z.
  • гироскопа: угловую скорость вокруг собственных осей X, Y, Z.
  • магнитометра: напряженность магнитного поля по осям X, Y, Z.
imu_demo.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("IMU-sensor Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
# Выводим ускорения свободного падения относительно осей X, Y и Z в м/с²
# Выводим угловую скорость относительно осей X, Y и Z в градусах в секунду
# Выводим значения напряженности магнитного поля по осям X, Y, Z в «мкТс»
while True:
    accelerometer = imu.read_accelerometer_a()
    gyroscope = imu.read_gyroscope_deg()
    magnetometer = imu.read_magnetometer_uT()
    print(f"Accelerometer:\t{accelerometer}")
    print(f"Gyroscope:\t{gyroscope}")
    print(f"Magnetometer:\t{magnetometer}")
    print()
    time.sleep(0.1)

Ориентация объекта в пространстве

Определим ориентацию объекта в пространстве с учётом направления на север. Для этого показания акселерометра, гироскопа и компаса используем как входные данные для фильтра Маджвика. На выходе получим кватернионы, которые для наглядности пересчитаем в самолётные углы Эйлера.

imu_9DOF.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("IMU-sensor Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
 
while True:
    # Считываем данных с акселерометра
    accelerometer = imu.read_accelerometer_g()
    # Считываем данных с гироскопа
    gyroscope = imu.read_gyroscope_rad()
    # Считываем данных с магнитометра
    magnetometer = imu.read_magnetometer_uT()
    ax = accelerometer[0]
    ay = accelerometer[1]
    az = accelerometer[2]
    gx = gyroscope[0]
    gy = gyroscope[1]
    gz = gyroscope[2]
    mx = magnetometer[0]
    my = magnetometer[1]
    mz = magnetometer[2]
    # Обновляем входные данные в фильтр Маджвика
    imu.AHRS_update(gx, gy, gz, ax, ay, az, mx, my, mz)
    # Получаем из фильтра углы Эйлера: yaw, pitch и roll
    yaw = imu.get_yaw();
    pitch = imu.get_pitch();
    roll = imu.get_roll();
    print(f"Yaw:\t{yaw}")
    print(f"Pitch:\t{pitch }")
    print(f"Roll:\t{roll}")
    print()
    time.sleep(0.1)

Визуальное отображение объекта в пространстве

Для наглядности приведём пример отображение IMU-сенсора в пространстве в виде самолёта.

Порядок действий

Код для Pico

imu_visualization.py
# Импортируем необходимые модули
from amperka_pico_imu_10dof import icm20948
import time
import math
 
# Печатаем тестовую строку в консоль
print("IMU-sensor Test Program")
# Создаём объект для работы с IMU-сенсором
imu=icm20948.ICM20948()
 
while True:
    # Считываем данных с акселерометра
    accelerometer = imu.read_accelerometer_g()
    # Считываем данных с гироскопа
    gyroscope = imu.read_gyroscope_rad()
    # Считываем данных с магнитометра
    magnetometer = imu.read_magnetometer_()
    ax = accelerometer[0]
    ay = accelerometer[1]
    az = accelerometer[2]
    gx = gyroscope[0]
    gy = gyroscope[1]
    gz = gyroscope[2]
    mx = magnetometer[0]
    my = magnetometer[1]
    mz = magnetometer[2]
    # Обновляем входные данные в фильтр Маджвика
    imu.AHRS_update(gx, gy, gz, ax, ay, az, mx, my, mz)
    # Считываем кватернион
    quaternion = imu.read_quaternion()
    # Выводим кватернион в консоль для передачи в Processing
    print(f"{quaternion[0]},{quaternion[1]},{quaternion[2]},{quaternion[3]}")
    time.sleep(0.1)

Код для Processing

imu_visualization_draw.pde
import processing.serial.*;
import toxi.geom.*;
import toxi.processing.*;
 
// NOTE: requires ToxicLibs to be installed in order to run properly.
// 1. Download from http://toxiclibs.org/downloads
// 2. Extract into [userdir]/Processing/libraries
//    (location may be different on Mac/Linux)
// 3. Run and bask in awesomeness
 
// The serial port
Serial port;                         
 
String message;
 
float[] q = new float[4];
Quaternion quat = new Quaternion(1, 0, 0, 0);
// New line character in ASCII
final char newLine = '\n';
String [] massQ = new String [4];
float[] ypr = new float[3];
 
void setup()  {
    // Size form 400x400
    size(400, 400, P3D); 
    // Open serial port
    // Replace "COM7" with the COM port on which your arduino is connected
    port = new Serial(this, "COM7", 9600);
}
 
void draw()  {
    // Read and parse incoming serial message
    serialEvent();
    // Set background to black
    background(0);
    printQuaternions();
    printYawPitchRoll();
    // Set position to centre
    translate(width / 2, height / 2);
    // Begin object
    pushMatrix();
    float[] axis = quat.toAxisAngle();
    rotate(axis[0], axis[2], axis[3], axis[1]);
    // Draw main body in red
    drawBody();
    // Draw front-facing tip in blue
    drawCylinder();
    // Draw Triangles
    drawTriangles();
    // Draw Quads
    drawQuads();
    // End of object
    popMatrix();
    // Send character 's' to Arduino
    //port.write('s');
}
 
void serialEvent() {
    // Read from port until new line (ASCII code 13)
    message = port.readStringUntil(newLine);
    if (message != null) {
        // Split message by commas and store in String array 
        massQ = split(message, ",");
        q[0] = float(massQ[0]);
        q[1] = float(massQ[1]);
        q[2] = float(massQ[2]);
        q[3] = float(massQ[3]);
    }
    // Print values to console
    print(q[0]);
    print("\t");
    print(q[1]); 
    print("\t");
    print(q[2]);   
    print("\t");
    print(q[3]);   
    println("\t");
    // Set our toxilibs quaternion to new data
    quat.set(q[0], q[1], q[2], q[3]);
 
}
 
void drawCylinder() {
    float topRadius = 0;
    float bottomRadius = 20;
    float tall = 20;
    int sides = 8;
    // Begin object
    pushMatrix();
    translate(0, 0, -120);
    rotateX(PI/2);
    fill(0, 0, 255, 200);
 
    float angle = 0;
    float angleIncrement = TWO_PI / sides;
    beginShape(QUAD_STRIP);
        for (int i = 0; i < sides + 1; ++i) {
        vertex(topRadius * cos(angle), 0, topRadius * sin(angle));
        vertex(bottomRadius * cos(angle), tall, bottomRadius * sin(angle));
        angle += angleIncrement;
    }
 
    endShape();
 
    // if it is not a cone, draw the circular top cap
    if (topRadius != 0) {
        angle = 0;
        beginShape(TRIANGLE_FAN);
        // Center point
        vertex(0, 0, 0);
        for (int i = 0; i < sides + 1; i++) {
            vertex(topRadius * cos(angle), 0, topRadius * sin(angle));
            angle += angleIncrement;
        }
        endShape();
    }
 
    // If it is not a cone, draw the circular bottom cap
    if (bottomRadius != 0) {
        angle = 0;
        beginShape(TRIANGLE_FAN);
        // Center point
        vertex(0, tall, 0);
        for (int i = 0; i < sides + 1; i++) {
            vertex(bottomRadius * cos(angle), tall, bottomRadius * sin(angle));
            angle += angleIncrement;
        }
        endShape();
    }
    popMatrix(); 
}
 
void drawBody() {
    fill(255, 0, 0, 200);
    box(10, 10, 200);
}
 
void drawTriangles() {
    // Draw wings and tail fin in green
    fill(0, 255, 0, 200);
    beginShape(TRIANGLES);
    // Wing top layer
    vertex(-100,  2, 30); vertex(0,  2, -80); vertex(100,  2, 30);
    // Wing bottom layer
    vertex(-100, -2, 30); vertex(0, -2, -80); vertex(100, -2, 30);
    // Tail left layer
    vertex(-2, 0, 98); vertex(-2, -30, 98); vertex(-2, 0, 70);
    // Tail right layer
    vertex( 2, 0, 98); vertex( 2, -30, 98); vertex( 2, 0, 70);
    endShape();
}
 
void drawQuads() {
    beginShape(QUADS);
    vertex(-100, 2, 30); vertex(-100, -2, 30); vertex(  0, -2, -80); vertex(  0, 2, -80);
    vertex( 100, 2, 30); vertex( 100, -2, 30); vertex(  0, -2, -80); vertex(  0, 2, -80);
    vertex(-100, 2, 30); vertex(-100, -2, 30); vertex(100, -2,  30); vertex(100, 2,  30);
    vertex(-2,   0, 98); vertex(2,   0, 98); vertex(2, -30, 98); vertex(-2, -30, 98);
    vertex(-2,   0, 98); vertex(2,   0, 98); vertex(2,   0, 70); vertex(-2,   0, 70);
    vertex(-2, -30, 98); vertex(2, -30, 98); vertex(2,   0, 70); vertex(-2,   0, 70);
    endShape();
}
 
void printQuaternions() {
    // Set text mode to shape
    textMode(SHAPE);
    textSize(13);
    fill(255, 255, 255);
    text("Quaternions:", 20, 20, 10);
    text(q[0], 20, 40, 10);
    text(q[1], 20, 60, 10);
    text(q[2], 20, 80, 10);
    text(q[3], 20, 100, 10);
}
 
void printYawPitchRoll() {
    // Calculate yaw/pitch/roll angles
    ypr[0] = atan2(2*q[1]*q[2] - 2*q[0]*q[3], 2*q[0]*q[0] + 2*q[1]*q[1] - 1) * 57.2;
    ypr[1] = atan2(2 * q[2] * q[3] - 2 * q[0] * q[1], 2 * q[0] * q[0] + 2 * q[3] * q[3] - 1) * 57.2;
    ypr[2] = -atan2(2 * (q[0] * q[2] - q[1] * q[3]), 1 - 2 * (q[2] * q[2] + q[1] *q[1])) * 57.2;
 
    text("Yaw:", 150, 20, 10);
    text(ypr[0], 150, 40, 10);
    text("Pitch:", 220, 20, 10);
    text(ypr[1], 220, 40, 10);
    text("Roll:", 290, 20, 10);
    text(ypr[2], 290, 40, 10);
}

При запуске визуализации на «processinge» откроется окно с графическим отображением IMU-сенсора в виде самолёта. Самолёт на экране будет повторять перемещения IMU-сенсора в пространстве.

Элементы платы

Инерциальный чип ICM-20948

За 9 степеней свободы отвечает инерциальный чип ICM-20948, который включает в себя акселерометр, гироскоп и магнитометр.

  • Акселерометр для определения величины ускорения свободного падения по осям X, Y, Z.
  • Гироскоп для определения угловой скорости вокруг собственных осей X, Y, Z.
  • Магнитометр для определения углов между собственными осями сенсора X, Y, Z и силовыми линиями магнитного поля Земли.

За определения десятой степени свободы отвечает отдельный чип барометр LPS22HB, который подскажет высоту над уровнем моря.

Барометр LPS22HB

Датчик атмосферного давления LPS22HB служит альтиметром для носимого гаджета, а также барометром для метеостанции.

Светодиодная индикация

Имя светодиода Назначение
PWR Индикатор питания. Горит — на плату поступает напряжение, не горит — на плату не поступает напряжение.

Преобразователь питания RT9193-33

Понижающий DC-DC преобразователь RT9193-33 обеспечивает питание барометра и другой логики на плате. Входное напряжение поступает от пина VSYS управляющей платы. Выходное напряжение 3,3 В с максимальным выходным током 300 мА.

Преобразователь питания RT9193-18

Понижающий DC-DC преобразователь RT9193-18 обеспечивает питание инерциального датчика и светодиодной индикации. Входное напряжение поступает от преобразователя RT9193-33. Выходное напряжение 1,8 В с максимальным выходным током 300 мА.

Контактные колодки

Плата IMU-сенсор подключается к контроллерам форм-фактора Raspberry Pi Pico через две параллельных контактных колодки на 20 пинов. Все подробности коммуникации и задействованные контакты читайте в разделе распиновка.

Распиновка

Принципиальная и монтажная схема

Габаритный чертёж

Характеристики

  • Модель: Waveshare Pico-10DOF-IMU (SKU 19358)
  • Совместимость: контроллеры Raspberry Pi Pico
  • Чипы: IMU-сенсор ICM20948, барометр LPS22HB
  • Напряжение питания: 5 В
  • Размеры: 52×25×13 мм

IMU-сенсор ICM20948

  • Интерфейс: I²C (адрес 0x68)
  • Диапазон измерения ускорения акселерометра: ±2/±4/±8/±16g
  • Диапазон измерения поворота гироскопа: ±125/±250/±500/±1000/±2000 град./с
  • Диапазон измерения магнитной индукции: ±4900 мкТл

Барометр LPS22HB

  • Интерфейс: I²C (адрес 0x5C)
  • Диапазон измерения давления: 260–1260 мбар
  • Погрешность давления: ±0,025 мбар

Ресурсы

Полезные статьи

Документация