This shows you the differences between two versions of the page.
pm:prj2024:vstoica:alexandru.micu1107 [2024/05/26 22:30] alexandru.micu1107 |
pm:prj2024:vstoica:alexandru.micu1107 [2024/05/30 08:46] (current) alexandru.micu1107 |
||
---|---|---|---|
Line 48: | Line 48: | ||
<note tip> | <note tip> | ||
{{https://i.imgur.com/3iUhPRu.jpeg?600x600}} | {{https://i.imgur.com/3iUhPRu.jpeg?600x600}} | ||
+ | |||
-Arduino UNO Rev3, ATmega328P | -Arduino UNO Rev3, ATmega328P | ||
| | ||
Line 64: | Line 65: | ||
- | <note tip> | ||
{{https://i.imgur.com/ArHAzHj.png}} | {{https://i.imgur.com/ArHAzHj.png}} | ||
- | Descrierea codului aplicaţiei (firmware): | + | |
- | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | *Medii de dezvoltare: Arduino IDE, Processing |
- | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | |
- | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | libs: |
- | * (etapa 3) surse şi funcţii implementate | + | github.com/bolderflight/invensense-imu |
- | </note> | + | github.com/adafruit/Adafruit_SSD1306 |
+ | github.com/adafruit/Adafruit_BusIO | ||
+ | github.com/adafruit/Adafruit-GFX-Library | ||
+ | | ||
===== Rezultate Obţinute ===== | ===== Rezultate Obţinute ===== | ||
- | <note tip> | ||
{{https://i.imgur.com/vjfrPvy.jpeg?854×480}} | {{https://i.imgur.com/vjfrPvy.jpeg?854×480}} | ||
{{https://i.imgur.com/XVqeO60.jpeg?854×480}} | {{https://i.imgur.com/XVqeO60.jpeg?854×480}} | ||
- | </note> | ||
===== Concluzii ===== | ===== Concluzii ===== | ||
+ | Proiectul de creare a unui orizont artificial și a unei busole utilizând Arduino și Processing a fost un succes partial, demonstrând capabilitățile senzorului MPU9250 în monitorizarea orientării în spațiu. Implementarea a oferit o experiență vizuală intuitivă și interactivă, utilă atât pentru educație, cât și pentru aplicații practice în domeniul aviației. În viitor, proiectul poate fi extins pentru a include mai multe funcționalități și optimizări. | ||
+ | Probleme intampinate: nu am reusit sa construiesc o busola care sa redea exact azimutul corect. | ||
===== Download ===== | ===== Download ===== | ||
Line 88: | Line 90: | ||
Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | ||
+ | </note> | ||
+ | |||
+ | ==== cod arduino ===== | ||
+ | |||
+ | <note tip> | ||
+ | #include <Wire.h> | ||
+ | #include <Adafruit_GFX.h> | ||
+ | #include <Adafruit_SSD1306.h> | ||
+ | #include "mpu9250.h" | ||
+ | |||
+ | // OLED display settings | ||
+ | #define SCREEN_WIDTH 128 | ||
+ | #define SCREEN_HEIGHT 64 | ||
+ | #define OLED_RESET -1 | ||
+ | #define OLED_I2C_ADDRESS 0x3C | ||
+ | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); | ||
+ | |||
+ | // MPU9250 settings | ||
+ | bfs::Mpu9250 imu; | ||
+ | |||
+ | // Calibration offsets | ||
+ | float pitchOffset = 0; | ||
+ | float rollOffset = 0; | ||
+ | |||
+ | void setup() { | ||
+ | Serial.begin(115200); | ||
+ | while (!Serial); // Wait for Serial to be ready | ||
+ | |||
+ | Wire.begin(); | ||
+ | |||
+ | // Initialize the MPU9250 sensor with the primary I2C address | ||
+ | imu.Config(&Wire, bfs::Mpu9250::I2C_ADDR_PRIM); | ||
+ | imu.Begin(); | ||
+ | |||
+ | // Optionally configure sensor ranges and other settings | ||
+ | imu.ConfigAccelRange(bfs::Mpu9250::ACCEL_RANGE_2G); | ||
+ | imu.ConfigGyroRange(bfs::Mpu9250::GYRO_RANGE_250DPS); | ||
+ | imu.ConfigDlpfBandwidth(bfs::Mpu9250::DLPF_BANDWIDTH_184HZ); | ||
+ | |||
+ | Serial.println("MPU9250 initialized and configured."); | ||
+ | |||
+ | // Initialize OLED display | ||
+ | if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDRESS)) { | ||
+ | Serial.println(F("SSD1306 allocation failed")); | ||
+ | for (;;); | ||
+ | } | ||
+ | display.display(); | ||
+ | delay(2000); | ||
+ | display.clearDisplay(); | ||
+ | |||
+ | // Calibration step: capture initial offsets | ||
+ | if (imu.Read()) { | ||
+ | pitchOffset = atan2(imu.accel_y_mps2(), sqrt(imu.accel_x_mps2() * imu.accel_x_mps2() + imu.accel_z_mps2() * imu.accel_z_mps2())) * 180.0 / PI; | ||
+ | rollOffset = atan2(-imu.accel_x_mps2(), imu.accel_z_mps2()) * 180.0 / PI; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | // Update sensor readings | ||
+ | if (!imu.Read()) { | ||
+ | Serial.println("Failed to read from MPU9250!"); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | // Get accelerometer and gyroscope data | ||
+ | float accelX = imu.accel_x_mps2(); | ||
+ | float accelY = imu.accel_y_mps2(); | ||
+ | float accelZ = imu.accel_z_mps2(); | ||
+ | |||
+ | // Get magnetometer data | ||
+ | float magX = imu.mag_x_ut(); | ||
+ | float magY = imu.mag_y_ut(); | ||
+ | |||
+ | // Calculate pitch, roll, and heading | ||
+ | float pitch = atan2(accelY, sqrt(accelX * accelX + accelZ * accelZ)) * 180.0 / PI - pitchOffset; | ||
+ | float roll = atan2(-accelX, accelZ) * 180.0 / PI - rollOffset; | ||
+ | float heading = atan2(magY, magX) * 180.0 / PI; | ||
+ | //Normalize heading between 0-360 degrees | ||
+ | if (heading < 0) { | ||
+ | heading += 360; | ||
+ | } | ||
+ | // Normalize roll to be within -180 to 180 degrees | ||
+ | if (roll > 180) { | ||
+ | roll -= 360; | ||
+ | } else if (roll < -180) { | ||
+ | roll += 360; | ||
+ | } | ||
+ | // Display the results on Serial Monitor | ||
+ | Serial.print(roll, 2); | ||
+ | Serial.print(" "); | ||
+ | Serial.print(pitch, 2); | ||
+ | Serial.print(" "); | ||
+ | Serial.print(heading, 2); | ||
+ | Serial.println(); | ||
+ | |||
+ | // Redraw the horizon and airplane only if pitch or roll changes significantly | ||
+ | static float lastPitch = 0; | ||
+ | static float lastRoll = 0; | ||
+ | static float lastheading = 0; | ||
+ | if (abs(lastPitch - pitch) > 0.01 || abs(lastRoll - roll) > 0.01 || abs(lastheading - heading) > 0.01) { | ||
+ | display.clearDisplay(); // Clear the display only when necessary | ||
+ | drawArtificialHorizon(roll, pitch); | ||
+ | drawAirplaneSymbol(); | ||
+ | drawAzimuth(heading); | ||
+ | display.display(); | ||
+ | lastPitch = pitch; | ||
+ | lastRoll = roll; | ||
+ | lastheading = heading; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void drawArtificialHorizon(float roll, float pitch) { | ||
+ | int centerX = SCREEN_WIDTH / 2; | ||
+ | int centerY = SCREEN_HEIGHT / 2; | ||
+ | |||
+ | // Convert pitch and roll to radians | ||
+ | float pitchRad = pitch * PI / 180.0; | ||
+ | float rollRad = roll * PI / 180.0; | ||
+ | |||
+ | // Calculate the y-position of the horizon line based on pitch | ||
+ | int horizonY = centerY - (int)(pitchRad * 33); // scale pitch to screen coordinates | ||
+ | |||
+ | // Calculate the x-offset of the horizon line based on roll | ||
+ | int rollOffset = (int)(rollRad * (SCREEN_WIDTH / 2)); // scale roll to screen coordinates | ||
+ | |||
+ | // Draw the horizon line | ||
+ | display.drawLine(0, horizonY + rollOffset, SCREEN_WIDTH, horizonY - rollOffset, SSD1306_WHITE); | ||
+ | |||
+ | // Draw perspective lines | ||
+ | display.drawLine(0, horizonY + rollOffset, centerX, SCREEN_HEIGHT, SSD1306_WHITE); | ||
+ | display.drawLine(SCREEN_WIDTH, horizonY - rollOffset, centerX, SCREEN_HEIGHT, SSD1306_WHITE); | ||
+ | |||
+ | // Draw additional lines parallel to the horizon line for perspective effect | ||
+ | } | ||
+ | |||
+ | |||
+ | void drawAirplaneSymbol() { | ||
+ | int centerX = SCREEN_WIDTH / 2; | ||
+ | int centerY = SCREEN_HEIGHT / 2; | ||
+ | |||
+ | display.drawLine(centerX - 5, centerY, centerX + 5, centerY, SSD1306_WHITE); // Body | ||
+ | display.drawLine(centerX - 10, centerY, centerX - 5, centerY - 3, SSD1306_WHITE); // Left wing up | ||
+ | display.drawLine(centerX + 10, centerY, centerX + 5, centerY - 3, SSD1306_WHITE); // Right wing up | ||
+ | display.drawLine(centerX - 10, centerY, centerX - 5, centerY + 3, SSD1306_WHITE); // Left wing down | ||
+ | display.drawLine(centerX + 10, centerY, centerX + 5, centerY + 3, SSD1306_WHITE); // Right wing down | ||
+ | display.drawLine(centerX, centerY - 2, centerX, centerY - 7, SSD1306_WHITE); // Vertical tail | ||
+ | } | ||
+ | |||
+ | void drawAzimuth(float heading) { | ||
+ | display.setTextSize(1); | ||
+ | display.setTextColor(SSD1306_WHITE); | ||
+ | display.setCursor(32, 0); // Top center | ||
+ | display.print("Bearing: "); | ||
+ | display.print(heading, 1); | ||
+ | } | ||
+ | |||
+ | </note> | ||
+ | |||
+ | ==== cod processing ==== | ||
+ | |||
+ | <note tip> | ||
+ | import processing.serial.*; | ||
+ | |||
+ | float Pitch; | ||
+ | float Bank; | ||
+ | float Azimuth; | ||
+ | float ArtificialHoizonMagnificationFactor = 0.7; | ||
+ | float CompassMagnificationFactor = 0.85; | ||
+ | float SpanAngle = 120; | ||
+ | int NumberOfScaleMajorDivisions; | ||
+ | int NumberOfScaleMinorDivisions; | ||
+ | PVector v1, v2; | ||
+ | |||
+ | Serial port; | ||
+ | float Phi; | ||
+ | float Theta; | ||
+ | float Psi; | ||
+ | |||
+ | void setup() | ||
+ | { | ||
+ | size(1366, 768); | ||
+ | rectMode(CENTER); | ||
+ | smooth(); | ||
+ | strokeCap(SQUARE); | ||
+ | |||
+ | println(Serial.list()); | ||
+ | port = new Serial(this, Serial.list()[0], 115200); | ||
+ | port.bufferUntil('\n'); | ||
+ | } | ||
+ | |||
+ | void draw() | ||
+ | { | ||
+ | background(0, 0, 102); // Dark blue background | ||
+ | translate(W / 4, H / 2.1); | ||
+ | MakeAnglesDependentOnMPU9250(); | ||
+ | Horizon(); | ||
+ | rotate(-Bank); | ||
+ | PitchScale(); | ||
+ | Axis(); | ||
+ | rotate(Bank); | ||
+ | Borders(); | ||
+ | Plane(); | ||
+ | Compass(); | ||
+ | } | ||
+ | |||
+ | void serialEvent(Serial port) // Reading the data by Processing. | ||
+ | { | ||
+ | String input = port.readStringUntil('\n'); | ||
+ | if (input != null) { | ||
+ | input = trim(input); | ||
+ | String[] values = split(input, " "); | ||
+ | if (values.length == 3) { | ||
+ | try { | ||
+ | float phi = float(values[0]); | ||
+ | float theta = float(values[1]); | ||
+ | float psi = float(values[2]); | ||
+ | println(phi, theta, psi); | ||
+ | Phi = phi; | ||
+ | Theta = theta; | ||
+ | Psi = psi; | ||
+ | } catch (Exception e) { | ||
+ | println("Error parsing values: " + e.getMessage()); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void MakeAnglesDependentOnMPU9250() | ||
+ | { | ||
+ | Bank = radians(-Phi); // Convert degrees to radians for Processing | ||
+ | Pitch = Theta; | ||
+ | Azimuth = Psi; | ||
+ | } | ||
+ | |||
+ | void Horizon() | ||
+ | { | ||
+ | scale(ArtificialHoizonMagnificationFactor); | ||
+ | noStroke(); | ||
+ | fill(0, 180, 255); | ||
+ | rect(0, -100, 900, 1000); | ||
+ | fill(95, 55, 40); | ||
+ | pushMatrix(); // Save the current transformation matrix | ||
+ | rotate(Bank); | ||
+ | rect(0, 400 + Pitch * 10, 900, 800); | ||
+ | popMatrix(); // Restore the previous transformation matrix | ||
+ | rotate(-PI - PI / 6); | ||
+ | SpanAngle = 120; | ||
+ | NumberOfScaleMajorDivisions = 12; | ||
+ | NumberOfScaleMinorDivisions = 24; | ||
+ | CircularScale(); | ||
+ | rotate(PI + PI / 6); | ||
+ | rotate(-PI / 6); | ||
+ | CircularScale(); | ||
+ | rotate(PI / 6); | ||
+ | } | ||
+ | |||
+ | void Compass() | ||
+ | { | ||
+ | translate(2 * W / 3, 0); | ||
+ | scale(CompassMagnificationFactor); | ||
+ | noFill(); | ||
+ | stroke(100); | ||
+ | strokeWeight(80); | ||
+ | ellipse(0, 0, 750, 750); | ||
+ | strokeWeight(50); | ||
+ | stroke(50); | ||
+ | fill(0, 0, 40); | ||
+ | ellipse(0, 0, 610, 610); | ||
+ | for (int k = 255; k > 0; k = k - 5) | ||
+ | { | ||
+ | noStroke(); | ||
+ | fill(0, 0, 255 - k); | ||
+ | ellipse(0, 0, 2 * k, 2 * k); | ||
+ | } | ||
+ | strokeWeight(20); | ||
+ | NumberOfScaleMajorDivisions = 18; | ||
+ | NumberOfScaleMinorDivisions = 36; | ||
+ | SpanAngle = 180; | ||
+ | CircularScale(); | ||
+ | rotate(PI); | ||
+ | SpanAngle = 180; | ||
+ | CircularScale(); | ||
+ | rotate(-PI); | ||
+ | fill(255); | ||
+ | textSize(60); | ||
+ | textAlign(CENTER); | ||
+ | text("W", -375, 0, 100, 80); | ||
+ | text("E", 370, 0, 100, 80); | ||
+ | text("N", 0, -365, 100, 80); | ||
+ | text("S", 0, 375, 100, 80); | ||
+ | textSize(30); | ||
+ | text("COMPASS", 0, -130, 500, 80); | ||
+ | rotate(PI / 4); | ||
+ | textSize(40); | ||
+ | text("NW", -370, 0, 100, 50); | ||
+ | text("SE", 365, 0, 100, 50); | ||
+ | text("NE", 0, -355, 100, 50); | ||
+ | text("SW", 0, 365, 100, 50); | ||
+ | rotate(-PI / 4); | ||
+ | CompassPointer(); | ||
+ | } | ||
+ | |||
+ | void CompassPointer() | ||
+ | { | ||
+ | rotate(PI + radians(Azimuth)); | ||
+ | stroke(0); | ||
+ | strokeWeight(4); | ||
+ | fill(100, 255, 100); | ||
+ | triangle(-20, -210, 20, -210, 0, 270); | ||
+ | triangle(-15, 210, 15, 210, 0, 270); | ||
+ | ellipse(0, 0, 45, 45); | ||
+ | fill(0, 0, 50); | ||
+ | noStroke(); | ||
+ | ellipse(0, 0, 10, 10); | ||
+ | triangle(-20, -213, 20, -213, 0, -190); | ||
+ | triangle(-15, -215, 15, -215, 0, -200); | ||
+ | rotate(-PI - radians(Azimuth)); | ||
+ | } | ||
+ | |||
+ | void Plane() | ||
+ | { | ||
+ | fill(0); | ||
+ | strokeWeight(1); | ||
+ | stroke(0, 255, 0); | ||
+ | triangle(-20, 0, 20, 0, 0, 25); | ||
+ | rect(110, 0, 140, 20); | ||
+ | rect(-110, 0, 140, 20); | ||
+ | } | ||
+ | |||
+ | void CircularScale() | ||
+ | { | ||
+ | float GaugeWidth = 800; | ||
+ | textSize(GaugeWidth / 30); | ||
+ | float StrokeWidth = 1; | ||
+ | float an; | ||
+ | float DivxPhasorCloser; | ||
+ | float DivxPhasorDistal; | ||
+ | float DivyPhasorCloser; | ||
+ | float DivyPhasorDistal; | ||
+ | strokeWeight(2 * StrokeWidth); | ||
+ | stroke(255); | ||
+ | float DivCloserPhasorLenght = GaugeWidth / 2 - GaugeWidth / 9 - StrokeWidth; | ||
+ | float DivDistalPhasorLenght = GaugeWidth / 2 - GaugeWidth / 7.5 - StrokeWidth; | ||
+ | for (int Division = 0; Division < NumberOfScaleMinorDivisions + 1; Division++) | ||
+ | { | ||
+ | an = SpanAngle / 2 + Division * SpanAngle / NumberOfScaleMinorDivisions; | ||
+ | DivxPhasorCloser = DivCloserPhasorLenght * cos(radians(an)); | ||
+ | DivxPhasorDistal = DivDistalPhasorLenght * cos(radians(an)); | ||
+ | DivyPhasorCloser = DivCloserPhasorLenght * sin(radians(an)); | ||
+ | DivyPhasorDistal = DivDistalPhasorLenght * sin(radians(an)); | ||
+ | line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); | ||
+ | } | ||
+ | DivCloserPhasorLenght = GaugeWidth / 2 - GaugeWidth / 10 - StrokeWidth; | ||
+ | DivDistalPhasorLenght = GaugeWidth / 2 - GaugeWidth / 7.4 - StrokeWidth; | ||
+ | for (int Division = 0; Division < NumberOfScaleMajorDivisions + 1; Division++) | ||
+ | { | ||
+ | an = SpanAngle / 2 + Division * SpanAngle / NumberOfScaleMajorDivisions; | ||
+ | DivxPhasorCloser = DivCloserPhasorLenght * cos(radians(an)); | ||
+ | DivxPhasorDistal = DivDistalPhasorLenght * cos(radians(an)); | ||
+ | DivyPhasorCloser = DivCloserPhasorLenght * sin(radians(an)); | ||
+ | DivyPhasorDistal = DivDistalPhasorLenght * sin(radians(an)); | ||
+ | if (Division == NumberOfScaleMajorDivisions / 2 || Division == 0 || Division == NumberOfScaleMajorDivisions) | ||
+ | { | ||
+ | strokeWeight(15); | ||
+ | stroke(0); | ||
+ | line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); | ||
+ | strokeWeight(8); | ||
+ | stroke(100, 255, 100); | ||
+ | line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | strokeWeight(3); | ||
+ | stroke(255); | ||
+ | line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void Axis() | ||
+ | { | ||
+ | stroke(255, 0, 0); | ||
+ | strokeWeight(3); | ||
+ | line(-115, 0, 115, 0); | ||
+ | line(0, 280, 0, -280); | ||
+ | fill(100, 255, 100); | ||
+ | stroke(0); | ||
+ | triangle(0, -285, -10, -255, 10, -255); | ||
+ | triangle(0, 285, -10, 255, 10, 255); | ||
+ | } | ||
+ | |||
+ | void Borders() | ||
+ | { | ||
+ | noFill(); | ||
+ | stroke(0); | ||
+ | strokeWeight(400); | ||
+ | rect(0, 0, 1100, 1100); | ||
+ | strokeWeight(200); | ||
+ | ellipse(0, 0, 1000, 1000); | ||
+ | fill(0); | ||
+ | noStroke(); | ||
+ | rect(4 * W / 5, 0, W, 2 * H); | ||
+ | rect(-4 * W / 5, 0, W, 2 * H); | ||
+ | } | ||
+ | |||
+ | void PitchScale() | ||
+ | { | ||
+ | stroke(255); | ||
+ | fill(255); | ||
+ | strokeWeight(3); | ||
+ | textSize(24); | ||
+ | textAlign(CENTER); | ||
+ | for (int i = -4; i < 5; i++) | ||
+ | { | ||
+ | if ((i == 0) == false) | ||
+ | { | ||
+ | line(110, 50 * i, -110, 50 * i); | ||
+ | } | ||
+ | text("" + i * 10, 140, 50 * i, 100, 30); | ||
+ | text("" + i * 10, -140, 50 * i, 100, 30); | ||
+ | } | ||
+ | textAlign(CORNER); | ||
+ | strokeWeight(2); | ||
+ | for (int i = -9; i < 10; i++) | ||
+ | { | ||
+ | if ((i == 0) == false) | ||
+ | { | ||
+ | line(25, 25 * i, -25, 25 * i); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
</note> | </note> | ||
Line 100: | Line 534: | ||
<note> | <note> | ||
Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe **Resurse Software** şi **Resurse Hardware**. | Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe **Resurse Software** şi **Resurse Hardware**. | ||
+ | |||
+ | Insipiratie: https://mfurkanbahat.blogspot.com/2014/11/artificial-horizon-and-compass-using.html | ||
+ | |||
+ | Datasheet mpu9250: https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf | ||
</note> | </note> | ||
<html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | ||