3.3.7
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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) \
|
||||
} \
|
||||
}
|
||||
Reference in New Issue
Block a user