Skip to content

Allign with Mozzi updates #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions AudioConfigESP32.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef AUDIOCONFIGESP32_H
#define AUDIOCONFIGESP32_H

#include <driver/i2s.h>
#include <soc/adc_channel.h>

#if not IS_ESP32()
#error This header should be included for ESP32 architecture, only
#endif
Expand All @@ -9,37 +12,69 @@
#error HIFI mode is not available for this CPU architecture (but check ESP32_AUDIO_OUT_MODE, and PDM_RESOLUTION)
#endif

// Audio output options
// Audio IO options
#define INTERNAL_DAC 1 // output using internal DAC via I2S, output on pin 26
#define PT8211_DAC 2 // output using an external PT8211 DAC via I2S
#define PDM_VIA_I2S 3 // output PDM coded sample on the I2S data pin (pin 33, by default, configurable, below)
#define PDM_VIA_I2S 3 // output PDM coded sample on the I2S data pin (pin 33, by default, configurable, below)
#define I2S_DAC_AND_I2S_ADC 4 // output using an external DAC , input with exteran ADC - both via I2S

// Set output mode
// Set output & input mode
#define ESP32_AUDIO_OUT_MODE INTERNAL_DAC

// For external I2S output, only: I2S_PINS
#define ESP32_I2S_BCK_PIN 26
#define ESP32_I2S_WS_PIN 25
#define ESP32_I2S_DATA_PIN 33
#define ESP32_I2S_DATA_PIN_IN 32

#include <driver/i2s.h>

// Preferred I2S port: note that this might be taken into consideration if there is a choice
// The Internal ADC and DAC only work on port 0 and can not be used at the same time!
const i2s_port_t i2s_num = I2S_NUM_0;


// Select the data range of the ADC
//#define ADC_VALUE(in) (in) // no scaling
#define ADC_VALUE(in) (0.015625*(in + 32768)) // scale to 0 to 1023

// Optionally select a higher Sample Rate
//#define ESP32_AUDIO_RATE 8000


///--------------------------------------------------------------------------------------------------------
/// User config end. Do not modify below this line
///--------------------------------------------------------------------------------------------------------
#if !EXTERNAL_AUDIO_OUTPUT
# if (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
# define AUDIO_BITS 8
# define PDM_RESOLUTION 1
# define IS_INTERNAL_DAC
# elif (ESP32_AUDIO_OUT_MODE == PT8211_DAC || ESP32_AUDIO_OUT_MODE == I2S_DAC_AND_I2S_ADC)
# define AUDIO_BITS 16
# define PDM_RESOLUTION 1
# define IS_I2S_DAC
# elif (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
# define AUDIO_BITS 16
# define PDM_RESOLUTION 4
# define IS_PDM
# else
# error Invalid output mode configured in AudioConfigESP32.h
# endif

#if (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
#define AUDIO_BITS 8
#define PDM_RESOLUTION 1
#elif (ESP32_AUDIO_OUT_MODE == PT8211_DAC)
#define AUDIO_BITS 16
#define PDM_RESOLUTION 1
#elif (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
#define AUDIO_BITS 16
#define PDM_RESOLUTION 4
#else
#error Invalid output mode configured in AudioConfigESP32.h
#endif
# define AUDIO_BIAS ((uint16_t) 1<<(AUDIO_BITS-1))
# define BYPASS_MOZZI_OUTPUT_BUFFER true

// Use defined rate as new AUDIO_RATE_PLATFORM_DEFAULT
# ifdef ESP32_AUDIO_RATE
# undef AUDIO_RATE_PLATFORM_DEFAULT
# define AUDIO_RATE_PLATFORM_DEFAULT ESP32_AUDIO_RATE
# endif

#define AUDIO_BIAS ((uint16_t) 1<<(AUDIO_BITS-1))
#define BYPASS_MOZZI_OUTPUT_BUFFER true
// If ADC is available we support getAudioInput()
# if ESP32_AUDIO_OUT_MODE == I2S_DAC_AND_I2S_ADC
# define USE_CUSTOM_AUDIO_INPUT
int getAudioInput();
# endif
#endif

#endif // #ifndef AUDIOCONFIGESP_H
2 changes: 1 addition & 1 deletion MozziGuts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ int mozziAnalogRead(uint8_t pin) {
#endif
}

