Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

**3D printed case (STL + assembly):** [MakerWorld](https://makerworld.com/en/models/2872376-esp32-plane-radar-live-ads-b-on-a-round-display#profileId-3207083) · **Firmware:** [Releases](https://github.com/MatixYo/ESP32-Plane-Radar/releases)

Firmware for an **ESP32-C3 Super Mini** and a **1.28″ round GC9A01** display (240×240). Shows a circular **ADS-B radar** around your configured location, with **WiFiManager** for first-time setup.
Firmware for a **1.28″ round GC9A01** display (240×240) on the **ESP32-C3**. Shows a circular **ADS-B radar** around your configured location, with **WiFiManager** for first-time setup.

**Supported boards** (see [Boards](#boards)):

- **ESP32-C3 Super Mini** + a separately wired GC9A01 panel (default)
- **Sunton/JCZN ESP32-2424S012** — integrated 1.28″ round GC9A01 + CST816 touch board

Pick the board at build time (PlatformIO env) and/or switch it at runtime from the Wi‑Fi setup portal.

## What it does

Expand Down Expand Up @@ -44,6 +51,7 @@ The same portal runs on the setup AP and on the device’s LAN IP while connecte
| **Latitude / Longitude** | Radar center and ADS-B query position (defaults in `config.h` until set) |
| **Display distances in miles** | Ring scale label in **mi** instead of **km** (e.g. `6mi` vs `10km`) |
| **Show airport runways** | Major-airport runway overlay on the radar (off to hide) |
| **Board** | Display board in use (Super Mini / ESP32-2424S012); reboots to apply |

After a reset, the device reboots and shows the setup screen immediately (no “Connecting” loop on stale credentials).

Expand Down Expand Up @@ -98,7 +106,8 @@ Edit **`include/config.h`** for hardware and behavior:
| Portal | `kPortalApName`, `kPortalIp`, `kPortalHostname` / `kPortalHostUrl` (mDNS; needs `-DWM_MDNS` in `platformio.ini`) |
| Wi‑Fi timing | connect attempts, reconnect grace, portal timeout (`0` = no timeout) |
| BOOT | `kBootPin`, `kBootResetHoldMs`, `kBootTapMinMs` |
| Display SPI | pins, `kDisplayInvert`, `kDisplayRgbOrder`, `kDisplaySpiWriteHz` |
| Display | `kDisplayInvert`, `kDisplaySpiWriteHz` in `config.h`; per-board pins and color order in `include/hardware/board.h` |
| Boards | `include/hardware/board.h` (`kBoards` table, `Board` enum); add a board by extending both |
| Default location | `kDefaultRadarLat`, `kDefaultRadarLon` (until portal overrides) |
| ADS-B | `kAdsbFetchIntervalMs`, `kAdsbShowGroundAircraft` |

Expand All @@ -110,6 +119,7 @@ Range presets: `include/ui/radar_range.h` (`kRangePresets`).
include/
config.h
hardware/
board.h — board registry (pins per board, runtime select)
lgfx_config.hpp
display.h
display_font.h
Expand Down Expand Up @@ -138,7 +148,16 @@ src/
services/
```

## Wiring (GC9A01 ↔ ESP32-C3 Super Mini)
## Boards

The display wiring per board lives in [`include/hardware/board.h`](include/hardware/board.h). Select a board two ways:

- **Compile time** — pick the PlatformIO env (sets the default): `supermini` or `esp32-2424s012` (see [Build](#build)).
- **Runtime** — choose the board in the Wi‑Fi setup portal (**Board** dropdown). The choice is saved to NVS and applied on the next boot, so one firmware image can run on either board.

> On first boot the display uses the build-time default. If you flash the “wrong” env, the screen may stay blank until you pick the correct board in the portal (connect to the `PlaneRadar-Setup` AP and open the page), then it reboots.

### ESP32-C3 Super Mini (separately wired GC9A01)

| Display | ESP32-C3 |
|---------|----------|
Expand All @@ -151,16 +170,26 @@ src/
| SCL (SCLK) | GPIO **4** |
| BOOT (user) | GPIO **9** |

### Sunton/JCZN ESP32-2424S012 (integrated)

Integrated board — no wiring needed. Display is fixed to: SCLK **6**, MOSI **7**, DC **2**, CS **10**, RST **not wired**, backlight **GPIO 3** (active high), panel color order **BGR**. (Touch/IMU are not used by this firmware.)

## Build

```bash
pio run -t upload
# ESP32-C3 Super Mini (default)
pio run -e supermini -t upload

# Sunton/JCZN ESP32-2424S012
pio run -e esp32-2424s012 -t upload

pio device monitor
```

- PlatformIO env: **`supermini`**
- PlatformIO envs: **`supermini`** (default) and **`esp32-2424s012`**
- Serial: **115200** baud
- USB CDC on boot enabled in `platformio.ini` for the Super Mini
- USB CDC on boot enabled in `platformio.ini` (native USB on both boards)
- To flash a board that is boot-looping or busy, enter download mode first (hold **BOOT**, tap **RESET** / replug)

### Web-flashable release image

Expand Down
11 changes: 3 additions & 8 deletions include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,14 @@ constexpr unsigned long kBootResetHoldMs = 3000UL;
constexpr unsigned long kBootTapMinMs = 40UL;

// --- Display: GC9A01 1.28" round 240×240 (SPI) ---
constexpr gpio_num_t kDisplayPinRst = GPIO_NUM_0;
constexpr gpio_num_t kDisplayPinCs = GPIO_NUM_1;
constexpr gpio_num_t kDisplayPinDc = GPIO_NUM_10;
constexpr gpio_num_t kDisplayPinMosi = GPIO_NUM_3; // display SDA
constexpr gpio_num_t kDisplayPinSclk = GPIO_NUM_4; // display SCL

// Per-board pin wiring and color order live in hardware/board.h (selectable at
// compile time via -DBOARD_ESP32_2424S012 and at runtime via the setup portal).
constexpr int kDisplayWidth = 240;
constexpr int kDisplayHeight = 240;

constexpr uint32_t kDisplaySpiWriteHz = 40000000;
// GC9A01 modules often need invert + BGR for correct black/green output
// GC9A01 modules need color inversion on for correct output.
constexpr bool kDisplayInvert = true;
constexpr bool kDisplayRgbOrder = true;

// --- Radar center defaults (overridden via WiFi setup portal) ---
constexpr double kDefaultRadarLat = 52.3676;
Expand Down
58 changes: 58 additions & 0 deletions include/hardware/board.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <cstdint>

// Board support: maps a target board to its GC9A01 display wiring. The board
// can be chosen at compile time (-DBOARD_ESP32_2424S012, see platformio.ini)
// and overridden at runtime from the Wi-Fi setup portal (persisted in NVS,
// applied on the next boot).
namespace hardware::board {

enum class Board : uint8_t {
kSuperMini = 0, // ESP32-C3 Super Mini + separately wired GC9A01
kEsp32_2424S012 = 1, // Sunton/JCZN integrated 1.28" round board
};
constexpr uint8_t kBoardCount = 2;

/** Board-dependent display wiring. Pins are GPIO numbers; -1 = not wired. */
struct DisplayPins {
int pin_sclk;
int pin_mosi;
int pin_dc;
int pin_cs;
int pin_rst; // -1 = tied to chip reset (not wired)
int pin_backlight; // -1 = hard-wired / not GPIO-controlled
bool rgb_order; // true = RGB, false = BGR
};

inline constexpr DisplayPins kBoards[kBoardCount] = {
// kSuperMini: ESP32-C3 Super Mini with a separately wired GC9A01.
{/*sclk*/ 4, /*mosi*/ 3, /*dc*/ 10, /*cs*/ 1, /*rst*/ 0, /*bl*/ -1,
/*rgb_order*/ true},
// kEsp32_2424S012: Sunton/JCZN integrated round board (BL on GPIO3, BGR).
{/*sclk*/ 6, /*mosi*/ 7, /*dc*/ 2, /*cs*/ 10, /*rst*/ -1, /*bl*/ 3,
/*rgb_order*/ false},
};

constexpr Board compileDefault() {
#if defined(BOARD_ESP32_2424S012)
return Board::kEsp32_2424S012;
#else
return Board::kSuperMini;
#endif
}

constexpr const DisplayPins& pins(Board b) {
return kBoards[static_cast<uint8_t>(b)];
}

constexpr bool isValidIndex(long v) { return v >= 0 && v < kBoardCount; }

const char* name(Board b);

// Runtime-selected board: NVS override if set and valid, else compileDefault().
Board active();
const DisplayPins& activePins();
void setActive(Board b); // persists to NVS; takes effect on next boot

} // namespace hardware::board
22 changes: 14 additions & 8 deletions include/hardware/lgfx_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,39 @@
#include <LovyanGFX.hpp>

#include "config.h"
#include "hardware/board.h"

/** LovyanGFX device: GC9A01 on SPI. Pin values come from config.h. */
/** LovyanGFX device: GC9A01 on SPI. Pin values come from hardware/board.h. */
class LGFX : public lgfx::LGFX_Device {
lgfx::Bus_SPI _bus;
lgfx::Panel_GC9A01 _panel;

public:
LGFX() {
applyBoard(hardware::board::pins(hardware::board::compileDefault()));
setPanel(&_panel);
}

/** (Re)configure SPI bus and panel for a board. Call before init(). */
void applyBoard(const hardware::board::DisplayPins& p) {
{
auto cfg = _bus.config();
cfg.spi_host = SPI2_HOST;
cfg.freq_write = config::kDisplaySpiWriteHz;
cfg.pin_sclk = static_cast<int>(config::kDisplayPinSclk);
cfg.pin_mosi = static_cast<int>(config::kDisplayPinMosi);
cfg.pin_sclk = p.pin_sclk;
cfg.pin_mosi = p.pin_mosi;
cfg.pin_miso = -1;
cfg.pin_dc = static_cast<int>(config::kDisplayPinDc);
cfg.pin_dc = p.pin_dc;
_bus.config(cfg);
_panel.setBus(&_bus);
}
{
auto cfg = _panel.config();
cfg.pin_cs = static_cast<int>(config::kDisplayPinCs);
cfg.pin_rst = static_cast<int>(config::kDisplayPinRst);
cfg.pin_cs = p.pin_cs;
cfg.pin_rst = p.pin_rst;
cfg.invert = config::kDisplayInvert;
cfg.rgb_order = config::kDisplayRgbOrder;
cfg.rgb_order = p.rgb_order;
_panel.config(cfg);
}
setPanel(&_panel);
}
};
9 changes: 9 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ lib_deps =
lovyan03/LovyanGFX@^1.2.7
tzapu/WiFiManager@^2.0.17
bblanchon/ArduinoJson@^7.4.2

; Sunton/JCZN ESP32-2424S012 — integrated 1.28" round GC9A01 + CST816 touch.
; Same ESP32-C3 target as supermini; only the default display pinout differs.
; The board can also be switched at runtime from the Wi-Fi setup portal.
[env:esp32-2424s012]
extends = env:supermini
build_flags =
${env:supermini.build_flags}
-DBOARD_ESP32_2424S012
57 changes: 57 additions & 0 deletions src/hardware/board.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "hardware/board.h"

#include <Arduino.h>
#include <Preferences.h>

namespace hardware::board {

namespace {

constexpr char kPrefsNamespace[] = "board";
constexpr char kKeySel[] = "sel";

const char* const kNames[kBoardCount] = {
"ESP32-C3 Super Mini",
"ESP32-2424S012",
};

bool s_loaded = false;
Board s_active = compileDefault();

} // namespace

const char* name(Board b) { return kNames[static_cast<uint8_t>(b)]; }

Board active() {
if (s_loaded) {
return s_active;
}
Preferences prefs;
if (prefs.begin(kPrefsNamespace, true)) {
if (prefs.isKey(kKeySel)) {
const long v = prefs.getUChar(kKeySel, static_cast<uint8_t>(compileDefault()));
if (isValidIndex(v)) {
s_active = static_cast<Board>(v);
}
}
prefs.end();
}
s_loaded = true;
Serial.printf("[board] active: %s (%s)\n", name(s_active),
s_active == compileDefault() ? "default" : "override");
return s_active;
}

const DisplayPins& activePins() { return pins(active()); }

void setActive(Board b) {
Preferences prefs;
if (prefs.begin(kPrefsNamespace, false)) {
prefs.putUChar(kKeySel, static_cast<uint8_t>(b));
prefs.end();
}
s_active = b;
s_loaded = true;
}

} // namespace hardware::board
17 changes: 17 additions & 0 deletions src/hardware/display.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
#include "hardware/display.h"

#include <Arduino.h>

#include "hardware/board.h"
#include "hardware/display_font.h"

LGFX tft;

void displayInit() {
// Apply the runtime-selected board (NVS override, else compile default) before
// init so a portal board change takes effect after reboot.
const hardware::board::DisplayPins& p = hardware::board::activePins();
tft.applyBoard(p);

// Boards that gate the backlight on a GPIO (e.g. ESP32-2424S012) need it
// driven high (active high). Driven directly rather than via LEDC so it does
// not depend on PWM channel availability. Boards with a hard-wired backlight
// use pin -1 and skip this.
if (p.pin_backlight >= 0) {
pinMode(p.pin_backlight, OUTPUT);
digitalWrite(p.pin_backlight, HIGH);
}

tft.init();
tft.setRotation(0);
tft.setBrightness(255);
Expand Down
8 changes: 8 additions & 0 deletions src/services/adsb_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ bool fetchUpdate(double center_lat, double center_lon, float fetch_radius_km) {

s_aircraft_count = n;
Serial.printf("adsb: %u aircraft\n", static_cast<unsigned>(n));
for (size_t i = 0; i < n; ++i) {
Serial.printf(" [%u] %s %.4f,%.4f %s trk=%.0f gs=%.0f\n",
static_cast<unsigned>(i),
s_aircraft[i].callsign[0] != '\0' ? s_aircraft[i].callsign
: "(no id)",
s_aircraft[i].lat, s_aircraft[i].lon, s_aircraft[i].alt,
s_aircraft[i].track_deg, s_aircraft[i].gs_knots);
}
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions src/services/radar_location.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ void persist(double lat, double lon) {
} // namespace

void init() {
bool loaded_from_nvs = false;
Preferences prefs;
prefs.begin(kPrefsNamespace, true);
if (prefs.isKey(kKeyLat) && prefs.isKey(kKeyLon)) {
Expand All @@ -55,9 +56,12 @@ void init() {
if (validLatLon(lat, lon)) {
s_lat = lat;
s_lon = lon;
loaded_from_nvs = true;
}
}
prefs.end();
Serial.printf("[location] init: lat=%.6f lon=%.6f (%s)\n", s_lat, s_lon,
loaded_from_nvs ? "saved" : "default");
}

double lat() { return s_lat; }
Expand Down
Loading