A A+ A++

Materiały do zajęć z cyklu

"Inżynier przyszłości" pt.:
Mechatronika - jak samodzielnie wykonać drona?

Prowadzący: dr inż. Adam Mańka
Politechnika Śląska 
Wydział Transportu i Inżynierii Lotniczej

Przykład 1 – Odczyt z GPS (GNSS).
W przykładzie wykorzystujemy: Arduino UNO + GPS NEO-6M / GY-GPS6MV2

Podłączenie:
Arduino UNO -> GPS NEO-6M, GND->GND, VCC->VCC, D03->TX
DO4->RX – nie podłączamy z uwagi na brak konieczności wysyłania sygnału z Arduino do GPS (dzielnik napięcia)

A. GPS – pobieranie surowych danych w formacie NMEA – bez dodatkowej biblioteki.
				
					//GPS NEO-6M
//Surowe dane w formacie NMEA
//Adam Mańka (adam.manka(at)polsl.pl)

#include <SoftwareSerial.h>

SoftwareSerial gpsSerial(3, 4); // RX, TX

void setup()
{
  Serial.begin(9600);
  gpsSerial.begin(9600);

  Serial.println("Test GPS NEO-6M");
  Serial.println("Czekam na dane NMEA...");
}

void loop()
{
  while (gpsSerial.available())
  {
    char c = gpsSerial.read();
    Serial.write(c);
  }
}
				
			
B. GPS – dane po analizie z biblioteką TinyGPSPlus
Uwaga: w tym ćwiczeniu potrzebny jest sam odczyt z GPS więc wystarczy podłączyć jego TX (transmit) do pinu np. 3.  Pin RX można zostawić nie podpięty.
				
					//GPS - NEO-6M
//Dane po analizie
//Adam Mańka (adam.manka(at)polsl.pl)
#include <SoftwareSerial.h>
#include <TinyGPSPlus.h>

SoftwareSerial gpsSerial(3, 4); // RX, TX
TinyGPSPlus gps;

void setup()
{
  Serial.begin(9600);
  gpsSerial.begin(9600);

  Serial.println("GPS NEO-6M + Arduino Uno");
}

void loop()
{
  while (gpsSerial.available())
  {
    gps.encode(gpsSerial.read());
  }

  if (gps.location.isUpdated())
  {
    Serial.print("Lat: ");
    Serial.println(gps.location.lat(), 6);

    Serial.print("Lon: ");
    Serial.println(gps.location.lng(), 6);

    Serial.print("Satelity: ");
    Serial.println(gps.satellites.value());

    Serial.print("Predkosc km/h: ");
    Serial.println(gps.speed.kmph());

    Serial.println();
  }
}
				
			
C. GPS – znacznie więcej danych z biblioteką TinyGPSPlus
Uwaga: w tym ćwiczeniu potrzebny jest sam odczyt z GPS więc wystarczy podłączyć jego TX (transmit) do pinu np. 3.  Pin RX można zostawić nie podpięty.
				
					//GPS NEO-6M
//Dane po analizie
//Adam Mańka (adam.manka(at)polsl.pl

#include <SoftwareSerial.h>              // UART programowy dla GPS
#include <TinyGPSPlus.h>                 // biblioteka do dekodowania NMEA

SoftwareSerial gpsSerial(3, 4);          // RX, TX: GPS TX -> D3, GPS RX -> D4 opcjonalnie
TinyGPSPlus gps;                         // obiekt GPS

const int UTC_OFFSET = 2;                // korekta czasu: Polska latem UTC+2
unsigned long lastPrint = 0;             // czas ostatniego wydruku

void p2(int v){ if(v < 10) Serial.print('0'); Serial.print(v); } // drukuje 01, 02, 03...

void drukujCzas(){                       // data i czas GPS z prosta korekta UTC+2, bez korekty daty
  if(!gps.date.isValid() || !gps.time.isValid()){ Serial.print("-"); return; }
  int h = gps.time.hour() + UTC_OFFSET; if(h >= 24) h -= 24;
  p2(gps.date.day()); Serial.print("."); p2(gps.date.month()); Serial.print("."); Serial.print(gps.date.year()); Serial.print(" ");
  p2(h); Serial.print(":"); p2(gps.time.minute()); Serial.print(":"); p2(gps.time.second()); Serial.print(" UTC+2");
}

void setup(){
  Serial.begin(9600);                    // Monitor portu: 9600
  gpsSerial.begin(9600);                 // GPS NEO-6M: zwykle 9600
  Serial.println("Politechnika Śląska \nTester modułu GPS NEO-6M \nMechatronika - Adam Mańka (adam.manka@polsl.pl)");
}

void loop(){
  while(gpsSerial.available()) gps.encode(gpsSerial.read()); // odczyt znakow NMEA z GPS
  if(millis() - lastPrint < 1000) return; lastPrint = millis(); // wydruk co 1 s

  Serial.println("\n----------------------------------------");
  Serial.print("Data/czas GPS z korekta UTC+2: "); drukujCzas(); Serial.println();

  Serial.print("HDOP TinyGPS: "); if(gps.hdop.isValid()) Serial.print(gps.hdop.hdop(),2); else Serial.print("-"); Serial.println(" / glowny wskaznik jakosci pozycji poziomej /");
  Serial.println("HDOP: <1.0 bardzo dobrze, 1-2 dobrze, 2-5 srednio, >5 slabo, \"-\" brak wiarygodnego fixa");

  Serial.print("Satelity: "); if(gps.satellites.isValid()) Serial.print(gps.satellites.value()); else Serial.print("-"); Serial.println(" / liczba satelitow uzytych lub widzianych przez GPS /");
  Serial.print("Szerokosc lat: "); if(gps.location.isValid()) Serial.print(gps.location.lat(),6); else Serial.print("-"); Serial.println();
  Serial.print("Dlugosc lon: "); if(gps.location.isValid()) Serial.print(gps.location.lng(),6); else Serial.print("-"); Serial.println();
  Serial.print("Wysokosc: "); if(gps.altitude.isValid()) Serial.print(gps.altitude.meters(),2); else Serial.print("-"); Serial.println(" m / wysokosc GPS nad poziomem morza /");
  Serial.print("Predkosc: "); if(gps.speed.isValid()) Serial.print(gps.speed.mps(),2); else Serial.print("-"); Serial.println(" m/s");
  Serial.print("Kurs ruchu: "); if(gps.course.isValid()) Serial.print(gps.course.deg(),2); else Serial.print("-"); Serial.println(" deg / kierunek ruchu, nie kompas /");
  Serial.print("Ramki OK checksum: "); Serial.print(gps.passedChecksum()); Serial.println(" / poprawne ramki NMEA /");
  Serial.print("Ramki BAD checksum: "); Serial.print(gps.failedChecksum()); Serial.println(" / uszkodzone lub blednie odebrane ramki NMEA /");
}
				
			

Przykład 2 – Odczyt z akcelerometru (IMU).
W przykładzie wykorzystujemy: Arduino UNO + MPU-6050 / GY-521

Podłączenie:
Arduino UNO
-> GPS NEO-6M, GND->GND, VCC->VCC, D03->TX

DO4->RX – nie podłączamy z uwagi na brak konieczności wysyłania sygnału z Arduino do GPS (dzielnik napięcia)

A. IMU – MPU-6050 – odczyt – oryginalny MPU-6050 (GY-521) – adres 0x68
				
					//Akcelerometr (IMU) MPU - 6050 / GY-521
//Adam Mańka (adam.manka(at)polsl.pl)

// Biblioteka: Adafruit MPU6050
// Połączenie:
// VCC -> 5V
// GND -> GND
// SDA -> A4
// SCL -> A5
// AD0 -> GND albo niepodłączone
// INT -> niepodłączone

#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <math.h>

Adafruit_MPU6050 mpu;

const float G = 9.80665;
const float RAD_TO_DEG_LOCAL = 57.2957795;

float gyroOffsetX = 0.0;
float gyroOffsetY = 0.0;
float gyroOffsetZ = 0.0;

