Community project

Flight Radar Display

John Roberts

Published August 10, 2026 · Updated August 11, 2026

ESP320 components2 assembly steps
Remix this project
Photo of Flight Radar DisplayGenerated with AI

This project turns an M5Dial into a flight radar display that visualizes aircraft tracks with bearing, range, altitude, and speed information. The display renders a classic radar scope with concentric range rings and a rotating sweep, plotting nearby aircraft as colored symbols with their callsigns and flight data.

The guide provides a complete firmware implementation for the ESP32-based M5Dial, including the wiring setup (power only), parts list, and step-by-step assembly instructions. The code demonstrates radar rendering with polar-to-cartesian coordinate conversion; users can extend it by connecting to a real flight-data API to replace the sample aircraft tracks with live ADS-B or aviation data.

Wiring diagram

Interactive · read-only

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Assembly

2 steps
  1. Use the M5Dial as supplied

    No external electronic parts are required. The round screen, rotary encoder, and buzzer used by this project are already built into the M5Dial.

    • Tip: Keep the protective film on the display only if it does not obscure viewing; remove it for the clearest radar image.
    • Do not open the enclosure or connect wires to the internal display pins; the M5Dial library controls its built-in hardware.
  2. Power the M5Dial

    Connect the M5Dial to a suitable USB power source using its USB-C connector. The board is powered through its normal USB port.

    • Tip: A standard USB power source is sufficient for this display-only project.
    • Use a sound USB-C cable and avoid loose connections while the device is operating.

Firmware

ESP32
main.cppDeploy to device
#include <Arduino.h>
#include <M5Dial.h>
#include <math.h>

// Demonstration radar: replace these sample tracks with data returned by
// a flight-data service when network credentials and an API are selected.
struct Track {
  const char *callsign;
  float bearing;
  float rangeNm;
  float speedKt;
  int altitudeFt;
  float turnRate;
};

float radiansFromBearing(float bearing);
void polarPoint(float bearing, float radius, int &x, int &y);
void drawStaticRadar();
void drawAircraft(const Track &track);
void renderFrame();

constexpr int SCREEN_W = 240;
constexpr int SCREEN_H = 240;
constexpr int CX = 120;
constexpr int CY = 120;
constexpr int RADAR_R = 92;
constexpr uint16_t BG = 0x0000;
constexpr uint16_t GRID = 0x03E0;
constexpr uint16_t DIM_GRID = 0x01A0;
constexpr uint16_t AIRCRAFT = 0xFFE0;
constexpr uint16_t TEXT = 0xFFFF;
constexpr uint16_t ALERT = 0xF800;
constexpr unsigned long FRAME_MS = 50;

Track tracks[] = {
  {"SWA482", 32.0f, 10.5f, 438.0f, 32100, 0.18f},
  {"DAL921", 148.0f, 23.0f, 465.0f, 28700, -0.12f},
  {"AAL76", 258.0f, 34.0f, 410.0f, 35600, 0.08f},
  {"UAL120", 312.0f, 42.0f, 492.0f, 30200, -0.16f}
};
constexpr size_t TRACK_COUNT = sizeof(tracks) / sizeof(tracks[0]);

float sweepBearing = 0.0f;
unsigned long lastFrame = 0;

float radiansFromBearing(float bearing) {
  return (bearing - 90.0f) * DEG_TO_RAD;
}

void polarPoint(float bearing, float radius, int &x, int &y) {
  const float angle = radiansFromBearing(bearing);
  x = CX + static_cast<int>(lroundf(cosf(angle) * radius));
  y = CY + static_cast<int>(lroundf(sinf(angle) * radius));
}

void drawStaticRadar() {
  M5Dial.Display.fillScreen(BG);
  M5Dial.Display.drawCircle(CX, CY, RADAR_R, GRID);
  M5Dial.Display.drawCircle(CX, CY, RADAR_R * 2 / 3, DIM_GRID);
  M5Dial.Display.drawCircle(CX, CY, RADAR_R / 3, DIM_GRID);
  M5Dial.Display.drawLine(CX - RADAR_R, CY, CX + RADAR_R, CY, DIM_GRID);
  M5Dial.Display.drawLine(CX, CY - RADAR_R, CX, CY + RADAR_R, DIM_GRID);
  M5Dial.Display.fillCircle(CX, CY, 3, GRID);

  M5Dial.Display.setTextColor(TEXT, BG);
  M5Dial.Display.setTextSize(1);
  M5Dial.Display.setTextDatum(middle_center);
  M5Dial.Display.drawString("FLIGHT RADAR", CX, 10);
  M5Dial.Display.drawString("N", CX, 23);
  M5Dial.Display.drawString("50 NM", CX, 218);
  M5Dial.Display.setTextDatum(top_left);
}

void drawAircraft(const Track &track) {
  const float radius = constrain(track.rangeNm / 50.0f, 0.0f, 1.0f) * RADAR_R;
  int x, y;
  polarPoint(track.bearing, radius, x, y);

  const uint16_t colour = track.rangeNm < 15.0f ? ALERT : AIRCRAFT;
  M5Dial.Display.fillCircle(x, y, 3, colour);
  M5Dial.Display.drawLine(
      x, y,
      x + static_cast<int>(cosf(radiansFromBearing(track.bearing)) * 8),
      y + static_cast<int>(sinf(radiansFromBearing(track.bearing)) * 8), colour);

  M5Dial.Display.setTextColor(colour, BG);
  M5Dial.Display.setTextSize(1);
  M5Dial.Display.setCursor(x + 6, y - 8);
  M5Dial.Display.print(track.callsign);
}

void renderFrame() {
  drawStaticRadar();
  for (size_t i = 0; i < TRACK_COUNT; ++i) {
    tracks[i].bearing += tracks[i].turnRate;
    if (tracks[i].bearing >= 360.0f) tracks[i].bearing -= 360.0f;
    if (tracks[i].bearing < 0.0f) tracks[i].bearing += 360.0f;
    drawAircraft(tracks[i]);
  }

  int sweepX, sweepY;
  polarPoint(sweepBearing, RADAR_R, sweepX, sweepY);
  M5Dial.Display.drawLine(CX, CY, sweepX, sweepY, AIRCRAFT);
  sweepBearing += 2.2f;
  if (sweepBearing >= 360.0f) sweepBearing -= 360.0f;
}

void setup() {
  auto cfg = M5.config();
  M5Dial.begin(cfg, true, false);
  M5Dial.Display.setRotation(0);
  M5Dial.Display.setTextWrap(false);
  renderFrame();
}

void loop() {
  M5Dial.update();
  const unsigned long now = millis();
  if (now - lastFrame >= FRAME_MS) {
    lastFrame = now;
    renderFrame();
  }
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik