/* ESP32 Frequency Modulation Broadcaster Some code is from my old game project but now is almost dead. Wei - 2026 You can do anything you want to this source code. BUT you MUST cite the original soruce of the code... */ #include #include #include #include "KT0803.h" KT0803L fm(&Wire); // ======================= Display ======================= #define SCREEN_W 128 #define SCREEN_H 64 #define OLED_MOSI 23 // SDA #define OLED_CLK 18 // SCK #define OLED_DC 27 #define OLED_CS 5 #define OLED_RST 14 // RES U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2( U8G2_R0, OLED_CS, OLED_DC, OLED_RST ); // ======================= Joystick Pins ======================= #define JOY_X 4 #define JOY_Y 13 #define JOY_SW 2 #define FM_SDA 21 #define FM_SCL 22 typedef struct CursorPos { int x; int y; } CursorPos; void setup() { // debug Serial.begin(115200); // Initialize display u8g2.begin(); // fm module Wire.begin(); Wire.setClock(100000); // scan i2c devices byte error, address; int nDevices; for(address = 1; address < 127; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); nDevices++; } } if (nDevices == 0) Serial.println("No I2C devices found\n"); // Set font and en-able unicode support u8g2.enableUTF8Print(); u8g2.setFont(u8g2_font_6x12_tf); u8g2.clearBuffer(); // Joystick pinMode(JOY_SW, INPUT_PULLUP); analogReadResolution(12); // Set center position of the joystick Serial.println("Correcting joystick... (Don't touch joystick when processing...)"); int rawX = analogRead(JOY_X); int rawY = analogRead(JOY_Y); int cx = 2048; int cy = 2048; Serial.println("Initialization Finished"); } void printScreen(int x, int y, const char* text, bool clear_screen, bool send_buffer) { if (clear_screen) { u8g2.clearBuffer(); } u8g2.setCursor(x, y); u8g2.print(text); if (send_buffer) { u8g2.sendBuffer(); } } struct JoyStickStatus; JoyStickStatus getJoyStickerStatus(int pin_x, int pin_y, int pin_sw); struct JoyStickStatus { bool up; bool down; bool left; bool right; bool pressed; }; JoyStickStatus getJoyStickerStatus(int pin_x, int pin_y, int pin_sw) { JoyStickStatus status = {0}; int x = analogRead(pin_x); int y = analogRead(pin_y); bool pressed = (digitalRead(pin_sw) == LOW); if (x >= 1800 && y >= 3800) { Serial.println("down"); status.down = true; } if (x >= 1800 && y <= 500) { Serial.println("up"); status.up = true; } if (x <= 500 && y >= 1800) { Serial.println("left"); status.left = true; } if (x >= 3800 && y >= 1800) { Serial.println("right"); status.right = true; } status.pressed = pressed; char buffer[100]; snprintf(buffer, sizeof(buffer), "X:%d , Y:%d, SW:%s", x, y, pressed ? "PRESSED" : "RELEASED"); Serial.println(buffer); return status; } void getJoyStickerDebugInfo(int start_x, int start_y, int pin_x, int pin_y, int pin_sw, bool print_in_serial) { int x = analogRead(pin_x); int y = analogRead(pin_y); bool pressed = (digitalRead(pin_sw) == LOW); u8g2.setCursor(start_x, start_y); u8g2.print("X: "); u8g2.print(x); u8g2.setCursor(start_x, start_y + 12); u8g2.print("Y: "); u8g2.print(y); u8g2.setCursor(start_x, start_y + 24); u8g2.print("SW: "); u8g2.print(pressed ? "PRESSED" : "RELEASED"); JoyStickStatus status = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW); u8g2.setCursor(start_x, start_y + 36); if (status.up) { u8g2.print("Direction: UP"); } else if (status.down) { u8g2.print("Direction: DOWN"); } else if (status.left) { u8g2.print("Direction: LEFT"); } else if (status.right) { u8g2.print("Direction: RIGHT"); } if (print_in_serial) { char buffer[100]; snprintf(buffer, sizeof(buffer), "X:%d , Y:%d, SW:%s", x, y, pressed ? "PRESSED" : "RELEASED"); Serial.println(buffer); } } int selectOptionID = 1; int currentOptionPage = 0; // 0: Home, 1: JoyStick Debug, 2: FM Broadcast const int optionIDList[] = {1,2}; int buttonPressCount = 0; bool wasPressed = false; unsigned long buttonPressTime = 0; float roundToOneDecimal(float num) { return (int)(num * 10 + 0.5) / 10.0; } void hanleQuitMenuEvent() { JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW); if (joyStatus.pressed && !wasPressed) { // Start time record buttonPressTime = millis(); wasPressed = true; } if (!joyStatus.pressed && wasPressed) { // Quit menu if pressed time more than over 3 sec unsigned long duration = millis() - buttonPressTime; if (duration >= 1500) { currentOptionPage=0; } wasPressed = false; } } void setFM(float freq, bool mute) { fm.setUSA(); fm.setFrequency(freq); fm.setMute(mute); } void fmBroadcast(void) { float fm_freq = 98.1; float old_freq = 98.1; if (!fm.begin(fm_freq, false)) { Serial.println("FM Module KT0803 not found. Exiting..."); printScreen(0, 14, "FM Module KT0803 not", true, false); u8g2.setCursor(0, 26); u8g2.print(" found. Exiting..."); u8g2.sendBuffer(); delay(3500); currentOptionPage=0; return; } setFM(fm_freq, false); while (currentOptionPage == 2) { int adc = analogRead(JOY_X); int offset = adc - 2048; if (abs(offset) > 200) { float speed = offset / 2048.0f; fm_freq += speed * 0.1f; fm_freq = roundToOneDecimal(fm_freq); // Max and min value check if (fm_freq < 88.0) { fm_freq = 88.0; } else if (fm_freq > 108.0) { fm_freq = 108.0; } } if (old_freq != fm_freq) { // Update frequency setFM(fm_freq, false); old_freq = fm_freq; Serial.print("Frequency: "); Serial.println(fm_freq); } printScreen(0, 14, "Brocasting_", true, false); u8g2.setCursor(0, 26); u8g2.print("Frequency: "); u8g2.print(fm.getFrequency()); u8g2.setCursor(0, 38); u8g2.print("Now you can leave"); u8g2.setCursor(0, 50); u8g2.print("this page."); u8g2.sendBuffer(); delay(300); printScreen(0, 14, "Brocasting ", true, false); u8g2.setCursor(0, 26); u8g2.print("Frequency: "); u8g2.print(fm.getFrequency()); u8g2.setCursor(0, 38); u8g2.print("Now you can leave"); u8g2.setCursor(0, 50); u8g2.print("this page."); u8g2.sendBuffer(); delay(300); hanleQuitMenuEvent(); } } void joyStickDebugMenu(void) { while (currentOptionPage == 1) { printScreen(0, 14, "Debugging joystick.", true, false); getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true); u8g2.sendBuffer(); delay(300); printScreen(0, 14, "Debugging joystick..", true, false); getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true); u8g2.sendBuffer(); delay(300); printScreen(0, 14, "Debugging joystick...", true, false); getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true); u8g2.sendBuffer(); delay(300); printScreen(0, 14, "Debugging joystick....", true, false); getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true); u8g2.sendBuffer(); delay(300); hanleQuitMenuEvent(); } } CursorPos printOptionMenu(int lastX, int lastY) { CursorPos pos = {lastX, lastY}; pos.y+=12; u8g2.setCursor(pos.x, pos.y); u8g2.print("1: Debug Joystick"); if (selectOptionID == 1) { u8g2.print(" [X]"); } else { u8g2.print(" [ ]"); } pos.y+=12; u8g2.setCursor(pos.x, pos.y); u8g2.print("2: FM Broadcast"); if (selectOptionID == 2) { u8g2.print(" [X]"); } else { u8g2.print(" [ ]"); } return pos; } void handleOptionSelectEvent() { JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW); size_t lengthOfOptions = sizeof(optionIDList) / sizeof(optionIDList[0]); if (joyStatus.down) { if (selectOptionID+1 <= lengthOfOptions) { selectOptionID+=1; } } else if (joyStatus.up) { if (selectOptionID-1 >= 1) { selectOptionID-=1; } } if (joyStatus.pressed) { buttonPressCount+=1; } // Do option page replace if button is pressed twice. if (buttonPressCount>=2) { buttonPressCount=0; currentOptionPage=selectOptionID; // Reset selectOptionID=1; } } void homeMenu(void) { while (currentOptionPage==0) { printScreen(0, 14, "Running_", true, false); CursorPos currentPos = printOptionMenu(0, 14); printScreen(currentPos.x, currentPos.y+24, "FM Brocaster v1.0", false, false); u8g2.sendBuffer(); delay(300); // Handle option select event while the joystick is moving (?) handleOptionSelectEvent(); printScreen(0, 14, "Running ", true, false); currentPos = printOptionMenu(0, 14); printScreen(currentPos.x, currentPos.y+24, "FM Brocaster v1.0", false, false); u8g2.sendBuffer(); delay(300); } } void loop(void) { if (currentOptionPage == 0) { homeMenu(); } else if (currentOptionPage == 1) { joyStickDebugMenu(); } else if (currentOptionPage == 2) { fmBroadcast(); } }