diff --git a/README.md b/README.md index b36d2ce..8db0ac4 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The device will scan for WiFi networks on boot. Once the list is displayed, you - [X] Screen timeout on inactivity *(default 30 seconds)* - [ ] Keyboard backlight timeout with screen timeout - [ ] Trackball support -- [ ] Speaker support +- [X] Speaker support - [ ] GPS support - [ ] Lora support - [ ] BLE support diff --git a/platformio.ini b/platformio.ini index 084c496..e1a690c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,4 @@ build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DDISABLE_ALL_LIBRARY_WARNINGS lib_deps = - ;mikalhart/TinyGPSPlus@^1.0.2 - marian-craciunescu/ESP32Ping@^1.7.0 - ;sandeepmistry/LoRa \ No newline at end of file +lib_extra_dirs = lib \ No newline at end of file diff --git a/src/Speaker.h b/src/Speaker.h new file mode 100644 index 0000000..eceafbf --- /dev/null +++ b/src/Speaker.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include + +#include "pins.h" + +#define BOARD_I2S_PORT I2S_NUM_0 +#define SAMPLE_RATE 44100 + + +const float NOTE_FREQS[] = { + 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, // C4 to B4 + 523.25, 554.37, 587.33, 622.25, 659.25, 698.46, 739.99, 783.99, 830.61, 880.00, 932.33, 987.77, // C5 to B5 + 1046.50, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1480.00, 1568.00, 1661.22, 1760.00, 1864.66, 1975.53, // C6 to B6 + 2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2960.00, 3136.00, 3322.44, 3520.00, 3729.31, 3951.07 // C7 to B7 +}; + + +float getNoteFrequency(char note, int octave) { + if (note == 'p') return 0; // Pause + int noteIndex = 0; + switch (note) { + case 'c': noteIndex = 0; break; + case 'd': noteIndex = 2; break; + case 'e': noteIndex = 4; break; + case 'f': noteIndex = 5; break; + case 'g': noteIndex = 7; break; + case 'a': noteIndex = 9; break; + case 'b': noteIndex = 11; break; + default: return 0; + } + return NOTE_FREQS[noteIndex + ((octave - 4) * 12)]; +} + + +void setupI2S() { + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = SAMPLE_RATE, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, // Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 64, + .use_apll = false, + .tx_desc_auto_clear = true, + .fixed_mclk = 0 + }; + + i2s_pin_config_t pin_config = { + .bck_io_num = BOARD_I2S_BCK, + .ws_io_num = BOARD_I2S_WS, + .data_out_num = BOARD_I2S_DOUT, + .data_in_num = I2S_PIN_NO_CHANGE + }; + + i2s_driver_install(BOARD_I2S_PORT, &i2s_config, 0, NULL); + i2s_set_pin(BOARD_I2S_PORT, &pin_config); + i2s_set_clk(BOARD_I2S_PORT, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO); +} + + +void playTone(float frequency, int duration, int volume = 16383) { + volume = constrain(volume, 0, 32767); // Max volume is 32767, we default to half volume if not specified + const int wave_period = SAMPLE_RATE / frequency; + int16_t sample_buffer[wave_period]; + + for (int i = 0; i < wave_period; ++i) + sample_buffer[i] = (i < wave_period / 2) ? volume : -volume; + + int total_samples = SAMPLE_RATE * duration / 1000; + int samples_written = 0; + + while (samples_written < total_samples) { + int to_write = min(wave_period, total_samples - samples_written); + i2s_write(BOARD_I2S_PORT, sample_buffer, to_write * sizeof(int16_t), (size_t *)&to_write, portMAX_DELAY); + samples_written += to_write; + } +} + + +void playRTTTL(const char* rtttl, int volume = 16383, int bpm = -1) { + int default_duration = 4; + int default_octave = 6; + int internal_bpm = 63; + + const char* p = rtttl; + + // Skip name + while (*p && *p != ':') p++; + if (*p == ':') p++; + + while (*p && *p != ':') { + char param = *p++; + if (*p == '=') p++; + int value = atoi(p); + while (*p && isdigit(*p)) p++; + if (*p == ',') p++; + switch (param) { + case 'd': default_duration = value; break; + case 'o': default_octave = value; break; + case 'b': internal_bpm = value; break; + } + } + + if (*p == ':') p++; + + if (bpm != -1) + internal_bpm = bpm; + + int beat_duration = 60000 / internal_bpm; + + while (*p) { + int duration = 0; + if (isdigit(*p)) { + duration = atoi(p); + while (isdigit(*p)) p++; + } else { + duration = default_duration; + } + + char note = *p++; + int frequency = getNoteFrequency(note, default_octave); + + if (*p == '#') { + frequency = getNoteFrequency(note + 1, default_octave); + p++; + } + + int octave = default_octave; + + if (isdigit(*p)) + octave = *p++ - '0'; + + if (*p == '.') { + duration = duration * 1.5; + p++; + } + + int note_duration = (beat_duration * 4) / duration; + + if (frequency > 0) + playTone(frequency, note_duration, volume); + else + delay(note_duration); + + if (*p == ',') p++; + } +} diff --git a/src/boot_screen.h b/src/bootScreen.h similarity index 100% rename from src/boot_screen.h rename to src/bootScreen.h diff --git a/src/main.ino b/src/main.ino index 6a06e8d..46ed710 100644 --- a/src/main.ino +++ b/src/main.ino @@ -14,8 +14,9 @@ #include // Local includes -#include "boot_screen.h" +#include "bootScreen.h" #include "pins.h" +#include "Speaker.h" // Constants @@ -81,7 +82,6 @@ bool screenOn = true; int selectedNetworkIndex = 0; - // Main functions --------------------------------------------------------------------------------- void displayXBM() { tft.fillScreen(TFT_BLACK); @@ -113,6 +113,7 @@ void displayXBM() { void setup() { // Initialize serial communication Serial.begin(115200); + while (!Serial); Serial.println("Booting device..."); // Turn on the power to the board @@ -132,6 +133,11 @@ void setup() { tft.invertDisplay(1); Serial.println("TFT initialized"); + // Initialize the speaker + setupI2S(); // Do we want to keep this open or uninstall after each use to keep resources free? + const char* rtttl_boot = "ff6_victory:d=4,o=5,b=100:32d6,32p,32d6,32p,32d6,32p,d6,a#,c6,16d6,8p,16c6,2d6"; // This will go in preferences soon + playRTTTL(rtttl_boot); + // Display the boot screen displayXBM(); diff --git a/src/pins.h b/src/pins.h index 9b573b9..da00365 100644 --- a/src/pins.h +++ b/src/pins.h @@ -4,9 +4,10 @@ // Board pin definitions ------------------------ #define BOARD_POWERON 10 +// Speaker #define BOARD_I2S_WS 5 -#define BOARD_I2S_BCK 7 #define BOARD_I2S_DOUT 6 +#define BOARD_I2S_BCK 7 #define BOARD_I2C_SDA 18 #define BOARD_I2C_SCL 8 @@ -55,7 +56,6 @@ // Battery definitions #define CONV_FACTOR 1.8 // Conversion factor for the ADC to voltage conversion -#define READS 20 // Number of readings for averaging - -#define LILYGO_KB_SLAVE_ADDRESS 0x55 +#define READS 20 // Number of readings for averaging +#define LILYGO_KB_SLAVE_ADDRESS 0x55 \ No newline at end of file