├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── main ├── CMakeLists.txt ├── es8388_registers.h └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | sdkconfig* 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(esp32-lyrat-passthrough) 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Florian Euchner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-LyraT Audio Passthrough 2 | ### Short Description 3 | This [esp-idf](https://github.com/espressif/esp-idf)-based application for the [ESP32-LyraT](https://www.espressif.com/en/products/hardware/esp32-lyrat) reads stereo samples from the board's analog "AUX IN" input jack and outputs them through the "PHONE JACK" analog output again. It does so without all the overhead that [esp-adf](https://github.com/espressif/esp-adf) brings at the cost of not being as portable as [esp-adf's pipeline_passthrough](https://github.com/espressif/esp-adf/tree/master/examples/audio_processing/pipeline_passthru). 4 | 5 | By default, this application uses a word length of 16 bits (bits per channel) and a sample rate of 44100kHz. It first configures the ES8388 audio codec chip on the ESP32-LyraT board through I²C and then transfers digital audio samples bidirectionally over I²S. 6 | 7 | This project can be used as a starting point for implementing **digital filters** such as FIR / IIR / biquad filters and other digital signal processing (DSP) applications on the ESP32 plattform. The ESP32's Tensilica Xtensa processor architecture is well-know for its DSP capabilities. For instance, FIR filters can be efficiently implemented on the ESP32 thanks to support for special 16-bit multiply-accumulate instructions with optional parallel loading (**MAC16** option for Tensilica Xtensa LX6).. See [esp-dsp](https://github.com/espressif/esp-dsp) and the Xtensa Instruction Set Architecture (ISA) Reference Manual for more information. 8 | 9 | ### Build and Flash 10 | Make sure to have a recent [esp-idf](https://github.com/espressif/esp-idf) version with support for cmake-based projects installed. Then compile and flash this project as usual using the following steps: 11 | 12 | * `idf.py all` to compile everything 13 | * Put your ESP32-LyraT into download mode: Press and hold `Boot`, press and release `RST`, release `Boot` 14 | * `idf.py -p /dev/ttyUSBX flash`to flash everything 15 | * Reset your ESP32-LyraT by pressing `RST` 16 | 17 | ### License 18 | This project is licensed under the MIT license. See `LICENSE` for details. 19 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.c" INCLUDE_DIRS "") 2 | -------------------------------------------------------------------------------- /main/es8388_registers.h: -------------------------------------------------------------------------------- 1 | #ifndef _ES8388_REGISTERS_H 2 | #define _ES8388_REGISTERS_H 3 | 4 | /* ES8388 register */ 5 | #define ES8388_CONTROL1 0x00 6 | #define ES8388_CONTROL2 0x01 7 | #define ES8388_CHIPPOWER 0x02 8 | #define ES8388_ADCPOWER 0x03 9 | #define ES8388_DACPOWER 0x04 10 | #define ES8388_CHIPLOPOW1 0x05 11 | #define ES8388_CHIPLOPOW2 0x06 12 | #define ES8388_ANAVOLMANAG 0x07 13 | #define ES8388_MASTERMODE 0x08 14 | 15 | /* ADC */ 16 | #define ES8388_ADCCONTROL1 0x09 17 | #define ES8388_ADCCONTROL2 0x0a 18 | #define ES8388_ADCCONTROL3 0x0b 19 | #define ES8388_ADCCONTROL4 0x0c 20 | #define ES8388_ADCCONTROL5 0x0d 21 | #define ES8388_ADCCONTROL6 0x0e 22 | #define ES8388_ADCCONTROL7 0x0f 23 | #define ES8388_ADCCONTROL8 0x10 24 | #define ES8388_ADCCONTROL9 0x11 25 | #define ES8388_ADCCONTROL10 0x12 26 | #define ES8388_ADCCONTROL11 0x13 27 | #define ES8388_ADCCONTROL12 0x14 28 | #define ES8388_ADCCONTROL13 0x15 29 | #define ES8388_ADCCONTROL14 0x16 30 | 31 | /* DAC */ 32 | #define ES8388_DACCONTROL1 0x17 33 | #define ES8388_DACCONTROL2 0x18 34 | #define ES8388_DACCONTROL3 0x19 35 | #define ES8388_DACCONTROL4 0x1a 36 | #define ES8388_DACCONTROL5 0x1b 37 | #define ES8388_DACCONTROL6 0x1c 38 | #define ES8388_DACCONTROL7 0x1d 39 | #define ES8388_DACCONTROL8 0x1e 40 | #define ES8388_DACCONTROL9 0x1f 41 | #define ES8388_DACCONTROL10 0x20 42 | #define ES8388_DACCONTROL11 0x21 43 | #define ES8388_DACCONTROL12 0x22 44 | #define ES8388_DACCONTROL13 0x23 45 | #define ES8388_DACCONTROL14 0x24 46 | #define ES8388_DACCONTROL15 0x25 47 | #define ES8388_DACCONTROL16 0x26 48 | #define ES8388_DACCONTROL17 0x27 49 | #define ES8388_DACCONTROL18 0x28 50 | #define ES8388_DACCONTROL19 0x29 51 | #define ES8388_DACCONTROL20 0x2a 52 | #define ES8388_DACCONTROL21 0x2b 53 | #define ES8388_DACCONTROL22 0x2c 54 | #define ES8388_DACCONTROL23 0x2d 55 | #define ES8388_DACCONTROL24 0x2e 56 | #define ES8388_DACCONTROL25 0x2f 57 | #define ES8388_DACCONTROL26 0x30 58 | #define ES8388_DACCONTROL27 0x31 59 | #define ES8388_DACCONTROL28 0x32 60 | #define ES8388_DACCONTROL29 0x33 61 | #define ES8388_DACCONTROL30 0x34 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "es8388_registers.h" 6 | 7 | /* 8 | * Basic I2S and I2C Configuration 9 | */ 10 | #define I2S_NUM I2S_NUM_0 11 | #define I2S_READLEN 50 * 4 12 | 13 | #define I2C_NUM I2C_NUM_0 14 | #define ES8388_ADDR 0x20 15 | 16 | /* 17 | * ES8388 Configuration Code 18 | * Configure ES8388 audio codec over I2C for AUX IN input and headphone jack output 19 | */ 20 | static esp_err_t es_write_reg(uint8_t slave_add, uint8_t reg_add, uint8_t data) 21 | { 22 | esp_err_t res = ESP_OK; 23 | 24 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 25 | res |= i2c_master_start(cmd); 26 | res |= i2c_master_write_byte(cmd, slave_add, 1 /*ACK_CHECK_EN*/); 27 | res |= i2c_master_write_byte(cmd, reg_add, 1 /*ACK_CHECK_EN*/); 28 | res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); 29 | res |= i2c_master_stop(cmd); 30 | res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS); 31 | i2c_cmd_link_delete(cmd); 32 | 33 | return res; 34 | } 35 | 36 | static esp_err_t es8388_init() 37 | { 38 | esp_err_t res = ESP_OK; 39 | 40 | i2c_config_t i2c_config = { 41 | .mode = I2C_MODE_MASTER, 42 | .sda_io_num = GPIO_NUM_18, 43 | .sda_pullup_en = true, 44 | .scl_io_num = GPIO_NUM_23, 45 | .scl_pullup_en = true, 46 | .master.clk_speed = 100000 47 | }; 48 | 49 | res |= i2c_param_config(I2C_NUM, &i2c_config); 50 | res |= i2c_driver_install(I2C_NUM, i2c_config.mode, 0, 0, 0); 51 | 52 | /* mute DAC during setup, power up all systems, slave mode */ 53 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL3, 0x04); 54 | res |= es_write_reg(ES8388_ADDR, ES8388_CONTROL2, 0x50); 55 | res |= es_write_reg(ES8388_ADDR, ES8388_CHIPPOWER, 0x00); 56 | res |= es_write_reg(ES8388_ADDR, ES8388_MASTERMODE, 0x00); 57 | 58 | /* power up DAC and enable only LOUT1 / ROUT1, ADC sample rate = DAC sample rate */ 59 | res |= es_write_reg(ES8388_ADDR, ES8388_DACPOWER, 0x30); 60 | res |= es_write_reg(ES8388_ADDR, ES8388_CONTROL1, 0x12); 61 | 62 | /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ 63 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL1, 0x18); 64 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL2, 0x02); 65 | 66 | /* DAC to output route mixer configuration */ 67 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL16, 0x00); 68 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL17, 0x90); 69 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL20, 0x90); 70 | 71 | /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ 72 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL21, 0x80); 73 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL23, 0x00); 74 | 75 | /* DAC volume control: 0dB (maximum, unattenuated) */ 76 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL5, 0x00); 77 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL4, 0x00); 78 | 79 | /* power down ADC while configuring; volume: +9dB for both channels */ 80 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCPOWER, 0xff); 81 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL1, 0x33); 82 | 83 | /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ 84 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL2, 0x50); 85 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL3, 0x00); 86 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL4, 0x0e); 87 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL5, 0x02); 88 | 89 | /* set ADC volume */ 90 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL8, 0x20); 91 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCCONTROL9, 0x20); 92 | 93 | /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ 94 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL24, 0x1e); 95 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL25, 0x1e); 96 | 97 | /* power up and enable DAC; power up ADC (no MIC bias) */ 98 | res |= es_write_reg(ES8388_ADDR, ES8388_DACPOWER, 0x3c); 99 | res |= es_write_reg(ES8388_ADDR, ES8388_DACCONTROL3, 0x00); 100 | res |= es_write_reg(ES8388_ADDR, ES8388_ADCPOWER, 0x09); 101 | 102 | return res; 103 | } 104 | 105 | /* 106 | * Digital Filtering Code 107 | * You can implement your own digital filters (e.g. FIR / IIR / biquad / ...) here. 108 | * Please mind that you need to use seperate storage for left / right channels for stereo filtering. 109 | */ 110 | static inline int16_t dummyfilter(int16_t x) 111 | { 112 | return x; 113 | } 114 | 115 | /* 116 | * Main 117 | */ 118 | void app_main(void) 119 | { 120 | printf("[filter-dsp] Initializing audio codec via I2C...\r\n"); 121 | 122 | if (es8388_init() != ESP_OK) { 123 | printf("[filter-dsp] Audio codec initialization failed!\r\n"); 124 | } else { 125 | printf("[filter-dsp] Audio codec initialization OK\r\n"); 126 | } 127 | 128 | /*******************/ 129 | 130 | printf("[filter-dsp] Initializing input I2S...\r\n"); 131 | 132 | i2s_config_t i2s_read_config = { 133 | .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX, 134 | .sample_rate = 44100, 135 | .bits_per_sample = 16, 136 | .communication_format = I2S_COMM_FORMAT_I2S, 137 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 138 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, 139 | .dma_buf_count = 3, 140 | .dma_buf_len = I2S_READLEN, 141 | .use_apll = 1, 142 | .tx_desc_auto_clear = 1, 143 | .fixed_mclk = 0 144 | }; 145 | 146 | i2s_pin_config_t i2s_read_pin_config = { 147 | .bck_io_num = GPIO_NUM_5, 148 | .ws_io_num = GPIO_NUM_25, 149 | .data_out_num = GPIO_NUM_26, 150 | .data_in_num = GPIO_NUM_35 151 | }; 152 | 153 | i2s_driver_install(I2S_NUM, &i2s_read_config, 0, NULL); 154 | i2s_set_pin(I2S_NUM, &i2s_read_pin_config); 155 | 156 | /*******************/ 157 | 158 | printf("[filter-dsp] Initializing MCLK output...\r\n"); 159 | 160 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); 161 | WRITE_PERI_REG(PIN_CTRL, 0xFFF0); 162 | 163 | /*******************/ 164 | 165 | printf("[filter-dsp] Enabling Passthrough mode...\r\n"); 166 | 167 | size_t i2s_bytes_read = 0; 168 | size_t i2s_bytes_written = 0; 169 | 170 | int16_t i2s_buffer_read[I2S_READLEN / sizeof(int16_t)]; 171 | int16_t i2s_buffer_write[I2S_READLEN / sizeof(int16_t)]; 172 | 173 | /* continuously read data over I2S, pass it through the filtering function and write it back */ 174 | while (true) { 175 | i2s_bytes_read = I2S_READLEN; 176 | i2s_read(I2S_NUM, i2s_buffer_read, I2S_READLEN, &i2s_bytes_read, 100); 177 | 178 | /* left channel filter */ 179 | for (uint32_t i = 0; i < i2s_bytes_read / 2; i += 2) 180 | i2s_buffer_write[i] = dummyfilter(i2s_buffer_read[i]); 181 | 182 | /* right channel filter */ 183 | for (uint32_t i = 1; i < i2s_bytes_read / 2; i += 2) 184 | i2s_buffer_write[i] = dummyfilter(i2s_buffer_read[i]); 185 | 186 | i2s_write(I2S_NUM, i2s_buffer_write, i2s_bytes_read, &i2s_bytes_written, 100); 187 | } 188 | } 189 | 190 | --------------------------------------------------------------------------------