ESP32 S3 a BMI160 - gyroskop a akcelerometr

24.03.2025 Arduino #esp32s3 #esp32 #bmi160 #gyroskop

V tomto tutoriálu budeme propojovat akcelerometr a gyroskop BMI160 s ESP32-S3. BMI160 je malá 16bitová inerciální měřicí jednotka (IMU) s nízkou spotřebou a nízkým šumem, která integruje 3osý akcelerometr a 3osý gyroskop. Poskytuje přesné snímání pohybu pro aplikace, jako je rozšířená realita, sledování fitness a vnitřní navigace.


BMI160-Arduino-master BMI160 3osý akcelerometr a gyroskop

BMI160 je vysoce výkonná, malá a ultranízkoenergetická 16bitová inerciální měřicí jednotka (IMU), která kombinuje 3osý akcelerometr a 3osý gyroskop v jediném balení.


BMI160

Je navržen tak, aby poskytoval přesné a spolehlivé snímání pohybu pro širokou škálu aplikací, jako je rozšířená realita, vnitřní navigace, nositelná zařízení a hraní her. Senzor nabízí pokročilé funkce, jako je detekce pohybu, počítání kroků a rozpoznávání gest, díky čemuž je všestranný pro potřeby moderních technologií.

BMI160 podporuje jak I2C , tak SPI rozhraní pro komunikaci, díky čemuž je kompatibilní s řadou mikrokontrolérů a procesorů. Má kompaktní rozměry pouhých 2,5 x 3,0 x 0,8 mm³, což je menší než mnoho konkurenčních IMU, jako je MPU6050, a dobře se hodí pro zařízení s omezeným prostorem, jako jsou nositelná zařízení. Další informace naleznete v datovém listu Bosch BMI160.

Technické specifikace BMI160

  • Provozní napětí: 1,71 – 3,6 V (Breakout Board pracuje mezi 3,2 V ~ 6 V)
  • Spotřeba energie :
    • Gyroskop: 900 µA (plný provoz)
    • Gyroskop + Akcelerometr: 950 µA (plný provoz)
    • Akcelerometr: 180 µA (plný provoz)
    • Režim pozastavení: 3 µA
    • Detekce pohybu: 200 µA
  • Rozsah zrychlení: ±2g/±4g/±8g/±16g
  • Rozsah gyroskopů: ±125°/s,±250°/s,±500°/s,±1000°/s,±2000°/s
  • Posun akcelerace Zero-g: ±40 mg
  • Gyroskopy Zero-g Offset: ±10°/s
  • Rozhraní :
    • Primární : I2C nebo SPI
    • Sekundární : Vysokorychlostní SPI pro optickou stabilizaci obrazu
  • Hustota hluku :
    • Akcelerometr: 180 µg/√Hz
    • Gyroskop: 0,008 °/s/√Hz
  • Programovatelná frekvence: 25/32Hz~1600Hz
  • 6D detekce a umístění
  • 16bitový datový výstup
  • Odolnost proti otřesům: 1000gx 200us
  • 2 nezávislé programovatelné generátory přerušení
  • Vestavěný 1024 Byte FIFO

Pinout BMI160

Pin Název Popis
VIN VIN Vstupní kolík napájení. Přijímá 3,3 V až 5 V a snižuje jej pro BMI160 pomocí integrovaného regulátoru.
3,3 V 3,3 V Regulovaný výstupní pin 3,3V.
GND GND Zemnící kolík. Připojte se k zemi vašeho systému.
OCS OCS Volitelný čip Select pro sekundární rozhraní SPI (používá se při optické stabilizaci obrazu).
INT1 INT1 Pin přerušení 1. Používá se pro události pohybu nebo upozornění na připravenost dat.
INT2 INT2 Přerušovací kolík 2. Další přerušovací kolík pro pokročilejší případy použití.
SCL/SCX SCL nebo SCX I2C hodinová linka při použití I2C komunikace. Funguje jako hodiny SPI (SCX) v režimu SPI.
SDA/SDX SDA nebo SDX I2C datová linka při použití I2C komunikace. V režimu SPI funguje jako data SPI (SDX) .
CS CS Čip Vyberte pin pro SPI komunikaci. Používá se k výběru BMI160 během transakcí SPI.
SA0 SA0 I2C pin pro výběr adresy. Připojte ke GND pro adresu 0x68nebo k 3,3V pro 0x69.

