Round Gauge Speedometer
ESP32 DevKit (WROOM-32)·Arduino C++·Updated Jul 27, 2026·by FluxBench TeamIncludes Lab + Schematic + PCB + Sprites
Flagship demo: a GC9A01 1.28" round display drawing a smooth analog speed gauge from a potentiometer on GPIO34. Flicker-free canvas rendering, colored zone arcs and live serial telemetry for the plotter. Fork it and make it yours.
sketch.ino
// FluxBench demo — Round Gauge Speedometer
// Board: ESP32 DevKit (WROOM-32)
// Display: GC9A01 1.28" round TFT, 240x240, SPI
// SCK -> GPIO18, MOSI(SDA) -> GPIO23, CS -> GPIO5, DC -> GPIO2, RST -> GPIO4, VCC -> 3V3, GND -> GND
// Input: 10k potentiometer wiper -> GPIO34 (outer legs to 3V3 and GND)
//
// Turn the pot and watch the needle sweep. Serial prints "speed:<kmh>" —
// open the Monitor tab to see the live plotter graph it automatically.
#include <Arduino_GFX_Library.h>
#define PIN_SCK 18
#define PIN_MOSI 23
#define PIN_CS 5
#define PIN_DC 2
#define PIN_RST 4
#define PIN_POT 34
// RGB565 colors (defined locally - works across all Arduino_GFX versions)
#define C_BLACK 0x0000
#define C_WHITE 0xFFFF
#define C_RED 0xF800
#define C_ORANGE 0xFD20
#define C_TEAL 0x0410
#define C_LGREY 0xC618
#define C_DGREY 0x7BEF
#define MAX_KMH 180
#define CX 120
#define CY 120
// GC9A01 over hardware SPI; all pins configured right here (no User_Setup.h needed)
Arduino_DataBus *bus = new Arduino_ESP32SPI(PIN_DC, PIN_CS, PIN_SCK, PIN_MOSI, GFX_NOT_DEFINED /* no MISO */);
Arduino_GC9A01 *tft = new Arduino_GC9A01(bus, PIN_RST, 0 /* rotation */, true /* IPS */);
// Full-screen canvas = flicker-free drawing (240*240*2 = 112 KB, fits ESP32 heap)
Arduino_Canvas *gfx = new Arduino_Canvas(240, 240, tft);
float smoothKmh = 0;
// Gauge sweep: 270 degrees, from 135 deg (lower-left) clockwise to 405 deg (lower-right)
float kmhToRad(float kmh) {
float deg = 135.0f + (kmh / MAX_KMH) * 270.0f;
return deg * DEG_TO_RAD;
}
void drawGaugeFace() {
gfx->fillScreen(C_BLACK);
// colored zone arcs
for (int k = 0; k <= MAX_KMH; k += 2) {
float a = kmhToRad((float)k);
uint16_t c = k < 100 ? C_TEAL : (k < 140 ? C_ORANGE : C_RED);
int x0 = CX + (int)(104 * cosf(a)), y0 = CY + (int)(104 * sinf(a));
int x1 = CX + (int)(112 * cosf(a)), y1 = CY + (int)(112 * sinf(a));
gfx->drawLine(x0, y0, x1, y1, c);
}
// major ticks + labels every 20 km/h
gfx->setTextSize(1);
gfx->setTextColor(C_LGREY);
for (int k = 0; k <= MAX_KMH; k += 20) {
float a = kmhToRad((float)k);
int x0 = CX + (int)(92 * cosf(a)), y0 = CY + (int)(92 * sinf(a));
int x1 = CX + (int)(112 * cosf(a)), y1 = CY + (int)(112 * sinf(a));
gfx->drawLine(x0, y0, x1, y1, C_WHITE);
int lx = CX + (int)(78 * cosf(a)), ly = CY + (int)(78 * sinf(a));
gfx->setCursor(lx - (k >= 100 ? 9 : 6), ly - 3);
gfx->print(k);
}
gfx->setTextColor(C_DGREY);
gfx->setCursor(CX - 14, CY + 46);
gfx->print("km/h");
}
void drawNeedle(float kmh) {
float a = kmhToRad(kmh);
// needle with a short tail
int x1 = CX + (int)(86 * cosf(a)), y1 = CY + (int)(86 * sinf(a));
int x2 = CX - (int)(16 * cosf(a)), y2 = CY - (int)(16 * sinf(a));
gfx->drawLine(x2, y2, x1, y1, C_RED);
gfx->drawLine(x2, y2 + 1, x1, y1 + 1, C_RED);
gfx->fillCircle(CX, CY, 5, C_LGREY);
// digital readout
gfx->setTextSize(3);
gfx->setTextColor(C_WHITE);
int v = (int)roundf(kmh);
int w = (v >= 100 ? 3 : (v >= 10 ? 2 : 1)) * 18;
gfx->setCursor(CX - w / 2, CY - 60);
gfx->print(v);
}
void setup() {
Serial.begin(115200);
analogReadResolution(12); // 0..4095
if (!gfx->begin(40000000)) { // 40 MHz SPI
Serial.println("Display init failed");
while (true) delay(1000);
}
drawGaugeFace();
gfx->flush();
Serial.println("Round gauge ready - turn the pot on GPIO34");
}
void loop() {
int raw = analogRead(PIN_POT);
float target = (raw / 4095.0f) * MAX_KMH;
smoothKmh += (target - smoothKmh) * 0.15f; // exponential smoothing
drawGaugeFace();
drawNeedle(smoothKmh);
gfx->flush();
static uint32_t lastPrint = 0;
if (millis() - lastPrint >= 100) {
lastPrint = millis();
Serial.print("speed:");
Serial.println((int)roundf(smoothKmh));
}
delay(16);
}
Built with FluxBench.AI — the AI workbench for embedded projects.