-----
The Flex Radio 6400 is an amazing state of the art ham radio transceiver with so many modern bells and whistles than no normal human could ever get around to using them all. However, this can also put off many olde tyme hams that resist changes from the Golden Age of Radio. In an effort to ease this transition we had to create the Drifty Flex and Rotary Phone Interface application. As helpful as this has been, there was still the common complaint that "Dammit, real radios have knobs and buttons!". So, to assist again with the transition to Flex Radio we launched another project.
-----
The Stream Deck is a relatively common Flex Radio add on, but those things can cost over a hundred bucks and that money could be better spent on old J38 Morse keys, etc. In all seriousness, we don't miss the buttons and knobs at all on the Flex. However, we did find moving the mouse quickly to click the small RIT and XIT adjustment buttons in the Smart SDR interface a burden. After seeing this ESP32 Module with onboard 2.8 inch touchscreen 2.8 and WiFi (also called a CYD for "Cheap Yellow Display") for only $9 on AliExpress we got an idea.
-----
The code below splits the CYD touchscreen into four sections to adjust for RIT-, RIT+, XIT-, and XIT+. Adjustment are made in 10Hz steps. If no change is made for 60 minutes the values revert to 0. The rig connects to the Flex Radio over WiFi so only 5VDC power is needed. The IP address of the CYD is displayed in yellow in the bottom right.
-----
It works great. Here is a short video of the rig in action:
-----Want to build your own? It's easy and here is the code to make it happen. As with all programs written today AI both sped up and slowed down progress at times:
/*
* "CYD_StreamDeck" uses ESP32 with touchscreen "Cheap Yellow Display" to provide s
* RIT and XIT control to a FLEXRADIO. If no change for 60 both RIT and XIT
* reset to "0"; edit AUTO_RESET_TIMEOUT variable to adjust.
* Arduino IDE Board Setting: ESP32 Dev Module, 240MHz (WiFi/BT), 921600
* MAR2026
* Project details at: whiskeytangohotel.com
* Custom User_Setup.h for CYD library is:
//#define ILI9341_DRIVER
#define ILI9341_2_DRIVER // ← Uncomment / use this instead of above
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1
#define TFT_BL 21 // Backlight Original = 21
#define TFT_BACKLIGHT_ON HIGH
#define SPI_FREQUENCY 40000000
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:.
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48
#define SMOOTH_FONT
*
*
*/
#include <WiFi.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
#include <SPI.h>
#include <esp_wifi.h>
// WiFi and FlexRadio settings
const char* ssid = "ur-ssid";
const char* password = "ur-wifi-passowrd";
const char* flexIP = "192.168.1.2";
const int flexPort = 4992;
// Touch pins
#define XPT2046_IRQ -1
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33
SPIClass touchSPI(VSPI);
XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);
// Display
TFT_eSPI tft = TFT_eSPI();
// Flex connection
WiFiClient flexClient;
unsigned long seq = 0;
int current_rit = 0;
int current_xit = 0;
const int step = 10;
// Button layout
#define BUTTON_WIDTH 160
#define BUTTON_HEIGHT 120
// Debounce
unsigned long lastTouchTime = 0;
const unsigned long touchDebounce = 250;
// Auto-reset timers (moved to global scope so parseStatus can see them)
static unsigned long lastRitChange = 0;
static unsigned long lastXitChange = 0;
// Timeout for auto-reset RIT and XIT to "0" if not changed
const unsigned long AUTO_RESET_TIMEOUT = 60UL * 60 * 1000; // 60UL = 60 mins, 30UL = 30 mins, etc.
// --- Display offsets centered on x-axis, narrow boxes for 4 chars, + for positive ---
void drawOffsets() {
tft.setTextColor(TFT_CYAN);
tft.setTextSize(2);
tft.setTextDatum(MC_DATUM); // Middle-center alignment
// Narrow rectangle just for 4 chars
const int rectWidth = 48;
const int rectHeight = 30;
// Horizontal center of the screen
int centerX = tft.width() / 2; // 160
// RIT - same vertical center
int ritY = 16;
// Format RIT: add "+" if positive
String ritStr = (current_rit > 0) ? "+" + String(current_rit) : String(current_rit);
tft.fillRect(centerX - rectWidth/2, ritY - rectHeight/2, rectWidth, rectHeight, TFT_BLACK);
//tft.drawRect(centerX - rectWidth/2, ritY - rectHeight/2, rectWidth, rectHeight, TFT_WHITE);
tft.drawString(ritStr, centerX, ritY);
// XIT - same vertical center
int xitY = 136;
// Format XIT: add "+" if positive
String xitStr = (current_xit > 0) ? "+" + String(current_xit) : String(current_xit);
tft.fillRect(centerX - rectWidth/2, xitY - rectHeight/2, rectWidth, rectHeight, TFT_BLACK);
//tft.drawRect(centerX - rectWidth/2, xitY - rectHeight/2, rectWidth, rectHeight, TFT_WHITE);
tft.drawString(xitStr, centerX, xitY);
}
// --- Display IP address in bottom-right corner ---
void drawIP() {
String ipStr = WiFi.localIP().toString();
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextSize(1); // small text
tft.setTextDatum(BR_DATUM); // align to bottom-right
// Clear previous IP area (110 pixels wide, 20 tall — adjust if text overflows)
tft.fillRect(tft.width() - 120, tft.height() - 30, 110, 20, TFT_BLACK);
// Draw new IP with 5px padding from edges
tft.drawString(ipStr, tft.width() - 5, tft.height() - 5);
}
void drawButtons();
void connectToFlex();
void sendCommand(String cmd);
void parseStatus(String line);
void setup() {
Serial.begin(115200);
delay(200);
pinMode(21, OUTPUT);
digitalWrite(21, HIGH);
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
delay(50);
tft.writecommand(ILI9341_GAMMASET);
tft.writedata(2);
tft.writecommand(ILI9341_GAMMASET);
tft.writedata(1);
// Touch init
touchSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
ts.begin(touchSPI);
ts.setRotation(2);
tft.invertDisplay(true);
drawButtons();
// Show initial offsets
drawOffsets();
// WiFi connect
WiFi.begin(ssid, password);
esp_wifi_set_ps(WIFI_PS_NONE); // no power saving at all
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// Show IP once connected
drawIP();
connectToFlex();
// Initialize change timers
lastRitChange = millis();
lastXitChange = millis();
}
void loop() {
if (flexClient.available()) {
String line = flexClient.readStringUntil('\n');
parseStatus(line);
}
if (ts.touched()) {
TS_Point p = ts.getPoint();
if (p.z > 900 && p.z < 3800 && millis() - lastTouchTime > touchDebounce) {
int x = map(p.x, 200, 3700, 0, 320);
int y = map(p.y, 300, 3800, 0, 240);
x = constrain(x, 0, 319);
y = constrain(y, 0, 239);
bool changed = false;
// -------- BUTTON MAPPING --------
if (x < 160 && y < 120) {
current_rit += step;
sendCommand("slice s 0 rit_on=1 rit_freq=" + String(current_rit));
Serial.println("RIT +");
lastRitChange = millis(); // update on change
changed = true;
drawOffsets();
}
else if (x >= 160 && y < 120) {
current_xit += step;
sendCommand("slice s 0 xit_on=1 xit_freq=" + String(current_xit));
Serial.println("XIT +");
lastXitChange = millis();
changed = true;
drawOffsets();
}
else if (x < 160 && y >= 120) {
current_rit -= step;
sendCommand("slice s 0 rit_on=1 rit_freq=" + String(current_rit));
Serial.println("RIT -");
lastRitChange = millis();
changed = true;
drawOffsets();
}
else {
current_xit -= step;
sendCommand("slice s 0 xit_on=1 xit_freq=" + String(current_xit));
Serial.println("XIT -");
lastXitChange = millis();
changed = true;
drawOffsets();
}
if (changed) {
lastTouchTime = millis();
}
}
}
// Auto-reset both RIT and XIT to zero after 60 min of no change
unsigned long now = millis();
if ((now - lastRitChange >= AUTO_RESET_TIMEOUT) &&
(now - lastXitChange >= AUTO_RESET_TIMEOUT)) {
if (current_rit != 0 || current_xit != 0) {
current_rit = 0;
current_xit = 0;
sendCommand("slice s 0 rit_on=0 rit_freq=0");
sendCommand("slice s 0 xit_on=0 xit_freq=0");
Serial.println("Auto-reset: RIT and XIT set to 0 (no change for 60 min)");
drawOffsets();
lastRitChange = now;
lastXitChange = now;
}
}
if (!flexClient.connected()) {
connectToFlex();
}
// Optional: refresh IP every 5 mins = 300 seconds
static unsigned long lastIPRefresh = 0;
if (millis() - lastIPRefresh > 300000) {
drawIP();
lastIPRefresh = millis();
}
}
void drawButtons() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(4);
tft.setTextDatum(MC_DATUM);
tft.drawRect(0,0,160,120,TFT_WHITE);
tft.drawString("RIT-",80,60);
tft.drawRect(160,0,160,120,TFT_WHITE);
tft.drawString("RIT+",240,60);
tft.drawRect(0,120,160,120,TFT_WHITE);
tft.drawString("XIT-",80,180);
tft.drawRect(160,120,160,120,TFT_WHITE);
tft.drawString("XIT+",240,180);
}
void connectToFlex() {
if (flexClient.connect(flexIP, flexPort)) {
sendCommand("client program CYD_Control");
sendCommand("sub slice all");
} else {
delay(2000);
}
}
void sendCommand(String cmd) {
String full = "C" + String(seq++) + "|" + cmd + "\n";
flexClient.print(full);
Serial.println(full);
}
void parseStatus(String line) {
if (line.startsWith("S")) {
int pos = line.indexOf("slice 0");
if (pos != -1) {
int ritPos = line.indexOf("rit_freq=", pos);
if (ritPos != -1) {
int end = line.indexOf(" ", ritPos + 9);
if (end == -1) end = line.length();
int newRit = line.substring(ritPos + 9, end).toInt();
if (newRit != current_rit) {
current_rit = newRit;
lastRitChange = millis(); // reset timer on change
}
}
int xitPos = line.indexOf("xit_freq=", pos);
if (xitPos != -1) {
int end = line.indexOf(" ", xitPos + 9);
if (end == -1) end = line.length();
int newXit = line.substring(xitPos + 9, end).toInt();
if (newXit != current_xit) {
current_xit = newXit;
lastXitChange = millis();
}
}
drawOffsets();
}
}
}
-----