ESP32-S3

Schéma zapojení

 

Pin BMI160 Pin ESP32 Popis
3,3 V 3,3 V Napájecí vstup pro BMI160.
GND GND Zemní spojení.
SCL 2 Linka hodin I2C.
SDA 42 I2C datová linka.
SA0 GND Nastaví I2C adresu na 0x68.

Připojení SA0 k GND nastaví I2C adresu na 0x68. Při připojení k 3,3V by se adresa I2C změnila na 0x69.

 

Čtení nezpracovaných dat zrychlení a gyroskopu BMI160

Pro získání hodnot BMI160 je k dispozici velmi dobře napsaná knihovna. Stáhněte si knihovnu BMI160 a přidejte ji do Arduino IDE nebo do složky lib v prostředí PlatformIO.

Následující kód inicializuje snímač BMI160 přes I2C, čte nezpracovaná data akcelerometru a gyroskopu a vytiskne je na sériový monitor pro monitorování v reálném čase.

Měření zrychlení s BMI160 & ESP32

Pomocí modulu akcelerometru a gyroskopu ESP32 & BMI160 můžeme vyvinout kód C++ pro čtení hodnot zrychlení. Hrubé hodnoty jsme převedli na zrychlení pomocí matematických rovnic.

Následující kód inicializuje snímač BMI160 pomocí komunikace I2C. Nastaví akcelerometr do normálního režimu. Kód provádí automatickou kalibraci, aby se snížil šum a chyby. Vypočítává offsety pro akcelerometr během kalibrace.

Po kalibraci čte nezpracovaná data akcelerometru z os X, Y a Z. Hrubé hodnoty jsou převedeny na zrychlení v m/s² a zobrazeny na sériovém monitoru.

Nezpracované hodnoty se převedou na ????/????^2 pomocí vzorce:

Citlivost pro ±2g je 16384 LSB/g. Upravte, pokud používáte jiný rozsah.

Osa Z také zohledňuje vliv gravitace během kalibrace. Konečné opravené hodnoty zrychlení se zobrazí na sériovém monitoru.

Měření úhlu sklonu pomocí BMI160 & ESP32

Následující kód také inicializuje snímač BMI160 pomocí komunikace I2C. Nastaví akcelerometr do normálního režimu. Kód provádí automatickou kalibraci pro výpočet offsetů a snížení šumu. To pomáhá zlepšit přesnost měření úhlu náklonu.

Po kalibraci čte data akcelerometru z os X, Y a Z. Vypočítává úhly náklonu (náklon, náklon a stáčení) na základě údajů z akcelerometru.

Úhel rotace kolem osy X se nazývá Pitch, který se vypočítá pomocí následujícího vzorce:

Úhel rotace kolem osy Y se nazývá Roll, který se vypočítá podle následujícího vzorce:

Nakonec se hodnoty rozteče a natočení vytisknou na sériový monitor ve stupních.

#include <Wire.h>
#include <BMI160Gen.h>

#define SDA_PIN 42
#define SCL_PIN 2
const int bmi160_i2c_addr = 0x69;

BMI160GenClass bmi160;

