From 3ba2fe4ee0e584640e5c8ea93ec2becaa31fa433 Mon Sep 17 00:00:00 2001 From: TroyHacks <5659019+troyhacks@users.noreply.github.com> Date: Fri, 20 Oct 2023 01:23:10 -0400 Subject: [PATCH] WM8978 support for PicoDSP, etc. --- usermods/audioreactive/audio_reactive.h | 23 ++++++- usermods/audioreactive/audio_source.h | 91 +++++++++++++++++++++++++ wled00/const.h | 2 +- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 8a1e0f5724..4726ee26f3 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1753,6 +1753,23 @@ class AudioReactive : public Usermod { if (i2c_sda >= 0) sdaPin = -1; // -1 = use global if (i2c_scl >= 0) sclPin = -1; + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + case 7: + #ifdef use_wm8978_mic + DEBUGSR_PRINTLN(F("AR: WM8978 Source (Mic)")); + #else + DEBUGSR_PRINTLN(F("AR: WM8978 Source (Line-In)")); + #endif + audioSource = new WM8978Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f); + //useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour + delay(100); + // WLEDMM align global pins + if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined) + if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; @@ -2562,7 +2579,11 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addOption(dd,'ES8388 ☾',6);")); #endif - + #if SR_DMTYPE==7 + oappend(SET_F("addOption(dd,'WM8978 ☾ (⎌)',7);")); + #else + oappend(SET_F("addOption(dd,'WM8978 ☾',7);")); + #endif #ifdef SR_SQUELCH oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field #endif diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 791210834f..2009efbb41 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -615,6 +615,97 @@ class ES8388Source : public I2SSource { }; +class WM8978Source : public I2SSource { + private: + // I2C initialization functions for WM8978 + void _wm8978I2cBegin() { + Wire.setClock(400000); + } + + void _wm8978I2cWrite(uint8_t reg, uint16_t val) { + #ifndef WM8978_ADDR + #define WM8978_ADDR 0x1A + #endif + char buf[2]; + buf[0] = (reg << 1) | ((val >> 8) & 0X01); + buf[1] = val & 0XFF; + Wire.beginTransmission(WM8978_ADDR); + Wire.write((const uint8_t*)buf, 2); + uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK + if (i2cErr != 0) { + DEBUGSR_PRINTF("AR: WM8978 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, WM8978_ADDR, reg, val); + } + } + + void _wm8978InitAdc() { + // https://www.mouser.com/datasheet/2/76/WM8978_v4.5-1141768.pdf + // Sets ADC to around what AudioReactive expects, and loops line-in to line-out/headphone for monitoring. + // Registries are decimal, settings are 9-bit binary as that's how everything is listed in the docs + // ...which makes it easier to reference the docs. + // + _wm8978I2cBegin(); + + _wm8978I2cWrite( 0,0b000000000); // Reset all settings + _wm8978I2cWrite( 1,0b000001011); // Power Management 1 - power off most things + _wm8978I2cWrite( 2,0b110110011); // Power Management 2 - enable output and amp stages (amps may lift signal but it works better on the ADCs) + _wm8978I2cWrite( 3,0b000001100); // Power Management 3 - enable L&R output mixers + _wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit + _wm8978I2cWrite( 5,0b000000001); // Loopback Enable + _wm8978I2cWrite( 6,0b000000000); // Clock generation control - use external mclk + _wm8978I2cWrite( 7,0b000000100); // Sets sample rate to ~24kHz (only used for internal calculations, not I2S) + _wm8978I2cWrite(14,0b010001000); // 128x ADC oversampling - high pass filter disabled as it kills the bass response + _wm8978I2cWrite(43,0b000110000); // Mute signal paths we don't use + _wm8978I2cWrite(44,0b000000000); // Disconnect microphones + _wm8978I2cWrite(45,0b111000000); // Mute signal paths we don't use + _wm8978I2cWrite(46,0b111000000); // Mute signal paths we don't use + _wm8978I2cWrite(47,0b001000000); // 0dB gain on left line-in + _wm8978I2cWrite(48,0b001000000); // 0dB gain on right line-in + _wm8978I2cWrite(49,0b000000010); // Mixer thermal shutdown enable + _wm8978I2cWrite(50,0b000010110); // Output mixer enable only left bypass at 0dB gain + _wm8978I2cWrite(51,0b000010110); // Output mixer enable only right bypass at 0dB gain + _wm8978I2cWrite(52,0b110111001); // Left line-out enabled at 0dB gain + _wm8978I2cWrite(53,0b110111001); // Right line-out enabled at 0db gain + _wm8978I2cWrite(54,0b001000000); // Mute left speaker output + _wm8978I2cWrite(55,0b101000000); // Mute right speaker output + + } + + public: + WM8978Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : + I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; + }; + + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("WM8978Source:: initialize();"); + + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S WM8978 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } + // BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here. + // Workaround: Set I2C pins here, which will also set them globally. + // Bug also exists in ES7243. + if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" + ERRORSR_PRINTF("\nAR: invalid WM8978 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins + ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + + // First route mclk, then configure ADC over I2C, then configure I2S + _wm8978InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + I2SSource::deinitialize(); + } + +}; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) #if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC) #warning this MCU does not support analog sound input diff --git a/wled00/const.h b/wled00/const.h index f78f19c62e..87375eb3e8 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -378,7 +378,7 @@ #ifdef ESP8266 #define SETTINGS_STACK_BUF_SIZE 2048 #else -#define SETTINGS_STACK_BUF_SIZE 3792 // WLEDMM added 696 bytes of margin (was 3096) for audioreactive UI +#define SETTINGS_STACK_BUF_SIZE 3802 // WLEDMM added 696+10 bytes of margin (was 3096) for audioreactive UI #endif #ifdef WLED_USE_ETHERNET