#if (USE_AUDIO_INPUT == true)
#if (USE_AUDIO_INPUT == true && !defined(USE_CUSTOM_AUDIO_INPUT))
static AudioOutputStorage_t audio_input; // holds the latest audio from input_buffer
AudioOutputStorage_t getAudioInput() { return audio_input; }
#endif
Expand Down
216 changes: 145 additions & 71 deletions MozziGuts_impl_ESP32.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
* Attribution-NonCommercial-ShareAlike 4.0 International License.
*
*/

#if !(IS_ESP32())
# error "Wrong implementation included for this platform"
#endif
#include "AudioConfigESP32.h"
#include <driver/i2s.h> // for I2S-based output modes
#include <driver/timer.h> // for EXTERNAL_AUDIO_OUTPUT

static const char module[]="Mozzi-ESP32";


// ////// BEGIN analog input code ////////
#define getADCReading() 0;

////// BEGIN analog input code ////////
//#define MOZZI_FAST_ANALOG_IMPLEMENTED // not yet
#define getADCReading() 0
#define channelNumToIndex(channel) channel
uint8_t adcPinToChannelNum(uint8_t pin) {
return pin;
Expand All @@ -36,32 +41,110 @@ void setupMozziADC(int8_t speed) {
////// END analog input code ////////


#if (EXTERNAL_AUDIO_OUTPUT) // for external audio output, set up a timer running a audio rate

void CACHED_FUNCTION_ATTR timer0_audio_output_isr(void *) {
TIMERG0.int_clr_timers.t0 = 1;
TIMERG0.hw_timer[0].config.alarm_en = 1;
defaultAudioOutput();
}


//// BEGIN AUDIO OUTPUT code ///////
#include <driver/i2s.h> // for I2S-based output modes
#include <driver/timer.h> // for EXTERNAL_AUDIO_OUTPUT
static void startAudio() {
static intr_handle_t s_timer_handle;
const int div = 2;
timer_config_t config = {
.alarm_en = (timer_alarm_t)true,
.counter_en = (timer_start_t)false,
.intr_type = (timer_intr_mode_t) TIMER_INTR_LEVEL,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = (timer_autoreload_t) true,
.divider = div // For max available precision: The APB_CLK clock signal is running at 80 MHz, i.e. 2/80 uS per tick
// Min acceptable value is 2
};
timer_init(TIMER_GROUP_0, TIMER_0, &config);
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 80000000UL / AUDIO_RATE / div);
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_isr_register(TIMER_GROUP_0, TIMER_0, &timer0_audio_output_isr, nullptr, 0, &s_timer_handle);
timer_start(TIMER_GROUP_0, TIMER_0);
}

void stopMozzi() {
timer_pause(TIMER_GROUP_0, TIMER_0);
timer_disable_intr(TIMER_GROUP_0, TIMER_0);
}

#else // I2S Output

#if (EXTERNAL_AUDIO_OUTPUT != true)
# include "AudioConfigESP32.h"
// On ESP32 we cannot test wether the DMA buffer has room. Instead, we have to use a one-sample mini buffer. In each iteration we
// _try_ to write that sample to the DMA buffer, and if successful, we can buffer the next sample. Somewhat cumbersome, but works.
// TODO: Should ESP32 gain an implemenation of i2s_available(), we should switch to using that, instead.
static bool _esp32_can_buffer_next = true;
# if (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
# if defined(IS_INTERNAL_DAC)
static uint16_t _esp32_prev_sample[2];
# define ESP_SAMPLE_SIZE (2*sizeof(uint16_t))
# elif (ESP32_AUDIO_OUT_MODE == PT8211_DAC)
# elif defined(IS_I2S_DAC)
static int16_t _esp32_prev_sample[2];
# define ESP_SAMPLE_SIZE (2*sizeof(int16_t))
# elif (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
# elif defined(IS_PDM)
static uint32_t _esp32_prev_sample[PDM_RESOLUTION];
# define ESP_SAMPLE_SIZE (PDM_RESOLUTION*sizeof(uint32_t))
# endif


/// Make sure that we provide a supported port
int getI2SPort(){
switch (ESP32_AUDIO_OUT_MODE){
case INTERNAL_DAC:
return 0;
case PDM_VIA_I2S:
return 0;
case PT8211_DAC:
return i2s_num;
case I2S_DAC_AND_I2S_ADC:
return i2s_num;
}
ESP_LOGE(module, "%s - %s", __func__, "ESP32_AUDIO_OUT_MODE invalid");
return -1;
}


/// Determine the I2S Output Mode (and input mode if on same port)
int getI2SMode(){
switch (ESP32_AUDIO_OUT_MODE){
case INTERNAL_DAC:
ESP_LOGD(module, "%s: %s", __func__, "I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN");
return I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN;
case PDM_VIA_I2S:
ESP_LOGD(module, "%s: %s", __func__, "I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_PDM");
return I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_PDM;
case PT8211_DAC:
ESP_LOGD(module, "%s: %s", __func__, "I2S_MODE_MASTER | I2S_MODE_TX");
return I2S_MODE_MASTER | I2S_MODE_TX;
case I2S_DAC_AND_I2S_ADC:
ESP_LOGD(module, "%s: %s", __func__, "I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX");
return I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX;
}
ESP_LOGE(module, "%s: %s", __func__, "ESP32_AUDIO_OUT_MODE invalid");
return -1;
}

/// @brief Reads a sample from the I2S buffer. I2S is stereo, so we combine the result to one single sample
int getAudioInput() {
static const int i2s_port = getI2SPort();
if (i2s_port==-1) return 0;

int16_t tmp[2];
size_t result;
esp_err_t rc = i2s_read((i2s_port_t)i2s_port, tmp, sizeof(tmp), &result, portMAX_DELAY);
return ADC_VALUE((tmp[0]+tmp[1]) / 2);
}


inline bool esp32_tryWriteSample() {
static i2s_port_t port = (i2s_port_t) getI2SPort();
size_t bytes_written;
i2s_write(i2s_num, &_esp32_prev_sample, ESP_SAMPLE_SIZE, &bytes_written, 0);
int write_len = sizeof(_esp32_prev_sample);
i2s_write(port, &_esp32_prev_sample, write_len, &bytes_written, 0);
//ESP_LOGD(module, "%s port:%d, len: %d, written: %d, value: %d", __func__, port, write_len, bytes_written, _esp32_prev_sample[0]);
return (bytes_written != 0);
}

Expand All @@ -72,15 +155,15 @@ inline bool canBufferAudioOutput() {
}

inline void audioOutput(const AudioOutput f) {
# if (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
# if defined(IS_INTERNAL_DAC)
_esp32_prev_sample[0] = (f.l() + AUDIO_BIAS) << 8;
# if (AUDIO_CHANNELS > 1)
_esp32_prev_sample[1] = (f.r() + AUDIO_BIAS) << 8;
# else
// For simplicity of code, even in mono, we're writing stereo samples
_esp32_prev_sample[1] = _esp32_prev_sample[0];
# endif
# elif (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
# elif defined(IS_PDM)
for (uint8_t i=0; i<PDM_RESOLUTION; ++i) {
_esp32_prev_sample[i] = pdmCode32(f.l() + AUDIO_BIAS);
}
Expand All @@ -91,72 +174,63 @@ inline void audioOutput(const AudioOutput f) {
# endif
_esp32_can_buffer_next = esp32_tryWriteSample();
}
#endif

#if (BYPASS_MOZZI_OUTPUT_BUFFER != true)
void CACHED_FUNCTION_ATTR timer0_audio_output_isr(void *) {
TIMERG0.int_clr_timers.t0 = 1;
TIMERG0.hw_timer[0].config.alarm_en = 1;
defaultAudioOutput();
}
#endif
static void startI2SAudio(i2s_port_t port, int mode){
ESP_LOGI(module, "%s: port=%d, mode=0x%x, rate=%d", __func__, port, mode, AUDIO_RATE * PDM_RESOLUTION);

static void startAudio() {
#if (BYPASS_MOZZI_OUTPUT_BUFFER != true) // for external audio output, set up a timer running a audio rate
static intr_handle_t s_timer_handle;
const int div = 2;
timer_config_t config = {
.alarm_en = (timer_alarm_t)true,
.counter_en = (timer_start_t)false,
.intr_type = (timer_intr_mode_t) TIMER_INTR_LEVEL,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = (timer_autoreload_t) true,
.divider = div // For max available precision: The APB_CLK clock signal is running at 80 MHz, i.e. 2/80 uS per tick
// Min acceptable value is 2
};
timer_init(TIMER_GROUP_0, TIMER_0, &config);
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 80000000UL / AUDIO_RATE / div);
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_isr_register(TIMER_GROUP_0, TIMER_0, &timer0_audio_output_isr, nullptr, 0, &s_timer_handle);
timer_start(TIMER_GROUP_0, TIMER_0);

#else
static const i2s_config_t i2s_config = {
# if (ESP32_AUDIO_OUT_MODE == PT8211_DAC) || (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
# elif (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
# endif
static i2s_config_t i2s_config = {
.mode = (i2s_mode_t)mode,
.sample_rate = AUDIO_RATE * PDM_RESOLUTION,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // only the top 8 bits will actually be used by the internal DAC, but using 8 bits straight away seems buggy
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // always use stereo output. mono seems to be buggy, and the overhead is insignifcant on the ESP32
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB), // this appears to be the correct setting for internal DAC and PT8211, but not for other dacs
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), // this appears to be the correct setting for internal DAC and PT8211, but not for other dacs
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8, // 8*128 bytes of buffer corresponds to 256 samples (2 channels, see above, 2 bytes per sample per channel)
.dma_buf_len = 128,
.use_apll = false
.use_apll = false,
};

i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
# if (ESP32_AUDIO_OUT_MODE == PT8211_DAC) || (ESP32_AUDIO_OUT_MODE == PDM_VIA_I2S)
static const i2s_pin_config_t pin_config = {
.bck_io_num = ESP32_I2S_BCK_PIN,
.ws_io_num = ESP32_I2S_WS_PIN,
.data_out_num = ESP32_I2S_DATA_PIN,
.data_in_num = -1
};
i2s_set_pin((i2s_port_t)i2s_num, &pin_config);
# elif (ESP32_AUDIO_OUT_MODE == INTERNAL_DAC)
i2s_set_pin((i2s_port_t)i2s_num, NULL);
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
# endif
i2s_zero_dma_buffer((i2s_port_t)i2s_num);
// install i2s driver
if (i2s_driver_install(port, &i2s_config, 0, NULL)!=ESP_OK){
ESP_LOGE(module, "%s - %s : %d", __func__, "i2s_driver_install", port);
}

#endif
// Internal DAC
if (ESP32_AUDIO_OUT_MODE==INTERNAL_DAC){
if (i2s_set_pin(port, NULL)!=ESP_OK) {
ESP_LOGE(module, "%s - %s", __func__, "i2s_set_pin");
}
if (i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN)!=ESP_OK) {
ESP_LOGE(module, "%s - %s", __func__, "i2s_set_dac_mode");
}
// Regular I2S
} else {
static const i2s_pin_config_t pin_config = {
.bck_io_num = ESP32_I2S_BCK_PIN,
.ws_io_num = ESP32_I2S_WS_PIN,
.data_out_num = ESP32_I2S_DATA_PIN,
.data_in_num = ESP32_I2S_DATA_PIN_IN,
};
if (i2s_set_pin(port, &pin_config)!=ESP_OK) {
ESP_LOGE(module, "%s - %s", __func__, "i2s_set_pin");
}
}
i2s_zero_dma_buffer(port);
}

