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.

PCM3060: full-duplex, DAC works, ADC not

Part Number: PCM3060

hello, i tried to submit this months ago, when i registered my TI account, but i never got the activation email

today i tried one last time, and the activation email arrived immediately, so here i am

i've made a board with an ARM Cortex-M4F-based MCU and the PCM3060 audio codec

i need full-duplex, input (2 channels) and output (2 channels)

i also want the audio to be DC-coupled

the MCU is running at 3.3V, the PCM3060 i've tried my best to set it up as the datasheet recommends (5V and 3.3V)

the MCU generates the I2S signals (including clocks) and drives the audio codec, there is nothing else on the I2S

the MCU also controls the audio codec over SPI, there are no other SPI devices

i tried to get the audio output working first, since that was supposed to be easier and once that worked i could use it to test if the audio input is working by feeding the input data as output data in software

the audio output worked

the audio input, i could never get it to work - the CODEC_SDO (DOUT from the codec) is flat out 0V

mainMCUaudio codec

PCB

i'm using a 24.576MHz crystal, and then all the I2S clocks are derived from that via the MCU's I2S peripheral

trying to set the codec up for full-duplex slave operation, at Fs=96kHz

  • here's the code:

    // pcm3060 header -------------------------------
    #include "as_bitfield.hpp"
    
    namespace pcm3060
    {
    template
    <
        typename T, // this should be an integer type
        size_t Offset,
        size_t Bits = 1,
        typename TV = T // this may be an enum
    >
    using BitField = asacmp::BitField<T, Offset, Bits, TV>;
    
    enum _fmt : uint8_t
    {
        // all formats are MSB-First, 2s complement (i think that means signed int)
        fmt_i2s_24bit = 0b00, // default, I2S 24bit (left-justified, one bit delay)
        fmt_lj_24bit  = 0b01, // left-justified 24bit
        fmt_rj_24bit  = 0b10, // right-justified 24bit
        fmt_rj_16bit  = 0b11, // right-justified 16bit
    };
    
    struct _write_command_t
    {
        uint8_t a; // address (top bit should always be zero)
        uint8_t r; // register value
    } __attribute__((packed));
    
    union _reg64
    {
        using _t = uint8_t;
        _t reg;
        //enum _comState : _t
        //BitField<_t, 0, 3, _comState> comState;
        BitField<_t, 0> SingleEnded; //!< 0=Differential or 1=SingleEnded, for DAC VoutX
        //BitField<_t, 1> _rsvd1; //!<
        //BitField<_t, 2> _rsvd2; //!<
        //BitField<_t, 3> _rsvd3; //!<
        BitField<_t, 4> DAPSV; //!< DAC Power-Save Control. Default: 1 (on)
        BitField<_t, 5> ADPSV; //!< ADC Power-Save Control. Default: 1 (on)
        BitField<_t, 6> SRST; //!< System Reset. Default: 1 (normal operation)
        BitField<_t, 7> MRST; //!< Mode Control Register Reset (ADC and DAC). Default: 1 (normal operation)
        // The MRST bit controls reset of the mode control registers to their default values. Pop noise may be generated. Returning the MRST bit to 1 is not required, as the MRST bit is automatically set to 1 after a mode control register reset.
        // Default value    : 0b1111xxx1
        // suggested value  : 0b1100xxx0
    };
    union _reg67
    {
        using _t = uint8_t;
        _t reg;
    
        enum _ms2 : _t
        {
            ms2_slave           = 0b000, // default
            ms2_master_768fs    = 0b001,
            ms2_master_512fs    = 0b010,
            ms2_master_384fs    = 0b011,
            ms2_master_256fs    = 0b100,
            ms2_master_192fs    = 0b101,
            ms2_master_128fs    = 0b110
            // reserved     = 0b111
        };
    
        BitField<_t, 0, 2, _fmt> FMT2; //!< Audio Interface Format for DAC, Default: 00
        //BitField<_t, 1> ; //!<
        //BitField<_t, 2> _rsvd2; //!<
        //BitField<_t, 3> _rsvd3; //!<
        BitField<_t, 4,3,_ms2> MS2; //!< Audio Interface Mode for DAC, Default 000
        //BitField<_t, 5> ; //!<
        //BitField<_t, 6> ; //!<
        BitField<_t, 7> CSEL2; //!< Clock Select for DAC operation. Default 0 (DAC uses Clocks2)
        // ... SCKI2, BCK2, LRCK2 are used for DAC operation if CSEL2 = 0 (default),
        // and SCKI1, BCK1, LRCK1 are used for DAC operation if CSEL2 = 1.
    
        // Default : 0b0000xx00
    };
    union _reg72
    {
        using _t = uint8_t;
        _t reg;
    
        enum _ms1 : _t
        {
            ms1_slave           = 0b000, // default
            ms1_master_768fs    = 0b001,
            ms1_master_512fs    = 0b010,
            ms1_master_384fs    = 0b011,
            ms1_master_256fs    = 0b100,
            // reserved         = 0b101
            // reserved         = 0b110
            // reserved         = 0b111
        };
    
        BitField<_t, 0, 2, _fmt> FMT1; //!< Audio Interface Format for ADC, Default: 00
        //BitField<_t, 1> ; //!<
        //BitField<_t, 2> _rsvd2; //!<
        //BitField<_t, 3> _rsvd3; //!<
        BitField<_t, 4,3,_ms1> MS1; //!< Audio Interface Mode for ADC, Default 000
        //BitField<_t, 5> ; //!<
        //BitField<_t, 6> ; //!<
        BitField<_t, 7> CSEL1; //!< Clock Select for ADC operation. Default 0 (ADC uses Clocks1)
        // ....SCKI1, BCK1, LRCK1 are used for ADC portion if CSEL1 = 0 (default),
        // and SCKI2, BCK2, LRCK2 are used for ADC portion if CSEL1 = 1.
    
        // Default : 0b0000xx00
    };
    
    union _reg68
    {
        using _t = uint8_t;
        _t reg;
    
        BitField<_t, 0> MUT21; //!< Soft Mute Control (DAC) for VoutL
        BitField<_t, 1> MUT22; //!< Soft Mute Control (DAC) for VoutR
        BitField<_t, 2> DREV2; //!< Output Phase Select (DAC) - inverts the phase
        //BitField<_t, 3> ; //!<
        //BitField<_t, 4> ; //!<
        //BitField<_t, 5> ; //!<
        BitField<_t, 6> OVER; //!< Oversampling Rate Control (DAC)
        //BitField<_t, 7> ; //!<
        // Default: 0bx0xxx000
    };
    
    union _reg69
    {
        using _t = uint8_t;
        _t reg;
    
        enum _dmf : _t
        {
            dmf_44k1    = 0b00,
            dmf_48k     = 0b01,
            dmf_32k     = 0b10
            // reserved = 0b11
        };
    
        BitField<_t, 0> AZRO; //!< Zero-Flag Function Select (DAC)
        BitField<_t, 1> ZREV; //!< Zero-Flag Polarity Select (DAC)
        //BitField<_t, 2> ; //!<
        //BitField<_t, 3> ; //!<
        BitField<_t, 4> DMC; //!< Digital De-Emphasis Function Control (DAC)
        BitField<_t, 5,2,_dmf> DMF; //!< Sample Freq Selection for the De-Emphasis Function (DAC)
        //BitField<_t, 6> DMF1; //!<
        BitField<_t, 7> FLT; //!< Digital Filter Rolloff Control (DAC) - 0=Sharp, 1=Slow
    };
    
    union _reg73
    {
        using _t = uint8_t;
        _t reg;
    
        BitField<_t, 0> MUT11; //!< Soft Mute Control (ADC) L
        BitField<_t, 1> MUT12; //!< Soft Mute Control (ADC) R
        BitField<_t, 2> DREV1; //!< Input Phase Select (ADC) - inverts the phase
        BitField<_t, 3> BYP; //!< HPF Bypass Control (ADC)
        BitField<_t, 4> ZCDD; //!< Zero-Cross Detection Disable for Digital Attenuation (ADC)
        //BitField<_t, 5> ; //!<
        //BitField<_t, 6> ; //!<
        //BitField<_t, 7> ; //!<
    };
    
    /*
        // default values:
        0b1100xxx0,
        255,
        255,
        0b0000xx00,
        0bx0xxx000,
        0b0000xx00,
        215,
        215,
        0b0000xx00,
        0bxxx00000
    */
    
    } // namespace pcm3060
    
    struct AS_PCM3060
    {
        // there are 10 registers
        // their addresses are from 64 to 73
        using _write_command_t = pcm3060::_write_command_t;
        static_assert(sizeof(_write_command_t) == sizeof(uint16_t));
    
        constexpr const uint8_t * get_reg(uint8_t addr)
        {
            _cmd.a = addr;
            switch (addr)
            {
                case 64: _cmd.r = r64.reg; break;
                case 65: _cmd.r = r65; break;
                case 66: _cmd.r = r66; break;
                case 67: _cmd.r = r67.reg; break;
                case 68: _cmd.r = r68.reg; break;
                case 69: _cmd.r = r69.reg; break;
                case 70: _cmd.r = r70; break;
                case 71: _cmd.r = r71; break;
                case 72: _cmd.r = r72.reg; break;
                case 73: _cmd.r = r73.reg; break;
                default: _cmd.a = 0; break; // ERR
            };
            return reinterpret_cast<const uint8_t*>(&_cmd);
        }
        _write_command_t _cmd;
    
        // registers:
        pcm3060::_reg64 r64;
        // attenuation from 255 (0dB, default) to 55 (-100dB) in 0.5dB steps, 54 to 0 is Muted
        uint8_t         r65; //!< Digital Attenuation Level Setting (DAC) for VoutL
        uint8_t         r66; //!< Digital Attenuation Level Setting (DAC) for VoutR
        pcm3060::_reg67 r67; //!< DAC stuff
        pcm3060::_reg68 r68; //!< DAC stuff
        pcm3060::_reg69 r69; //!< DAC stuff
        // attenuation from 255 (+20dB) to 215 (0dB, default), to 15 (-100dB), 14 to 0 is Muted
        uint8_t         r70; //!< Digital Attenuation Level Setting (ADC) L
        uint8_t         r71; //!< Digital Attenuation Level Setting (ADC) R
        pcm3060::_reg72 r72; //!< ADC stuff
        pcm3060::_reg73 r73; //!< ADC stuff
    
    };
    // pcm3060 header ------ EOF
    
    // #############################################################################
    
    // main.cpp -----------------
    
    AS_PCM3060 sac; // stereo audio codec
    
    // initialize PINs:
    AS_IPin i2s_sdi     ( PinB10, PULL_R::OFF,  PMUX_FUNC::J_I2S );
    AS_OPin codec_reset ( PinB11, 1 ); // initial value: 1 (logic-high)
    
    AS_OPin i2s_mck0    ( PinA08, 0, PMUX_FUNC::J_I2S );
    AS_OPin i2s_fs0     ( PinA09, 0, PMUX_FUNC::J_I2S );
    AS_OPin i2s_sck0    ( PinA10, 0, PMUX_FUNC::J_I2S );
    AS_OPin i2s_sdo     ( PinA11, 0, PMUX_FUNC::J_I2S );
    
    AS_OPin spi6_ss     ( PinC10, 1,            PMUX_FUNC::C_SERCOM ); // sercom6 pad2 PMUX_FUNC C
    AS_IPin spi6_miso   ( PinC11, PULL_R::UP,   PMUX_FUNC::C_SERCOM ); // sercom6 pad3 PMUX_FUNC C
    AS_OPin spi6_sck    ( PinC12, 1,            PMUX_FUNC::D_SERCOM ); // sercom6 pad1 PMUX_FUNC D
    AS_OPin spi6_mosi   ( PinC13, 1,            PMUX_FUNC::D_SERCOM ); // sercom6 pad0 PMUX_FUNC D
    
    int main(void)
    {
    	atmel_start_init();
    
    	uint32_t test1, test2;
    	cyccnt::init();
    	{
            cyccnt::BlockCounter bc(&test1);
            delay_ms(1);
    	}
    	delay_ms(100);
    	delay_ms(1);
    	uint32_t ufreq;
    
    	{
            constexpr auto r = calc_optimal_baud_async(96000000, 115200.f, 0.001f);
            ufreq = r.freq;
            dbg.init(r.baud, 0, 2); // tx on PB31, rx on PB00
            NVIC_DisableIRQ     (SERCOM5_0_IRQn);
            NVIC_ClearPendingIRQ(SERCOM5_0_IRQn);
            NVIC_DisableIRQ     (SERCOM5_2_IRQn);
            NVIC_ClearPendingIRQ(SERCOM5_2_IRQn);
            NVIC_EnableIRQ      (SERCOM5_0_IRQn);
            NVIC_EnableIRQ      (SERCOM5_2_IRQn);
    	}
    	delay_ms(100);
    	dbg.print("\n");
    
    	dbg.print("\n" "::: same54_audio :::\n"
               "Built: " __DATE__ " " __TIME__ "\n"
               "Initializing...\n");
    	dbg.print("  USART bitrate: "); dbg.printn(ufreq); dbg.print("\n");
    
    	print_pin_info(midi_rx, dbg);
    	print_pin_info(u5_rx, dbg);
    	print_pin_info(midi_tx, dbg);
    	print_pin_info(u5_tx, dbg);
    	dbg.print("--------\n");
    
    	delay_ms(20);
    	out0led.on();
    
    	codec_reset.on();
    	delay_ms(100);
    	codec_reset.off(); // ----
    
    	dbg.print("  audio codec reset\n");
    
    	delay_ms(100);
    	{
            // intended settings: 96kHz with 256*Fs MCK (system clock)
            // that gives 24.576MHz
            // i can't get the required SCK (bitclock) freq of 4.608MHz with the divisor
            // thus using 32bit samples, even tho the audio codec will be set to 24bit
            // so with 32bit samples, SCK = 6.144MHz
            // same54_audio board has I2S signals going only to "Clocks1", set DAC and ADC to use that one
            sac.r64.reg = 0;
            sac.r64.MRST = 0; // mode control register reset to default values
            sac.r64.SRST = 1;
            sac.r64.SingleEnded = 1;
            sac.r64.ADPSV = 1; // power save
            sac.r64.DAPSV = 1; // power save
    
            sac.r67.reg = 0;
            sac.r67.CSEL2 = 1; // DAC should use Clock1 signals
            sac.r67.FMT2 = pcm3060::fmt_i2s_24bit;
            sac.r67.MS2 = pcm3060::_reg67::ms2_slave;
    
            sac.r68.reg = 0;
            sac.r68.OVER = 1;
    
            // ------------- ADC -----------
            // ADC attenuation, 215 = 0dB
            sac.r70 = 215;
            sac.r71 = 215;
    
            sac.r72.reg = 0;
            sac.r72.CSEL1 = 0; // ADC should use Clock1 signals
            sac.r72.FMT1 = sac.r67.FMT2;
            sac.r72.MS1 = pcm3060::_reg72::ms1_slave;
    
            sac.r73.reg = 0;
            sac.r73.ZCDD = 1; // disable zero-crossing detection for the attenuator
            sac.r73.BYP = 1; // disable the HPF on the ADC
    
            struct io_descriptor *io;
            spi_m_sync_get_io_descriptor(&SPI_0, &io);
    
            spi_m_sync_enable(&SPI_0);
    
            delay_ms(20);
    
            // write registers 64, 67, 68, 70, 71, 72, 73 over SPI to the audio codec
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(64), 2);
    
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(67), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(68), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(70), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(71), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(72), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(73), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
    
            codec_reset.on(); // start
            delay_ms(100);
    
            sac.r64.MRST = 1;
            sac.r64.SRST = 0; // system reset, resyncs the ADC and DAC
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(64), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
            delay_ms(100);
            sac.r64.ADPSV = 0; // normal operation
            sac.r64.DAPSV = 0; // normal operation
            {
                spi6_ss.off(); delay_us(100);
                io_write(io, sac.get_reg(64), 2);
                delay_us(100);
                spi6_ss.on(); delay_us(100);
            }
    
            #if 1
            // print all codec registers:
            {
                dbg.print("    r64: "); dbg.printhex(&sac.r64.reg, 1); dbg.print("\n");
                dbg.print("    r65: "); dbg.printhex(&sac.r65,     1); dbg.print("\n");
                dbg.print("    r66: "); dbg.printhex(&sac.r66,     1); dbg.print("\n");
                dbg.print("    r67: "); dbg.printhex(&sac.r67.reg, 1); dbg.print("\n");
                dbg.print("    r68: "); dbg.printhex(&sac.r68.reg, 1); dbg.print("\n");
                dbg.print("    r69: "); dbg.printhex(&sac.r69.reg, 1); dbg.print("\n");
                dbg.print("    r70: "); dbg.printhex(&sac.r70,     1); dbg.print("\n");
                dbg.print("    r71: "); dbg.printhex(&sac.r71,     1); dbg.print("\n");
                dbg.print("    r72: "); dbg.printhex(&sac.r72.reg, 1); dbg.print("\n");
                dbg.print("    r73: "); dbg.printhex(&sac.r73.reg, 1); dbg.print("\n");
            }
            #endif // 0
    	}
    	dbg.print("  audio codec configured\n");
    	out0led.on();
    
    	dbg.print("  setting up MIDI\n");
        delay_ms(100);
    	{
            constexpr auto r = calc_optimal_baud_async(32000000, 31250.f, 0.000001f);
            midi.fifo_reset();
            midi.init(r.baud, 0, 1); // tx on PA04, rx on PA05
            NVIC_EnableIRQ(SERCOM0_0_IRQn);
            NVIC_EnableIRQ(SERCOM0_2_IRQn);
    
            midi_in.reset();
    
            dbg.print("  MIDI bitrate: ");
            dbg.printn(r.freq);
            dbg.print("\n");
    	}
        {
            cyccnt::BlockCounter bc(&test2);
            delay_ms(1);
        }
        dbg.print("- Tests: "); dbg.printn(test1); dbg.print(", "); dbg.printn(test2); dbg.print("\n");
    
        delay_ms(500);
        dbg.print("  setting up I2S\n");
        as_i2s.sw_reset();
    
        if (as_i2s.init()) { dbg.print("  - okay\n"); }
    
        dbg.print("Initialization done.\n");
        dbg.print("Main loop...\n");
        // main loop here
    }

    and here's the serial output from the MCU:

    ::: same54_audio :::
    Built: Sep  4 2021 15:45:00
    Initializing...
      USART bitrate: 115173
    ------
      audio codec reset
        r64: 81
        r65: 00
        r66: 00
        r67: 80
        r68: 40
        r69: 00
        r70: D7
        r71: D7
        r72: 80
        r73: 18
      audio codec configured
      setting up MIDI
      MIDI bitrate: 31250
    - Tests: 120083, 120738
      setting up I2S
      - okay
    Initialization done.
    Main loop...

    the PCB was partially populated because there was no firmware for it yet, so when i wrote the initial SPI / PCM3060 code, i populated the PCM3060 and its related components

    after that i spent quite some time where i was writing and adjusting the I2S+DMA code, untill eventually i started getting the first sounds from the analog output

    eventually i got smooth, glitch-free audio output on both audio channels

    that's when looked into the audio input, and it just wasn't working, no matter what i tried

    then i thought, perhaps, since the DOUT is the only output signal from the PCM (all other signals are inputs), maybe i might have had some periods of time of unintended bad electrical configuration, like, my MCU's SDI pin (PinB10) might have been set to output and then the only thing in the middle would be the 33R resistor which is too low to prevent damage (i'd think)

    so i thought maybe while my firmware was "under construction" i might have fried the PCM3060's DOUT pin... so i desoldered R85 (the 33R resistor) and the PCM3060, then very carefully soldered a brand new PCM3060, and powered the thing on

    i was still getting flat 0V on the DOUT side of the (now missing) resistor

    since then i've actually still not soldered R85 back in

  • Hi,

    Can you take the following signals with a logic analyzer/scope showing the relevant clock and data?

    -. /RST, SCK1, LRCK1, BCK1 and DOUT

    You have checked there is an input on VINR and VINL - correct?

    Have you tried looping your working DAC analog output back into the VINL/R and check the DOUT?

    Regards.

  • not at the moment, no

    i could oscilloscope it in the coming days

    during the period where i was working on the I2S+DMA code, i scoped the I2S signals, and they looked right (on a 50MHz scope)

    - given that analog output on both channels was working glitch-free (i am generating/synthesizing the audio in software on the MCU), that means that the codec SPI initialization can't be completely wrong, and at least the framesync/bitclock/systemclock and DIN (on the codec) are working

    - DOUT (from the codec) is giving flat 0V as i already said, yes there's audio on the codec's analog input, but i think even a "noisy silence" would turn into some "1" bits on the DOUT, wouldn't it?

    right now i actually don't even have the analog output working.. no idea what happened.. this board just sat here gathering dust for the past months

    I2S_SDO (from the MCU) keeps working since i can hear the digital data bits (and can distinguish my software oscillator's pitch changing) when probing thru a resistor with a soundcard, but nothing comes out of the audio codec's analog outputs

    i think i have a 3rd chip but this doesn't feel right

    i was also websearching about PCM3060 before this, and i found a few different people reporting inability to get audio input working (they only got audio output working, like me), and these did not report a success at the end, which seems discouraging

  • Hi,

    Ya, let's start with the loopback once you have the DAC working again, at least we know all the clocks are correct.

    DOUT will be forced to ZERO in reset state, so might be good to check that as well.

    Regards.