This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TLV320AIC3101: ESP32 S3 With this Codec

Part Number: TLV320AIC3101

Tool/software:

Hi!

I have been developing a product for a while now and I have been recommended the Texas Instruments Codec TLV320AIC3101IRHBR. Unfortunately, I have never programmed a codec like this and am a little confused. I have ordered many boards with this and just need some help getting it going. This is connected to an ESP32S3 and I put the pins below. Basically, I need to know how to initialize this and send audio through I2S to it. The outputs of this are connected to a PAM8403 amp. This is only going to play mp3 at 44.1k in stereo. But I am not sure what things need to be initialized. I also have a potentiomter connected I want to use the ADC to control the volume. I have put the pins below. Can someone help me?

I2C_SDA = IO15
I2C_SCL = IO12
I2S_DOUT = IO17
I2S_DIN = IO18
I2S_WCLK = IO19
I2S_BCLK = IO20
I2S_MCLK = IO21
Potentiometer A0 = IO1

  • Hi,

    To set the registers to get your audio routing initialized, I would recommend using the EVM GUI software to generate I2C commands. You can download it from the EVM page, it is the software called "SLAC249": https://www.ti.com/tool/TLV320AIC3101EVM-K

    From the software's DAC preset for stereo playback to the line outs, here is the script that it gives:
    w 30 07 8A
    w 30 66 A0
    w 30 25 C0
    w 30 29 02
    w 30 2B 00
    w 30 52 80
    w 30 5C 80
    w 30 4B 80
    w 30 4E 80
    w 30 56 09
    w 30 5D 09
    w 30 4F 09

    You can further specify the clocks that you want to use with the GUI, which will generate the I2C you need in the Command Line Interface tab.

    As for the potentiometer into the ADC, I would recommend using an ADC on your ESP32 and then changing the volume of the DAC with I2C. You can change the digital volume of the DAC with page 0/register 43, specified in Table 49 in the datasheet.

    Let me know if you need more help or explanation!

    Best,
    Mir

  • Thank you for the response! I will check out the tool! I thought this codec also has an ADC? Can I use that for the volume knob?

  • Hi,

    Yes, the codec has an ADC, but it is meant for audio. It converts the analog input to I2S, which is a digital audio protocol. I thought it may be more difficult to code your ESP32 to interpret I2S in and then send I2C commands to change the volume than using the ADC pins on the ESP itself, but it would be possible if you wanted to. The audio ADC may be overkill for your volume knob application since it has a high bit depth and a specific input voltage range it is limited to. 

    Best,
    Mir

  • Thank you. I have gotten to this point. I am getting a i2s error I cant figure out. It initlizes the DAC and the speakers make a pop when it turns on but I get this error. 

    the only error i got. 20:08:14.340 -> Initializing...

    20:08:14.340 -> I2C initialized.

    20:08:14.958 -> Codec initialized.

    20:08:15.023 -> Connecting to Wi-Fi..

    20:08:16.047 -> Wi-Fi connected.

    20:08:16.410 -> MP3 stream opened.

    20:08:16.410 -> E (3077) i2s(legacy): i2s_set_pin(1862): ws_io_num invalid

    20:08:16.410 -> MP3 decoder started.

  • Here is my Arduino Code.

    #include <Wire.h>
    #include <WiFi.h>
    #include <AudioFileSourceHTTPStream.h>
    #include <AudioGeneratorMP3.h>
    // Include the base AudioOutputI2S class
    #include <AudioOutputI2S.h>
    #include "tlv320aic3101.h"

    // ----- Wi-Fi Credentials -----
    const char* ssid = "SSID"; // Replace with your Wi-Fi SSID
    const char* password = "PASS"; // Replace with your Wi-Fi Password

    // ----- MP3 Stream URL -----
    const char* mp3_url = "exampleURL;

    // ----- Codec Configuration -----
    #define CODEC_RESET_PIN 14 // GPIO14 connected to codec's RESET pin

    // ----- I2C and I2S Pin Definitions -----
    #define I2C_SDA_PIN 3
    #define I2C_SCL_PIN 8
    #define I2S_DOUT_PIN 9
    #define I2S_DIN_PIN 10
    #define I2S_WCLK_PIN 11
    #define I2S_BCLK_PIN 12
    #define I2S_MCLK_PIN 13

    // ----- Initialize Codec Instance -----
    tlv320aic3101 codec(CODEC_RESET_PIN, &Wire);

    // ----- I2S Pin Configuration -----
    i2s_pin_config_t pin_config = {
    .mck_io_num = I2S_MCLK_PIN,
    .bck_io_num = I2S_BCLK_PIN,
    .ws_io_num = I2S_WCLK_PIN,
    .data_out_num = I2S_DOUT_PIN,
    .data_in_num = I2S_DIN_PIN
    };

    // ----- Custom AudioOutputI2SEx Class -----
    class AudioOutputI2SEx : public AudioOutputI2S {
    public:
    AudioOutputI2SEx(int portNo = 0, int output_mode = 1, i2s_pin_config_t* pin_config = NULL)
    : AudioOutputI2S(false, portNo), _portNo(portNo)
    {
    _pin_config = pin_config;
    }

    virtual bool begin() override {
    // Call the base class begin
    if (!AudioOutputI2S::begin())
    return false;

    // Now set the I2S pins if pin_config is provided
    if (_pin_config != NULL) {
    i2s_set_pin((i2s_port_t)_portNo, _pin_config);
    }
    return true;
    }

    private:
    i2s_pin_config_t* _pin_config;
    int _portNo;
    };

    // ----- Initialize Audio Components -----
    AudioGeneratorMP3 mp3;
    AudioFileSourceHTTPStream file;
    AudioOutputI2SEx audioOutput(0, 1, &pin_config); // Use port 0, output mode 1, and our pin configuration

    void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("Initializing...");

    // ----- Initialize I2C -----
    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    Serial.println("I2C initialized.");

    // ----- Initialize Codec -----
    codec.begin(NULL); // Since we're not setting up I2S pins here
    Serial.println("Codec initialized.");

    // ----- Connect to Wi-Fi -----
    WiFi.begin(ssid, password);
    Serial.print("Connecting to Wi-Fi");
    while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    }
    Serial.println("\nWi-Fi connected.");

    // ----- Initialize MP3 Stream -----
    if (!file.open(mp3_url)) {
    Serial.println("Failed to open MP3 stream");
    while (1);
    }
    Serial.println("MP3 stream opened.");

    // ----- Initialize MP3 Decoder -----
    mp3.begin(&file, &audioOutput);
    Serial.println("MP3 decoder started.");
    }

    void loop() {
    if (mp3.isRunning()) {
    mp3.loop();
    } else {
    Serial.println("MP3 playback finished. Restarting...");
    delay(1000);
    // ----- Restart MP3 Stream -----
    file.close();
    if (!file.open(mp3_url)) {
    Serial.println("Failed to reopen MP3 stream");
    while (1);
    }
    mp3.begin(&file, &audioOutput);
    }
    }

    Here is my tlv320aic3101.cpp

    #include <sys/_stdint.h>
    #include "esp32-hal-log.h"
    #include "tlv320aic3101.h"

    // Constructor with three parameters
    tlv320aic3101::tlv320aic3101(uint8_t reset_pin, TwoWire* i2c_bus, i2s_port_t i2s_num)
    : reset_pin_(reset_pin), i2c_bus_(i2c_bus), i2s_num_(i2s_num) {
    active_page_ = -1;
    log_i("Codec initializing...");
    }

    // Destructor defined only once
    tlv320aic3101::~tlv320aic3101() {
    i2s_driver_uninstall(i2s_num_);
    }

    void tlv320aic3101::codec_reg_write(int reg, int val) {
    log_i("reg %d : %d", reg, val);
    write_register(reg, 0, val);
    delay(10);
    // uint8_t readval = read_register(reg, 0);
    // if (val != readval) {

    // delay(100);
    // log_e("err: sent %d read %d", val, readval );

    // }
    }


    void tlv320aic3101::codec_set_left_pga_gain(uint8_t gain) {
    if (gain > 127) gain = 127;
    codec_reg_write(15, gain);
    }

    void tlv320aic3101::codec_set_right_pga_gain(uint8_t gain) {
    if (gain > 127) gain = 127;
    codec_reg_write(16, gain);
    }

    void tlv320aic3101::codec_set_left_PGA_mix(uint8_t level) {
    if (level > 127) level = 127;
    level = 127 - level;
    codec_reg_write(46, level | 1 << 7); // 1 st bit enables routing pga to out
    //codec_reg_write(46, 0); // 1 st bit enables routing pga to out
    }

    void tlv320aic3101::codec_set_left_out_level(uint8_t level) {
    if (level > 127) level = 127;
    level = 127 - level;
    codec_reg_write(47, level | 1 << 7); // 1 st bit enables routing
    }

    void tlv320aic3101::codec_set_right_PGA_mix(uint8_t level) {
    if (level > 127) level = 127;
    level = 127 - level;
    codec_reg_write(63, level | 1 << 7); // 1 st bit enables routing pga to out
    //codec_reg_write(63, 0); // 1 st bit enables routing pga to out
    }

    void tlv320aic3101::codec_set_right_out_level(uint8_t level) {
    if (level > 127) level = 127;
    level = 127 - level;
    codec_reg_write(64, level | 1 << 7); // 1 st bit enables routing
    }


    void tlv320aic3101::begin(i2s_pin_config_t *i2s_pins) {
    log_i("Codec init");

    i2c_bus_->begin();

    pinMode(reset_pin_, OUTPUT);
    digitalWrite(reset_pin_, LOW);
    delay(100);
    digitalWrite(reset_pin_, HIGH);
    delay(100);

    codec_reg_write(1, 1);

    codec_reg_write(2, 0); // sample rate fsref/1
    codec_reg_write(3, 0b00010001);
    // codec_reg_write(4, 8<<2);
    // codec_reg_write(5, 0b00011110);
    // codec_reg_write(6, 0b0);
    // codec_reg_write(7, 0b01101010); // dual rate dac and adc, right data to right left to left
    codec_reg_write(7, 0b00001010); // 48khz
    //codec_reg_write(8, 0);
    codec_reg_write(9, 0b00000000); //

    //codec_reg_write(10, 0); // data offset
    //codec_reg_write(12, 0); // filter disabled
    codec_reg_write(14, 0b10000000); // high power outputs to AC coupled mode

    codec_set_left_pga_gain(127);
    codec_set_right_pga_gain(70);
    codec_reg_write(17, 0b00001111); //MIC2R/LINE2R is not connected to the left-ADC PGA.
    codec_reg_write(18, 0b11110000); // MIC2L/LINE2L is not connected to the right-ADC PGA.
    codec_reg_write(19, 0b11111100); // power up adc
    codec_reg_write(22, 0b11111111); // power up adc

    codec_reg_write(25, 0b10000000); // mic bias
    // AGC
    codec_reg_write(26, 0b10001101); // right chan compression
    codec_reg_write(27, 0b11000000); // MAX GAIN ALLOWED
    codec_reg_write(29, 0b10001101); // right chan compression
    codec_reg_write(30, 0b11000000); // MAX GAIN ALLOWED
    //codec_reg_write(31, 0b11000011);

    codec_reg_write(37, 0b11010000); //(37) Turn ON RIGHT and LEFT DACs, HPLCOM set as independent VCM output
    codec_reg_write(38, 0b00001100); //(38) HPRCOM set as independent VCM output, short circuit protection activated with curr limit
    codec_reg_write(40, 0b10000010); //(40) output common-mode voltage = 1.65 V - output soft stepping each fs
    codec_reg_write(41, 0b00000000); //(41) set DAC path, DAC_L2 to left high power, DAC_R2 to right high power, independent volume
    codec_reg_write(42, 0b00010100); //(42) Pop-reduction register, voltage divider
    codec_reg_write(43, 0); //(43) un-mute left DAC, gain 0dB
    codec_reg_write(44, 0); //(44) un-mute right DAC, gain 0dB

    codec_set_left_PGA_mix(0);
    codec_set_left_out_level(127);
    codec_set_right_PGA_mix(0);
    codec_set_right_out_level(127);

    // note for future upd: mixing left pga to right out and vice versa is also possible

    codec_reg_write(51, 0b00001101); //(51) unmute HPLOUT, 9dB, high impedance when powered down, HPLOUT fully powered
    codec_reg_write(65, 0b10011101); // unmute HPROUT, 9dB, high impedance when powered down, HPROUT fully powered

    codec_reg_write(101, 0b00000001); //(101) CLK source selection, CLKDIV_OUT
    codec_reg_write(102, 0b00000010); //(102) CLK source selection, MCLK


    // // Codec data-path, no dual rate, left DAC - left,
    // write_register(7, 0, 0b00001000);

    // // Audio serial data B, I2S mode, 16 bit data
    // write_register(8, 0, 0b00100000);

    // // CODEC filter, no high-pass, no dac effects, dac de-emph
    // write_register(12, 0, 0b00000101);

    // // Left PGA, unmuted
    // write_register(15, 0, 0b00000000);

    // // LINE1L ctrl, fully differential, powered on, soft stepping
    // write_register(19, 0, 0b10000100);

    // // Unselected inputs to CM
    // write_register(20, 0, 0b01111100);
    // write_register(23, 0, 0b01111100);

    // // DAC power, left channel power up, HPLCOM diff
    // write_register(37, 0, 0b10000000);

    // // HPR ctrl, short circuit protection enable
    // write_register(38, 0, 0b00000110);

    // // DAC switch, Left dac to line
    // write_register(41, 0, 0b01000000);

    // // Left DAC unmute, full volume
    // write_register(43, 0, 0b00000000);

    // // Left LO, unmute
    // write_register(86, 0, 0b00001011);

    // // clock, use CLKDIV
    // write_register(101, 0, 0b00000001);


    // i2s_config_t conf = {
    // .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX),
    // .sample_rate = 48000,
    // .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    // .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    // .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    // .dma_buf_count = 4,
    // .dma_buf_len = 512,
    // .use_apll = true,
    // .tx_desc_auto_clear = true,
    // .mclk_multiple = I2S_MCLK_MULTIPLE_256,
    // };

    // i2s_driver_install(i2s_num_, &conf, 0, NULL);
    // i2s_set_pin(i2s_num_, i2s_pins);

    // log_i("i2s driver started");
    }

    // void tlv320aic3101::write(float * buffer, size_t size){

    // if(size > AIC3101_BUFF_SIZE){
    // log_w("Too many samples, input %d, max %d", size, AIC3101_BUFF_SIZE);
    // size = AIC3101_BUFF_SIZE;
    // }

    // size_t bytes_written;

    // for (size_t i = 0; i < size; i++)
    // {
    // buff_[i] = (int16_t) (buffer[i] * INT16_MAX);
    // }

    // i2s_write(i2s_num_, buff_, size * sizeof(int16_t), &bytes_written, 1000);

    // if(bytes_written != size*sizeof(int16_t)){
    // log_w("I2S write mismatch");
    // }
    // }

    // void tlv320aic3101::read(float * buffer, size_t size){

    // if(size > AIC3101_BUFF_SIZE){
    // log_w("Too many samples, input %d, max %d", size, AIC3101_BUFF_SIZE);
    // size = AIC3101_BUFF_SIZE;
    // }

    // size_t bytes_read;

    // i2s_read(i2s_num_, buff_, size * sizeof(int16_t), &bytes_read, 1000);

    // if(bytes_read != size*sizeof(int16_t)){
    // log_w("I2S read mismatch");
    // }

    // for (size_t i = 0; i < size; i++)
    // {
    // buffer[i] = ((float) buff_[i]) * (1.0/INT16_MAX);
    // }

    // }

    // uint8_t tlv320aic3101::get_DAC_status(){

    // return read_register(94, 0);

    // }


    void tlv320aic3101::write_register(uint8_t reg, uint8_t page, uint8_t val) {
    // Set correct page
    if (page != active_page_) {
    i2c_bus_->beginTransmission(i2c_addr_);
    i2c_bus_->write(0);
    i2c_bus_->write(page);
    i2c_bus_->endTransmission();
    active_page_ = page;
    delay(100);
    }

    // write requested register
    i2c_bus_->beginTransmission(i2c_addr_);
    i2c_bus_->write(reg);
    i2c_bus_->write(val);
    i2c_bus_->endTransmission();
    }

    uint8_t tlv320aic3101::read_register(uint8_t reg, uint8_t page) {
    if (page != active_page_) {
    i2c_bus_->beginTransmission(i2c_addr_);
    i2c_bus_->write(0);
    i2c_bus_->write(page);
    i2c_bus_->endTransmission();
    active_page_ = page;
    }

    uint8_t result;

    i2c_bus_->beginTransmission(i2c_addr_);
    i2c_bus_->write(reg);
    i2c_bus_->endTransmission();

    i2c_bus_->requestFrom(i2c_addr_, 1);
    while (i2c_bus_->available()) {
    result = i2c_bus_->read();
    }

    return result;
    }
    // **Add the getter implementation below:**
    i2s_port_t tlv320aic3101::get_i2s_num(){
    return i2s_num_;
    }

    And here is my tlv320aic3101.h

    #ifndef TLV320AIC3101_TLV320AIC3101_H_
    #define TLV320AIC3101_TLV320AIC3101_H_

    #include <Arduino.h>
    #include <Wire.h>
    #include <driver/i2s.h>

    #define AIC3101_BUFF_SIZE 512

    class tlv320aic3101
    {
    private:
    // Reset pin
    uint8_t reset_pin_;

    // I2C config bus
    TwoWire* i2c_bus_;
    uint8_t i2c_addr_ = 0x18;

    // I2S communication
    i2s_port_t i2s_num_;

    // Current active page (register 0)
    uint8_t active_page_;

    // Temp buffer
    int16_t buff_[AIC3101_BUFF_SIZE];

    // I2C read/write configuration registers
    void write_register(uint8_t reg, uint8_t page, uint8_t val);
    uint8_t read_register(uint8_t reg, uint8_t page);

    public:
    // Updated constructor with three parameters
    tlv320aic3101(uint8_t reset_pin, TwoWire* i2c_bus, i2s_port_t i2s_num = I2S_NUM_0);
    ~tlv320aic3101();

    void begin(i2s_pin_config_t* i2s_p);

    void write(float* buffer, size_t size);
    void read(float* buffer, size_t size);
    void playback_stop();
    void playback_start();
    void set_sample_rate();
    void set_bit_depth();
    void set_input_gain(uint8_t gain);
    void set_output_volume(uint8_t vol);
    void bypass_enable();
    void bypass_disable();
    uint8_t get_DAC_status();

    void codec_reg_write(int reg, int val);
    void codec_set_left_pga_gain(uint8_t gain);
    void codec_set_right_pga_gain(uint8_t gain);
    void codec_set_left_PGA_mix(uint8_t level);
    void codec_set_left_out_level(uint8_t level);
    void codec_set_right_PGA_mix(uint8_t level);
    void codec_set_right_out_level(uint8_t level);

    // Getter for I2S port
    i2s_port_t get_i2s_num();
    };

    #endif /* TLV320AIC3101_TLV320AIC3101_H_ */

  • Hi,

    It seems like with that error you are never running any of the I2C because you are not initializing the connection to the device. I'm not an expert with ESP32 or this Arduino code, but I wonder can you specify the port as 0 directly in the i2s_set_pin command? If the reference you are using is invalid, it seems that the code expects a specific pin that maybe your reference wasn't correct. Also, in the documentation for the ESP API for I2S (https://docs.espressif.com/projects/esp-idf/en/v3.3/api-reference/peripherals/i2s.html), it says the port could be 0 or 1? So maybe try to switch it to 1 and see if that works.

    Best,
    Mir

  • Okay thank you for the help. I have done a ton of arduino and DAC coding but have never had to setup a codec like this with all of the initial writes. I think I can get it working with this code.

    Are you able to tell me how I would use the writes in this code? I need 44.1k audio since it is an Mp3, I2S 16 BIT, stereo, and the DACS enabled. So It will just play the audio file through I2S to the dacs. Can you tell me what those writes are and how I would code them in?

    Here is my code.

    #include <Wire.h>
    #include <WiFi.h>
    #include "Audio.h"

    #define I2C_SDA 3
    #define I2C_SCL 8
    #define I2S_DOUT 9
    #define I2S_DIN 10
    #define I2S_WCLK 11
    #define I2S_BCLK 12
    #define I2S_MCLK 13
    #define CODEC_RESET_PIN 14 // Define reset pin

    // Wi-Fi credentials
    const char* ssid = "WIFI";
    const char* password = "SSID";

    const char* mp3_url = "EXAMPLEURL";

    // Pass the I2C instance to the codec
    tlv320aic3101 codec(CODEC_RESET_PIN, &Wire);
    Audio audio;

    void setup() {
    Serial.begin(115200);
    // Configure the reset pin
    pinMode(CODEC_RESET_PIN, OUTPUT);
    // Reset the codec
    digitalWrite(CODEC_RESET_PIN, LOW);
    delay(10); // Hold reset low for 10ms
    digitalWrite(CODEC_RESET_PIN, HIGH);
    delay(10); // Wait for the codec to stabilize after reset

    // Initialize I2C for the codec
    Wire.begin(I2C_SDA, I2C_SCL);

    // Connect to Wi-Fi
    WiFi.begin(ssid, password);
    Serial.print("Connecting to Wi-Fi");
    while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    }
    Serial.println("\nConnected to Wi-Fi!");

    // Set up I2S configuration for the codec
    i2s_pin_config_t i2s_pins = {
    .bck_io_num = I2S_BCLK,
    .ws_io_num = I2S_WCLK,
    .data_out_num = I2S_DOUT,
    .data_in_num = I2S_DIN
    };

    codec.begin(&i2s_pins); // Initialize codec
    audio.setPinout(I2S_BCLK, I2S_WCLK, I2S_DOUT); // Initialize I2S for audio
    }

    void loop() {
    if (!audio.isRunning()) {
    audio.connecttohost(mp3_url); // Connect to the MP3 stream
    }
    audio.loop(); // Keep the audio streaming
    }
  • Hi Jack,

    I think with the Wire Arduino library, it is Wire.write(command), so for writing to a specific register, you would do Wire.write(register in hex) and then Wire.write(value to set in hex).

    You would want to implement the script I sent earlier. For the first line as an example, w 30 07 8A:

    Wire.write(0x07)

    Wire.write(0x8a)

    You may need to play around with the formatting to make sure this works but I believe that is the protocol to use. Let me know what you find out and if you have any more questions.

    Best,
    Mir