void setup() {
  Serial.begin(115200);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  pinMode(47, OUTPUT);      // Set EN pin for second stabilisator as output
  digitalWrite(47, HIGH);   // Turn on the second stabilisator
  delay(1000);

  // Inicializace I2C
  Wire.begin(SDA_PIN, SCL_PIN);

  Serial.println("Start ini component ...");
  // Inicializace BMI160
  if (!bmi160.begin(BMI160GenClass::I2C_MODE, bmi160_i2c_addr)) {
    Serial.println("BMI160 inicializace selhala!");
    while (1);
  }
  Serial.println("BMI160 inicializace úspěšná!");

  // Nastavení rozsahů
  bmi160.setAccelerometerRange(2);  // Možnosti: 2, 4, 8, 16 G
  bmi160.setGyroRange(250);         // Možnosti: 125, 250, 500, 1000, 2000 dps
}

void loop() {
  int ax, ay, az; // Akcelerometr
  int gx, gy, gz; // Gyroskop

  // Čtení hodnot akcelerometru a gyroskopu
  bmi160.readMotionSensor(ax, ay, az, gx, gy, gz);

  // Převod hodnot na g a dps
  float accelX = ax / 16384.0; // Pokud je rozsah ±2G
  float accelY = ay / 16384.0;
  float accelZ = az / 16384.0;

  float gyroX = gx / 131.0; // Pokud je rozsah ±250 dps
  float gyroY = gy / 131.0;
  float gyroZ = gz / 131.0;

  // Výpis hodnot do sériového monitoru
  Serial.print("Accel [g]: X=");
  Serial.print(accelX, 2);
  Serial.print(" Y=");
  Serial.print(accelY, 2);
  Serial.print(" Z=");
  Serial.print(accelZ, 2);

  Serial.print(" | Gyro [dps]: X=");
  Serial.print(gyroX, 2);
  Serial.print(" Y=");
  Serial.print(gyroY, 2);
  Serial.print(" Z=");
  Serial.println(gyroZ, 2);

  delay(100);
}

Vizualizace gyroskopu na OLED displeji

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <BMI160Gen.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define SDA_PIN 42
#define SCL_PIN 2

const int BMI160_ADDR = 0x69;
const int SCREEN_CENTER_X = SCREEN_WIDTH / 2;
const int SCREEN_CENTER_Y = SCREEN_HEIGHT / 2;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
BMI160GenClass bmi160;

// Pozice kuličky
float ballX = SCREEN_CENTER_X;
float ballY = SCREEN_CENTER_Y;

// Nastavení citlivosti a času
float sensitivity = 5.0;         // čím vyšší, tím pomalejší pohyb
unsigned long prevTime = 0;

void setup() {
  Serial.begin(115200);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  pinMode(47, OUTPUT);
  digitalWrite(47, HIGH);
  delay(1000);
  Wire.begin(SDA_PIN, SCL_PIN);

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Inicializace...");
  display.display();

  if (!bmi160.begin(BMI160GenClass::I2C_MODE, BMI160_ADDR)) {
    Serial.println(F("BMI160 init failed"));
    display.println("BMI160 fail!");
    display.display();
    while (1);
  }

  bmi160.setGyroRange(250); // Nastavení rozsahu ±250°/s
  delay(1000);
  prevTime = millis();
}

void loop() {
  int ax, ay, az, gx, gy, gz;

  // Čtení dat ze senzoru
  bmi160.readMotionSensor(ax, ay, az, gx, gy, gz);

  // Časová delta (v sekundách)
  unsigned long currentTime = millis();
  float dt = (currentTime - prevTime) / 1000.0;
  prevTime = currentTime;

  // Převod na °/s
  float gyroX = gx / 131.0;  // ROLL
  float gyroY = gy / 131.0;  // PITCH

  // Výpočet změny pozice
  ballX += gyroX * dt * sensitivity;
  ballY += gyroY * dt * sensitivity;

  // Omezení na hranice displeje
  ballX = constrain(ballX, 0, SCREEN_WIDTH - 1);
  ballY = constrain(ballY, 0, SCREEN_HEIGHT - 1);

  // Výpis do sériového monitoru (pro ladění)
  Serial.print("GyroX: "); Serial.print(gyroX);
  Serial.print(" GyroY: "); Serial.print(gyroY);
  Serial.print(" X: "); Serial.print(ballX);
  Serial.print(" Y: "); Serial.println(ballY);

  // Vykreslení
  display.clearDisplay();
  display.fillCircle((int)ballX, (int)ballY, 3, SSD1306_WHITE);
  display.display();

  delay(20);  // Plynulé vykreslování
}

