/* 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 source of the code. */ #include #include #include #include "KT0803.h" #include 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 34 #define JOY_Y 35 #define JOY_SW 32 #define FM_SDA 21 #define FM_SCL 22 // ======================= SD (microSD) Card ======================= #define SD_MOSI 23 // sd reader share spi pin with the display #define SD_MISO 19 #define SD_SCK 18 #define SD_CS 33 // ======================= PCM5102A DAC ======================= #define I2S_BCLK 26 #define I2S_LRCK 25 #define I2S_DOUT 17 #define SAMPLE_RATE 44100 #define FREQUENCY 440.0f #define AMPLITUDE 12000 I2SClass I2S; typedef struct CursorPos { int x; int y; } CursorPos; 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; // Change its value if your joystick's position is not same as you expected. bool swapJoyStickVertical = false; bool swapJoyStickHorizontal = false; 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); bool isUp = x >= 1800 && y <= 500; bool isDown = x >= 1800 && y >= 3800; bool isLeft = x <= 500 && y >= 1800; bool isRight = x >= 3800 && y >= 1800; if (swapJoyStickVertical) { // Wow, c++ have this cool thing std::swap(isUp, isDown); } if (swapJoyStickHorizontal) { std::swap(isLeft, isRight); } if (isDown) { Serial.println("down"); status.down = true; } if (isUp) { Serial.println("up"); status.up = true; } if (isLeft) { Serial.println("left"); status.left = true; } if (isRight) { 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 1.5 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 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; swapJoyStickVertical = true; // pcm5102 // I2S.setPins( // I2S_BCLK, // I2S_LRCK, // I2S_DOUT, // -1, // -1 // ); // if (!I2S.begin( // I2S_MODE_STD, // SAMPLE_RATE, // I2S_DATA_BIT_WIDTH_16BIT, // I2S_SLOT_MODE_STEREO // )) { // Serial.println("Unable to init PCM5102!\n"); // } else { // Serial.println("Found PCM5102\n"); // } Serial.println("Initialization Finished"); } void loop(void) { // static float phase = 0.0f; // int16_t sample = (int16_t)( // sinf(phase) * AMPLITUDE // ); // // Stereo: Left + Right // int16_t stereo[2] = { // sample, // sample // }; // I2S.write( // (uint8_t *)stereo, // sizeof(stereo) // ); // phase += 2.0f * PI * FREQUENCY / SAMPLE_RATE; // if (phase >= 2.0f * PI) { // phase -= 2.0f * PI; // } if (currentOptionPage == 0) { homeMenu(); } else if (currentOptionPage == 1) { joyStickDebugMenu(); } else if (currentOptionPage == 2) { fmBroadcast(); } }