SB-200 Plate Meter Design with GC9A01 & Arduino Nano

 SB-200 Plate Meter Design with GC9A01 & Arduino Nano

This design replaces the analog meter in your Heathkit SB-200 with a digital gauge on a 1.28" GC9A01 Round TFT. It measures plate current via a shunt resistor and displays it on a colorful, animated dial.


1. Hardware Connections

Critical Warning: The GC9A01 is a 3.3V logic device.  The Arduino Nano is 5V. You must use a logic level converter or series resistors (220Ω–330Ω) on the SPI lines (MOSI, SCK, CS, DC) to prevent damage. 


GC9A01 Pin Arduino Nano Pin Note

VCC 3.3V Do NOT use 5V

GND GND Common Ground

CS D10 Chip Select

DC D9 Data/Command

SDA (MOSI) D11 Data In (Use 220Ω resistor)

SCL (SCK) D13 Clock (Use 220Ω resistor)

RST D8 Reset (Optional, can tie to 3.3V if not used in code)

BL 3.3V Backlight (Or PWM pin for dimming)


Current Sensing Circuit (Shunt): The SB-200 cathode current (Plate + Grid) flows to ground. Insert a low-value shunt resistor in the cathode ground path.


Shunt Value: 0.1Ω to 0.5Ω (5W+ Wirewound).

At 500mA (0.5A), a 0.1Ω shunt drops 0.05V (50mV).

At 500mA, a 0.5Ω shunt drops 0.25V (250mV). 

Recommendation: Use 0.22Ω @ 5W.

Max Drop: $0.5A \times 0.22\Omega = 0.11V$ (110mV). This is safe for the tube bias but requires amplification or careful ADC reading.

Wiring: Connect the shunt between the tube cathodes and the amplifier chassis ground. Connect the Arduino A0 to the tube side of the shunt and GND to the chassis side.

Note: The Arduino reads 0–1023 for 0–5V. 110mV is only ~22 counts. For better resolution, use the Internal 1.1V Reference (analogReference(INTERNAL)), which gives ~930 counts for 110mV. 


Arduino Nano current shunt sensing circuit


View all

2. Required Libraries

Install these via the Arduino Library Manager:


Adafruit GFX Library

Adafruit GC9A01A Library (or GC9A01 by Bodmer if preferred) 

3. Arduino Code

#include <SPI.h>

#include <Adafruit_GFX.h>

#include <Adafruit_GC9A01A.h>


// --- Pin Definitions ---

#define TFT_CS   10

#define TFT_DC   9

#define TFT_RST  8

#define SHUNT_PIN A0


// --- Configuration ---

#define SHUNT_RESISTANCE 0.22 // Ohms (Change to match your shunt)

#define MAX_CURRENT_MA   600  // Full scale deflection (mA)

#define REF_VOLTAGE      1.1  // Internal reference voltage (1.1V for better resolution)


// Initialize Display

Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_RST);


// Gauge Geometry

#define CENTER_X 120

#define CENTER_Y 120

#define RADIUS   100

#define GAUGE_START_ANGLE 150 // Degrees (Left bottom)

#define GAUGE_END_ANGLE   390 // Degrees (Right bottom, wraps around)


void setup() {

  Serial.begin(9600);

  

  // Use Internal 1.1V Reference for higher resolution on small voltages

  analogReference(INTERNAL); 

  

  tft.begin();

  tft.fillScreen(GC9A01A_BLACK);

  tft.setRotation(2); // Adjust based on USB port orientation

  

  // Draw Static Gauge Background

  drawGaugeBackground();

}


void loop() {

  // Read ADC (0-1023 for 0-1.1V)

  int sensorValue = analogRead(SHUNT_PIN);

  

  // Convert to Voltage (1.1V ref)

  float voltage = sensorValue * (REF_VOLTAGE / 1023.0);

  

  // Convert to Current (Ohm's Law: I = V/R)

  float currentA = voltage / SHUNT_RESISTANCE;

  float currentMA = currentA * 1000.0;

  

  // Clamp values

  if (currentMA < 0) currentMA = 0;

  if (currentMA > MAX_CURRENT_MA) currentMA = MAX_CURRENT_MA;

  

  // Draw Needle

  drawNeedle(currentMA);

  

  // Display Numeric Value

  displayNumericValue(currentMA);

  

  delay(100); // Update rate

}


