
FM-Kreisskala mit Display
von Günther Zöppel
Beim Beschäftigen mit FM-Empfangs-Chips, welche mittels Poti in der
Frequenz abgestimmt werden können, brauchte ich eine Übersichtsanzeige,
in welchem Frequenzbereich man sich etwa aufhält. Bei bisherigen
Projekten hatte ich ein LCD Display 20x4 und ein Mini-OLED 0.96“ (siehe
Osterwettbewerb 2025) dazu verwendet. Es kommt gegenwärtig noch eine
Alternative hinzu, ein preiswert erhältliches 1.28“ Farbdisplay mit der
Bezeichnung GC9A01A, welches eine kreisförmige Darstellfläche bietet
und damit prädestiniert ist für die Darstellung einer Kreisskala. Es
wird über SPI-Interface an den Mikrocontroller angeschlossen. Dafür
habe ich einen Sketch erstellt, der auf einem ESP8266 ausgeführt wird
und die sich von 0 – 3,3V ändernde Steuerspannung (für die
Frequenzeinstellung des jeweiligen Empfangschips) in eine
Zeigerbewegung umsetzt, die auf die entsprechend markierbare Frequenz
zeigt. Alle Parameter, die für die Darstellung relevant sind, können im
Sketch angepasst werden, indem man sie in einem Editor ändert und dann
neu kompiliert und auf den Controller hochlädt. Der Sketch ist mit
vielen Kommentaren versehen, um Nachnutzern vielseitige
Anpassmöglichkeiten zu geben.
Hier der Sketch :
//Kreisskala FM
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
// Pinbelegung für ESP8266 D1 mini
#define TFT_CS D2
#define TFT_RST D3
#define TFT_DC D4
#define TFT_MOSI D7
#define TFT_SCLK D5
// Display-Objekt initialisieren
Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
// Zeigerposition
const int centerX = 120;
const int centerY = 120;
const int pointerLength = 80; // Länge des Zeigers
const int pointerWidth = 5; // Breite des Zeigers
// Dreieckspitze-Eigenschaften
const int triangleWidth = 4; // Breite der Dreiecksspitze (kleiner gemacht)
const int triangleOffset = 5; // Abstand der Dreiecksspitze vom Zeigerende
// Breite der gelben Kreislinie
const int lineWidth = 2;
float lastVoltage = -1.0; // Letzter Spannungsspeicher
void setup() {
tft.begin();
tft.setRotation(1); // Display-Ausrichtung
tft.fillScreen(GC9A01A_BLACK); // Bildschirm löschen
drawScale();
drawFMLabel(); // Separates Zeichnen der Beschriftung
}
void loop() {
// Spannung am Pin A0 einlesen
float voltage = analogRead(A0) * 3.3 / 1023.0;
// Zeichne den Zeiger nur neu, wenn sich die Spannung geändert hat
if (abs(voltage - lastVoltage) > 0.01) {
float angle = mapVoltageToAngle(voltage);
drawPointer(angle);
lastVoltage = voltage;
}
delay(50); // Kurze Pause
}
void drawScale() {
const int radiusOuter = 100; // Radius für äußere Markierungen
const int radiusInner = 90; // Radius für innere Markierungen
const int radiusText = 110; // Radius für die Beschriftung
const float startAngle = -270; // Startwinkel in Grad (unten)
const float angleStep = 330.0 / 21; // Schrittweite in Grad
// Gelbe Kreislinie zeichnen
for (int i = 0; i <= 330; i++) {
float rad = radians(startAngle + i);
int x1 = centerX + (radiusOuter - lineWidth) * cos(rad);
int y1 = centerY + (radiusOuter - lineWidth) * sin(rad);
int x2 = centerX + radiusOuter * cos(rad);
int y2 = centerY + radiusOuter * sin(rad);
tft.drawLine(x1, y1, x2, y2, GC9A01A_YELLOW);
}
for (int i = 0; i <= 21; i++) {
float angle = startAngle + i * angleStep;
float rad = radians(angle);
// Koordinaten für die äußere Markierung
int xOuter = centerX + radiusOuter * cos(rad);
int yOuter = centerY + radiusOuter * sin(rad);
// Koordinaten für die innere Markierung
int xInner = centerX + radiusInner * cos(rad);
int yInner = centerY + radiusInner * sin(rad);
// Linie für die Markierung zeichnen
tft.drawLine(xInner, yInner, xOuter, yOuter, GC9A01A_WHITE);
// Beschriftung
int xText = centerX + radiusText * cos(rad);
int yText = centerY + radiusText * sin(rad);
if (i == 1) { // Anfangsmarkierung 88 MHz
tft.setTextSize(1);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(xText - 6, yText - 3);
tft.print("88");
} else if (i == 7) { // Markierung 94 MHz
tft.setTextSize(1);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(xText - 6, yText - 3);
tft.print("94");
} else if (i == 13) { // Markierung 100 MHz
tft.setTextSize(1);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(xText - 6, yText - 3);
tft.print("100");
} else if (i == 18) { // Markierung 105 MHz
tft.setTextSize(1);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(xText - 6, yText - 3);
tft.print("105");
} else if (i == 21) { // Endmarkierung 108 MHz
tft.setTextSize(1);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(xText - 6, yText - 3);
tft.print("108");
}
}
// Roten Mittelpunkt zeichnen
tft.fillCircle(centerX, centerY, 5, GC9A01A_RED);
}
void drawFMLabel() {
// "FM MHz" mittig oben zeichnen
tft.setTextSize(2);
tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
tft.setCursor(centerX - 36, centerY - 50); // Position angepasst
tft.print("FM MHz");
}
float mapVoltageToAngle(float voltage) {
// Mappe Spannung (0-3,3 V) auf Winkelbereich (88 bis 108 MHz auf Skala)
return -270 + (voltage / 3.3) * 330;
}
void drawPointer(float angle) {
static float prevAngle = -270; // Letzte Position des Zeigers
float radPrev = radians(prevAngle);
// Vorherigen Zeiger und Pfeilspitze löschen
int xEndPrev = centerX + pointerLength * cos(radPrev);
int yEndPrev = centerY + pointerLength * sin(radPrev);
tft.drawLine(centerX, centerY, xEndPrev, yEndPrev, GC9A01A_BLACK);
drawTriangle(radPrev, GC9A01A_BLACK);
// Aktuellen Zeiger zeichnen
float rad = radians(angle);
int xEnd = centerX + pointerLength * cos(rad);
int yEnd = centerY + pointerLength * sin(rad);
tft.drawLine(centerX, centerY, xEnd, yEnd, GC9A01A_YELLOW);
drawTriangle(rad, GC9A01A_YELLOW);
// Roter Mittelpunkt neu zeichnen
tft.fillCircle(centerX, centerY, 5, GC9A01A_RED);
// "FM MHz" erneut zeichnen, um Überschreiben zu vermeiden
drawFMLabel();
// Update vorherigen Winkel
prevAngle = angle;
}
void drawTriangle(float rad, uint16_t color) {
int xTip = centerX + (pointerLength + triangleOffset) * cos(rad);
int yTip = centerY + (pointerLength + triangleOffset) * sin(rad);
int xLeft = centerX + (pointerLength - triangleWidth) * cos(rad - radians(10));
int yLeft = centerY + (pointerLength - triangleWidth) * sin(rad - radians(10));
int xRight = centerX + (pointerLength - triangleWidth) * cos(rad + radians(10));
int yRight = centerY + (pointerLength - triangleWidth) * sin(rad + radians(10));
tft.fillTriangle(xTip, yTip, xLeft, yLeft, xRight, yRight, color);
}
//Ende Sketch
Infrage kommende FM-Empfangs-Chips sind z.B. RDA5807,
RDA7088 oder CL6017S, um nur einige zu nennen. Die Steuerspannung
greift man massebezogen dann einfach am Schleifer des
Frequenzeinstellungspotis ab und führt sie dem Analogeingang des
Mikrocontrollers (hier A0 des ESP8266 D1 mini) zu. Das Hochladen
des kompilierten Sketches kann z. B. über USB mit der Arduino IDE
1.8.19 erfolgen, nachdem man die im Sketch unter #include genannten
erforderlichen Bibliotheken installiert hat. Auf dem beigefügten Video
erkennt man den sich bewegenden Zeiger beim Abstimmen eines Potis,
welches einfach zwischen Masse und dem 3,3 V Ausgang des ESP8266
geschaltet ist.