Add PCM5102 Synthesizer and Play Music (mp3) support.
The original source is FM_Brocaster_original.
This commit is contained in:
+439
-332
@@ -1,407 +1,514 @@
|
|||||||
/*
|
|
||||||
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 <SPI.h>
|
||||||
|
#include <SD.h>
|
||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "KT0803.h"
|
#include "KT0803.h"
|
||||||
|
#include <ESP_I2S.h>
|
||||||
|
#include "MP3DecoderHelix.h"
|
||||||
|
|
||||||
|
using namespace libhelix;
|
||||||
|
|
||||||
|
#define VERSION "1.2"
|
||||||
|
#define OLED_CS 5
|
||||||
|
#define OLED_DC 27
|
||||||
|
#define OLED_RST 14
|
||||||
|
#define JOY_X 34
|
||||||
|
#define JOY_Y 35
|
||||||
|
#define JOY_SW 32
|
||||||
|
#define SD_MOSI 23
|
||||||
|
#define SD_MISO 19
|
||||||
|
#define SD_SCK 18
|
||||||
|
#define SD_CS 33
|
||||||
|
#define I2S_BCLK 26
|
||||||
|
#define I2S_LRCK 25
|
||||||
|
#define I2S_DOUT 17
|
||||||
|
|
||||||
|
constexpr uint32_t SAMPLE_RATE = 44100;
|
||||||
|
constexpr float TONE_FREQ = 440.0f;
|
||||||
|
constexpr int MAX_MUSIC_FILES = 20;
|
||||||
|
constexpr int MAX_FILENAME_LEN = 48;
|
||||||
|
|
||||||
KT0803L fm(&Wire);
|
KT0803L fm(&Wire);
|
||||||
|
I2SClass I2S;
|
||||||
|
U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RST);
|
||||||
|
|
||||||
// ======================= Display =======================
|
struct CursorPos { int x, y; };
|
||||||
#define SCREEN_W 128
|
struct JoyStickStatus { bool up, down, left, right, pressed; };
|
||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
bool swapJoyStickVertical = false;
|
||||||
|
bool swapJoyStickHorizontal = false;
|
||||||
|
int dacVolume = 12;
|
||||||
int selectOptionID = 1;
|
int selectOptionID = 1;
|
||||||
int currentOptionPage = 0; // 0: Home, 1: JoyStick Debug, 2: FM Broadcast
|
int currentOptionPage = 0;
|
||||||
const int optionIDList[] = {1,2};
|
|
||||||
int buttonPressCount = 0;
|
int buttonPressCount = 0;
|
||||||
bool wasPressed = false;
|
bool wasPressed = false;
|
||||||
unsigned long buttonPressTime = 0;
|
unsigned long buttonPressTime = 0;
|
||||||
|
|
||||||
float roundToOneDecimal(float num) {
|
const char* menuNames[] = {
|
||||||
return (int)(num * 10 + 0.5) / 10.0;
|
"Debug Joystick", "FM Brocaster", "Debug PCM5102", "Volume", "Play Music"
|
||||||
|
};
|
||||||
|
constexpr int OPTION_COUNT = sizeof(menuNames) / sizeof(menuNames[0]);
|
||||||
|
|
||||||
|
char musicFiles[MAX_MUSIC_FILES][MAX_FILENAME_LEN];
|
||||||
|
int musicFileCount = 0;
|
||||||
|
int selectedMusic = 0;
|
||||||
|
|
||||||
|
File playingFile;
|
||||||
|
bool musicPlaying = false;
|
||||||
|
bool musicButtonWasPressed = false;
|
||||||
|
uint32_t currentAudioRate = SAMPLE_RATE;
|
||||||
|
|
||||||
|
void mp3DataCallback(MP3FrameInfo &info, int16_t *pcm, size_t len, void*) {
|
||||||
|
if (!pcm || !len || info.nChans < 1) return;
|
||||||
|
|
||||||
|
if ((uint32_t)info.samprate != currentAudioRate) {
|
||||||
|
if (I2S.configureTX(info.samprate, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO))
|
||||||
|
currentAudioRate = info.samprate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr size_t CHUNK_SAMPLES = 256;
|
||||||
|
int16_t out[CHUNK_SAMPLES * 2];
|
||||||
|
|
||||||
|
if (info.nChans == 1) {
|
||||||
|
for (size_t pos = 0; pos < len;) {
|
||||||
|
size_t n = min((size_t)CHUNK_SAMPLES, len - pos);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
int32_t v = (int32_t)pcm[pos + i] * dacVolume / 255;
|
||||||
|
out[i * 2] = out[i * 2 + 1] = (int16_t)v;
|
||||||
|
}
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<const uint8_t*>(out),
|
||||||
|
n * 2 * sizeof(int16_t)
|
||||||
|
);
|
||||||
|
pos += n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (size_t pos = 0; pos < len;) {
|
||||||
|
size_t n = min((size_t)(CHUNK_SAMPLES * 2), len - pos);
|
||||||
|
for (size_t i = 0; i < n; i++)
|
||||||
|
out[i] = (int16_t)((int32_t)pcm[pos + i] * dacVolume / 255);
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<const uint8_t*>(out),
|
||||||
|
n * sizeof(int16_t)
|
||||||
|
);
|
||||||
|
pos += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MP3DecoderHelix mp3(mp3DataCallback);
|
||||||
|
|
||||||
|
void printScreen(int x, int y, const char* text, bool clearScreen = true, bool send = true) {
|
||||||
|
if (clearScreen) u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(x, y);
|
||||||
|
u8g2.print(text);
|
||||||
|
if (send) u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int readJoystickAxis(int pin) {
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < 4; i++) sum += analogRead(pin);
|
||||||
|
return sum / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
JoyStickStatus getJoyStickerStatus() {
|
||||||
|
constexpr int CENTER_LOW = 1500, CENTER_HIGH = 2600;
|
||||||
|
int x = readJoystickAxis(JOY_X), y = readJoystickAxis(JOY_Y);
|
||||||
|
JoyStickStatus s = {
|
||||||
|
x >= CENTER_LOW && x <= CENTER_HIGH && y < 500,
|
||||||
|
x >= CENTER_LOW && x <= CENTER_HIGH && y > 3800,
|
||||||
|
y >= CENTER_LOW && y <= CENTER_HIGH && x < 500,
|
||||||
|
y >= CENTER_LOW && y <= CENTER_HIGH && x > 3800,
|
||||||
|
digitalRead(JOY_SW) == LOW
|
||||||
|
};
|
||||||
|
if (swapJoyStickVertical) std::swap(s.up, s.down);
|
||||||
|
if (swapJoyStickHorizontal) std::swap(s.left, s.right);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getJoyStickerDebugInfo(bool serialOut) {
|
||||||
|
int x = readJoystickAxis(JOY_X), y = readJoystickAxis(JOY_Y);
|
||||||
|
bool pressed = digitalRead(JOY_SW) == LOW;
|
||||||
|
JoyStickStatus s = getJoyStickerStatus();
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 26); u8g2.print("X: "); u8g2.print(x);
|
||||||
|
u8g2.setCursor(0, 38); u8g2.print("Y: "); u8g2.print(y);
|
||||||
|
u8g2.setCursor(0, 50); u8g2.print("SW: "); u8g2.print(pressed ? "PRESSED" : "RELEASED");
|
||||||
|
u8g2.setCursor(0, 62);
|
||||||
|
if (s.up) u8g2.print("Direction: UP");
|
||||||
|
else if (s.down) u8g2.print("Direction: DOWN");
|
||||||
|
else if (s.left) u8g2.print("Direction: LEFT");
|
||||||
|
else if (s.right) u8g2.print("Direction: RIGHT");
|
||||||
|
|
||||||
|
if (serialOut) {
|
||||||
|
Serial.print("X:"); Serial.print(x);
|
||||||
|
Serial.print(" , Y:"); Serial.print(y);
|
||||||
|
Serial.print(", SW:"); Serial.println(pressed ? "PRESSED" : "RELEASED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float roundToOneDecimal(float n) { return (int)(n * 10 + 0.5f) / 10.0f; }
|
||||||
|
|
||||||
void hanleQuitMenuEvent() {
|
void hanleQuitMenuEvent() {
|
||||||
JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
bool pressed = getJoyStickerStatus().pressed;
|
||||||
|
if (pressed && !wasPressed) {
|
||||||
if (joyStatus.pressed && !wasPressed) {
|
|
||||||
// Start time record
|
|
||||||
buttonPressTime = millis();
|
buttonPressTime = millis();
|
||||||
wasPressed = true;
|
wasPressed = true;
|
||||||
}
|
} else if (!pressed && wasPressed) {
|
||||||
|
if (millis() - buttonPressTime >= 1000) currentOptionPage = 0;
|
||||||
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;
|
wasPressed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFM(float freq, bool mute) {
|
void setFM(float freq, bool mute = false) {
|
||||||
fm.setUSA();
|
fm.setUSA();
|
||||||
fm.setFrequency(freq);
|
fm.setFrequency(freq);
|
||||||
fm.setMute(mute);
|
fm.setMute(mute);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fmBroadcast(void) {
|
void drawFM(float freq, bool frame) {
|
||||||
float fm_freq = 98.1;
|
printScreen(0, 14, frame ? "Brocasting_" : "Brocasting ", true, false);
|
||||||
float old_freq = 98.1;
|
u8g2.setCursor(0, 26); u8g2.print("Frequency: "); u8g2.print(freq);
|
||||||
|
u8g2.setCursor(0, 38); u8g2.print("Now you can leave");
|
||||||
|
u8g2.setCursor(0, 50); u8g2.print("this page.");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
if (!fm.begin(fm_freq, false)) {
|
void fmBroadcast() {
|
||||||
Serial.println("FM Module KT0803 not found. Exiting...");
|
float freq = 98.1f, oldFreq = freq;
|
||||||
|
if (!fm.begin(freq, false)) {
|
||||||
printScreen(0, 14, "FM Module KT0803 not", true, false);
|
printScreen(0, 14, "FM Module KT0803 not", true, false);
|
||||||
u8g2.setCursor(0, 26);
|
u8g2.setCursor(0, 26); u8g2.print(" found. Exiting...");
|
||||||
u8g2.print(" found. Exiting...");
|
|
||||||
u8g2.sendBuffer();
|
u8g2.sendBuffer();
|
||||||
delay(3500);
|
delay(3500);
|
||||||
currentOptionPage = 0;
|
currentOptionPage = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setFM(freq);
|
||||||
setFM(fm_freq, false);
|
bool frame = false;
|
||||||
|
|
||||||
while (currentOptionPage == 2) {
|
while (currentOptionPage == 2) {
|
||||||
int adc = analogRead(JOY_X);
|
int offset = analogRead(JOY_X) - 2048;
|
||||||
int offset = adc - 2048;
|
if (abs(offset) > 200) {
|
||||||
|
freq = roundToOneDecimal(freq + (offset / 2048.0f) * 0.1f);
|
||||||
if (abs(offset) > 200)
|
freq = constrain(freq, 88.0f, 108.0f);
|
||||||
{
|
|
||||||
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) {
|
if (oldFreq != freq) {
|
||||||
fm_freq = 108.0;
|
setFM(freq);
|
||||||
|
oldFreq = freq;
|
||||||
|
Serial.print("Frequency: "); Serial.println(freq);
|
||||||
}
|
}
|
||||||
}
|
drawFM(fm.getFrequency(), frame = !frame);
|
||||||
|
|
||||||
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);
|
delay(300);
|
||||||
hanleQuitMenuEvent();
|
hanleQuitMenuEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void joyStickDebugMenu(void) {
|
void joyStickDebugMenu() {
|
||||||
|
int dots = 1;
|
||||||
|
char title[24];
|
||||||
while (currentOptionPage == 1) {
|
while (currentOptionPage == 1) {
|
||||||
printScreen(0, 14, "Debugging joystick.", true, false);
|
strcpy(title, "Debugging joystick");
|
||||||
getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true);
|
for (int i = 0; i < dots; i++) strcat(title, ".");
|
||||||
u8g2.sendBuffer();
|
printScreen(0, 14, title, true, false);
|
||||||
delay(300);
|
getJoyStickerDebugInfo(true);
|
||||||
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();
|
u8g2.sendBuffer();
|
||||||
|
dots = dots % 4 + 1;
|
||||||
delay(300);
|
delay(300);
|
||||||
hanleQuitMenuEvent();
|
hanleQuitMenuEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CursorPos printOptionMenu(int lastX, int lastY) {
|
CursorPos printOptionMenu(int x, int y, int availableHeight, int textSize) {
|
||||||
CursorPos pos = {lastX, lastY};
|
CursorPos pos = {x, y};
|
||||||
|
int maxVisible = availableHeight / textSize;
|
||||||
|
int start = selectOptionID - maxVisible / 2;
|
||||||
|
start = constrain(start, 0, max(0, OPTION_COUNT - maxVisible));
|
||||||
|
int end = min(start + maxVisible, OPTION_COUNT);
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
pos.y += 12;
|
pos.y += 12;
|
||||||
u8g2.setCursor(pos.x, pos.y);
|
u8g2.setCursor(pos.x, pos.y);
|
||||||
u8g2.print("1: Debug Joystick");
|
u8g2.print(i + 1); u8g2.print(": "); u8g2.print(menuNames[i]);
|
||||||
if (selectOptionID == 1) {
|
u8g2.print(selectOptionID == i + 1 ? " [X]" : " [ ]");
|
||||||
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;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleOptionSelectEvent() {
|
void handleOptionSelectEvent() {
|
||||||
JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
JoyStickStatus s = getJoyStickerStatus();
|
||||||
size_t lengthOfOptions = sizeof(optionIDList) / sizeof(optionIDList[0]);
|
if (s.down && selectOptionID < OPTION_COUNT) selectOptionID++;
|
||||||
|
else if (s.up && selectOptionID > 1) selectOptionID--;
|
||||||
if (joyStatus.down) {
|
if (s.pressed) buttonPressCount++;
|
||||||
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) {
|
if (buttonPressCount >= 2) {
|
||||||
buttonPressCount = 0;
|
buttonPressCount = 0;
|
||||||
currentOptionPage = selectOptionID;
|
currentOptionPage = selectOptionID;
|
||||||
|
|
||||||
// Reset
|
|
||||||
selectOptionID = 1;
|
selectOptionID = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void homeMenu(void) {
|
void printLogo(int x, int y) {
|
||||||
|
u8g2.drawBox(x, y, 128 - x, 1);
|
||||||
|
u8g2.setCursor(x + 2, y + 10);
|
||||||
|
u8g2.print("FM Brocaster v" VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void homeMenu() {
|
||||||
|
bool frame = false;
|
||||||
|
unsigned long displayAt = millis(), inputAt = millis(), animateAt = millis();
|
||||||
while (currentOptionPage == 0) {
|
while (currentOptionPage == 0) {
|
||||||
printScreen(0, 14, "Running_", true, false);
|
unsigned long now = millis();
|
||||||
CursorPos currentPos = printOptionMenu(0, 14);
|
if (now - inputAt >= 500) { inputAt = now; handleOptionSelectEvent(); }
|
||||||
printScreen(currentPos.x, currentPos.y+24, "FM Brocaster v1.0", false, false);
|
if (now - animateAt >= 500) { animateAt = now; frame = !frame; }
|
||||||
|
if (now - displayAt >= 33) {
|
||||||
|
displayAt = now;
|
||||||
|
printScreen(0, 14, frame ? "Running_" : "Running ", true, false);
|
||||||
|
CursorPos p = printOptionMenu(0, 14, 36, 12);
|
||||||
|
printLogo(p.x, p.y + 2);
|
||||||
u8g2.sendBuffer();
|
u8g2.sendBuffer();
|
||||||
delay(300);
|
}
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle option select event while the joystick is moving (?)
|
void test_pcm5102(float sineFreq, float squareFreq, float triangleFreq) {
|
||||||
handleOptionSelectEvent();
|
static float phase[3] = {0, 0, 0};
|
||||||
|
constexpr size_t FRAMES = 256;
|
||||||
|
int16_t samples[FRAMES * 2];
|
||||||
|
float step[3] = {
|
||||||
|
TWO_PI * sineFreq / SAMPLE_RATE,
|
||||||
|
TWO_PI * squareFreq / SAMPLE_RATE,
|
||||||
|
TWO_PI * triangleFreq / SAMPLE_RATE
|
||||||
|
};
|
||||||
|
|
||||||
printScreen(0, 14, "Running ", true, false);
|
for (size_t i = 0; i < FRAMES; i++) {
|
||||||
currentPos = printOptionMenu(0, 14);
|
float mixed = 0;
|
||||||
printScreen(currentPos.x, currentPos.y+24, "FM Brocaster v1.0", false, false);
|
if (sineFreq > 0) mixed += sinf(phase[0]) * 2500.0f;
|
||||||
|
if (squareFreq > 0) mixed += (phase[1] < PI ? 1.0f : -1.0f) * 1000.0f;
|
||||||
|
if (triangleFreq > 0)
|
||||||
|
mixed += (phase[2] < PI ? 2.0f * phase[2] / PI - 1.0f : 3.0f - 2.0f * phase[2] / PI) * 1000.0f;
|
||||||
|
|
||||||
|
int16_t sample = (int16_t)constrain(mixed, -32768.0f, 32767.0f);
|
||||||
|
samples[i * 2] = samples[i * 2 + 1] = sample;
|
||||||
|
for (int p = 0; p < 3; p++) {
|
||||||
|
phase[p] += step[p];
|
||||||
|
if (phase[p] >= TWO_PI) phase[p] -= TWO_PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2S.write((uint8_t*)samples, sizeof(samples));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pcm5102DebugMenu() {
|
||||||
|
unsigned long displayAt = 0;
|
||||||
|
int frame = 0;
|
||||||
|
float sine = TONE_FREQ, square = 0, triangle = 0;
|
||||||
|
while (currentOptionPage == 3) {
|
||||||
|
test_pcm5102(sine, square, triangle);
|
||||||
|
int offset = readJoystickAxis(JOY_X) - 2048;
|
||||||
|
if (abs(offset) > 200) sine = roundToOneDecimal(sine + (offset / 2048.0f) * 0.1f);
|
||||||
|
|
||||||
|
offset = readJoystickAxis(JOY_Y) - 2048;
|
||||||
|
float speed = abs(offset) / 2048.0f * 0.1f;
|
||||||
|
if (offset > 0) triangle = roundToOneDecimal(triangle + speed);
|
||||||
|
else if (offset < 0) square = roundToOneDecimal(square + speed);
|
||||||
|
|
||||||
|
if (millis() - displayAt >= 300) {
|
||||||
|
displayAt = millis();
|
||||||
|
char title[24] = "Debugging PCM5102";
|
||||||
|
for (int i = 0; i <= frame; i++) strcat(title, ".");
|
||||||
|
frame = (frame + 1) % 4;
|
||||||
|
printScreen(0, 14, title, true, false);
|
||||||
|
u8g2.setCursor(0, 28); u8g2.print("Sine Freq: "); u8g2.print(sine);
|
||||||
|
u8g2.setCursor(0, 40); u8g2.print("Square Freq: "); u8g2.print(square);
|
||||||
|
u8g2.setCursor(0, 52); u8g2.print("Triangle Freq: "); u8g2.print(triangle);
|
||||||
u8g2.sendBuffer();
|
u8g2.sendBuffer();
|
||||||
delay(300);
|
hanleQuitMenuEvent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void) {
|
void volumeMenu() {
|
||||||
if (currentOptionPage == 0) {
|
unsigned long updateAt = 0;
|
||||||
homeMenu();
|
while (currentOptionPage == 4) {
|
||||||
|
if (millis() - updateAt < 30) continue;
|
||||||
|
updateAt = millis();
|
||||||
|
JoyStickStatus s = getJoyStickerStatus();
|
||||||
|
dacVolume = constrain(dacVolume + (s.left ? 1 : s.right ? -1 : 0), 0, 255);
|
||||||
|
int barWidth = dacVolume * 84 / 255;
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14); u8g2.print("Volume");
|
||||||
|
u8g2.drawFrame(20, 30, 88, 12);
|
||||||
|
u8g2.drawBox(22, 32, barWidth, 8);
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
hanleQuitMenuEvent();
|
||||||
|
delay(1);
|
||||||
}
|
}
|
||||||
else if (currentOptionPage == 1) {
|
|
||||||
joyStickDebugMenu();
|
|
||||||
}
|
}
|
||||||
else if (currentOptionPage == 2) {
|
|
||||||
fmBroadcast();
|
void scanMusicFiles() {
|
||||||
|
musicFileCount = 0;
|
||||||
|
File dir = SD.open("/music");
|
||||||
|
if (!dir || !dir.isDirectory()) return;
|
||||||
|
while (musicFileCount < MAX_MUSIC_FILES) {
|
||||||
|
File file = dir.openNextFile();
|
||||||
|
if (!file) break;
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
const char* name = file.name();
|
||||||
|
const char* ext = strrchr(name, '.');
|
||||||
|
if (ext && !strcasecmp(ext, ".mp3")) {
|
||||||
|
strncpy(musicFiles[musicFileCount], name, MAX_FILENAME_LEN - 1);
|
||||||
|
musicFiles[musicFileCount++][MAX_FILENAME_LEN - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMusicSelect() {
|
||||||
|
int y = readJoystickAxis(JOY_Y);
|
||||||
|
if (musicFileCount == 0) return;
|
||||||
|
if (y < 500) { selectedMusic = (selectedMusic + musicFileCount - 1) % musicFileCount; delay(200); }
|
||||||
|
else if (y > 3800) { selectedMusic = (selectedMusic + 1) % musicFileCount; delay(200); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawMusicMenu() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 10); u8g2.print("Music");
|
||||||
|
if (!musicFileCount) {
|
||||||
|
u8g2.setCursor(0, 30); u8g2.print("No MP3 files");
|
||||||
|
} else {
|
||||||
|
for (int row = -1; row <= 1; row++) {
|
||||||
|
int index = selectedMusic + row;
|
||||||
|
if (index < 0 || index >= musicFileCount) continue;
|
||||||
|
int y = 32 + row * 12;
|
||||||
|
if (index == selectedMusic) { u8g2.setCursor(0, y); u8g2.print('>'); }
|
||||||
|
const char* name = musicFiles[index];
|
||||||
|
if (!strncmp(name, "/music/", 7)) name += 7;
|
||||||
|
char shown[18];
|
||||||
|
strncpy(shown, name, 17); shown[17] = '\0';
|
||||||
|
u8g2.setCursor(10, y); u8g2.print(shown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopMusic() {
|
||||||
|
if (playingFile) playingFile.close();
|
||||||
|
if (musicPlaying) mp3.end();
|
||||||
|
musicPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startMusic(int index) {
|
||||||
|
stopMusic();
|
||||||
|
|
||||||
|
char path[MAX_FILENAME_LEN + 8];
|
||||||
|
const char* name = musicFiles[index];
|
||||||
|
if (name[0] == '/') snprintf(path, sizeof(path), "%s", name);
|
||||||
|
else if (!strncmp(name, "music/", 6)) snprintf(path, sizeof(path), "/%s", name);
|
||||||
|
else snprintf(path, sizeof(path), "/music/%s", name);
|
||||||
|
|
||||||
|
playingFile = SD.open(path, FILE_READ);
|
||||||
|
if (!playingFile) {
|
||||||
|
Serial.print("Unable to open: ");
|
||||||
|
Serial.println(path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("Playing: ");
|
||||||
|
Serial.println(path);
|
||||||
|
mp3.begin();
|
||||||
|
musicPlaying = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void feedMusicDecoder() {
|
||||||
|
if (!musicPlaying || !playingFile) return;
|
||||||
|
|
||||||
|
uint8_t input[1024];
|
||||||
|
int n = playingFile.read(input, sizeof(input));
|
||||||
|
if (n > 0) {
|
||||||
|
mp3.write(input, n);
|
||||||
|
} else {
|
||||||
|
stopMusic();
|
||||||
|
Serial.println("Playback finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void musicMenu() {
|
||||||
|
scanMusicFiles();
|
||||||
|
musicButtonWasPressed = digitalRead(JOY_SW) == LOW;
|
||||||
|
|
||||||
|
while (currentOptionPage == 5) {
|
||||||
|
if (!musicPlaying) handleMusicSelect();
|
||||||
|
drawMusicMenu();
|
||||||
|
|
||||||
|
bool pressed = digitalRead(JOY_SW) == LOW;
|
||||||
|
if (pressed && !musicButtonWasPressed && musicFileCount > 0)
|
||||||
|
startMusic(selectedMusic);
|
||||||
|
musicButtonWasPressed = pressed;
|
||||||
|
|
||||||
|
feedMusicDecoder();
|
||||||
|
hanleQuitMenuEvent();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
u8g2.begin();
|
||||||
|
u8g2.setContrast(255);
|
||||||
|
u8g2.enableUTF8Print();
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tf);
|
||||||
|
Wire.begin();
|
||||||
|
Wire.setClock(100000);
|
||||||
|
|
||||||
|
if (psramFound()) {
|
||||||
|
Serial.println("PSRAM found!");
|
||||||
|
Serial.printf("PSRAM size: %u bytes\n", ESP.getPsramSize());
|
||||||
|
Serial.printf("Free PSRAM: %u bytes\n", ESP.getFreePsram());
|
||||||
|
} else Serial.println("No PSRAM found.");
|
||||||
|
|
||||||
|
int nDevices = 0;
|
||||||
|
for (byte address = 1; address < 127; address++) {
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
if (Wire.endTransmission() == 0) {
|
||||||
|
Serial.printf("I2C device found at address 0x%02X\n", address);
|
||||||
|
nDevices++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nDevices) Serial.println("No I2C devices found");
|
||||||
|
|
||||||
|
pinMode(JOY_SW, INPUT_PULLUP);
|
||||||
|
analogReadResolution(12);
|
||||||
|
|
||||||
|
I2S.setPins(I2S_BCLK, I2S_LRCK, I2S_DOUT, -1, -1);
|
||||||
|
Serial.println(I2S.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO)
|
||||||
|
? "Found PCM5102" : "Unable to init PCM5102!");
|
||||||
|
|
||||||
|
SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
|
||||||
|
if (!SD.begin(SD_CS, SPI)) {
|
||||||
|
Serial.println("Unable to init SD card module.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SD.cardType() == CARD_NONE) Serial.println("No SD card detected.");
|
||||||
|
else {
|
||||||
|
Serial.printf("SD Card Size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));
|
||||||
|
scanMusicFiles();
|
||||||
|
for (int i = 0; i < musicFileCount; i++) Serial.println(musicFiles[i]);
|
||||||
|
}
|
||||||
|
Serial.println("Initialization Finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
switch (currentOptionPage) {
|
||||||
|
case 0: homeMenu(); break;
|
||||||
|
case 1: joyStickDebugMenu(); break;
|
||||||
|
case 2: fmBroadcast(); break;
|
||||||
|
case 3: pcm5102DebugMenu(); break;
|
||||||
|
case 4: volumeMenu(); break;
|
||||||
|
case 5: musicMenu(); break;
|
||||||
|
default: currentOptionPage = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,487 @@
|
|||||||
|
/*
|
||||||
|
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 <SPI.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "KT0803.h"
|
||||||
|
#include <ESP_I2S.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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,887 @@
|
|||||||
|
/*
|
||||||
|
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 <SPI.h>
|
||||||
|
#include <SD.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "KT0803.h"
|
||||||
|
#include <ESP_I2S.h>
|
||||||
|
#include "Audio.h"
|
||||||
|
|
||||||
|
#define VERSION "1.2"
|
||||||
|
|
||||||
|
KT0803L fm(&Wire);
|
||||||
|
Audio audio;
|
||||||
|
|
||||||
|
// ======================= 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
|
||||||
|
|
||||||
|
constexpr uint32_t SAMPLE_RATE = 44100;
|
||||||
|
constexpr float TONE_FREQ = 440.0f;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
int readJoystickAxis(int pin) {
|
||||||
|
constexpr int SAMPLE_COUNT = 4;
|
||||||
|
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < SAMPLE_COUNT; i++) {
|
||||||
|
sum += analogRead(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / SAMPLE_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
JoyStickStatus getJoyStickerStatus(int pin_x, int pin_y, int pin_sw) {
|
||||||
|
JoyStickStatus status = {0};
|
||||||
|
|
||||||
|
constexpr int CENTER_LOW = 1500;
|
||||||
|
constexpr int CENTER_HIGH = 2600;
|
||||||
|
|
||||||
|
int x = readJoystickAxis(pin_x);
|
||||||
|
int y = readJoystickAxis(pin_y);
|
||||||
|
bool pressed = (digitalRead(pin_sw) == LOW);
|
||||||
|
|
||||||
|
status.up = x >= CENTER_LOW && x <= CENTER_HIGH && y < 500;
|
||||||
|
status.down = x >= CENTER_LOW && x <= CENTER_HIGH && y > 3800;
|
||||||
|
status.left = y >= CENTER_LOW && y <= CENTER_HIGH && x < 500;
|
||||||
|
status.right = y >= CENTER_LOW && y <= CENTER_HIGH && x > 3800;
|
||||||
|
|
||||||
|
if (swapJoyStickVertical) {
|
||||||
|
// Wow, c++ have this cool thing
|
||||||
|
std::swap(status.up, status.down);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapJoyStickHorizontal) {
|
||||||
|
std::swap(status.left, status.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
status.pressed = pressed;
|
||||||
|
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 = readJoystickAxis(pin_x);
|
||||||
|
int y = readJoystickAxis(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
int dacVolume = 12;
|
||||||
|
|
||||||
|
// Option & Menu
|
||||||
|
int selectOptionID = 1;
|
||||||
|
int currentOptionPage = 0; // 0: Home, 1: JoyStick Debug, 2: FM Broadcast, 3: PCM5102 Debug
|
||||||
|
const int optionIDList[] = {1,2,3,4,5};
|
||||||
|
const char* menuNames[] = {
|
||||||
|
"Debug Joystick",
|
||||||
|
"FM Brocaster",
|
||||||
|
"Debug PCM5102",
|
||||||
|
"Volume",
|
||||||
|
"Play Music"
|
||||||
|
};
|
||||||
|
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 >= 1000) {
|
||||||
|
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, int availableHeight, int textSize) {
|
||||||
|
CursorPos pos = {lastX, lastY};
|
||||||
|
|
||||||
|
int optionCount = sizeof(menuNames) / sizeof(menuNames[0]);
|
||||||
|
int maxDisplayOptionCount = availableHeight / textSize;
|
||||||
|
int usedHeight = 0;
|
||||||
|
|
||||||
|
// Below code is for scrolling menu support
|
||||||
|
int startIndex = selectOptionID - maxDisplayOptionCount / 2;
|
||||||
|
|
||||||
|
// border check
|
||||||
|
if (startIndex < 0) {
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex + maxDisplayOptionCount > optionCount) {
|
||||||
|
startIndex = optionCount - maxDisplayOptionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex < 0) {
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = min(startIndex + maxDisplayOptionCount, optionCount);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; i++) {
|
||||||
|
pos.y += 12;
|
||||||
|
u8g2.setCursor(pos.x, pos.y);
|
||||||
|
|
||||||
|
// Content
|
||||||
|
u8g2.print(i+1);
|
||||||
|
u8g2.print(": ");
|
||||||
|
u8g2.print(menuNames[i]);
|
||||||
|
|
||||||
|
if (selectOptionID == i + 1) {
|
||||||
|
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 printLogo(int x, int y) {
|
||||||
|
// Draw background
|
||||||
|
u8g2.setDrawColor(1);
|
||||||
|
u8g2.drawBox(x, y, 128 - x, 1);
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
u8g2.setCursor(x + 2, y + 10);
|
||||||
|
|
||||||
|
u8g2.print("FM Brocaster v");
|
||||||
|
u8g2.print(VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void homeMenu(void) {
|
||||||
|
bool frameMoment = false;
|
||||||
|
unsigned long lastDisplayUpdate = millis();
|
||||||
|
unsigned long lastTickUpdate = millis();
|
||||||
|
unsigned long lastAnimateUpdate = millis();
|
||||||
|
unsigned long perFrameTime = 33;
|
||||||
|
unsigned long perTickTime = 500;
|
||||||
|
unsigned long perAnimateTime = 500;
|
||||||
|
|
||||||
|
while (currentOptionPage == 0) {
|
||||||
|
unsigned long time = millis();
|
||||||
|
if (time - lastTickUpdate >= perTickTime) {
|
||||||
|
lastTickUpdate = millis();
|
||||||
|
handleOptionSelectEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time - lastAnimateUpdate >= perAnimateTime) {
|
||||||
|
lastAnimateUpdate = millis();
|
||||||
|
frameMoment = !frameMoment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time - lastDisplayUpdate >= perFrameTime) {
|
||||||
|
lastDisplayUpdate = millis();
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
if (frameMoment) {
|
||||||
|
u8g2.print("Running_");
|
||||||
|
} else {
|
||||||
|
u8g2.print("Running ");
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorPos currentPos = printOptionMenu(0, 14, 36, 12);
|
||||||
|
printLogo(currentPos.x, currentPos.y + 2);
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_pcm5102(float sineFreq, float squareFreq, float triangleFreq) {
|
||||||
|
static float sinePhase = 0.0f;
|
||||||
|
static float squarePhase = 0.0f;
|
||||||
|
static float trianglePhase = 0.0f;
|
||||||
|
|
||||||
|
constexpr size_t FRAMES = 256;
|
||||||
|
|
||||||
|
int16_t samples[FRAMES * 2];
|
||||||
|
|
||||||
|
const float sineStep = TWO_PI * sineFreq / SAMPLE_RATE;
|
||||||
|
const float squareStep = TWO_PI * squareFreq / SAMPLE_RATE;
|
||||||
|
const float triangleStep = TWO_PI * triangleFreq / SAMPLE_RATE;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < FRAMES; i++) {
|
||||||
|
float sine = 0.0f;
|
||||||
|
float square = 0.0f;
|
||||||
|
float triangle = 0.0f;
|
||||||
|
|
||||||
|
if (sineFreq > 0.0f) {
|
||||||
|
sine = sinf(sinePhase) * 2500.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squareFreq > 0.0f) {
|
||||||
|
square = (squarePhase < PI ? 1.0f : -1.0f) * 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangleFreq > 0.0f) {
|
||||||
|
triangle =
|
||||||
|
(trianglePhase < PI
|
||||||
|
? (2.0f * trianglePhase / PI - 1.0f)
|
||||||
|
: (3.0f - 2.0f * trianglePhase / PI))
|
||||||
|
* 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float mixed = sine + square + triangle;
|
||||||
|
|
||||||
|
mixed = constrain(mixed, -32768.0f, 32767.0f);
|
||||||
|
|
||||||
|
int16_t sample = static_cast<int16_t>(mixed);
|
||||||
|
|
||||||
|
samples[i * 2] = sample;
|
||||||
|
samples[i * 2 + 1] = sample;
|
||||||
|
|
||||||
|
sinePhase += sineStep;
|
||||||
|
squarePhase += squareStep;
|
||||||
|
trianglePhase += triangleStep;
|
||||||
|
|
||||||
|
if (sinePhase >= TWO_PI)
|
||||||
|
sinePhase -= TWO_PI;
|
||||||
|
|
||||||
|
if (squarePhase >= TWO_PI)
|
||||||
|
squarePhase -= TWO_PI;
|
||||||
|
|
||||||
|
if (trianglePhase >= TWO_PI)
|
||||||
|
trianglePhase -= TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<uint8_t *>(samples),
|
||||||
|
sizeof(samples)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pcm5102DebugMenu(void) {
|
||||||
|
unsigned long lastDisplayUpdate = 0;
|
||||||
|
int frameMoment = 0;
|
||||||
|
float frequency = TONE_FREQ;
|
||||||
|
float squareFreq = 0.0f;
|
||||||
|
float triangleFreq = 0.0f;
|
||||||
|
|
||||||
|
while (currentOptionPage == 3) {
|
||||||
|
test_pcm5102(frequency, squareFreq, triangleFreq);
|
||||||
|
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
int adc = readJoystickAxis(JOY_X);
|
||||||
|
int offset = adc - 2048;
|
||||||
|
|
||||||
|
if (abs(offset) > 200)
|
||||||
|
{
|
||||||
|
float speed = offset / 2048.0f;
|
||||||
|
|
||||||
|
frequency += speed * 0.1f;
|
||||||
|
frequency = roundToOneDecimal(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
int adcY = readJoystickAxis(JOY_Y);
|
||||||
|
offset = adcY - 2048;
|
||||||
|
|
||||||
|
if (offset > 0) {
|
||||||
|
// For triangle
|
||||||
|
offset = abs(offset);
|
||||||
|
|
||||||
|
float speed = offset / 2048.0f;
|
||||||
|
triangleFreq += speed * 0.1f;
|
||||||
|
triangleFreq = roundToOneDecimal(triangleFreq);
|
||||||
|
} else {
|
||||||
|
// For square
|
||||||
|
offset = abs(offset);
|
||||||
|
|
||||||
|
float speed = offset / 2048.0f;
|
||||||
|
squareFreq += speed * 0.1f;
|
||||||
|
squareFreq = roundToOneDecimal(squareFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - lastDisplayUpdate >= 300) {
|
||||||
|
lastDisplayUpdate = millis();
|
||||||
|
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
switch (frameMoment) {
|
||||||
|
case 0:
|
||||||
|
u8g2.print("Debugging PCM5102.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
u8g2.print("Debugging PCM5102..");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
u8g2.print("Debugging PCM5102...");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
u8g2.print("Debugging PCM5102....");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 28);
|
||||||
|
u8g2.print("Sine Freq: ");
|
||||||
|
u8g2.print(frequency);
|
||||||
|
u8g2.setCursor(0, 40);
|
||||||
|
u8g2.print("Square Freq: ");
|
||||||
|
u8g2.print(squareFreq);
|
||||||
|
u8g2.setCursor(0, 52);
|
||||||
|
u8g2.print("Triangle Freq: ");
|
||||||
|
u8g2.print(triangleFreq);
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
|
||||||
|
frameMoment = (frameMoment + 1) % 4;
|
||||||
|
|
||||||
|
hanleQuitMenuEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void volumeMenu() {
|
||||||
|
static unsigned long lastVolumeUpdate = 0;
|
||||||
|
while (currentOptionPage == 4) {
|
||||||
|
if (millis() - lastVolumeUpdate >= 30) {
|
||||||
|
lastVolumeUpdate = millis();
|
||||||
|
|
||||||
|
JoyStickStatus status = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
||||||
|
int offset=0;
|
||||||
|
if (status.left) {
|
||||||
|
offset = 1;
|
||||||
|
} else if (status.right){
|
||||||
|
offset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dacVolume += offset;
|
||||||
|
|
||||||
|
dacVolume = constrain(dacVolume, 0, 255);
|
||||||
|
|
||||||
|
int barWidth = (dacVolume * 84) / 255;
|
||||||
|
|
||||||
|
u8g2.firstPage();
|
||||||
|
do {
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
u8g2.print("Volume");
|
||||||
|
|
||||||
|
u8g2.drawFrame(20, 30, 88, 12);
|
||||||
|
u8g2.drawBox(22, 32, barWidth, 8);
|
||||||
|
} while (u8g2.nextPage());
|
||||||
|
|
||||||
|
hanleQuitMenuEvent();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int MAX_MUSIC_FILES = 20;
|
||||||
|
constexpr int MAX_FILENAME_LEN = 48;
|
||||||
|
|
||||||
|
char musicFiles[MAX_MUSIC_FILES][MAX_FILENAME_LEN];
|
||||||
|
int musicFileCount = 0;
|
||||||
|
int selectedMusic = 0;
|
||||||
|
|
||||||
|
void scanMusicFiles() {
|
||||||
|
musicFileCount = 0;
|
||||||
|
|
||||||
|
File dir = SD.open("/music");
|
||||||
|
|
||||||
|
if (!dir || !dir.isDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = dir.openNextFile();
|
||||||
|
|
||||||
|
while (file && musicFileCount < MAX_MUSIC_FILES) {
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
const char* name = file.name();
|
||||||
|
|
||||||
|
const char* ext = strrchr(name, '.');
|
||||||
|
|
||||||
|
if (ext && strcasecmp(ext, ".mp3") == 0) {
|
||||||
|
strncpy(
|
||||||
|
musicFiles[musicFileCount],
|
||||||
|
name,
|
||||||
|
MAX_FILENAME_LEN - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
musicFiles[musicFileCount][MAX_FILENAME_LEN - 1] = '\0';
|
||||||
|
|
||||||
|
musicFileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
file = dir.openNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMusicSelect() {
|
||||||
|
int y = readJoystickAxis(JOY_Y);
|
||||||
|
|
||||||
|
if (y < 500) {
|
||||||
|
selectedMusic--;
|
||||||
|
|
||||||
|
if (selectedMusic < 0) {
|
||||||
|
selectedMusic = musicFileCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > 3800) {
|
||||||
|
selectedMusic++;
|
||||||
|
|
||||||
|
if (selectedMusic >= musicFileCount) {
|
||||||
|
selectedMusic = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawMusicMenu() {
|
||||||
|
u8g2.firstPage();
|
||||||
|
|
||||||
|
do {
|
||||||
|
u8g2.setCursor(0, 10);
|
||||||
|
u8g2.print("Music");
|
||||||
|
|
||||||
|
if (musicFileCount == 0) {
|
||||||
|
u8g2.setCursor(0, 30);
|
||||||
|
u8g2.print("No MP3 files");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = -1; row <= 1; row++) {
|
||||||
|
int index = selectedMusic + row;
|
||||||
|
|
||||||
|
if (index < 0 || index >= musicFileCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int y = 32 + row * 12;
|
||||||
|
|
||||||
|
if (index == selectedMusic) {
|
||||||
|
u8g2.setCursor(0, y);
|
||||||
|
u8g2.print(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = musicFiles[index];
|
||||||
|
|
||||||
|
if (name.startsWith("/music/")) {
|
||||||
|
name.remove(0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.length() > 17) {
|
||||||
|
name = name.substring(0, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setCursor(10, y);
|
||||||
|
u8g2.print(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (u8g2.nextPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
void musicMenu() {
|
||||||
|
scanMusicFiles();
|
||||||
|
while (currentOptionPage == 5) {
|
||||||
|
handleMusicSelect();
|
||||||
|
|
||||||
|
drawMusicMenu();
|
||||||
|
|
||||||
|
if (digitalRead(JOY_SW) == LOW) {
|
||||||
|
if (musicFileCount > 0) {
|
||||||
|
Serial.print("Selected: ");
|
||||||
|
Serial.println(musicFiles[selectedMusic]);
|
||||||
|
audio.connecttoFS(SD, musicFiles[selectedMusic]);
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
hanleQuitMenuEvent();
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// debug
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Initialize display
|
||||||
|
u8g2.begin();
|
||||||
|
u8g2.setContrast(255);
|
||||||
|
|
||||||
|
// fm module
|
||||||
|
Wire.begin();
|
||||||
|
Wire.setClock(100000);
|
||||||
|
|
||||||
|
// ram check
|
||||||
|
if (psramFound()) {
|
||||||
|
Serial.println("PSRAM found!");
|
||||||
|
Serial.printf("PSRAM size: %u bytes\n", ESP.getPsramSize());
|
||||||
|
Serial.printf("Free PSRAM: %u bytes\n", ESP.getFreePsram());
|
||||||
|
} else {
|
||||||
|
Serial.println("No PSRAM found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// pcm5102
|
||||||
|
I2S.setPins(
|
||||||
|
I2S_BCLK,
|
||||||
|
I2S_LRCK,
|
||||||
|
I2S_DOUT,
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
audio.setPinout(I2S_BCLK, I2S_LRCK, I2S_DOUT);
|
||||||
|
audio.setVolume(dacVolume);
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SPI bus for sd card reader
|
||||||
|
SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
|
||||||
|
|
||||||
|
// sd card
|
||||||
|
if (!SD.begin(SD_CS, SPI)) {
|
||||||
|
Serial.println("Unable to init SD card module. Did the SD module is installed?\n");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Serial.println("Found SD card module.");
|
||||||
|
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
|
||||||
|
if (cardType == CARD_NONE) {
|
||||||
|
Serial.println("No SD card detected.");
|
||||||
|
} else {
|
||||||
|
Serial.printf("SD Card Size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));
|
||||||
|
scanMusicFiles();
|
||||||
|
|
||||||
|
for (int i = 0; i < musicFileCount; i++) {
|
||||||
|
Serial.println(musicFiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Initialization Finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
if (currentOptionPage == 0) {
|
||||||
|
homeMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 1) {
|
||||||
|
joyStickDebugMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 2) {
|
||||||
|
fmBroadcast();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 3) {
|
||||||
|
pcm5102DebugMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 4) {
|
||||||
|
volumeMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 5) {
|
||||||
|
musicMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user