407 lines
9.1 KiB
Arduino
407 lines
9.1 KiB
Arduino
/*
|
|||
|
|
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 <SPI.h>
|
||
|
|
#include <U8g2lib.h>
|
||
|
|
#include <Wire.h>
|
||
|
|
#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();
|
||
|
|
}
|
||
|
|
}
|