void calibrateGyro() {
  Serial.println("Kalibracja zyroskopu...");
  Serial.println("Trzymaj modul nieruchomo.");

  const int samples = 200;

  float sumX = 0.0;
  float sumY = 0.0;
  float sumZ = 0.0;

  sensors_event_t acc, gyro, temp;

  for (int i = 0; i < samples; i++) {
    mpu.getEvent(&acc, &gyro, &temp);

    // Biblioteka Adafruit zwraca zyroskop w rad/s
    sumX += gyro.gyro.x * RAD_TO_DEG_LOCAL;
    sumY += gyro.gyro.y * RAD_TO_DEG_LOCAL;
    sumZ += gyro.gyro.z * RAD_TO_DEG_LOCAL;

    delay(10);
  }

  gyroOffsetX = sumX / samples;
  gyroOffsetY = sumY / samples;
  gyroOffsetZ = sumZ / samples;

  Serial.println("Kalibracja zakonczona.");
  Serial.print("Offset gyro [deg/s]: X=");
  Serial.print(gyroOffsetX, 3);
  Serial.print(" Y=");
  Serial.print(gyroOffsetY, 3);
  Serial.print(" Z=");
  Serial.println(gyroOffsetZ, 3);
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("Start MPU-6050 / GY-521");

  Wire.begin();
  Wire.setClock(100000);   // bezpieczne I2C dla Arduino Uno R3

  if (!mpu.begin(0x68, &Wire)) {
    Serial.println("Nie wykryto MPU-6050.");
    Serial.println("Sprawdz: VCC, GND, SDA=A4, SCL=A5, AD0=GND.");
    while (1) {
      delay(100);
    }
  }

  Serial.println("MPU-6050 wykryty.");

  // Zakresy pomiarowe dobre na poczatek zajec
  mpu.setAccelerometerRange(MPU6050_RANGE_2_G);
  mpu.setGyroRange(MPU6050_RANGE_250_DEG);
  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);

  delay(500);

  calibrateGyro();

  Serial.println("Odczyt danych:");
  Serial.println("ACC w g, GYRO w deg/s, katy w stopniach");
  Serial.println();
}

void loop() {
  sensors_event_t acc, gyro, temp;
  mpu.getEvent(&acc, &gyro, &temp);

  // Przyspieszenie z biblioteki jest w m/s^2
  float ax_g = acc.acceleration.x / G;
  float ay_g = acc.acceleration.y / G;
  float az_g = acc.acceleration.z / G;

  float accNorm = sqrt(ax_g * ax_g + ay_g * ay_g + az_g * az_g);

  // Zyroskop z biblioteki jest w rad/s
  float gx_dps = gyro.gyro.x * RAD_TO_DEG_LOCAL - gyroOffsetX;
  float gy_dps = gyro.gyro.y * RAD_TO_DEG_LOCAL - gyroOffsetY;
  float gz_dps = gyro.gyro.z * RAD_TO_DEG_LOCAL - gyroOffsetZ;

  // Proste katy z akcelerometru
  // Dzialaja dobrze, gdy modul nie jest silnie przyspieszany dynamicznie
  float roll = atan2(ay_g, az_g) * RAD_TO_DEG_LOCAL;
  float pitch = atan2(-ax_g, sqrt(ay_g * ay_g + az_g * az_g)) * RAD_TO_DEG_LOCAL;

  Serial.print("ACC[g] X=");
  Serial.print(ax_g, 3);
  Serial.print(" Y=");
  Serial.print(ay_g, 3);
  Serial.print(" Z=");
  Serial.print(az_g, 3);

  Serial.print(" | |ACC|=");
  Serial.print(accNorm, 3);

  Serial.print(" | GYRO[deg/s] X=");
  Serial.print(gx_dps, 2);
  Serial.print(" Y=");
  Serial.print(gy_dps, 2);
  Serial.print(" Z=");
  Serial.print(gz_dps, 2);

  Serial.print(" | Roll=");
  Serial.print(roll, 1);
  Serial.print(" Pitch=");
  Serial.print(pitch, 1);

  Serial.print(" | Temp=");
  Serial.print(temp.temperature, 1);
  Serial.println(" C");

  delay(200);
}
				
			
B. IMU – MPU-6050 – odczyt (adres 0x68) – do drona
				
					//Akcelerometr (IMU) MPU - 6050 / GY-521
//Adam Mańka (adam.manka(at)polsl.pl)

// Arduino Uno R3 + GY-521 / MPU-6050
// Wersja B - estymacja roll/pitch do zastosowan dronowych
//
// Biblioteka:
// Adafruit MPU6050
// Adafruit Unified Sensor
// Adafruit BusIO
//
// Polaczenie:
// VCC -> 5V
// GND -> GND
// SDA -> A4
// SCL -> A5
// AD0 -> GND
// INT -> niepodlaczone

#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <math.h>

Adafruit_MPU6050 mpu;

const float G = 9.80665;
const float RAD_TO_DEG_LOCAL = 57.2957795;

// Czestotliwosc pracy petli
const unsigned long LOOP_PERIOD_US = 5000;   // 5000 us = 200 Hz

// Filtr komplementarny
// Im wieksze ALPHA, tym mocniej ufamy zyroskopowi.
// Typowe wartosci: 0.96 ... 0.995
const float ALPHA = 0.98;

// Korekcja akcelerometrem tylko gdy modul nie jest silnie przyspieszany dynamicznie
const float ACC_MIN_VALID = 0.75;   // g
const float ACC_MAX_VALID = 1.25;   // g

// Offsety zyroskopu [deg/s]
float gyroOffsetX = 0.0;
float gyroOffsetY = 0.0;
float gyroOffsetZ = 0.0;

// Estymowane katy [deg]
float roll = 0.0;
float pitch = 0.0;

// Czas
unsigned long lastLoopUs = 0;

// ------------------------------------------------------------

void calibrateGyro() {
  Serial.println("Kalibracja zyroskopu - nie ruszaj modulu");

  const int samples = 500;

  float sumGX = 0.0;
  float sumGY = 0.0;
  float sumGZ = 0.0;

  sensors_event_t acc, gyro, temp;

  for (int i = 0; i < samples; i++) {
    mpu.getEvent(&acc, &gyro, &temp);

    sumGX += gyro.gyro.x * RAD_TO_DEG_LOCAL;
    sumGY += gyro.gyro.y * RAD_TO_DEG_LOCAL;
    sumGZ += gyro.gyro.z * RAD_TO_DEG_LOCAL;

    delay(4);   // ok. 250 Hz podczas kalibracji
  }

  gyroOffsetX = sumGX / samples;
  gyroOffsetY = sumGY / samples;
  gyroOffsetZ = sumGZ / samples;

  Serial.println("Kalibracja zakonczona");
  Serial.print("Gyro offset [deg/s]: ");
  Serial.print(gyroOffsetX, 3);
  Serial.print(", ");
  Serial.print(gyroOffsetY, 3);
  Serial.print(", ");
  Serial.println(gyroOffsetZ, 3);
}

// ------------------------------------------------------------

void initializeAnglesFromAccelerometer() {
  sensors_event_t acc, gyro, temp;
  mpu.getEvent(&acc, &gyro, &temp);

  float ax = acc.acceleration.x / G;
  float ay = acc.acceleration.y / G;
  float az = acc.acceleration.z / G;

  roll = atan2(ay, az) * RAD_TO_DEG_LOCAL;
  pitch = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG_LOCAL;

  Serial.print("Kat poczatkowy Roll=");
  Serial.print(roll, 2);
  Serial.print(" Pitch=");
  Serial.println(pitch, 2);
}

// ------------------------------------------------------------

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("MPU-6050 / GY-521 - wersja B dronowa");

  Wire.begin();
  Wire.setClock(400000);   // 400 kHz - szybsze I2C, zwykle OK dla krotkich przewodow

  if (!mpu.begin(0x68, &Wire)) {
    Serial.println("Nie wykryto MPU-6050.");
    Serial.println("Sprawdz: VCC, GND, SDA=A4, SCL=A5, AD0=GND.");
    while (1) {
      delay(100);
    }
  }

  Serial.println("MPU-6050 wykryty.");

  // Dla drona lepiej nie ustawiac +/-2g, bo latwo o nasycenie przy manewrach i wibracjach.
  mpu.setAccelerometerRange(MPU6050_RANGE_4_G);

  // Do prostych testow wystarczy 500 deg/s.
  // Przy bardzo dynamicznym dronie mozna dac 1000 albo 2000 deg/s.
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);

  // Filtr sprzetowy. 21 Hz jest spokojny i odporny na szum.
  // Dla szybszej reakcji mozna testowac 44 Hz albo 94 Hz.
  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);

  delay(500);

  calibrateGyro();
  initializeAnglesFromAccelerometer();

  lastLoopUs = micros();

  Serial.println();
  Serial.println("t_ms,roll,pitch,gx,gy,gz,accNorm,accValid");
}

