#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Wire.h>
#include <MPU6050.h>
#define TFT_CS 10
#define TFT_RST 8
#define TFT_DC 9
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
MPU6050 mpu;
// Colors
#define SKY_BLUE tft.color565(0, 102, 204)
#define GROUND_BR tft.color565(153, 76, 0)
#define YELLOW ST77XX_YELLOW
#define WHITE ST77XX_WHITE
#define BLACK ST77XX_BLACK
// Center
const int cx = 80;
const int cy = 64;
// ===== Control Tuning =====
const int frameDelay = 1; // ⏱ ~40 FPS
const float threshold = 0.80; // redraw sensitivity
const float alpha = 0.58; // smoothing
// ===== Variables =====
float pitchFiltered = 0;
float lastPitch = 999;
unsigned long lastFrame = 0;
void setup() {
Wire.begin();
mpu.initialize();
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
tft.fillScreen(BLACK);
drawFrame();
}
void loop() {
// ⏱ frame limiter
if (millis() - lastFrame < frameDelay) return;
lastFrame = millis();
updateIMU();
// 🔥 redraw only if movement detected
if (abs(pitchFiltered - lastPitch) > threshold) {
drawHorizon();
lastPitch = pitchFiltered;
}
}
// ================= MPU READ =================
void updateIMU() {
int16_t ax, ay, az, gx, gy, gz;
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
float axf = ax / 16384.0;
float azf = az / 16384.0;
float pitch = atan2(axf, azf) * 180 / PI;
// smoothing filter (removes jitter)
pitchFiltered = alpha * pitchFiltered + (1 - alpha) * pitch;
// dead zone (stop micro noise)
if (abs(pitchFiltered) < 0.1) pitchFiltered = 0;
}
// ================= FRAME =================
void drawFrame() {
tft.drawCircle(cx, cy, 60, WHITE);
}
// ================= HORIZON =================
void drawHorizon() {
int scale = 3;
int offset = pitchFiltered * scale;
offset = constrain(offset, -50, 50);
int horizonY = cy + offset;
// SKY
tft.fillRect(0, 0, 160, horizonY, SKY_BLUE);
// GROUND
tft.fillRect(0, horizonY, 160, 128, GROUND_BR);
// Horizon line
tft.drawFastHLine(0, horizonY, 160, WHITE);
// Pitch markings
for (int i = -3; i <= 3; i++) {
int y = horizonY - (i * 15);
if (i != 0) {
tft.drawFastHLine(cx - 20, y, 40, WHITE);
}
int value = i * 10;
String valStr = (value > 0) ? "+" + String(value) : String(value);
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(2, y - 3);
tft.print(valStr);
tft.setCursor(142, y - 3);
tft.print(valStr);
}
// Aircraft symbol (fixed center)
tft.fillRect(cx - 30, cy, 60, 2, YELLOW);
tft.drawLine(cx - 10, cy, cx, cy + 8, YELLOW);
tft.drawLine(cx, cy + 8, cx + 10, cy, YELLOW);
tft.fillCircle(cx, cy, 2, YELLOW);
// frame border
tft.drawCircle(cx, cy, 60, WHITE);
}