Hello rsmls,
Sorry for the late reply. I have inserted the various examples of the code in this reply. As for the breakout board, yes that is the correct one, and no we are not using the power amplifier on the board itself, as it is connected to a guitar amplifier with a 6.3mm jack. The examples from the Sparkfun WM8960 library do give the desired result, as do our own code when not calling the function through a header file.
We have created a short video demonstrating the issue, this can be found here: https://www.youtube.com/watch?v=EMAZWze6yJU
Here is the minimal versions of the code, sadly they cannot be made any shorter since there is a lot of initialization for the codec, and creating an example where random values in the range of an int16_t are generated seems kind of moot, since the problem is not output vs. no output, but rather output vs. wrongly altered output.
Minimal working example:
#include <Wire.h>
#include <math.h>
#include <SparkFun_WM8960_Arduino_Library.h>
// Click here to get the library: http://librarymanager/All#SparkFun_WM8960
WM8960 codec;
// Include I2S driver
#include <driver/i2s.h>
// Connections to I2S
#define I2S_WS 25
#define I2S_SD 17
#define I2S_SDO 4
#define I2S_SCK 16
// Use I2S Processor 0
#define I2S_PORT I2S_NUM_0
// Define input buffer length
#define bufferLen 64 //Define the buffer length
int16_t lBuffer[bufferLen]; //Create the buffer that is used to store the input data
int16_t TXBuffer[2 * bufferLen], RXBuffer[2 * bufferLen]; //Create the receive and the transmit buffers
float Fc = 500; //Initialise the centre frequency to be 500Hz
int16_t Q = 15; // Use a quality factor of 15
float Q1 = 1.0 / Q; // Create a reciprocal of the quality factor, to be used in the filter coeficients
int32_t Fs = 44100; //Create a variable for the smapling frequency
float F1 = 2 * sin((3.14 * Fc) / Fs); //Create the first filter coefficinet
float q = 1 - F1 * Q1; //Create the second filter coefficient
//Initialise the delay coefficients
float x_delay_1 = 0;
float y_delay_1 = 0;
float y_delay_2 = 0;
bool increasing = true; //Create the boolean for whether the centre frequency should be increasing or decreasing.
//Setup standard from the I2S Passthrough exmaple from the SparkFun_WM8960_Arduino_Library.
void setup() {
Serial.begin(115200);
Wire.begin();
if (codec.begin() == false)
//Begin communication over I2C
{
Serial.println("The device did not respond. Please check wiring.");
while (1)
; // Freeze
}
Serial.println("Device is connected properly.");
codec_setup();
// Set up I2S
i2s_install();
i2s_setpin();
i2s_start(I2S_PORT);
}
int16_t autoWah(int16_t x) {
x = x >> 1; //Bit shifting the input to avoid overflow
float y; //Function output definition
y = F1 * x; //Calculation of the first index in the input buffer
y = y - F1 * x_delay_1; //Calculation of the delayed index in the input buffer, at index 0 the value of x_delay_1 will be 0
y = (y - (F1 * F1 - q - 1) * y_delay_1); //Calculation of the first delayed output. At index 0, y_delay_1 will be 0.
y = y - q * y_delay_2; //Calculation of the second delayed output. At index 0 and 1 this will be 0.
//Update variable values
x_delay_1 = x; //The current input is set as the singularly delayed input, as the current x-value will represent the delayed input at the next index.
y_delay_2 = y_delay_1; //The double delayed output is set as the singularly delayed output, as y_delay_1 will represent the double delayed output as the next index.
y_delay_1 = y; //The output is set as the single delayed output, as y will represent the single delayed output at the next index.
return y; //The function returns a processed signal that is dependant upon a current input, a singularly delayed input, a singularly delayed output and a double delayed output.
}
void loop() {
// Get I2S data and place in data buffer
size_t bytesIn = 0; //From SparkFun_WM8960_Arduino_Library
size_t bytesOut = 0; //From SparkFun_WM8960_Arduino_Library
esp_err_t result; //
result = i2s_read(I2S_PORT, &RXBuffer, bufferLen * 4, &bytesIn, portMAX_DELAY);
if (result == ESP_OK) {
//Checks the values of the input, and corrects them if they exceed the thresholds defined in the 'if else'-statement.
for (int i = 0; i < bufferLen; i++) {
if (25000 < RXBuffer[i * 2]) {
lBuffer[i] = (RXBuffer[i * 2] - 32767);
} else if (RXBuffer[i * 2] < -25000) {
lBuffer[i] = (RXBuffer[i * 2] + 32768);
} else {
lBuffer[i] = (RXBuffer[i * 2]);
}
}
F1 = 2 * sin((3.14 * Fc) / Fs); //Updates the F1 filter coeficient.
q = 1 - F1 * Q1; //Updates the q filter coefficient.
for (int i = 0; i < bufferLen; i++) {
TXBuffer[i * 2] = autoWah(lBuffer[i]); //Places the processed signal in every other index in the buffer, as the codec reads the buffer indexes as Left-Right iterations, and we are only using the left output.
}
//Checks the conditions for the centre frequency Fc, and either increase or decreases it depending on the state of the 'increasing'-bool.
if (Fc == 1500) {
increasing = false;
} else if (Fc == 500) {
increasing = true;
}
if (increasing == true) {
Fc = Fc + 2;
} else {
Fc = Fc - 2;
}
result = i2s_write(I2S_PORT, &TXBuffer, bufferLen * 4, &bytesOut, portMAX_DELAY); //Sends the TXBuffer out through the codec.
} else {
Serial.println("TX ERR. Check connections and variable names"); //Prints if there could not be established a connection to the codec.
}
}
//The remaining code below is standard to the SparkFun_WM8960_Arduino_Library I2S-Passthrough example, and has not been modified
void codec_setup() {
// General setup needed
codec.enableVREF();
codec.enableVMID();
// Setup signal flow to the ADC
codec.enableLMIC();
codec.enableRMIC();
// Connect from INPUT1 to "n" (aka inverting) inputs of PGAs.
codec.connectLMN1();
codec.connectRMN1();
// Disable mutes on PGA inputs (aka INTPUT1)
codec.disableLINMUTE();
codec.disableRINMUTE();
// Set pga volumes
codec.setLINVOLDB(0.00);
//Valid options are - 17.25dB to + 30dB(0.75dB steps)
codec.setRINVOLDB(0.00); //
//Valid options are - 17.25dB to + 30dB(0.75dB steps)
// Set input boosts to get inputs 1 to the boost mixers
codec.setLMICBOOST(WM8960_MIC_BOOST_GAIN_0DB);
codec.setRMICBOOST(WM8960_MIC_BOOST_GAIN_0DB);
// Connect from MIC inputs (aka pga output) to boost mixers
codec.connectLMIC2B();
codec.connectRMIC2B();
// Enable boost mixers
codec.enableAINL();
codec.enableAINR();
// Disconnect LB2LO (booster to output mixer (analog bypass)
// For this example, we are going to pass audio throught the ADC and DAC
codec.disableLB2LO();
codec.disableRB2RO();
// Connect from DAC outputs to output mixer
codec.enableLD2LO();
codec.enableRD2RO();
// Set gainstage between booster mixer and output mixer
// For this loopback example, we are going to keep these as low as they go
codec.setLB2LOVOL(WM8960_OUTPUT_MIXER_GAIN_NEG_21DB);
codec.setRB2ROVOL(WM8960_OUTPUT_MIXER_GAIN_NEG_21DB);
// Enable output mixers
codec.enableLOMIX();
codec.enableROMIX();
// CLOCK STUFF, These settings will get you 44.1KHz sample rate, and class-d
// freq at 705.6kHz
codec.enablePLL(); // Needed for class-d amp clock
codec.setPLLPRESCALE(WM8960_PLLPRESCALE_DIV_2);
codec.setSMD(WM8960_PLL_MODE_FRACTIONAL);
codec.setCLKSEL(WM8960_CLKSEL_PLL);
codec.setSYSCLKDIV(WM8960_SYSCLK_DIV_BY_2);
codec.setBCLKDIV(4);
codec.setDCLKDIV(WM8960_DCLKDIV_16);
codec.setPLLN(7);
codec.setPLLK(0x86, 0xC2, 0x26); // PLLK=86C226h
//codec.setADCDIV(0); // Default is 000 (what we need for 44.1KHz)
//codec.setDACDIV(0); // Default is 000 (what we need for 44.1KHz)
codec.setWL(WM8960_WL_16BIT);
codec.enablePeripheralMode();
//codec.enableMasterMode();
//codec.setALRCGPIO(); // Note, should not be changed while ADC is enabled.
// Enable ADCs and DACs
codec.enableAdcLeft();
codec.enableAdcRight();
codec.enableDacLeft();
codec.enableDacRight();
codec.disableDacMute();
//codec.enableLoopBack(); // Loopback sends ADC data directly into DAC
codec.disableLoopBack();
// Default is "soft mute" on, so we must disable mute to make channels active
codec.disableDacMute();
codec.enableHeadphones();
codec.enableOUT3MIX(); // Provides VMID as buffer for headphone ground
Serial.println("Volume set to +0dB");
codec.setHeadphoneVolumeDB(0.00);
Serial.println("Codec Setup complete. Listen to left/right INPUT1 on Headphone outputs.");
}
void i2s_install() {
// Set up I2S Processor configuration
const i2s_driver_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //JDN
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_MSB),
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = bufferLen,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = i2s_mclk_multiple_t(I2S_MCLK_MULTIPLE_DEFAULT),
.bits_per_chan = i2s_bits_per_chan_t(I2S_BITS_PER_CHAN_DEFAULT)
};
i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}
void i2s_setpin() {
// Set I2S pin configuration
const i2s_pin_config_t pin_config = {
.mck_io_num = I2S_PIN_NO_CHANGE,
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_SDO,
.data_in_num = I2S_SD
};
i2s_set_pin(I2S_PORT, &pin_config);
}
This code produces the desired output/effect where we have a center frequency that increases and decreases automatically.
Minimal example showing the problem:
#include <Wire.h>
#include <math.h>
#include <SparkFun_WM8960_Arduino_Library.h>
#include <wahHeader.h>
// Click here to get the library: http://librarymanager/All#SparkFun_WM8960
WM8960 codec;
//This is a version of our code, where we are using a custom library that we ourselves created. In this library is a singular function, the 'autoWah'-function.
//This was done as an experiment to see if we could create a custom library for our project, to save on space in our main file.
//The only difference between this file and the working one is that we are not defining the function in this, but rather calling it from the header file,
// with the necessary variables. The surrounding code around the last 'for'-loop in 'void loop' should be more or less identical to the working code.
// Include I2S driver
#include <driver/i2s.h>
// Connections to I2S
#define I2S_WS 25
#define I2S_SD 17
#define I2S_SDO 4
#define I2S_SCK 16
// Use I2S Processor 0
#define I2S_PORT I2S_NUM_0
// Define input buffer length
#define bufferLen 64 //Define the buffer length
int16_t lBuffer[bufferLen]; //Create the buffer that is used to store the input data
int16_t TXBuffer[2 * bufferLen], RXBuffer[2 * bufferLen]; //Create the receive and the transmit buffers
float Fc = 500; //Initialise the centre frequency to be 500Hz
int16_t Q = 15; // Use a quality factor of 15
float Q1 = 1.0 / Q; // Create a reciprocal of the quality factor, to be used in the filter coeficients
int32_t Fs = 44100; //Create a variable for the smapling frequency
float F1 = 2 * sin((3.14 * Fc) / Fs); //Create the first filter coefficinet
float q = 1 - F1 * Q1; //Create the second filter coefficient
//Initialise the delay coefficients
float x_delay_1 = 0;
float y_delay_1 = 0;
float y_delay_2 = 0;
bool increasing = true; //Create the boolean for whether the centre frequency should be increasing or decreasing.
//Setup standard from the I2S Passthrough exmaple from the SparkFun_WM8960_Arduino_Library.
void setup() {
Serial.begin(115200);
Serial.println("Example 8 - I2S Passthough");
Wire.begin();
if (codec.begin() == false)
//Begin communication over I2C
{
Serial.println("The device did not respond. Please check wiring.");
while (1)
; // Freeze
}
Serial.println("Device is connected properly.");
codec_setup();
// Set up I2S
i2s_install();
i2s_setpin();
i2s_start(I2S_PORT);
}
void loop() {
// Get I2S data and place in data buffer
size_t bytesIn = 0;
size_t bytesOut = 0;
esp_err_t result;
static int lll = 0;
result = i2s_read(I2S_PORT, &RXBuffer, bufferLen * 4, &bytesIn, portMAX_DELAY);
if (result == ESP_OK) {
//Checks the values of the input, and corrects them if they exceed the thresholds defined in the 'if else'-statement.
for (int i = 0; i < bufferLen; i++) {
if (25000 < RXBuffer[i * 2]) {
lBuffer[i] = (RXBuffer[i * 2] - 32767);
} else if (RXBuffer[i * 2] < -25000) {
lBuffer[i] = (RXBuffer[i * 2] + 32768);
} else {
lBuffer[i] = (RXBuffer[i * 2]);
}
}
F1 = 2 * sin((3.14 * Fc) / Fs); //Updates the F1 filter coeficient.
q = 1 - F1 * Q1; //Updates the q filter coefficient.
for (int i = 0; i < bufferLen; i++) {
TXBuffer[i * 2] = autoWah(lBuffer[i], F1, q, x_delay_1, y_delay_1, y_delay_2); //Places the processed signal in every other index in the buffer, as the codec reads the buffer indexes as Left-Right iterations, and we are only using the left output.
}
//Checks the conditions for the centre frequency Fc, and either increase or decreases it depending on the state of the 'increasing'-bool.
if (Fc == 1500) {
increasing = false;
} else if (Fc == 500) {
increasing = true;
}
if (increasing == true) {
Fc = Fc + 2;
} else {
Fc = Fc - 2;
}
result = i2s_write(I2S_PORT, &TXBuffer, bufferLen * 4, &bytesOut, portMAX_DELAY); //Sends the TXBuffer out through the codec.
} else {
Serial.println("TX ERR. Check connections and variable names");
}
}
void codec_setup() {
// General setup needed
codec.enableVREF();
codec.enableVMID();
// Setup signal flow to the ADC
codec.enableLMIC();
codec.enableRMIC();
// Connect from INPUT1 to "n" (aka inverting) inputs of PGAs.
codec.connectLMN1();
codec.connectRMN1();
// Disable mutes on PGA inputs (aka INTPUT1)
codec.disableLINMUTE();
codec.disableRINMUTE();
// Set pga volumes
codec.setLINVOLDB(0.00);
//Valid options are - 17.25dB to + 30dB(0.75dB steps)
codec.setRINVOLDB(0.00); //
//Valid options are - 17.25dB to + 30dB(0.75dB steps)
// Set input boosts to get inputs 1 to the boost mixers
codec.setLMICBOOST(WM8960_MIC_BOOST_GAIN_0DB);
codec.setRMICBOOST(WM8960_MIC_BOOST_GAIN_0DB);
// Connect from MIC inputs (aka pga output) to boost mixers
codec.connectLMIC2B();
codec.connectRMIC2B();
// Enable boost mixers
codec.enableAINL();
codec.enableAINR();
// Disconnect LB2LO (booster to output mixer (analog bypass)
// For this example, we are going to pass audio throught the ADC and DAC
codec.disableLB2LO();
codec.disableRB2RO();
// Connect from DAC outputs to output mixer
codec.enableLD2LO();
codec.enableRD2RO();
// Set gainstage between booster mixer and output mixer
// For this loopback example, we are going to keep these as low as they go
codec.setLB2LOVOL(WM8960_OUTPUT_MIXER_GAIN_NEG_21DB);
codec.setRB2ROVOL(WM8960_OUTPUT_MIXER_GAIN_NEG_21DB);
// Enable output mixers
codec.enableLOMIX();
codec.enableROMIX();
// CLOCK STUFF, These settings will get you 44.1KHz sample rate, and class-d
// freq at 705.6kHz
codec.enablePLL(); // Needed for class-d amp clock
codec.setPLLPRESCALE(WM8960_PLLPRESCALE_DIV_2);
codec.setSMD(WM8960_PLL_MODE_FRACTIONAL);
codec.setCLKSEL(WM8960_CLKSEL_PLL);
codec.setSYSCLKDIV(WM8960_SYSCLK_DIV_BY_2);
codec.setBCLKDIV(4);
codec.setDCLKDIV(WM8960_DCLKDIV_16);
codec.setPLLN(7);
codec.setPLLK(0x86, 0xC2, 0x26); // PLLK=86C226h
//codec.setADCDIV(0); // Default is 000 (what we need for 44.1KHz)
//codec.setDACDIV(0); // Default is 000 (what we need for 44.1KHz)
codec.setWL(WM8960_WL_16BIT);
codec.enablePeripheralMode();
//codec.enableMasterMode();
//codec.setALRCGPIO(); // Note, should not be changed while ADC is enabled.
// Enable ADCs and DACs
codec.enableAdcLeft();
codec.enableAdcRight();
codec.enableDacLeft();
codec.enableDacRight();
codec.disableDacMute();
//codec.enableLoopBack(); // Loopback sends ADC data directly into DAC
codec.disableLoopBack();
// Default is "soft mute" on, so we must disable mute to make channels active
codec.disableDacMute();
codec.enableHeadphones();
codec.enableOUT3MIX(); // Provides VMID as buffer for headphone ground
Serial.println("Volume set to +0dB");
codec.setHeadphoneVolumeDB(0.00);
Serial.println("Codec Setup complete. Listen to left/right INPUT1 on Headphone outputs.");
}
void i2s_install() {
// Set up I2S Processor configuration
const i2s_driver_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //JDN
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_MSB),
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = bufferLen,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = i2s_mclk_multiple_t(I2S_MCLK_MULTIPLE_DEFAULT),
.bits_per_chan = i2s_bits_per_chan_t(I2S_BITS_PER_CHAN_DEFAULT)
};
i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}
void i2s_setpin() {
// Set I2S pin configuration
const i2s_pin_config_t pin_config = {
.mck_io_num = I2S_PIN_NO_CHANGE,
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_SDO,
.data_in_num = I2S_SD
};
i2s_set_pin(I2S_PORT, &pin_config);
}
This code does not behave as expected and rather than oscillating the center frequency there is an oscillating amplitude which is a new problem that has just arisen.
Cpp file:
#include "wahHeader.h"
//This function takes an input x, and processes it according to the following difference equation:
// y[n] = F1*x[n] - F1*x[n-1] - (F1*F1 - q - 1)*y[n-1] - q*y[n-2]
int16_t autoWah(int16_t x, float f1Scalar, float qScalar, float xDelay1, float yDelay1, float yDelay2) {
x = x >> 1; //Bit shifting the input to avoid overflow
float y; //Function output definition
y = f1Scalar * x; //Calculation of the first index in the input buffer
y = y - f1Scalar * xDelay1; //Calculation of the delayed index in the input buffer, at index 0 the value of x_delay_1 will be 0
y = (y - (f1Scalar * f1Scalar - qScalar - 1) * yDelay1); //Calculation of the first delayed output. At index 0, y_delay_1 will be 0.
y = y - qScalar * yDelay2; //Calculation of the second delayed output. At index 0 and 1 this will be 0.
//Update variable values
xDelay1 = x; //The current input is set as the singularly delayed input, as the current x-value will represent the delayed input at the next index.
yDelay2 = yDelay1; //The double delayed output is set as the singularly delayed output, as y_delay_1 will represent the double delayed output as the next index.
yDelay1 = y; //The output is set as the single delayed output, as y will represent the single delayed output at the next index.
return y; //The function returns a processed signal that is dependant upon a current input, a singularly delayed input, a singularly delayed output and a double delayed output.
}
Here is the cpp for the library. The function is called using the exact same name, and with the exact same operations internal to the function, though with variable names having been changed.
Header file:
#ifndef WAHHEADER_H
#define WAHHEADER_H
#include <Arduino.h>
int16_t autoWah(int16_t x, float f1Scalar, float qScalar, float xDelay1, float yDelay1, float yDelay2);
#endif