3D Vizualizace gyroskopu pomocí pythonu

Z ESP32 se posílají data na sériový monitor. Pomocí Pythonu se tato data čtou a skript pak provádí 3D vizualizaci polohy gyroskopu.

#include <Wire.h>
#include <BMI160Gen.h>

#define SDA_PIN 42
#define SCL_PIN 2
const int bmi160_i2c_addr = 0x69;

BMI160GenClass bmi160;

void setup() {
  Serial.begin(115200);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  pinMode(47, OUTPUT);
  digitalWrite(47, HIGH);
  delay(1000);

  Wire.begin(SDA_PIN, SCL_PIN);

  Serial.println("Start ini component ...");

  if (!bmi160.begin(BMI160GenClass::I2C_MODE, bmi160_i2c_addr)) {
    Serial.println("BMI160 inicializace selhala!");
    while (1);
  }
  Serial.println("BMI160 inicializace úspěšná!");

  bmi160.setAccelerometerRange(2);
  bmi160.setGyroRange(250);
}

void loop() {
  int gx, gy, gz;
  int ax, ay, az; // Akcelerometr

  bmi160.readMotionSensor(ax, ay, az, gx, gy, gz);

  float gyroX = gx / 131.0;
  float gyroY = gy / 131.0;
  float gyroZ = gz / 131.0;

  Serial.printf(";%.2f,%.2f,%.2f;\n", gyroX, gyroY, gyroZ); 

  delay(50); // Krátká prodleva pro plynulý přenos
}

Python

import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import serial
import re
import time

# ✅ Nastavení sériového portu pro ESP32-S3
usbPort = '/dev/cu.usbserial-57620044481'  # Mac/Linux
# usbPort = 'COM4'  # Windows
baudRate = 115200

try:
    ser = serial.Serial(usbPort, baudRate, timeout=1)
    print(f"✅ Připojeno k {usbPort}")
except serial.SerialException:
    print(f"❌ Chyba: Nelze otevřít {usbPort}. Zkontroluj připojení ESP32-S3!")
    exit()

# ✅ Počáteční úhly rotace (kalibrace)
calibrated_x, calibrated_y, calibrated_z = 0.0, 0.0, 0.0
angle_x, angle_y, angle_z = 0.0, 0.0, 0.0
scale_factor = 1.0  # Citlivost otáčení

# ✅ Čas posledního vzorku pro integraci
last_time = time.time()

# ✅ Rozměry kvádru (odpovídající letadlu)
vertices = [
    [-3, -2, -1], [3, -2, -1], [3, 2, -1], [-3, 2, -1],
    [-3, -2,  1], [3, -2,  1], [3, 2,  1], [-3, 2,  1]
]

edges = [
    (0, 1), (1, 2), (2, 3), (3, 0),
    (4, 5), (5, 6), (6, 7), (7, 4),
    (0, 4), (1, 5), (2, 6), (3, 7)
]

faces = [
    (0, 1, 2, 3),
    (4, 5, 6, 7),
    (0, 1, 5, 4),
    (2, 3, 7, 6),
    (0, 3, 7, 4),
    (1, 2, 6, 5)
]

colors = [
    (1, 0, 0), (0, 1, 0), (0, 0, 1),
    (1, 1, 0), (1, 0, 1), (0, 1, 1)
]

# ✅ Funkce pro vykreslení kvádru
def draw_cube():
    glBegin(GL_QUADS)
    for i, face in enumerate(faces):
        glColor3fv(colors[i])
        for vertex in face:
            glVertex3fv(vertices[vertex])
    glEnd()

    glBegin(GL_LINES)
    glColor3fv((0, 0, 0))  # Černé hrany
    for edge in edges:
        for vertex in edge:
            glVertex3fv(vertices[vertex])
    glEnd()