/// Use I2S for the Output and Input
static void startAudio() {
// start output
i2s_port_t port = (i2s_port_t)getI2SPort();
startI2SAudio(port, getI2SMode());
}

void stopMozzi() {
// TODO: implement me
i2s_port_t port = (i2s_port_t)getI2SPort();
i2s_stop(port);
i2s_driver_uninstall(port);
}

#endif

//// END AUDIO OUTPUT code ///////
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ port by Dieter Vandoren and Thomas Friedrichsmeier
- Output (DATA) pin can be configured in AudioConfigESP32.h. Default 33. Note that the BCK and WS pins are also used in this mode.
- The PDM_RESOLUTION parameter can be used to reduce noise at the cost of more CPU power.
- Mono, only.
- I2S_DAC_AND_I2S_ADC: Output is sent via I2S in a format, Input is read from I2S in douplex mode.
- 16 bits resolution, mono or stereo. Remember to shift your audio accordingly.
- I2S ins can be configured in AudioConfigESP32.h. Default is BCK: 26, WS: 15, DATA: 33, DATA_IN: 32.
- "HIFI_MODE" is not currently implemented, but could conceivably be realized for the INTERNAL_DAC mode. Patches welcome.
- WIFI-activity not yet tested, but likely the same notes as for ESP8266 apply. Consider turning off WIFI.
- The implementation of audioTicks() may be slightly inaccurate on this platform.
Expand Down