Содержание

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

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

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

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

На роль контроллера для работы с 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)

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

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

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-сенсора в пространстве в виде самолёта.

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

  1. Скачайте библиотеки для Processing и распакуйте их в директорию хранения модулей для Processing: userdir Processing libraries

Код для 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, который включает в себя акселерометр, гироскоп и магнитометр.

За определения десятой степени свободы отвечает отдельный чип барометр 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 пинов. Все подробности коммуникации и задействованные контакты читайте в разделе распиновка.

Распиновка

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

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

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

IMU-сенсор ICM20948

Барометр LPS22HB

Ресурсы

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

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