void drawGaugeBackground() {

  // Outer Ring

  tft.drawCircle(CENTER_X, CENTER_Y, RADIUS, GC9A01A_WHITE);

  tft.drawCircle(CENTER_X, CENTER_Y, RADIUS - 5, GC9A01A_WHITE);

  

  // Scale Ticks

  for (int i = 0; i <= MAX_CURRENT_MA; i += 50) {

    float angle = map(i, 0, MAX_CURRENT_MA, GAUGE_START_ANGLE, GAUGE_END_ANGLE);

    int x1 = CENTER_X + (RADIUS - 10) * cos(radians(angle));

    int y1 = CENTER_Y + (RADIUS - 10) * sin(radians(angle));

    int x2 = CENTER_X + (RADIUS - 20) * cos(radians(angle));

    int y2 = CENTER_Y + (RADIUS - 20) * sin(radians(angle));

    

    if (i % 100 == 0) {

      tft.drawLine(x1, y1, x2, y2, GC9A01A_WHITE); // Major tick

      // Draw Numbers (Simplified positioning)

      tft.setCursor(CENTER_X + (RADIUS - 35) * cos(radians(angle)), 

                    CENTER_Y + (RADIUS - 35) * sin(radians(angle)));

      tft.setTextColor(GC9A01A_WHITE);

      tft.setTextSize(1);

      tft.print(i/100); // Print 0, 1, 2...

    } else {

      tft.drawLine(x1, y1, x2, y2, GC9A01A_GRAY); // Minor tick

    }

  }

  

  // Center Label

  tft.setCursor(CENTER_X - 20, CENTER_Y + 40);

  tft.setTextColor(GC9A01A_GREEN);

  tft.setTextSize(2);

  tft.print("mA");

}


void drawNeedle(float currentMA) {

  static float lastAngle = -1;

  float angle = map(currentMA, 0, MAX_CURRENT_MA, GAUGE_START_ANGLE, GAUGE_END_ANGLE);

  

  // Simple erase/redraw (could be optimized with XOR or buffer)

  // Clear previous needle (draw over with background color)

  if (lastAngle >= 0) {

    int x1 = CENTER_X + (RADIUS - 25) * cos(radians(lastAngle));

    int y1 = CENTER_Y + (RADIUS - 25) * sin(radians(lastAngle));

    tft.drawLine(CENTER_X, CENTER_Y, x1, y1, GC9A01A_BLACK);

  }

  

  // Draw new needle

  int x2 = CENTER_X + (RADIUS - 25) * cos(radians(angle));

  int y2 = CENTER_Y + (RADIUS - 25) * sin(radians(angle));

  

  // Color changes based on current (Green -> Yellow -> Red)

  uint16_t needleColor = GC9A01A_GREEN;

  if (currentMA > 400) needleColor = GC9A01A_YELLOW;

  if (currentMA > 550) needleColor = GC9A01A_RED;

  

  tft.drawLine(CENTER_X, CENTER_Y, x2, y2, needleColor);

  tft.fillCircle(CENTER_X, CENTER_Y, 5, GC9A01A_WHITE); // Center cap

  

  lastAngle = angle;

}


void displayNumericValue(float currentMA) {

  // Clear old number area

  tft.fillRect(80, 160, 80, 30, GC9A01A_BLACK);

  

  tft.setCursor(90, 165);

  tft.setTextColor(GC9A01A_CYAN);

  tft.setTextSize(2);

  tft.print(currentMA, 0); // Print integer

}


4. Calibration & Safety

Calibration: Transmit a carrier into a dummy load. Adjust the SHUNT_RESISTANCE value in the code until the display matches a trusted external multimeter reading. 

Safety: The shunt resistor is at RF Ground, but ensure the Arduino is powered by an isolated USB source (laptop battery or isolated supply) to prevent ground loops or RF feedback into the USB port.

Filtering: Add a 0.1µF capacitor across the shunt resistor to filter RF noise from the ADC reading. 

Comments