// ------------------------------------------------------------

void loop() {
  unsigned long nowUs = micros();

  if (nowUs - lastLoopUs < LOOP_PERIOD_US) {
    return;
  }

  float dt = (nowUs - lastLoopUs) / 1000000.0;
  lastLoopUs = nowUs;

  sensors_event_t acc, gyro, temp;
  mpu.getEvent(&acc, &gyro, &temp);

  // Akcelerometr w g
  float ax = acc.acceleration.x / G;
  float ay = acc.acceleration.y / G;
  float az = acc.acceleration.z / G;

  float accNorm = sqrt(ax * ax + ay * ay + az * az);

  // Zyroskop w deg/s po kalibracji
  float gx = gyro.gyro.x * RAD_TO_DEG_LOCAL - gyroOffsetX;
  float gy = gyro.gyro.y * RAD_TO_DEG_LOCAL - gyroOffsetY;
  float gz = gyro.gyro.z * RAD_TO_DEG_LOCAL - gyroOffsetZ;

  // Kat z samego akcelerometru
  float rollAcc = atan2(ay, az) * RAD_TO_DEG_LOCAL;
  float pitchAcc = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG_LOCAL;

  // Predykcja z zyroskopu
  // Uwaga: to uproszczona postac dobra do malych i srednich katow.
  roll += gx * dt;
  pitch += gy * dt;

  // Korekcja akcelerometrem tylko wtedy, gdy |ACC| jest blisko 1 g
  bool accValid = (accNorm > ACC_MIN_VALID && accNorm < ACC_MAX_VALID);

  if (accValid) {
    roll = ALPHA * roll + (1.0 - ALPHA) * rollAcc;
    pitch = ALPHA * pitch + (1.0 - ALPHA) * pitchAcc;
  }

  // Wyjscie CSV do Serial Plotter / logowania / dalszej analizy
  Serial.print(millis());
  Serial.print(",");
  Serial.print(roll, 2);
  Serial.print(",");
  Serial.print(pitch, 2);
  Serial.print(",");
  Serial.print(gx, 2);
  Serial.print(",");
  Serial.print(gy, 2);
  Serial.print(",");
  Serial.print(gz, 2);
  Serial.print(",");
  Serial.print(accNorm, 3);
  Serial.print(",");
  Serial.println(accValid ? 1 : 0);
}
				
			
B. IMU – MPU-6050 – odczyt – UWAGA: klon MPU-6050 – adres 0x72
				
					//Akcelerometr (IMU) MPU - 6050 / KLON!
//Adam Mańka (adam.manka(at)polsl.pl)

// Arduino Uno R3 + MPU6050-kompatybilny modul / klon
// Wariant dla modulu z WHO_AM_I = 0x72
//
// Biblioteka:
// MPU6050 by Electronic Cats
//
// Polaczenie:
// VCC -> 5V
// GND -> GND
// SDA -> A4
// SCL -> A5
// AD0 -> GND
// INT -> niepodlaczone

#include <Wire.h>
#include <MPU6050.h>
#include <math.h>

MPU6050 mpu;

const float ACC_SCALE_2G = 16384.0;   // LSB/g dla zakresu +/-2g
const float GYRO_SCALE_250 = 131.0;   // LSB/(deg/s) dla zakresu +/-250 deg/s
const float RAD_TO_DEG_LOCAL = 57.2957795;

float gyroOffsetX = 0.0;
float gyroOffsetY = 0.0;
float gyroOffsetZ = 0.0;

byte readReg(byte reg) {
  Wire.beginTransmission(0x68);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 1);

  if (Wire.available()) {
    return Wire.read();
  }

  return 0xFF;
}

void calibrateGyro() {
  Serial.println("Kalibracja zyroskopu...");
  Serial.println("Trzymaj modul nieruchomo.");

  const int samples = 300;

  long sumGX = 0;
  long sumGY = 0;
  long sumGZ = 0;

  int16_t axRaw, ayRaw, azRaw;
  int16_t gxRaw, gyRaw, gzRaw;

  for (int i = 0; i < samples; i++) {
    mpu.getMotion6(&axRaw, &ayRaw, &azRaw, &gxRaw, &gyRaw, &gzRaw);

    sumGX += gxRaw;
    sumGY += gyRaw;
    sumGZ += gzRaw;

    delay(5);
  }

  gyroOffsetX = (sumGX / (float)samples) / GYRO_SCALE_250;
  gyroOffsetY = (sumGY / (float)samples) / GYRO_SCALE_250;
  gyroOffsetZ = (sumGZ / (float)samples) / GYRO_SCALE_250;

  Serial.println("Kalibracja zakonczona.");
  Serial.print("Offset gyro [deg/s]: X=");
  Serial.print(gyroOffsetX, 3);
  Serial.print(" Y=");
  Serial.print(gyroOffsetY, 3);
  Serial.print(" Z=");
  Serial.println(gyroOffsetZ, 3);
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("MPU6050-kompatybilny modul - wariant WHO_AM_I = 0x72");

  Wire.begin();
  Wire.setClock(100000);   // bezpiecznie dla Arduino Uno R3

  byte who = readReg(0x75);

  Serial.print("WHO_AM_I = 0x");
  if (who < 16) Serial.print("0");
  Serial.println(who, HEX);

  mpu.initialize();

  Serial.print("testConnection(): ");
  if (mpu.testConnection()) {
    Serial.println("OK");
  } else {
    Serial.println("FAIL - nietypowy identyfikator, ale probujemy dalej");
  }

  // Konfiguracja jak dla MPU-6050
  mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2);
  mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250);
  mpu.setDLPFMode(MPU6050_DLPF_BW_20);

  delay(300);

  calibrateGyro();

  Serial.println("Odczyt danych:");
  Serial.println("ACC[g], |ACC|, GYRO[deg/s], Roll, Pitch, Temp");
  Serial.println();
}

void loop() {
  int16_t axRaw, ayRaw, azRaw;
  int16_t gxRaw, gyRaw, gzRaw;

  mpu.getMotion6(&axRaw, &ayRaw, &azRaw, &gxRaw, &gyRaw, &gzRaw);

  int16_t tempRaw = mpu.getTemperature();

  float ax = axRaw / ACC_SCALE_2G;
  float ay = ayRaw / ACC_SCALE_2G;
  float az = azRaw / ACC_SCALE_2G;

  float gx = gxRaw / GYRO_SCALE_250 - gyroOffsetX;
  float gy = gyRaw / GYRO_SCALE_250 - gyroOffsetY;
  float gz = gzRaw / GYRO_SCALE_250 - gyroOffsetZ;

  float accNorm = sqrt(ax * ax + ay * ay + az * az);

  float roll  = atan2(ay, az) * RAD_TO_DEG_LOCAL;
  float pitch = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG_LOCAL;

  // Uwaga: dla tego klona temperatura moze miec inny offset.
  // Traktujemy ja raczej jako wartosc kontrolna stabilnosci, nie jako dokladna temperature otoczenia.
  float tempC = tempRaw / 340.0 + 36.53;

  Serial.print("ACC[g] X=");
  Serial.print(ax, 3);
  Serial.print(" Y=");
  Serial.print(ay, 3);
  Serial.print(" Z=");
  Serial.print(az, 3);

  Serial.print(" | |ACC|=");
  Serial.print(accNorm, 3);

  Serial.print(" | GYRO[deg/s] X=");
  Serial.print(gx, 2);
  Serial.print(" Y=");
  Serial.print(gy, 2);
  Serial.print(" Z=");
  Serial.print(gz, 2);

  Serial.print(" | Roll=");
  Serial.print(roll, 1);
  Serial.print(" Pitch=");
  Serial.print(pitch, 1);

  Serial.print(" | Temp=");
  Serial.print(tempC, 1);

  Serial.print(" | WHO=0x");
  byte who = readReg(0x75);
  if (who < 16) Serial.print("0");
  Serial.println(who, HEX);

  delay(200);
}
				
			

