commit fca0a2c618cbaeb72908e0409cba530d4b0b459e Author: wei Date: Sat Aug 22 17:16:12 2026 +0800 first commit diff --git a/FM_Brocaster.ino b/FM_Brocaster.ino new file mode 100644 index 0000000..8f623c1 --- /dev/null +++ b/FM_Brocaster.ino @@ -0,0 +1,407 @@ +/* +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(); + } +} \ No newline at end of file