// --- ESP32 + Adafruit_ILI9341 Radar-Demo ---
// Halbkreis-Radar mit Reichweitenringen, Tickmarks, zufälligen Returns und Sweep-Animation
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// ==== Display-Pins (an dein Setup anpassen) ====
#define TFT_DC 4
#define TFT_CS 5
Adafruit_ILI9341 tft(TFT_CS, TFT_DC); // VSPI (ESP32): SCK=18, MOSI=23, MISO=19
// ==== Geometrie des Radar-Scopes ====
// Wir zentrieren das Radar am unteren Bildschirmrand (Halbkreis nach oben)
int16_t W=0, H=0;
int16_t cx=0, cy=0; // Zentrum (unten mittig)
int16_t rMax=0; // maximaler Radius
const int16_t MARGIN = 8; // linker/rechter Rand
// Drei Reichweiten-Ringe (Radius in Pixeln)
int16_t rings[3] = {60, 100, 140};
// ==== Sweep / Animation ====
float sweepDeg = 0.0f; // aktueller Winkel in Grad (0..180)
float sweepSpeedDeg = 1.6f; // Schritt pro Frame
uint16_t sweepColor = ILI9341_MAGENTA;
uint16_t bgColor = ILI9341_BLACK;
// ==== Returns (Flecken) ====
struct ReturnBlob {
float angleDeg; // Winkel (0..180)
int16_t radius; // Radius entlang des Halbkreises
uint16_t color; // grün/gelb/rot
uint8_t size; // Pixelgröße
};
const uint8_t N_RETURNS = 28;
ReturnBlob blobs[N_RETURNS];
// ==== Hilfsfunktionen ====
// Grad -> Radiant
inline float deg2rad(float d) { return d * 3.14159265f / 180.0f; }
// Punkt auf Halbkreis aus Winkel & Radius
inline void polarToXY(float deg, int16_t r, int16_t &x, int16_t &y) {
// Halbkreis von 0..180° um das Zentrum (cx, cy), "oben" ist 90°
float rad = deg2rad(deg);
x = cx + (int16_t)(r * cos(rad));
y = cy - (int16_t)(r * sin(rad));
}
// einfachen Bogen zeichnen (dicker = mehrere Pixelspuren)
void drawArc(float degStart, float degEnd, int16_t radius, uint16_t color, uint8_t thickness=1) {
float step = 1.0f; // 1° Schritt
for (float d = degStart; d <= degEnd; d += step) {
int16_t x,y;
polarToXY(d, radius, x, y);
// Dicke simulieren
for (uint8_t t=0; t<thickness; ++t) {
tft.drawPixel(x, y-t, color);
}
}
}
// kleine Tickmarks alle 10°
void drawTicks(int16_t rOuter, int16_t rInner, uint16_t color) {
for (int deg=0; deg<=180; deg+=10) {
int16_t x1,y1,x2,y2;
polarToXY(deg, rOuter, x1, y1);
polarToXY(deg, rInner, x2, y2);
tft.drawLine(x1,y1,x2,y2,color);
}
}
// zufällige Returns erzeugen (Cluster entlang eines Winkelbandes)
void initReturns() {
// Beispiel: ein „Wetterband“ grob 20..90° und ein paar vereinzelte Punkte
for (uint8_t i=0; i<N_RETURNS; ++i) {
float base = (i < 40) ? (40 + random(0,70)) : random(0,180); // Winkel
blobs[i].angleDeg = base + random(-4, 5) * 0.5f; // Jitter
blobs[i].radius = rings[random(0,3)] + random(-6, 7); // nahe am Ring
// Farbe: überwiegend grün, teils gelb, selten rot
int r = random(0,100);
blobs[i].color = (r < 70) ? ILI9341_GREEN : (r < 92 ? ILI9341_YELLOW : ILI9341_RED);
blobs[i].size = 2 + (r > 92 ? 1 : 0);
}
}
// Returns zeichnen
void drawReturns() {
for (uint8_t i=0; i<N_RETURNS; ++i) {
int16_t x,y;
polarToXY(blobs[i].angleDeg, blobs[i].radius, x, y);
// kleiner Fleck (gefüllter Kreis)
tft.fillCircle(x, y, blobs[i].size, blobs[i].color);
}
}
// Radar-Scope (statisch) rendern
void renderScopeStatic() {
tft.fillScreen(bgColor);
// Halbkreis-Bereich: 0..180°
// Reichweitenringe
for (uint8_t i=0; i<3; ++i) {
drawArc(0, 180, rings[i], ILI9341_DARKGREY, 1);
}
// Tickmarks auf äußerem Ring
drawTicks(rings[2], rings[2]-8, ILI9341_LIGHTGREY);
// Mittellinie (0° und 180°)
int16_t xL,yL,xR,yR;
polarToXY(0, rings[2], xR, yR);
polarToXY(180,rings[2], xL, yL);
tft.drawLine(xL,yL, xR,yR, ILI9341_LIGHTGREY);
// Radialer „up‑vector“ (90°)
int16_t xU,yU;
polarToXY(90, rings[2], xU, yU);
tft.drawLine(cx,cy, xU,yU, ILI9341_LIGHTGREY);
// einfache Skalenbeschriftung
tft.setTextColor(ILI9341_LIGHTGREY);
tft.setTextSize(1);
tft.setCursor(8, 8);
tft.print("TRK MAG");
tft.setCursor(W/2 - 14, 6);
tft.setTextSize(2);
tft.print("182");
tft.setTextSize(1);
tft.setCursor(W-70, 10);
tft.print("TFC");
}
// Sweep zeichnen: neuen Winkel, alten Winkel rückstandsfrei löschen
void updateSweep() {
static float lastDeg = -1.0f;
// alten Sweep in Hintergrundfarbe übermalen
if (lastDeg >= 0.0f) {
int16_t xOld,yOld;
polarToXY(lastDeg, rings[2], xOld, yOld);
tft.drawLine(cx,cy, xOld,yOld, bgColor);
}
// neuen Sweep zeichnen
int16_t xNew,yNew;
polarToXY(sweepDeg, rings[2], xNew, yNew);
tft.drawLine(cx,cy, xNew,yNew, sweepColor);
lastDeg = sweepDeg;
sweepDeg += sweepSpeedDeg;
if (sweepDeg > 180.0f) sweepDeg -= 180.0f;
}
void setup() {
// Display
tft.begin();
tft.setRotation(1); // Querformat (240x320)
W = tft.width();
H = tft.height();
// Zentrum am unteren Bildschirmrand
cx = W / 2;
cy = H - MARGIN; // etwas vom Rand weg
rMax = min(W, H) - 2*MARGIN;
// Sicherstellen, dass unsere Ringe reinpassen:
// Sicherstellen, dass unsere Ringe reinpassen:
int16_t maxR = (int16_t)(H - 2 * MARGIN);
for (uint8_t i = 0; i < 3; ++i) {
if (rings[i] > maxR) rings[i] = maxR;
}
// statisches Scope + Returns
renderScopeStatic();
initReturns();
drawReturns();
}
void loop() {
updateSweep(); // animierter Zeiger
delay(12); // Geschwindigkeit feinfühlen
}