Przykład 3 – Odczyt z kompasu (magnetometru)
W przykładzie wykorzystujemy: Arduino Kompas – GY-271 / HW-246

Podłączenie:
Arduino UNO
-> GY-271,

5V ->VCC, GND->GY-271 GND, A4 (SDA)-> GY-271 SDA, A5 (SCL)->SCL
DRDY -> nie podłączać

A. KOMPAS (magnetometr) – GY-271 / HW-246
				
					//KOMPAS GY-271 / HW-246
//Adam Mańka (adam.manka(at)polsl.pl)

// AM - GY-271 / HW-246, adres I2C 0x2C
// Program edukacyjny: RAW + gaussy + wstepna kalibracja + azymut

#include <Wire.h>                 // I2C
#include <Adafruit_QMC5883P.h>    // biblioteka dla QMC5883P / HP5883
#include <math.h>                 // atan2()

Adafruit_QMC5883P mag;            // obiekt magnetometru

const byte MAG_ADDR = 0x2C;       // adres I2C wykryty skanerem

// Wstepne offsety z pierwszego obrotu modulu
const float OFFSET_X = -1543.0;   // srodek zakresu X
const float OFFSET_Y =   -78.0;   // srodek zakresu Y
const float OFFSET_Z =  2460.0;   // srodek zakresu Z, tylko informacyjnie

void setup()
{
  Serial.begin(9600);             // port szeregowy 9600 baud
  delay(1000);                    // pauza na start monitora

  Wire.begin();                   // I2C dla Arduino Uno / Uno R4

  Serial.println("AM - kompas GY-271 / HW-246, wersja edukacyjna");
  Serial.println("RAW = dane surowe, Gs = gaussy, CAL = dane po odjeciu offsetu");
  Serial.println("Azymut liczony jest z CAL X i CAL Y.");
  Serial.println("Uwaga: kalibracja jest tylko wstepna, modul powinien lezec poziomo.");
  Serial.println();

  if (!mag.begin(MAG_ADDR))       // sprawdzenie, czy czujnik odpowiada
  {
    Serial.println("BLAD: nie znaleziono magnetometru pod adresem 0x2C.");
    while (1);
  }

  mag.setMode(QMC5883P_MODE_CONTINUOUS);     // pomiar ciagly
  mag.setODR(QMC5883P_ODR_50HZ);             // 50 pomiarow/s
  mag.setRange(QMC5883P_RANGE_8G);           // zakres +/- 8 gauss

  Serial.println("Czujnik wykryty. Obracaj modul powoli na stole.");
  Serial.println();
}

void loop()
{
  int16_t xRaw, yRaw, zRaw;       // surowe dane X/Y/Z
  float xG, yG, zG;               // dane w gaussach

  if (mag.isDataReady())          // czy jest nowy pomiar
  {
    mag.getRawMagnetic(&xRaw, &yRaw, &zRaw); // odczyt RAW
    mag.getGaussField(&xG, &yG, &zG);        // odczyt w gaussach

    float xCal = xRaw - OFFSET_X; // X po odjeciu offsetu
    float yCal = yRaw - OFFSET_Y; // Y po odjeciu offsetu
    float zCal = zRaw - OFFSET_Z; // Z po odjeciu offsetu, tylko pokazowo

    float azymut = atan2(yCal, xCal) * 180.0 / PI; // kat z CAL Y i CAL X
    if (azymut < 0) azymut += 360.0;               // zakres 0...360 stopni

    Serial.print("RAW X="); Serial.print(xRaw);        // surowa os X
    Serial.print(" Y="); Serial.print(yRaw);            // surowa os Y
    Serial.print(" Z="); Serial.print(zRaw);            // surowa os Z

    Serial.print(" | Gs X="); Serial.print(xG, 3);      // pole X w gaussach
    Serial.print(" Y="); Serial.print(yG, 3);           // pole Y w gaussach
    Serial.print(" Z="); Serial.print(zG, 3);           // pole Z w gaussach

    Serial.print(" | CAL X="); Serial.print(xCal, 0);   // X po korekcji
    Serial.print(" Y="); Serial.print(yCal, 0);         // Y po korekcji
    Serial.print(" Z="); Serial.print(zCal, 0);         // Z po korekcji

    Serial.print(" | Azymut="); Serial.print(azymut, 1); // kierunek z danych CAL
    Serial.println(" deg");
  }

  delay(500);                    // wolniejszy wydruk dla 9600 baud
}
				
			
B. KOMPAS (magnetometr) – GY-271 / HW-246 – Wersja z kalibracją
				
					//KOMPAS GY-271 / HW-246
//Adam Mańka (adam.manka(at)polsl.pl)

// AM - GY-271 / HW-246, adres I2C 0x2C
// Program nr 2: magnetometr z automatyczna kalibracja po starcie
// Czujnik: QMC5883P / HP5883, biblioteka: Adafruit_QMC5883P

#include <Wire.h>                 // I2C
#include <Adafruit_QMC5883P.h>    // biblioteka dla QMC5883P / HP5883
#include <math.h>                 // atan2()

Adafruit_QMC5883P mag;            // obiekt magnetometru

const byte MAG_ADDR = 0x2C;       // adres I2C wykryty skanerem
const unsigned long CAL_TIME_MS = 30000; // czas kalibracji: 30 sekund

int16_t minX, maxX;               // minimalna i maksymalna wartosc X
int16_t minY, maxY;               // minimalna i maksymalna wartosc Y
int16_t minZ, maxZ;               // minimalna i maksymalna wartosc Z

float offsetX = 0.0;              // przesuniecie srodka osi X
float offsetY = 0.0;              // przesuniecie srodka osi Y
float offsetZ = 0.0;              // przesuniecie srodka osi Z

float scaleX = 1.0;               // wspolczynnik skali osi X
float scaleY = 1.0;               // wspolczynnik skali osi Y
float scaleZ = 1.0;               // wspolczynnik skali osi Z

void setup()
{
  Serial.begin(9600);             // port szeregowy 9600 baud
  delay(1000);                    // pauza na start monitora portu

  Wire.begin();                   // I2C dla Arduino Uno / Uno R4

  Serial.println();
  Serial.println("AM - kompas GY-271 / HW-246, wersja z kalibracja");
  Serial.println("RAW = dane surowe, Gs = gaussy, CAL = dane po kalibracji");
  Serial.println("Azymut liczony jest z CAL X i CAL Y.");
  Serial.println();

  if (!mag.begin(MAG_ADDR))       // sprawdzenie, czy czujnik odpowiada
  {
    Serial.println("BLAD: nie znaleziono magnetometru pod adresem 0x2C.");
    while (1);
  }

  mag.setMode(QMC5883P_MODE_CONTINUOUS);     // pomiar ciagly
  mag.setODR(QMC5883P_ODR_50HZ);             // 50 pomiarow/s
  mag.setOSR(QMC5883P_OSR_4);                // nadprobkowanie
  mag.setDSR(QMC5883P_DSR_2);                // dodatkowe usrednianie
  mag.setRange(QMC5883P_RANGE_8G);           // zakres +/- 8 gauss
  mag.setSetResetMode(QMC5883P_SETRESET_ON); // stabilizacja pomiaru

  Serial.println("Czujnik wykryty.");
  Serial.println("Za chwile rozpocznie sie kalibracja.");
  Serial.println();

  calibrateMagnetometer();        // automatyczna kalibracja po starcie

  Serial.println();
  Serial.println("Praca normalna.");
  Serial.println("Poloz modul poziomo. Obracaj go i obserwuj Azymut.");
  Serial.println();
}

void loop()
{
  int16_t xRaw, yRaw, zRaw;       // surowe dane X/Y/Z
  float xG, yG, zG;               // dane w gaussach

  if (mag.isDataReady())          // czy jest nowy pomiar
  {
    bool okRaw = mag.getRawMagnetic(&xRaw, &yRaw, &zRaw); // odczyt RAW
    bool okG = mag.getGaussField(&xG, &yG, &zG);          // odczyt w gaussach

    if (okRaw && okG)
    {
      float xCal = (xRaw - offsetX) * scaleX; // X po odjeciu offsetu i korekcji skali
      float yCal = (yRaw - offsetY) * scaleY; // Y po odjeciu offsetu i korekcji skali
      float zCal = (zRaw - offsetZ) * scaleZ; // Z po kalibracji, pokazowo

      float azymut = atan2(yCal, xCal) * 180.0 / PI; // kat z osi X/Y po kalibracji
      if (azymut < 0) azymut += 360.0;               // zakres 0...360 stopni

      Serial.print("RAW X="); Serial.print(xRaw);       // surowa os X
      Serial.print(" Y="); Serial.print(yRaw);           // surowa os Y
      Serial.print(" Z="); Serial.print(zRaw);           // surowa os Z

      Serial.print(" | Gs X="); Serial.print(xG, 3);     // pole X w gaussach
      Serial.print(" Y="); Serial.print(yG, 3);          // pole Y w gaussach
      Serial.print(" Z="); Serial.print(zG, 3);          // pole Z w gaussach

      Serial.print(" | CAL X="); Serial.print(xCal, 0);  // X po kalibracji
      Serial.print(" Y="); Serial.print(yCal, 0);        // Y po kalibracji
      Serial.print(" Z="); Serial.print(zCal, 0);        // Z po kalibracji

      Serial.print(" | Azymut="); Serial.print(azymut, 1); // kierunek z CAL X/Y
      Serial.println(" deg | po kalibracji, bez kompensacji przechylu");
    }
  }

  delay(700);                     // wolniejszy wydruk dla 9600 baud
}

void calibrateMagnetometer()
{
  minX = 32767;                   // startowa wartosc minimum X
  minY = 32767;                   // startowa wartosc minimum Y
  minZ = 32767;                   // startowa wartosc minimum Z

  maxX = -32768;                  // startowa wartosc maksimum X
  maxY = -32768;                  // startowa wartosc maksimum Y
  maxZ = -32768;                  // startowa wartosc maksimum Z

  Serial.println("KALIBRACJA 30 s");
  Serial.println("Obracaj modul powoli poziomo na stole.");
  Serial.println("Najlepiej wykonaj pelny obrot 360 stopni kilka razy.");
  Serial.println();

  unsigned long startTime = millis();       // czas startu kalibracji
  unsigned long lastPrint = 0;              // czas ostatniego komunikatu

  while (millis() - startTime < CAL_TIME_MS)
  {
    int16_t xRaw, yRaw, zRaw;

    if (mag.isDataReady())
    {
      if (mag.getRawMagnetic(&xRaw, &yRaw, &zRaw))
      {
        if (xRaw < minX) minX = xRaw;       // aktualizacja minimum X
        if (xRaw > maxX) maxX = xRaw;       // aktualizacja maksimum X

        if (yRaw < minY) minY = yRaw;       // aktualizacja minimum Y
        if (yRaw > maxY) maxY = yRaw;       // aktualizacja maksimum Y

        if (zRaw < minZ) minZ = zRaw;       // aktualizacja minimum Z
        if (zRaw > maxZ) maxZ = zRaw;       // aktualizacja maksimum Z
      }
    }

    if (millis() - lastPrint > 1000)
    {
      lastPrint = millis();

      int secondsLeft = (CAL_TIME_MS - (millis() - startTime)) / 1000;

      Serial.print("Kalibracja, zostalo ");
      Serial.print(secondsLeft);
      Serial.print(" s | X=");
      Serial.print(minX); Serial.print(".."); Serial.print(maxX);
      Serial.print(" Y=");
      Serial.print(minY); Serial.print(".."); Serial.print(maxY);
      Serial.print(" Z=");
      Serial.print(minZ); Serial.print(".."); Serial.print(maxZ);
      Serial.println();
    }

    delay(20);
  }

  offsetX = (maxX + minX) / 2.0;             // srodek zakresu X
  offsetY = (maxY + minY) / 2.0;             // srodek zakresu Y
  offsetZ = (maxZ + minZ) / 2.0;             // srodek zakresu Z

  float radiusX = (maxX - minX) / 2.0;       // polowa zakresu X
  float radiusY = (maxY - minY) / 2.0;       // polowa zakresu Y
  float radiusZ = (maxZ - minZ) / 2.0;       // polowa zakresu Z

  float avgRadiusXY = (radiusX + radiusY) / 2.0; // srednia amplituda X/Y

  if (radiusX > 1.0) scaleX = avgRadiusXY / radiusX; else scaleX = 1.0;
  if (radiusY > 1.0) scaleY = avgRadiusXY / radiusY; else scaleY = 1.0;
  if (radiusZ > 1.0) scaleZ = 1.0; else scaleZ = 1.0; // Z zostawiamy pokazowo

  Serial.println();
  Serial.println("KONIEC KALIBRACJI");
  Serial.print("offsetX="); Serial.print(offsetX, 1);
  Serial.print(" offsetY="); Serial.print(offsetY, 1);
  Serial.print(" offsetZ="); Serial.println(offsetZ, 1);

  Serial.print("scaleX="); Serial.print(scaleX, 3);
  Serial.print(" scaleY="); Serial.print(scaleY, 3);
  Serial.print(" scaleZ="); Serial.println(scaleZ, 3);

  Serial.println();
  Serial.println("Te wartosci zostaly policzone automatycznie z min/max.");
}
				
			

Przykład 4 – Podłączenie radia RC FS-iA6B (FlySky)
W przykładzie wykorzystujemy: Arduino Radio RC – FS-iA6B (FlySky)

Podłączenie:
Arduino UNO -> odbiornik RC FS-iA6B

GND->iBUS GND (pomiędzy CH4 i CH5)
VCC->iBUS VCC (pomiędzy CH5 i CH6)
D08->iBUS sygnal (pomiędzy CH6 a B/VCC)

				
					//Radio RC FS iA6B - iBUS
//Adam Mańka (adam.manka(at)polsl.pl)

#include <AltSoftSerial.h>

AltSoftSerial ibus;

// Piny LED
const byte LED_PWM = 11;   // CH3 -> jasność PWM / alarm
const byte LED_CH5 = 12;   // CH5 -> OFF / BLINK / ON / alarm
const byte LED_CH6 = 13;   // CH6 -> OFF / ON / alarm

// iBUS
const byte FRAME_SIZE = 32;
const byte CHANNELS = 14;

byte frame[FRAME_SIZE];
byte idx = 0;

uint16_t ch[CHANNELS];

unsigned long ok = 0;
unsigned long err = 0;

unsigned long lastGoodMs = 0;
unsigned long lastPrintMs = 0;

const unsigned long PRINT_MS = 500;
const unsigned long TIMEOUT_MS = 700;

// ------------------------------------------------------------
// Funkcje pomocnicze
// ------------------------------------------------------------
uint16_t rcLimit(uint16_t v)
{
  if (v < 1000) return 1000;
  if (v > 2000) return 2000;
  return v;
}

byte rcToPwm(uint16_t v)
{
  v = rcLimit(v);
  return map(v, 1000, 2000, 0, 255);
}

byte rc3pos(uint16_t v)
{
  if (v < 1300) return 0;
  if (v < 1700) return 1;
  return 2;
}

bool linkOk()
{
  if (ok == 0) return false;
  return (millis() - lastGoodMs <= TIMEOUT_MS);
}

// ------------------------------------------------------------
// Dekoder iBUS
// ------------------------------------------------------------
void readByte(byte b)
{
  // Początek ramki iBUS: 0x20 0x40
  if (idx == 0)
  {
    if (b == 0x20)
    {
      frame[0] = b;
      idx = 1;
    }
    return;
  }

  if (idx == 1)
  {
    if (b == 0x40)
    {
      frame[1] = b;
      idx = 2;
    }
    else if (b == 0x20)
    {
      frame[0] = b;
      idx = 1;
    }
    else
    {
      idx = 0;
    }
    return;
  }

  frame[idx] = b;
  idx++;

  if (idx < FRAME_SIZE) return;

  idx = 0;

  // Sprawdzenie sumy kontrolnej
  uint16_t sum = 0xFFFF;

  for (byte i = 0; i < 30; i++)
  {
    sum -= frame[i];
  }

  uint16_t rxSum = frame[30] | ((uint16_t)frame[31] << 8);

  if (sum != rxSum)
  {
    err++;
    return;
  }

  // Odczyt kanałów
  for (byte i = 0; i < CHANNELS; i++)
  {
    ch[i] = frame[2 + i * 2] | ((uint16_t)frame[3 + i * 2] << 8);
  }

  ok++;
  lastGoodMs = millis();
}

// ------------------------------------------------------------
// Odbiór iBUS
// ------------------------------------------------------------
void readIbus()
{
  while (ibus.available() > 0)
  {
    readByte(ibus.read());
  }
}

// ------------------------------------------------------------
// Alarm braku ramek iBUS
// ------------------------------------------------------------
void alarmBlink()
{
  bool s = (millis() / 250) % 2;

  analogWrite(LED_PWM, s ? 255 : 0);
  digitalWrite(LED_CH5, s ? HIGH : LOW);
  digitalWrite(LED_CH6, s ? HIGH : LOW);
}

// ------------------------------------------------------------
// Sterowanie LED
// ------------------------------------------------------------
void updateLeds()
{
  if (!linkOk())
  {
    alarmBlink();
    return;
  }

  // CH3 -> PWM na D11
  analogWrite(LED_PWM, rcToPwm(ch[2]));

  // CH5 -> D12: OFF / BLINK / ON
  byte p = rc3pos(ch[4]);

  if (p == 0)
  {
    digitalWrite(LED_CH5, LOW);
  }
  else if (p == 1)
  {
    digitalWrite(LED_CH5, (millis() / 250) % 2);
  }
  else
  {
    digitalWrite(LED_CH5, HIGH);
  }

  // CH6 -> D13
  digitalWrite(LED_CH6, ch[5] > 1500);
}

// ------------------------------------------------------------
// Wydruk diagnostyczny
// ------------------------------------------------------------
void printData()
{
  if (!linkOk())
  {
    Serial.print(F("BRAK iBUS / BRAK RAMEK | OK="));
    Serial.print(ok);
    Serial.print(F(" ERR="));
    Serial.println(err);
    return;
  }

  Serial.print(F("CH1=")); Serial.print(ch[0]);
  Serial.print(F(" CH2=")); Serial.print(ch[1]);
  Serial.print(F(" CH3=")); Serial.print(ch[2]);
  Serial.print(F(" CH4=")); Serial.print(ch[3]);
  Serial.print(F(" CH5=")); Serial.print(ch[4]);
  Serial.print(F(" CH6=")); Serial.print(ch[5]);

  Serial.print(F(" | PWM11="));
  Serial.print(rcToPwm(ch[2]));

  Serial.print(F(" | OK="));
  Serial.print(ok);

  Serial.print(F(" ERR="));
  Serial.print(err);

  Serial.print(F(" AGE="));
  Serial.print(millis() - lastGoodMs);
  Serial.println(F("ms"));
}

// ------------------------------------------------------------
// SETUP
// ------------------------------------------------------------
void setup()
{
  Serial.begin(115200);
  ibus.begin(115200);

  pinMode(LED_PWM, OUTPUT);
  pinMode(LED_CH5, OUTPUT);
  pinMode(LED_CH6, OUTPUT);

  analogWrite(LED_PWM, 0);
  digitalWrite(LED_CH5, LOW);
  digitalWrite(LED_CH6, LOW);

  for (byte i = 0; i < CHANNELS; i++)
  {
    ch[i] = 1500;
  }

  Serial.println(F("FS-iA6B iBUS UNO - alarm braku ramek"));
}

// ------------------------------------------------------------
// LOOP
// ------------------------------------------------------------
void loop()
{
  readIbus();
  updateLeds();

  if (millis() - lastPrintMs >= PRINT_MS)
  {
    lastPrintMs = millis();
    printData();
  }
}
				
			

Przykład 5 – Podłączenie silników drona BLDC przez ESC (Electronic Speed Controler)
W przykładzie wykorzystujemy: Arduino  i ESC 40A – oraz zasilacz laboratoryjny 11V

Podłączenie:
Arduino UNO -> ESC 

A. Test silnika drona ESC (bez zadajnika z potencjometru)
				
					//Silnik dronowy BLDC - ESC (bez potencjometru)
//Adam Mańka (adam.manka(at)polsl.pl)

#include <Servo.h>

Servo esc;

const int ESC_PIN = 9;

const int THROTTLE_MIN = 1000;      // minimum / STOP / uzbrojenie
const int THROTTLE_MAX = 2000;      // maksimum ESC
const int TEST_MAX = 1300;          // maksimum używane w teście

void setup()
{
  Serial.begin(9600);
  esc.attach(ESC_PIN);

  Serial.println("Test BLDC przez ESC");
  Serial.println("1000 us = minimum / STOP");
  Serial.println("2000 us = maksimum ESC");
  Serial.println("Test ograniczony do 1300 us");
  Serial.println("UWAGA: smiglo musi byc zdjete.");

  esc.writeMicroseconds(THROTTLE_MIN);
  Serial.println("Uzbrajanie ESC: 1000 us przez 3 s");
  delay(3000);
}

void loop()
{
  for (int us = THROTTLE_MIN; us <= TEST_MAX; us += 10)
  {
    esc.writeMicroseconds(us);
    Serial.println(us);
    delay(150);
  }

  delay(1000);

  for (int us = TEST_MAX; us >= THROTTLE_MIN; us -= 10)
  {
    esc.writeMicroseconds(us);
    Serial.println(us);
    delay(150);
  }

  esc.writeMicroseconds(THROTTLE_MIN);
  delay(2000);
}
				
			
B. Test silnika drona ESC (z zadajnikiem z potencjometru)
				
					//Silnik dronowy BLDC - ESC (z potencjometrem)
//Adam Mańka (adam.manka(at)polsl.pl)

#include <Servo.h>

Servo esc;

const int ESC_PIN = 9;
const int POT_PIN = A0;

const int THROTTLE_MIN = 1000;  // minimum / STOP
const int THROTTLE_ESC_MAX = 2000;  // maksimum ESC
const int THROTTLE_TEST_MAX = 1300; // maksimum w tym teście

unsigned long lastPrint = 0;

void setup()
{
  Serial.begin(9600);
  esc.attach(ESC_PIN);

  Serial.println("Kod B - ESC sterowany potencjometrem");
  Serial.println("1000 us = minimum / STOP");
  Serial.println("2000 us = maksimum ESC");
  Serial.println("Test ograniczony do 1300 us");
  Serial.println("Przed zasileniem ESC ustaw potencjometr na minimum.");
  Serial.println("UWAGA: smiglo musi byc zdjete.");
}

void loop()
{
  int pot = analogRead(POT_PIN);  // 0...1023

  int throttle = map(pot, 0, 1023, THROTTLE_MIN, THROTTLE_TEST_MAX);
  throttle = constrain(throttle, THROTTLE_MIN, THROTTLE_TEST_MAX);

  // mała strefa bezpieczeństwa przy samym dole potencjometru
  if (pot < 20)
  {
    throttle = THROTTLE_MIN;
  }

  esc.writeMicroseconds(throttle);

  if (millis() - lastPrint > 300)
  {
    lastPrint = millis();

    Serial.print("POT=");
    Serial.print(pot);
    Serial.print("  Gaz=");
    Serial.print(throttle);
    Serial.println(" us");
  }
}
				
			

Przykład 6 – Wyświetlacz 4×16 (oscyloskop)

Podłączenie:
Arduino UNO -> wyświetlacz 20×4 przez I2C tj. zasilanie oraz A4 i A5

Pomiar napięcia – A0 i GND

				
					// Adam Manka
// LCD I2C 20x4 + mini oscyloskop A0
// Logo Politechniki Slaskiej, potem oscyloskop

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

const byte AIN = A0;
const int VREF = 5000;              // mV
const unsigned long DT = 120;       // szybkosc wykresu [ms]

int y[20];
unsigned long t0 = 0;

// Ś
byte ch_S[8] = {
  B00010, B01110, B10000, B10000,
  B01110, B00001, B00001, B11110
};

// ą
byte ch_a[8] = {
  B00000, B00000, B01110, B00001,
  B01111, B10001, B01111, B00010
};

// ż
byte ch_z[8] = {
  B00100, B00000, B11111, B00010,
  B00100, B01000, B11111, B00000
};