# ✅ Inicializace OpenGL (pohled odpovídající gyroskopu v letadle)
def init_pygame():
    pygame.init()
    display = (800, 600)
    pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

    # ✅ RESETUJEME OpenGL transformační matici
    glLoadIdentity()

    # ✅ Nastavení perspektivy a kamery
    gluPerspective(45, (display[0] / display[1]), 0.1, 50.0)
    glTranslatef(0.0, 0.0, -12)  # Posun kvádru dozadu
    glRotatef(90, 1, 0, 0)  # Správná orientace gyroskopu
    glRotatef(180, 0, 0, 1)

# ✅ Funkce pro čtení dat ze senzoru
def read_serial_data():
    line = ser.readline().decode('utf-8').strip()
    match = re.match(r";([-0-9.]+),([-0-9.]+),([-0-9.]+);", line)
    if match:
        return list(map(float, match.groups()))
    return None

# ✅ Kalibrace – počáteční stav senzoru se uloží jako nulová rotace
def calibrate_sensor():
    global calibrated_x, calibrated_y, calibrated_z
    print("???? Kalibrace... Polož senzor do vodorovné polohy a nehýbej s ním.")

    samples = 100
    sum_x, sum_y, sum_z = 0.0, 0.0, 0.0
    count = 0

    for _ in range(samples):
        data = read_serial_data()
        if data:
            sum_x += data[0]
            sum_y += data[1]
            sum_z += data[2]
            count += 1

    if count > 0:
        calibrated_x = sum_x / count
        calibrated_y = sum_y / count
        calibrated_z = sum_z / count

    print(f"✅ Kalibrace dokončena! Offsety: X={calibrated_x:.2f}, Y={calibrated_y:.2f}, Z={calibrated_z:.2f}")

# ✅ Hlavní smyčka programu
def main():
    global angle_x, angle_y, angle_z, last_time, calibrated_x, calibrated_y, calibrated_z

    init_pygame()
    calibrate_sensor()  # Spustíme kalibraci před hlavní smyčkou

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:  # Stisk klávesy "r" resetuje polohu kvádru
                    print("???? RESET kalibrace do výchozí polohy!")
                    angle_x, angle_y, angle_z = 0.0, 0.0, 0.0  # Vynulování úhlů

        # ???? Vypočítání časového rozdílu mezi vzorky
        current_time = time.time()
        delta_time = current_time - last_time
        last_time = current_time

        # ???? Čtení dat ze senzoru
        data = read_serial_data()
        if data:
            gyro_x, gyro_y, gyro_z = data

            # ✅ Aplikace kalibračních offsetů
            gyro_x -= calibrated_x
            gyro_y -= calibrated_y
            gyro_z -= calibrated_z

            # ✅ Integrace úhlové rychlosti pro přesné úhly
            angle_x += gyro_x * delta_time * scale_factor  # ROLL (náklon)
            angle_y += gyro_y * delta_time * scale_factor  # PITCH (sklon)
            angle_z += gyro_z * delta_time * scale_factor  # YAW (zatáčení)

            print(f"???? Rotace: ROLL={angle_x:.2f}, PITCH={angle_y:.2f}, YAW={angle_z:.2f}")

        # ✅ Aktualizace scény OpenGL
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glPushMatrix()
        glRotatef(angle_x, 1, 0, 0)  # ROLL (náklon)
        glRotatef(angle_y, 0, 1, 0)  # PITCH (sklon)
        glRotatef(angle_z, 0, 0, 1)  # YAW (zatáčení)
        draw_cube()
        glPopMatrix()

        pygame.display.flip()
        pygame.time.wait(10)  # Hladší vykreslování

    pygame.quit()

# ✅ Spuštění programu
if __name__ == "__main__":
    main()</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="mce-pagebreak" data-mce-resize="false" data-mce-placeholder />

Ke stažení

platformio.ini 

Knihovna BMI160