This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,704 @@
/*
This is a very basic driver for the ES8388 based on the driver written by
me for NuttX. It is not complete and is missing master mode, mono mode, many
features and configuration options. You can use readReg and writeReg to
access the registers directly and configure missing features. Feel free to
contribute by adding missing features and improving the driver.
It is intended to be used only with arduino-esp32.
The default configuration can be found in the ES8388.h file.
This was only tested with the ESP32-LyraT board using 44100Hz 16-bit stereo
audio. It may not work with other configurations.
Created for arduino-esp32 on 20 Dec, 2023
by Lucas Saavedra Vaz (lucasssvaz)
*/
#include <cmath>
#include "ESP_I2S.h"
#include "Wire.h"
#include "ES8388.h"
/****************************************************************************
* Private Methods
****************************************************************************/
/*
Name: start
Description:
Unmute and start the ES8388 codec data transmission.
*/
void ES8388::start() {
uint8_t prev_regval = 0;
uint8_t regval = 0;
log_v("Starting ES8388 transmission...");
_running = true;
prev_regval = readReg(ES8388_DACCONTROL21);
if (_audio_mode == ES_MODULE_LINE) {
writeReg(ES8388_DACCONTROL16, ES8388_RMIXSEL_RIN2 | ES8388_LMIXSEL_LIN2);
writeReg(ES8388_DACCONTROL17, ES8388_LI2LOVOL(ES8388_MIXER_GAIN_0DB) | ES8388_LI2LO_ENABLE | ES8388_LD2LO_DISABLE);
writeReg(ES8388_DACCONTROL20, ES8388_RI2ROVOL(ES8388_MIXER_GAIN_0DB) | ES8388_RI2RO_ENABLE | ES8388_RD2RO_DISABLE);
writeReg(
ES8388_DACCONTROL21,
ES8388_DAC_DLL_PWD_NORMAL | ES8388_ADC_DLL_PWD_NORMAL | ES8388_MCLK_DIS_NORMAL | ES8388_OFFSET_DIS_DISABLE | ES8388_LRCK_SEL_ADC | ES8388_SLRCK_SAME
);
} else {
writeReg(
ES8388_DACCONTROL21,
ES8388_DAC_DLL_PWD_NORMAL | ES8388_ADC_DLL_PWD_NORMAL | ES8388_MCLK_DIS_NORMAL | ES8388_OFFSET_DIS_DISABLE | ES8388_LRCK_SEL_DAC | ES8388_SLRCK_SAME
);
}
regval = readReg(ES8388_DACCONTROL21);
if (regval != prev_regval) {
writeReg(
ES8388_CHIPPOWER, ES8388_DACVREF_PDN_PWRUP | ES8388_ADCVREF_PDN_PWRUP | ES8388_DACDLL_PDN_NORMAL | ES8388_ADCDLL_PDN_NORMAL | ES8388_DAC_STM_RST_RESET
| ES8388_ADC_STM_RST_RESET | ES8388_DAC_DIGPDN_RESET | ES8388_ADC_DIGPDN_RESET
);
writeReg(
ES8388_CHIPPOWER, ES8388_DACVREF_PDN_PWRUP | ES8388_ADCVREF_PDN_PWRUP | ES8388_DACDLL_PDN_NORMAL | ES8388_ADCDLL_PDN_NORMAL | ES8388_DAC_STM_RST_NORMAL
| ES8388_ADC_STM_RST_NORMAL | ES8388_DAC_DIGPDN_NORMAL | ES8388_ADC_DIGPDN_NORMAL
);
}
if (_audio_mode == ES_MODULE_LINE || _audio_mode == ES_MODULE_ADC_DAC || _audio_mode == ES_MODULE_ADC) {
writeReg(
ES8388_ADCPOWER, ES8388_INT1LP_NORMAL | ES8388_FLASHLP_NORMAL | ES8388_PDNADCBIASGEN_NORMAL | ES8388_PDNMICB_PWRON | ES8388_PDNADCR_PWRUP
| ES8388_PDNADCL_PWRUP | ES8388_PDNAINR_NORMAL | ES8388_PDNAINL_NORMAL
);
}
if (_audio_mode == ES_MODULE_LINE || _audio_mode == ES_MODULE_ADC_DAC || _audio_mode == ES_MODULE_DAC) {
writeReg(
ES8388_DACPOWER, ES8388_ROUT2_ENABLE | ES8388_LOUT2_ENABLE | ES8388_ROUT1_ENABLE | ES8388_LOUT1_ENABLE | ES8388_PDNDACR_PWRUP | ES8388_PDNDACL_PWRUP
);
}
setmute(_audio_mode, false);
log_v("ES8388 transmission started.");
}
/*
Name: reset
Description:
Reset the ES8388 codec to a known state depending on the audio mode.
*/
void ES8388::reset() {
uint8_t regconfig;
log_v("Resetting ES8388...");
log_d(
"Current configuration: _bpsamp=%d, _samprate=%d, _nchannels=%d, _audio_mode=%d, _dac_output=%d, _adc_input=%d, _mic_gain=%d", _bpsamp, _samprate,
_nchannels, _audio_mode, _dac_output, _adc_input, _mic_gain
);
writeReg(ES8388_DACCONTROL3, ES8388_DACMUTE_MUTED | ES8388_DACLER_NORMAL | ES8388_DACSOFTRAMP_DISABLE | ES8388_DACRAMPRATE_4LRCK);
writeReg(
ES8388_CONTROL2,
ES8388_PDNVREFBUF_NORMAL | ES8388_VREFLO_NORMAL | ES8388_PDNIBIASGEN_NORMAL | ES8388_PDNANA_NORMAL | ES8388_LPVREFBUF_LP | ES8388_LPVCMMOD_NORMAL | (1 << 6)
); /* Default value of undocumented bit */
writeReg(
ES8388_CHIPPOWER, ES8388_DACVREF_PDN_PWRUP | ES8388_ADCVREF_PDN_PWRUP | ES8388_DACDLL_PDN_NORMAL | ES8388_ADCDLL_PDN_NORMAL | ES8388_DAC_STM_RST_NORMAL
| ES8388_ADC_STM_RST_NORMAL | ES8388_DAC_DIGPDN_NORMAL | ES8388_ADC_DIGPDN_NORMAL
);
/* Disable the internal DLL to improve 8K sample rate */
writeReg(0x35, 0xa0);
writeReg(0x37, 0xd0);
writeReg(0x39, 0xd0);
writeReg(ES8388_MASTERMODE, ES8388_BCLKDIV(ES_MCLK_DIV_AUTO) | ES8388_BCLK_INV_NORMAL | ES8388_MCLKDIV2_NODIV | ES8388_MSC_SLAVE);
writeReg(
ES8388_DACPOWER, ES8388_ROUT2_DISABLE | ES8388_LOUT2_DISABLE | ES8388_ROUT1_DISABLE | ES8388_LOUT1_DISABLE | ES8388_PDNDACR_PWRDN | ES8388_PDNDACL_PWRDN
);
writeReg(
ES8388_CONTROL1, ES8388_VMIDSEL_500K | ES8388_ENREF_DISABLE | ES8388_SEQEN_DISABLE | ES8388_SAMEFS_SAME | ES8388_DACMCLK_ADCMCLK | ES8388_LRCM_ISOLATED
| ES8388_SCPRESET_NORMAL
);
setBitsPerSample(_bpsamp);
setSampleRate(_samprate);
writeReg(ES8388_DACCONTROL16, ES8388_RMIXSEL_RIN1 | ES8388_LMIXSEL_LIN1);
writeReg(ES8388_DACCONTROL17, ES8388_LI2LOVOL(ES8388_MIXER_GAIN_0DB) | ES8388_LI2LO_DISABLE | ES8388_LD2LO_ENABLE);
writeReg(ES8388_DACCONTROL20, ES8388_RI2ROVOL(ES8388_MIXER_GAIN_0DB) | ES8388_RI2RO_DISABLE | ES8388_RD2RO_ENABLE);
writeReg(
ES8388_DACCONTROL21,
ES8388_DAC_DLL_PWD_NORMAL | ES8388_ADC_DLL_PWD_NORMAL | ES8388_MCLK_DIS_NORMAL | ES8388_OFFSET_DIS_DISABLE | ES8388_LRCK_SEL_DAC | ES8388_SLRCK_SAME
);
writeReg(ES8388_DACCONTROL23, ES8388_VROI_1_5K);
writeReg(ES8388_DACCONTROL24, ES8388_LOUT1VOL(ES8388_DAC_CHVOL_DB(0)));
writeReg(ES8388_DACCONTROL25, ES8388_ROUT1VOL(ES8388_DAC_CHVOL_DB(0)));
writeReg(ES8388_DACCONTROL26, ES8388_LOUT2VOL(ES8388_DAC_CHVOL_DB(0)));
writeReg(ES8388_DACCONTROL27, ES8388_ROUT2VOL(ES8388_DAC_CHVOL_DB(0)));
setmute(ES_MODULE_DAC, ES8388_DEFAULT_MUTE);
setvolume(ES_MODULE_DAC, ES8388_DEFAULT_VOL_OUT, ES8388_DEFAULT_BALANCE);
if (_dac_output == ES8388_DAC_OUTPUT_LINE2) {
regconfig = ES_DAC_CHANNEL_LOUT1 | ES_DAC_CHANNEL_ROUT1;
} else if (_dac_output == ES8388_DAC_OUTPUT_LINE1) {
regconfig = ES_DAC_CHANNEL_LOUT2 | ES_DAC_CHANNEL_ROUT2;
} else {
regconfig = ES_DAC_CHANNEL_LOUT1 | ES_DAC_CHANNEL_ROUT1 | ES_DAC_CHANNEL_LOUT2 | ES_DAC_CHANNEL_ROUT2;
}
writeReg(ES8388_DACPOWER, regconfig);
writeReg(
ES8388_ADCPOWER, ES8388_INT1LP_LP | ES8388_FLASHLP_LP | ES8388_PDNADCBIASGEN_LP | ES8388_PDNMICB_PWRDN | ES8388_PDNADCR_PWRDN | ES8388_PDNADCL_PWRDN
| ES8388_PDNAINR_PWRDN | ES8388_PDNAINL_PWRDN
);
setMicGain(24); /* +24 dB */
if (_adc_input == ES8388_ADC_INPUT_LINE1) {
regconfig = ES_ADC_CHANNEL_LINPUT1_RINPUT1;
} else if (_adc_input == ES8388_ADC_INPUT_LINE2) {
regconfig = ES_ADC_CHANNEL_LINPUT2_RINPUT2;
} else {
regconfig = ES_ADC_CHANNEL_DIFFERENCE;
}
writeReg(ES8388_ADCCONTROL2, regconfig);
writeReg(
ES8388_ADCCONTROL3, (1 << 1) | /* Default value of undocumented bit */
ES8388_TRI_NORMAL | ES8388_MONOMIX_STEREO | ES8388_DS_LINPUT1_RINPUT1
);
setBitsPerSample(_bpsamp);
setSampleRate(_samprate);
setmute(ES_MODULE_ADC, ES8388_DEFAULT_MUTE);
setvolume(ES_MODULE_ADC, ES8388_DEFAULT_VOL_IN, ES8388_DEFAULT_BALANCE);
writeReg(
ES8388_ADCPOWER, ES8388_INT1LP_LP | ES8388_FLASHLP_NORMAL | ES8388_PDNADCBIASGEN_NORMAL | ES8388_PDNMICB_PWRDN | ES8388_PDNADCR_PWRUP | ES8388_PDNADCL_PWRUP
| ES8388_PDNAINR_NORMAL | ES8388_PDNAINL_NORMAL
);
/* Stop sequence to avoid noise at boot */
stop();
log_v("ES8388 reset.");
}
/*
Name: stop
Description:
Mute and stop the ES8388 codec data transmission.
*/
void ES8388::stop() {
log_v("Stopping ES8388 transmission...");
_running = false;
if (_audio_mode == ES_MODULE_LINE) {
writeReg(
ES8388_DACCONTROL21,
ES8388_DAC_DLL_PWD_NORMAL | ES8388_ADC_DLL_PWD_NORMAL | ES8388_MCLK_DIS_NORMAL | ES8388_OFFSET_DIS_DISABLE | ES8388_LRCK_SEL_DAC | ES8388_SLRCK_SAME
);
writeReg(ES8388_DACCONTROL16, ES8388_RMIXSEL_RIN1 | ES8388_LMIXSEL_LIN1);
writeReg(ES8388_DACCONTROL17, ES8388_LI2LOVOL(ES8388_MIXER_GAIN_0DB) | ES8388_LI2LO_DISABLE | ES8388_LD2LO_ENABLE);
writeReg(ES8388_DACCONTROL20, ES8388_RI2ROVOL(ES8388_MIXER_GAIN_0DB) | ES8388_RI2RO_DISABLE | ES8388_RD2RO_ENABLE);
goto stop_msg;
}
if (_audio_mode == ES_MODULE_DAC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(
ES8388_DACPOWER, ES8388_ROUT2_DISABLE | ES8388_LOUT2_DISABLE | ES8388_ROUT1_DISABLE | ES8388_LOUT1_DISABLE | ES8388_PDNDACR_PWRUP | ES8388_PDNDACL_PWRUP
);
}
if (_audio_mode == ES_MODULE_ADC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(
ES8388_ADCPOWER, ES8388_INT1LP_LP | ES8388_FLASHLP_LP | ES8388_PDNADCBIASGEN_LP | ES8388_PDNMICB_PWRDN | ES8388_PDNADCR_PWRDN | ES8388_PDNADCL_PWRDN
| ES8388_PDNAINR_PWRDN | ES8388_PDNAINL_PWRDN
);
}
if (_audio_mode == ES_MODULE_ADC_DAC) {
writeReg(
ES8388_DACCONTROL21,
ES8388_DAC_DLL_PWD_PWRDN | ES8388_ADC_DLL_PWD_PWRDN | ES8388_MCLK_DIS_DISABLE | ES8388_OFFSET_DIS_DISABLE | ES8388_LRCK_SEL_DAC | ES8388_SLRCK_SAME
);
}
stop_msg:
setmute(_audio_mode, true);
log_v("ES8388 transmission stopped.");
}
/*
Name: setvolume
Description:
Set the volume of the ES8388 codec.
Input Parameters:
module - Module to set the volume for.
volume - Volume level {0..1000}.
balance - Balance level {0..1000}.
*/
void ES8388::setvolume(es_module_e module, uint16_t volume, uint16_t balance) {
uint16_t leftlvl;
int16_t dbleftlvl;
uint16_t rightlvl;
int16_t dbrightlvl;
log_d("Volume = %u, Balance = %u", volume, balance);
if (volume > 1000) {
log_w("Warning: Volume greater than 1000, setting to 1000.");
volume = 1000;
}
if (balance > 1000) {
log_w("Warning: Balance greater than 1000, setting to 1000.");
balance = 1000;
}
_balance = balance;
/* Calculate the left channel volume level {0..1000} */
if (_balance <= 500) {
leftlvl = volume;
} else if (_balance == 1000) {
leftlvl = 0;
} else {
leftlvl = ((((1000 - _balance) * 100) / 500) * volume) / 100;
}
/* Calculate the right channel volume level {0..1000} */
if (_balance >= 500) {
rightlvl = volume;
} else if (_balance == 0) {
rightlvl = 0;
} else {
rightlvl = (((_balance * 100) / 500) * volume) / 100;
}
/* Convert from (0..1000) to (-96..0) */
dbleftlvl = (int16_t)(leftlvl ? (20 * log10f((float)rightlvl / 1000)) : -96);
dbrightlvl = (int16_t)(rightlvl ? (20 * log10f((float)rightlvl / 1000)) : -96);
log_v("Volume: dbleftlvl = %d, dbrightlvl = %d", dbleftlvl, dbrightlvl);
/* Convert and truncate to 1 byte */
dbleftlvl = ((-dbleftlvl) << 1) & 0xff;
dbrightlvl = ((-dbrightlvl) << 1) & 0xff;
/* Set the volume */
if (module == ES_MODULE_DAC || module == ES_MODULE_ADC_DAC) {
writeReg(ES8388_DACCONTROL4, ES8388_LDACVOL(dbleftlvl));
writeReg(ES8388_DACCONTROL5, ES8388_RDACVOL(dbrightlvl));
_volume_out = volume;
}
if (module == ES_MODULE_ADC || module == ES_MODULE_ADC_DAC) {
writeReg(ES8388_ADCCONTROL8, ES8388_LADCVOL(dbleftlvl));
writeReg(ES8388_ADCCONTROL9, ES8388_RADCVOL(dbrightlvl));
_volume_in = volume;
}
}
/*
Name: setmute
Description:
Mute or unmute the selected ES8388 codec module.
Input Parameters:
module - Module to mute or unmute.
enable - Mute or unmute.
*/
void ES8388::setmute(es_module_e module, bool enable) {
uint8_t reg = 0;
log_d("module=%d, mute=%d", module, (int)enable);
_mute = enable;
if (module == ES_MODULE_DAC || module == ES_MODULE_ADC_DAC) {
reg = readReg(ES8388_DACCONTROL3) & (~ES8388_DACMUTE_BITMASK);
writeReg(ES8388_DACCONTROL3, reg | ES8388_DACMUTE(enable));
}
if (module == ES_MODULE_ADC || module == ES_MODULE_ADC_DAC) {
reg = readReg(ES8388_ADCCONTROL7) & (~ES8388_ADCMUTE_BITMASK);
writeReg(ES8388_ADCCONTROL7, reg | ES8388_ADCMUTE(enable));
}
}
/****************************************************************************
* Public Methods
****************************************************************************/
ES8388::~ES8388() {
end();
}
/*
Name: begin
Description:
Initialize the ES8388 codec. Requires the I2S and I2C buses to be initialized
before calling this function.
Input Parameters:
i2s - I2S bus instance.
i2c - I2C bus instance. Defaults to Wire.
addr - I2C address of the ES8388 codec. Defaults to 0x10.
Return:
true - Success.
false - Failure.
*/
bool ES8388::begin(I2SClass &i2s, TwoWire &i2c, uint8_t addr) {
log_v("Initializing ES8388...");
_i2c = &i2c;
_i2s = &i2s;
_addr = addr;
_fmt = ES8388_DEFAULT_FMT;
_mode = ES8388_DEFAULT_MODE;
_samprate = ES8388_DEFAULT_SAMPRATE;
_nchannels = ES8388_DEFAULT_NCHANNELS;
_bpsamp = ES8388_DEFAULT_BPSAMP;
_audio_mode = ES8388_DEFAULT_AUDIO_MODE;
_dac_output = ES8388_DEFAULT_DAC_OUTPUT;
_adc_input = ES8388_DEFAULT_ADC_INPUT;
_mic_gain = ES8388_DEFAULT_MIC_GAIN;
_running = false;
_lclk_div = ES_LCLK_DIV_256;
_word_length = ES_WORD_LENGTH_16BITS;
_i2c->beginTransmission(_addr);
if (_i2c->endTransmission() != 0) {
log_e("Device not found at address 0x%02x. Check if the I2C and I2S buses are initialized.", _addr);
return false;
}
reset();
log_v("ES8388 initialized.");
return true;
}
/*
Name: end
Description:
Stop the ES8388 codec and reset it to a known state.
*/
void ES8388::end() {
log_v("Ending ES8388...");
stop();
setmute(ES_MODULE_ADC_DAC, true);
_audio_mode = ES_MODULE_ADC_DAC;
reset();
log_v("ES8388 ended.");
}
/*
Name: readReg
Description:
Read a register from the ES8388 codec.
Input Parameters:
reg - Register address.
Return:
Register value.
*/
uint8_t ES8388::readReg(uint8_t reg) {
int data;
_i2c->beginTransmission(_addr);
if (_i2c->write(reg) == 0) {
log_e("Error writing register address 0x%02x.", reg);
return 0;
}
if (_i2c->endTransmission(false) != 0) {
log_e("Error ending transmission.");
return 0;
}
if (!_i2c->requestFrom(_addr, (uint8_t)1)) {
log_e("Error requesting data.");
return 0;
}
if ((data = _i2c->read()) < 0) {
log_e("Error reading data.");
return 0;
}
return (uint8_t)data;
}
/*
Name: writeReg
Description:
Write a register to the ES8388 codec.
Input Parameters:
reg - Register address.
data - Data to write.
*/
void ES8388::writeReg(uint8_t reg, uint8_t data) {
_i2c->beginTransmission(_addr);
if (_i2c->write(reg) == 0) {
log_e("Error writing register address 0x%02x.", reg);
return;
}
if (_i2c->write(data) == 0) {
log_e("Error writing data 0x%02x.", data);
return;
}
if (_i2c->endTransmission(true) != 0) {
log_e("Error ending transmission.");
return;
}
}
/*
Name: setMicGain
Description:
Set the microphone gain.
Input Parameters:
gain - Gain level in dB {0..24}.
*/
void ES8388::setMicGain(uint8_t gain) {
static const es_mic_gain_e gain_map[] = {
ES_MIC_GAIN_0DB, ES_MIC_GAIN_3DB, ES_MIC_GAIN_6DB, ES_MIC_GAIN_9DB, ES_MIC_GAIN_12DB,
ES_MIC_GAIN_15DB, ES_MIC_GAIN_18DB, ES_MIC_GAIN_21DB, ES_MIC_GAIN_24DB,
};
log_d("gain=%d", gain);
_mic_gain = gain_map[min(gain, (uint8_t)24) / 3];
writeReg(ES8388_ADCCONTROL1, ES8388_MICAMPR(_mic_gain) | ES8388_MICAMPL(_mic_gain));
log_v("Mic gain set to %d", _mic_gain);
}
/*
Name: setBitsPerSample
Description:
Set the number of bits per sample. This also configures the I2S bus.
Input Parameters:
bpsamp - Bits per sample {16, 24, 32}.
*/
void ES8388::setBitsPerSample(uint8_t bpsamp) {
/* ES8388 also supports 18 and 20 bits per sample, but the I2S bus does not */
switch (bpsamp) {
case 16: _word_length = ES_WORD_LENGTH_16BITS; break;
case 24: _word_length = ES_WORD_LENGTH_24BITS; break;
case 32: _word_length = ES_WORD_LENGTH_32BITS; break;
default: log_e("Data length not supported."); return;
}
_bpsamp = bpsamp;
_i2s->configureTX(_samprate, (i2s_data_bit_width_t)_bpsamp, I2S_SLOT_MODE_STEREO);
_i2s->configureRX(_samprate, (i2s_data_bit_width_t)_bpsamp, I2S_SLOT_MODE_STEREO);
if (_audio_mode == ES_MODULE_ADC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(ES8388_ADCCONTROL4, ES8388_ADCFORMAT(ES_I2S_NORMAL) | ES8388_ADCWL(_word_length) | ES8388_ADCLRP_NORM_2ND | ES8388_DATSEL_LL);
}
if (_audio_mode == ES_MODULE_DAC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(ES8388_DACCONTROL1, ES8388_DACFORMAT(ES_I2S_NORMAL) | ES8388_DACWL(_word_length) | ES8388_DACLRP_NORM_2ND | ES8388_DACLRSWAP_NORMAL);
}
log_v("Datawidth set to %u", _bpsamp);
}
/*
Name: setSampleRate
Description:
Set the sample rate. This also configures the I2S bus.
The divider depends on the sample rate and the MCLK frequency.
This needs to be re-implemented to properly support all cases.
ES8388 should also support 88100Hz and 96000Hz sample rates in
double speed mode but setting it makes the audio sound distorted.
Input Parameters:
rate - Sample rate {8000, 11025, 12000, 16000, 22050, 24000, 32000,
44100, 48000}.
*/
void ES8388::setSampleRate(uint32_t rate) {
/*
According to the datasheet, this should only matter for the master mode
but it seems to affect the slave mode as well.
*/
switch (rate) {
case 8000: _lclk_div = ES_LCLK_DIV_1536; break;
case 11025:
case 12000: _lclk_div = ES_LCLK_DIV_1024; break;
case 16000: _lclk_div = ES_LCLK_DIV_768; break;
case 22050:
case 24000: _lclk_div = ES_LCLK_DIV_512; break;
case 32000: _lclk_div = ES_LCLK_DIV_384; break;
case 44100:
case 48000: _lclk_div = ES_LCLK_DIV_256; break;
default: log_e("Sample rate not supported."); return;
}
_samprate = rate;
_i2s->configureTX(_samprate, (i2s_data_bit_width_t)_bpsamp, I2S_SLOT_MODE_STEREO);
_i2s->configureRX(_samprate, (i2s_data_bit_width_t)_bpsamp, I2S_SLOT_MODE_STEREO);
if (_audio_mode == ES_MODULE_ADC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(ES8388_ADCCONTROL5, ES8388_ADCFSRATIO(_lclk_div));
}
if (_audio_mode == ES_MODULE_DAC || _audio_mode == ES_MODULE_ADC_DAC) {
writeReg(ES8388_DACCONTROL2, ES8388_DACFSRATIO(_lclk_div));
}
log_v("Sample rate set to %d in single speed mode", _samprate);
}
/*
Name: playWAV
Description:
Wrapper for the I2SClass::playWAV() method. This method starts the ES8388
codec before playing the WAV file and stops it after the WAV file has been
played.
Input Parameters:
data - Pointer to the WAV file data.
len - Length of the WAV file data.
*/
void ES8388::playWAV(uint8_t *data, size_t len) {
_audio_mode = ES_MODULE_DAC;
reset();
log_v("Playing WAV file...");
start();
_i2s->playWAV(data, len);
stop();
log_v("WAV file played.");
}
/*
Name: recordWAV
Description:
Wrapper for the I2SClass::recordWAV() method. This method starts the ES8388
codec before recording the WAV file and stops it after the WAV file has been
recorded.
Input Parameters:
rec_seconds - Length of the WAV file to record in seconds.
out_size - Pointer to the variable that will hold the size of the WAV file.
Return:
Pointer to the WAV file data.
*/
uint8_t *ES8388::recordWAV(size_t rec_seconds, size_t *out_size) {
uint8_t *data;
size_t size;
_audio_mode = ES_MODULE_ADC;
reset();
log_v("Recording WAV file...");
start();
data = _i2s->recordWAV(rec_seconds, &size);
stop();
log_v("WAV file recorded.");
*out_size = size;
return data;
}
void ES8388::setOutputVolume(uint16_t volume, uint16_t balance) {
setvolume(ES_MODULE_DAC, volume, balance);
}
void ES8388::setInputVolume(uint16_t volume, uint16_t balance) {
setvolume(ES_MODULE_ADC, volume, balance);
}
void ES8388::setOutputMute(bool enable) {
setmute(ES_MODULE_DAC, enable);
}
void ES8388::setInputMute(bool enable) {
setmute(ES_MODULE_ADC, enable);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
/*
ESP32-LyraT I2S ES8388 loopback example
This simple example demonstrates using the I2S library in combination
with the ES8388 codec on the ESP32-LyraT board to record and play back
audio data.
Don't forget to enable the PSRAM in the Tools menu!
Created for arduino-esp32 on 20 Dec, 2023
by Lucas Saavedra Vaz (lucasssvaz)
*/
#include "ESP_I2S.h"
#include "Wire.h"
#include "ES8388.h"
/* Pin definitions */
/* I2C */
const uint8_t I2C_SCL = 23;
const uint8_t I2C_SDA = 18;
const uint32_t I2C_FREQ = 400000;
/* I2S */
const uint8_t I2S_MCLK = 0; /* Master clock */
const uint8_t I2S_SCK = 5; /* Audio data bit clock */
const uint8_t I2S_WS = 25; /* Audio data left and right clock */
const uint8_t I2S_SDOUT = 26; /* ESP32 audio data output (to speakers) */
const uint8_t I2S_SDIN = 35; /* ESP32 audio data input (from microphone) */
/* PA */
const uint8_t PA_ENABLE = 21; /* Power amplifier enable */
void setup() {
I2SClass i2s;
ES8388 codec;
uint8_t *wav_buffer;
size_t wav_size;
// Initialize the serial port
Serial.begin(115200);
pinMode(PA_ENABLE, OUTPUT);
digitalWrite(PA_ENABLE, HIGH);
Serial.println("Initializing I2C bus...");
// Initialize the I2C bus
Wire.begin(I2C_SDA, I2C_SCL, I2C_FREQ);
Serial.println("Initializing I2S bus...");
// Set up the pins used for audio input
i2s.setPins(I2S_SCK, I2S_WS, I2S_SDOUT, I2S_SDIN, I2S_MCLK);
// Initialize the I2S bus in standard mode
if (!i2s.begin(I2S_MODE_STD, 44100, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH)) {
Serial.println("Failed to initialize I2S bus!");
return;
}
Serial.println("Initializing ES8388...");
if (!codec.begin(i2s)) {
Serial.println("Failed to initialize ES8388!");
return;
}
Serial.println("Recording 10 seconds of audio data...");
// Record 10 seconds of audio data
wav_buffer = codec.recordWAV(10, &wav_size);
Serial.println("Recording complete. Playing audio data in 3 seconds.");
delay(3000);
// Play the audio data
Serial.println("Playing audio data...");
codec.playWAV(wav_buffer, wav_size);
Serial.println("Application complete.");
}
void loop() {}
@@ -0,0 +1,3 @@
requires:
- CONFIG_SOC_I2S_SUPPORTED=y
- CONFIG_SOC_I2C_SUPPORTED=y
@@ -0,0 +1,89 @@
/*
ESP32-S2-EYE I2S record to WAV example
This simple example demonstrates using the I2S library to record
5 seconds of audio data and write it to a WAV file on the SD card.
Don't forget to select the OPI PSRAM, 8MB flash size and Enable USB CDC
on boot in the Tools menu!
Created for arduino-esp32 on 18 Dec, 2023
by Lucas Saavedra Vaz (lucasssvaz)
*/
#include "ESP_I2S.h"
#include "FS.h"
#include "SD_MMC.h"
const uint8_t I2S_SCK = 41;
const uint8_t I2S_WS = 42;
const uint8_t I2S_DIN = 2;
const uint8_t SD_CMD = 38;
const uint8_t SD_CLK = 39;
const uint8_t SD_DATA0 = 40;
void setup() {
// Create an instance of the I2SClass
I2SClass i2s;
// Create variables to store the audio data
uint8_t *wav_buffer;
size_t wav_size;
// Initialize the serial port
Serial.begin(115200);
Serial.println("Initializing I2S bus...");
// Set up the pins used for audio input
i2s.setPins(I2S_SCK, I2S_WS, -1, I2S_DIN);
// Initialize the I2S bus in standard mode
if (!i2s.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO, I2S_STD_SLOT_LEFT)) {
Serial.println("Failed to initialize I2S bus!");
return;
}
Serial.println("I2S bus initialized.");
Serial.println("Initializing SD card...");
// Set up the pins used for SD card access
if (!SD_MMC.setPins(SD_CLK, SD_CMD, SD_DATA0)) {
Serial.println("Failed to set SD pins!");
return;
}
// Mount the SD card
if (!SD_MMC.begin("/sdcard", true)) {
Serial.println("Failed to initialize SD card!");
return;
}
Serial.println("SD card initialized.");
Serial.println("Recording 5 seconds of audio data...");
// Record 5 seconds of audio data
wav_buffer = i2s.recordWAV(5, &wav_size);
// Create a file on the SD card
File file = SD_MMC.open("/test.wav", FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing!");
return;
}
Serial.println("Writing audio data to file...");
// Write the audio data to the file
if (file.write(wav_buffer, wav_size) != wav_size) {
Serial.println("Failed to write audio data to file!");
return;
}
// Close the file
file.close();
Serial.println("Application complete.");
}
void loop() {}
@@ -0,0 +1,3 @@
requires:
- CONFIG_SOC_SDMMC_HOST_SUPPORTED=y
- CONFIG_SOC_I2S_SUPPORTED=y
@@ -0,0 +1,82 @@
/*
This example generates a square wave based tone at a specified frequency
and sample rate. Then outputs the data using the I2S interface to a
MAX08357 I2S Amp Breakout board.
I2S Circuit:
* Arduino/Genuino Zero, MKR family and Nano 33 IoT
* MAX08357:
* GND connected GND
* VIN connected 5V
* LRC connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32)
* BCLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32)
* DIN connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32)
DAC Circuit:
* ESP32 or ESP32-S2
* Audio amplifier
- Note:
- ESP32 has DAC on GPIO pins 25 and 26.
- ESP32-S2 has DAC on GPIO pins 17 and 18.
- Connect speaker(s) or headphones.
created 17 November 2016
by Sandeep Mistry
For ESP extended
Tomas Pilny
2nd September 2021
Lucas Saavedra Vaz (lucasssvaz)
22nd December 2023
anon
10nd February 2025
*/
#include <ESP_I2S.h>
// The GPIO pins are not fixed, most other pins could be used for the I2S function.
#define I2S_LRC 25
#define I2S_BCLK 5
#define I2S_DIN 26
const int frequency = 440; // frequency of square wave in Hz
const int amplitude = 500; // amplitude of square wave
const int sampleRate = 8000; // sample rate in Hz
i2s_data_bit_width_t bps = I2S_DATA_BIT_WIDTH_16BIT;
i2s_mode_t mode = I2S_MODE_STD;
i2s_slot_mode_t slot = I2S_SLOT_MODE_STEREO;
const unsigned int halfWavelength = sampleRate / frequency / 2; // half wavelength of square wave
int32_t sample = amplitude; // current sample value
unsigned int count = 0;
I2SClass i2s;
void setup() {
Serial.begin(115200);
Serial.println("I2S simple tone");
i2s.setPins(I2S_BCLK, I2S_LRC, I2S_DIN);
// start I2S at the sample rate with 16-bits per sample
if (!i2s.begin(mode, sampleRate, bps, slot)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
if (count % halfWavelength == 0) {
// invert the sample every half wavelength count multiple to generate square wave
sample = -1 * sample;
}
// Left channel, the low 8 bits then high 8 bits
i2s.write(sample);
i2s.write(sample >> 8);
// Right channel, the low 8 bits then high 8 bits
i2s.write(sample);
i2s.write(sample >> 8);
// increment the counter for the next sample
count++;
}
@@ -0,0 +1,2 @@
requires:
- CONFIG_SOC_I2S_SUPPORTED=y
+21
View File
@@ -0,0 +1,21 @@
#######################################
# Syntax Coloring Map For ESP_I2S
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
ESP_I2S KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
onEvent KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
SR_EVENT_WAKEWORD LITERAL1
+9
View File
@@ -0,0 +1,9 @@
name=ESP_I2S
version=3.3.7
author=me-no-dev
maintainer=me-no-dev
sentence=Library for ESP I2S communication
paragraph=Supports ESP32 Arduino platforms.
category=Signal Input/Output
url=https://github.com/espressif/arduino-esp32/
architectures=esp32
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
#pragma once
#if defined __has_include && __has_include("mp3dec.h")
#define ARDUINO_HAS_MP3_DECODER 1
#endif
#include "soc/soc_caps.h"
#if SOC_I2S_SUPPORTED
#include "Arduino.h"
#include "esp_err.h"
#include "driver/i2s_std.h"
#if SOC_I2S_SUPPORTS_TDM
#include "driver/i2s_tdm.h"
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
#include "driver/i2s_pdm.h"
#endif
typedef esp_err_t (*i2s_channel_read_fn)(i2s_chan_handle_t handle, char *tmp_buf, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms);
typedef enum {
I2S_MODE_STD,
#if SOC_I2S_SUPPORTS_TDM
I2S_MODE_TDM,
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
I2S_MODE_PDM_TX,
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
I2S_MODE_PDM_RX,
#endif
I2S_MODE_MAX
} i2s_mode_t;
typedef enum {
I2S_RX_TRANSFORM_NONE,
I2S_RX_TRANSFORM_32_TO_16,
I2S_RX_TRANSFORM_16_STEREO_TO_MONO,
I2S_RX_TRANSFORM_MAX
} i2s_rx_transform_t;
class I2SClass : public Stream {
public:
I2SClass();
~I2SClass();
//STD + TDM mode
void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din = -1, int8_t mclk = -1);
void setInverted(bool bclk, bool ws, bool mclk = false);
//PDM TX + PDM RX mode
#if SOC_I2S_SUPPORTS_PDM_TX
void setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1 = -1);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
void setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1 = -1, int8_t din2 = -1, int8_t din3 = -1);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
void setInvertedPdm(bool clk);
#endif
bool begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask = -1);
bool configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask = -1);
bool configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform = I2S_RX_TRANSFORM_NONE);
bool end();
size_t readBytes(char *buffer, size_t size);
size_t write(const uint8_t *buffer, size_t size);
i2s_chan_handle_t txChan();
uint32_t txSampleRate();
i2s_data_bit_width_t txDataWidth();
i2s_slot_mode_t txSlotMode();
i2s_chan_handle_t rxChan();
uint32_t rxSampleRate();
i2s_data_bit_width_t rxDataWidth();
i2s_slot_mode_t rxSlotMode();
int lastError();
int available();
int peek();
int read();
size_t write(uint8_t d);
// Record short PCM WAV to memory with current RX settings. Returns buffer that must be freed by the user.
uint8_t *recordWAV(size_t rec_seconds, size_t *out_size);
// Play short PCM WAV from memory
void playWAV(uint8_t *data, size_t len);
#if ARDUINO_HAS_MP3_DECODER
// Play short MP3 from memory
bool playMP3(uint8_t *src, size_t src_len);
#endif
private:
esp_err_t last_error;
i2s_mode_t _mode;
i2s_chan_handle_t tx_chan;
uint32_t tx_sample_rate;
i2s_data_bit_width_t tx_data_bit_width;
i2s_slot_mode_t tx_slot_mode;
i2s_channel_read_fn rx_fn;
i2s_rx_transform_t rx_transform;
char *rx_transform_buf;
size_t rx_transform_buf_len;
i2s_chan_handle_t rx_chan;
uint32_t rx_sample_rate;
i2s_data_bit_width_t rx_data_bit_width;
i2s_slot_mode_t rx_slot_mode;
//STD and TDM mode
int8_t _mclk, _bclk, _ws, _dout, _din;
bool _mclk_inv, _bclk_inv, _ws_inv;
//PDM mode
#if SOC_I2S_SUPPORTS_PDM_RX
int8_t _rx_clk, _rx_din0, _rx_din1, _rx_din2, _rx_din3; //TODO: soc_caps.h 1/4
bool _rx_clk_inv;
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
int8_t _tx_clk, _tx_dout0, _tx_dout1;
bool _tx_clk_inv;
#endif
bool allocTranformRX(size_t buf_len);
bool transformRX(i2s_rx_transform_t transform);
static bool i2sDetachBus(void *bus_pointer);
bool initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask);
#if SOC_I2S_SUPPORTS_TDM
bool initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
bool initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
bool initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
};
#endif /* SOC_I2S_SUPPORTED */
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include <stdint.h>
/**
* @brief Header structure for WAV file with only one data chunk
*
* @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
*
* @note Assignment to variables in this struct directly is only possible for little endian architectures
* (including Xtensa & RISC-V)
*/
typedef struct {
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */
char chunk_format[4]; /*!< Contains the letters "WAVE" */
} __attribute__((packed)) wav_descriptor_chunk_t; /*!< Canonical WAVE format starts with the RIFF header */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< PCM = 16, This is the size of the rest of the Subchunk which follows this number */
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< 8000, 44100, etc. */
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
} __attribute__((packed)) pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< ALAW/MULAW = 18, This is the size of the rest of the Subchunk which follows this number */
uint16_t audio_format; /*!< ALAW = 6, MULAW = 7, values other than 1 indicate some form of compression */
uint16_t num_of_channels; /*!< ALAW/MULAW = 1, Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< ALAW/MULAW = 8000, 8000, 44100, etc. */
uint32_t byte_rate; /*!< ALAW/MULAW = 8000, ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ALAW/MULAW = 1, ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< ALAW/MULAW = 8, 8 bits = 8, 16 bits = 16, etc. */
uint16_t ext_size; /*!< ALAW/MULAW = 0, Size of the extension (0 or 22) */
} __attribute__((packed)) non_pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "data" */
uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
} __attribute__((packed)) wav_data_chunk_t; /*!< The "data" subchunk contains the size of the data and the actual sound */
typedef struct {
wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
} __attribute__((packed)) pcm_wav_header_t;
typedef struct {
wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
non_pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
} __attribute__((packed)) non_pcm_wav_header_t;
#define WAVE_FORMAT_PCM 1 // PCM
#define WAVE_FORMAT_IEEE_FLOAT 3 // IEEE float
#define WAVE_FORMAT_ALAW 6 // 8-bit ITU-T G.711 A-law
#define WAVE_FORMAT_MULAW 7 // 8-bit ITU-T G.711 µ-law
#define PCM_WAV_HEADER_SIZE 44
#define NON_PCM_WAV_HEADER_SIZE 46
/**
* @brief Default header for PCM format WAV files
*
*/
#define PCM_WAV_HEADER_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) \
{ \
.descriptor_chunk = \
{.chunk_id = {'R', 'I', 'F', 'F'}, .chunk_size = (wav_sample_size) + sizeof(pcm_wav_header_t) - 8, .chunk_format = {'W', 'A', 'V', 'E'}}, \
.fmt_chunk = \
{.subchunk_id = {'f', 'm', 't', ' '}, \
.subchunk_size = 16, /* 16 for PCM */ \
.audio_format = WAVE_FORMAT_PCM, /* 1 for PCM */ \
.num_of_channels = (uint16_t)(wav_channel_num), \
.sample_rate = (wav_sample_rate), \
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \
.block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \
.bits_per_sample = (uint16_t)(wav_sample_bits)}, \
.data_chunk = { \
.subchunk_id = {'d', 'a', 't', 'a'}, \
.subchunk_size = (wav_sample_size) \
} \
}