// ł
byte ch_l[8] = {
  B01100, B00100, B00100, B01100,
  B00100, B00100, B01110, B00000
};

// gorna linia
byte ch_top[8] = {
  B11111, B00000, B00000, B00000,
  B00000, B00000, B00000, B00000
};

void logo()
{
  lcd.clear();

  lcd.setCursor(0, 0);
  lcd.print("Politechnika ");
  lcd.write(byte(0));      // Ś
  lcd.print("l");
  lcd.write(byte(1));      // ą
  lcd.print("ska");

  lcd.setCursor(0, 1);
  lcd.print("Wydzia");
  lcd.write(byte(3));      // ł
  lcd.print(" Transportu");

  lcd.setCursor(0, 2);
  lcd.print("i In");
  lcd.write(byte(2));      // ż
  lcd.print("ynierii");

  lcd.setCursor(0, 3);
  lcd.print("Lotniczej");

  delay(5000);
  lcd.clear();
}

int adcNaMv(int adc)
{
  return (long)adc * VREF / 1023;
}

void pokazNapiecie(int adc)
{
  int mv = adcNaMv(adc);
  int setne = (mv % 1000) / 10;

  lcd.setCursor(0, 0);
  lcd.print("U=");
  lcd.print(mv / 1000);
  lcd.print(".");
  if (setne < 10) lcd.print("0");
  lcd.print(setne);
  lcd.print("V ");
}

void wyslijSerial(int adc)
{
  int mv = adcNaMv(adc);
  int setne = (mv % 1000) / 10;

  Serial.print(mv / 1000);
  Serial.print(".");
  if (setne < 10) Serial.print("0");
  Serial.println(setne);
}

void znakWykresu(byte w)
{
  if (w == 0) lcd.write(byte(4));    // wysoka linia
  else if (w == 3) lcd.print("_");   // dolna linia
  else lcd.print("-");               // linia srodkowa
}

void rysujWiersz(byte w, byte odKolumny)
{
  lcd.setCursor(odKolumny, w);

  for (byte x = odKolumny; x < 20; x++)
  {
    if (y[x] == w) znakWykresu(w);
    else lcd.print(" ");
  }
}

void rysujWykres()
{
  // Pierwszy wiersz: od kolumny 9, żeby nie kasować U=2.50V
  rysujWiersz(0, 9);

  // Pozostale wiersze: cala szerokosc
  rysujWiersz(1, 0);
  rysujWiersz(2, 0);
  rysujWiersz(3, 0);
}

void setup()
{
  Serial.begin(9600);

  lcd.init();
  lcd.backlight();

  lcd.createChar(0, ch_S);
  lcd.createChar(1, ch_a);
  lcd.createChar(2, ch_z);
  lcd.createChar(3, ch_l);
  lcd.createChar(4, ch_top);

  for (byte i = 0; i < 20; i++) y[i] = 3;

  logo();
}

void loop()
{
  if (millis() - t0 >= DT)
  {
    t0 = millis();

    int adc = analogRead(AIN);
    int nowyY = map(adc, 0, 1023, 3, 0);

    for (byte i = 0; i < 19; i++) y[i] = y[i + 1];
    y[19] = nowyY;

    rysujWykres();
    pokazNapiecie(adc);
    wyslijSerial(adc);
  }
}
				
			

Przykład 7 – PID – huśtawka aerodynamiczna (2 silniki na osi)

Podłączenie:
Arduino UNO -> czujnik IMU -> L298N

				
					// Adam Mańka (adam.manka(at)polsl.pl
// Rownowaznia aerodynamiczna - PID
// Arduino Uno + L298N + MPU6050
// Wersja dydaktyczna uproszczona
//
// Biblioteka: MPU6050 by Electronic Cats
//
// Komendy Serial Monitor 115200, Newline:
// a       - uzbroj silniki
// s       - stop
// p4.0    - Kp
// i0.01   - Ki
// d1.0    - Kd
// b100    - bazowy PWM
// m60     - max korekta PID
// sp0     - kat zadany
// sg1     - normalny znak sterowania
// sg-1    - odwroc znak sterowania
// z       - wyzeruj aktualny kat
// ?       - pokaz komendy

#include <Wire.h>
#include <MPU6050.h>
#include <math.h>

MPU6050 mpu;

// ===== Piny L298N =====
const byte ENA = 5;   // PWM - lewy silnik
const byte IN1 = 7;
const byte IN2 = 8;

const byte ENB = 6;   // PWM - prawy silnik
const byte IN3 = 9;
const byte IN4 = 10;

// ===== Skale MPU6050 =====
const float ACC_SCALE = 16384.0;   // +/-2g
const float GYRO_SCALE = 131.0;    // +/-250 deg/s
const float RAD_TO_DEG_LOCAL = 57.2957795;

// ===== PID =====
float Kp = 3.0;
float Ki = 0.0;
float Kd = 0.8;

float setAngle = 0.0;       // kat zadany [deg]
float basePWM = 90.0;       // podstawowa predkosc silnikow
float maxCorr = 50.0;       // maksymalna korekta PID
int controlSign = 1;        // jesli ucieka, wpisz sg-1

// ===== Pomiar kata =====
float angle = 0.0;
float lastAngle = 0.0;
float zeroRoll = 0.0;
float gyroOffsetX = 0.0;

const float alpha = 0.96;   // filtr komplementarny
const int LOOP_MS = 10;     // petla 100 Hz

// ===== Zmienne PID =====
float integral = 0.0;
float pidOut = 0.0;

bool armed = false;

unsigned long lastLoopMs = 0;
unsigned long lastMicros = 0;
unsigned long lastPrintMs = 0;

// ===== Komendy z Serial Monitor =====
char cmd[20];
byte cmdIndex = 0;

// ------------------------------------------------------------

void printCommands()
{
  Serial.println();
  Serial.println("=== KOMENDY ===");
  Serial.println("a       - uzbroj silniki");
  Serial.println("s       - stop");
  Serial.println("p4.0    - ustaw Kp");
  Serial.println("i0.01   - ustaw Ki");
  Serial.println("d1.0    - ustaw Kd");
  Serial.println("b100    - bazowy PWM silnikow");
  Serial.println("m60     - maksymalna korekta PID");
  Serial.println("sp0     - kat zadany");
  Serial.println("sg1     - normalny znak sterowania");
  Serial.println("sg-1    - odwroc znak sterowania");
  Serial.println("z       - aktualny kat jako zero");
  Serial.println("?       - pokaz komendy");
  Serial.println();
}

// ------------------------------------------------------------

byte readReg(byte reg)
{
  Wire.beginTransmission(0x68);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 1);

  if (Wire.available()) return Wire.read();
  return 0xFF;
}

// ------------------------------------------------------------

float calcRollDeg(int16_t ayRaw, int16_t azRaw)
{
  float ay = ayRaw / ACC_SCALE;
  float az = azRaw / ACC_SCALE;

  return atan2(ay, az) * RAD_TO_DEG_LOCAL;
}

// ------------------------------------------------------------

void setMotor(byte en, byte inA, byte inB, int pwm)
{
  pwm = constrain(pwm, 0, 255);

  if (pwm == 0)
  {
    digitalWrite(inA, LOW);
    digitalWrite(inB, LOW);
    analogWrite(en, 0);
  }
  else
  {
    digitalWrite(inA, HIGH);
    digitalWrite(inB, LOW);
    analogWrite(en, pwm);
  }
}

// ------------------------------------------------------------

void setMotors(int leftPWM, int rightPWM)
{
  setMotor(ENA, IN1, IN2, leftPWM);
  setMotor(ENB, IN3, IN4, rightPWM);
}

// ------------------------------------------------------------

void stopMotors()
{
  setMotors(0, 0);
}

// ------------------------------------------------------------

void countdownBeforeCalibration()
{
  Serial.println("Ustaw rownowaznie poziomo.");
  Serial.println("Kalibracja rozpocznie sie za:");

  for (int s = 5; s > 0; s--)
  {
    Serial.print(s);
    Serial.println(" s");
    delay(1000);
  }

  Serial.println("Start kalibracji...");
}

// ------------------------------------------------------------

void calibrateMPU()
{
  const int calibTimeSec = 5;

  long sumGX = 0;
  float sumRoll = 0.0;
  int samples = 0;

  int16_t axRaw, ayRaw, azRaw;
  int16_t gxRaw, gyRaw, gzRaw;

  unsigned long startTime = millis();
  unsigned long endTime = startTime + calibTimeSec * 1000UL;

  int lastShownSecond = -1;

  while (millis() < endTime)
  {
    mpu.getMotion6(&axRaw, &ayRaw, &azRaw, &gxRaw, &gyRaw, &gzRaw);

    sumGX += gxRaw;
    sumRoll += calcRollDeg(ayRaw, azRaw);
    samples++;

    int secondsLeft = (endTime - millis() + 999) / 1000;

    if (secondsLeft != lastShownSecond)
    {
      lastShownSecond = secondsLeft;
      Serial.print("Do konca kalibracji: ");
      Serial.print(secondsLeft);
      Serial.println(" s");
    }

    delay(5);
  }

  gyroOffsetX = (sumGX / (float)samples) / GYRO_SCALE;
  zeroRoll = sumRoll / (float)samples;

  angle = 0.0;
  lastAngle = 0.0;
  integral = 0.0;

  Serial.println("Kalibracja zakonczona.");
  Serial.print("Liczba probek: ");
  Serial.println(samples);
  Serial.print("gyroOffsetX = ");
  Serial.println(gyroOffsetX, 4);
  Serial.print("zeroRoll = ");
  Serial.println(zeroRoll, 2);
  Serial.println();
}

// ------------------------------------------------------------

void printSettings()
{
  Serial.print("Kp=");
  Serial.print(Kp);
  Serial.print(" Ki=");
  Serial.print(Ki);
  Serial.print(" Kd=");
  Serial.print(Kd);
  Serial.print(" basePWM=");
  Serial.print(basePWM);
  Serial.print(" maxCorr=");
  Serial.print(maxCorr);
  Serial.print(" setAngle=");
  Serial.print(setAngle);
  Serial.print(" sign=");
  Serial.println(controlSign);
}

// ------------------------------------------------------------

void parseCommand(char *c)
{
  if (strcmp(c, "a") == 0)
  {
    armed = true;
    integral = 0.0;
    lastAngle = angle;
    Serial.println("UZBROJONO");
  }
  else if (strcmp(c, "s") == 0)
  {
    armed = false;
    stopMotors();
    integral = 0.0;
    Serial.println("STOP");
  }
  else if (strcmp(c, "z") == 0)
  {
    zeroRoll += angle;
    angle = 0.0;
    lastAngle = 0.0;
    integral = 0.0;
    Serial.println("Aktualny kat ustawiony jako zero.");
  }
  else if (strncmp(c, "sp", 2) == 0)
  {
    setAngle = atof(c + 2);
  }
  else if (strncmp(c, "sg", 2) == 0)
  {
    int v = atoi(c + 2);
    controlSign = (v < 0) ? -1 : 1;
  }
  else if (c[0] == 'p')
  {
    Kp = atof(c + 1);
  }
  else if (c[0] == 'i')
  {
    Ki = atof(c + 1);
  }
  else if (c[0] == 'd')
  {
    Kd = atof(c + 1);
  }
  else if (c[0] == 'b')
  {
    basePWM = constrain(atof(c + 1), 0, 255);
  }
  else if (c[0] == 'm')
  {
    maxCorr = constrain(atof(c + 1), 0, 255);
  }
  else if (c[0] == '?')
  {
    printCommands();
  }

  printSettings();
}

// ------------------------------------------------------------

void handleSerial()
{
  while (Serial.available())
  {
    char ch = Serial.read();

    if (ch == '\n' || ch == '\r')
    {
      if (cmdIndex > 0)
      {
        cmd[cmdIndex] = '\0';
        parseCommand(cmd);
        cmdIndex = 0;
      }
    }
    else
    {
      if (cmdIndex < sizeof(cmd) - 1)
      {
        cmd[cmdIndex++] = ch;
      }
    }
  }
}

// ------------------------------------------------------------

void setup()
{
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);

  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);

  stopMotors();

  Serial.begin(115200);
  delay(1000);

  Serial.println("Rownowaznia aerodynamiczna - PID");
  Serial.println("Arduino Uno + L298N + MPU6050");

  Wire.begin();
  Wire.setClock(100000);

  byte who = readReg(0x75);
  Serial.print("WHO_AM_I = 0x");
  if (who < 16) Serial.print("0");
  Serial.println(who, HEX);

  mpu.initialize();

  Serial.print("testConnection(): ");
  if (mpu.testConnection())
  {
    Serial.println("OK");
  }
  else
  {
    Serial.println("FAIL - ale probujemy dalej");
  }

  mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2);
  mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250);
  mpu.setDLPFMode(MPU6050_DLPF_BW_20);

  printCommands();

  countdownBeforeCalibration();
  calibrateMPU();

  Serial.println("Gotowe.");
  Serial.println("Najpierw sprawdz wykres kata bez silnikow.");
  Serial.println("Potem wpisz: a");
  Serial.println();

  lastLoopMs = millis();
  lastMicros = micros();
}

// ------------------------------------------------------------

void loop()
{
  handleSerial();

  unsigned long nowMs = millis();

  if (nowMs - lastLoopMs < LOOP_MS) return;
  lastLoopMs = nowMs;

  unsigned long nowUs = micros();
  float dt = (nowUs - lastMicros) / 1000000.0;
  lastMicros = nowUs;

  if (dt <= 0.0 || dt > 0.1)
  {
    dt = LOOP_MS / 1000.0;
  }

  int16_t axRaw, ayRaw, azRaw;
  int16_t gxRaw, gyRaw, gzRaw;

  mpu.getMotion6(&axRaw, &ayRaw, &azRaw, &gxRaw, &gyRaw, &gzRaw);

  float accRoll = calcRollDeg(ayRaw, azRaw) - zeroRoll;
  float gyroX = gxRaw / GYRO_SCALE - gyroOffsetX;

  // Filtr komplementarny
  angle = alpha * (angle + gyroX * dt) + (1.0 - alpha) * accRoll;

  float error = setAngle - angle;
  float dAngle = (angle - lastAngle) / dt;

  if (!armed)
  {
    pidOut = 0.0;
    integral = 0.0;
    stopMotors();
  }
  else
  {
    if (abs(angle) > 45.0)
    {
      armed = false;
      stopMotors();
      Serial.println("STOP: zbyt duzy kat");
      return;
    }

    integral += error * dt;
    integral = constrain(integral, -100.0, 100.0);

    pidOut = Kp * error + Ki * integral - Kd * dAngle;
    pidOut = constrain(pidOut, -maxCorr, maxCorr);

    int leftPWM  = constrain((int)(basePWM + controlSign * pidOut), 0, 255);
    int rightPWM = constrain((int)(basePWM - controlSign * pidOut), 0, 255);

    setMotors(leftPWM, rightPWM);
  }

  lastAngle = angle;

  if (nowMs - lastPrintMs >= 100)
  {
    lastPrintMs = nowMs;

    Serial.print("kat:");
    Serial.print(angle, 2);

    Serial.print(" zad:");
    Serial.print(setAngle, 2);

    Serial.print(" pid:");
    Serial.print(pidOut, 2);

    Serial.print(" gx:");
    Serial.print(gyroX, 2);

    Serial.print(" armed:");
    Serial.println(armed ? 1 : 0);
  }
}
				
			
Nie ponosimy odpowiedzialności za ewentualne szkody spowodowane wykorzystaniem materiałów tu zawartych.
Adam Mańka - adam.manka(znak)polsl.pl

© Politechnika Śląska

Polityka prywatności

Całkowitą odpowiedzialność za poprawność, aktualność i zgodność z przepisami prawa materiałów publikowanych za pośrednictwem serwisu internetowego Politechniki Śląskiej ponoszą ich autorzy - jednostki organizacyjne, w których materiały informacyjne wytworzono. Prowadzenie: Centrum Informatyczne Politechniki Śląskiej (www@polsl.pl)

Deklaracja dostępności

„E-Politechnika Śląska - utworzenie platformy elektronicznych usług publicznych Politechniki Śląskiej”

Fundusze Europejskie
Fundusze Europejskie
Fundusze Europejskie
Fundusze Europejskie