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.

CC2564MODA: A2DP data format and decoding timing

Part Number: CC2564MODA
Other Parts Discussed in Thread: CC2564, CC3200AUDBOOST

Hi,

My customer is implementing A2DP with below HW+SW.
HW: DK-TM4C129X + CC2564MODA
SW: www.ti.com/.../CC256XM4BTBLESW

Customer changed the A2DP demo so that single SSI (SPI master mode) is used to send the data.
(LRCLK is generated from BCLK by on board circuit)
Customer found SSI suddenly stops sending data and debugging it now.
It seems CPU stops writing new data to SSI TX FIFO and it results in SSI stop.

Customer checked data decoding timing and found not enough audio data are received.
The system requires to send 48kHz L-ch and R-ch data to an external DAC, but it seems Tiva can receive less data from CC2564.
Please see attached waveforms.
A2DP SSI stop.xlsx
Is my understanding correct? Sending 48kHz Stereo data is not possible ?
Is there any way to increase data receive speed?

Thanks and regards,
Koichiro Tashiro

  • Hi Tashiro,

    Is the I2S capture between the tiva and your external DAC?

    Are you using assisted A2DP or unassisted A2DP? 

    With unassisted A2DP the tiva will need to decode the audio stream, instead of relying on the hardware of the CC2564. Depending on the other tasks you have running on the system, this can cause bottlenecks that result in audio not being decoded in real time, thus causing the SSI interface to not get data in time. I would suggest examining the running threads if you are using an RTOS, to ensure that the decode task is prioritized correctly.

    Regards,

    Michael

  • Hi Michael,

    Michael Reymond said:

    Is the I2S capture between the tiva and your external DAC?



    Yes. The I2S capture is between Tiva and an external DAC.


    Michael Reymond said:

    Are you using assisted A2DP or unassisted A2DP? 



    Unassisted A2DP is used.

    Michael Reymond said:

    With unassisted A2DP the tiva will need to decode the audio stream, instead of relying on the hardware of the CC2564. Depending on the other tasks you have running on the system, this can cause bottlenecks that result in audio not being decoded in real time, thus causing the SSI interface to not get data in time. I would suggest examining the running threads if you are using an RTOS, to ensure that the decode task is prioritized correctly.



    The code is non-RTOS version.
    In the code no additional tasks are running other than;
    - PlaybackThread() => called when new audio data are received from CC2564
    - SoundIntHandler() => called to send new data to SSI
    - TimerIntHandler() => timer tick
    - ConsoleIntHandler() => UART between PC (not used during audio playback)
    - Hcitr_UARTIntHandler() => UART between Tiva and CC2564

    And it seems Tiva does not do anything until new audio data are received from CC2564.
    (as soon as ProcessAudioData finishes, PlaybackThread is called)
    So I do not think other tasks are blocking audio. Just audio data receive timing from CC2564 seems too slow.

    The audio data parameters are below (according to console output):
    Buffer High Limit: 4608
    Buffer Low Limit : 3072
    Frame Length     : 119
    Bit Pool         : 53
    Bit Rate         : 327993
    Buffer Length   : 128
    Frames/GAVD     : 16

    I thinks this should be supported by all A2DP SNK.

    Thanks and regards,
    Koichiro Tashiro

     

  • Hi Tashiro,

    Thanks for the info. I will use my DK-TM4C129X + CC2564MODA and see if I can reproduce the same result.

    Regards,

    Michael

  • Hi Michael,

    Do you have an external DAC which can be connected to Tiva?
    If not, you cannot run the A2DP demo as is.
    In such case, please replace below two sources in A2DP demo so that all DAC and SSI related codes are bypassed.
    (no music is played, the code simply repeats receiving data and decoding them.)

    4073.dac.c
    //*****************************************************************************
    //
    // dac.c - Functions supporting the TLV320AIC3262 audio codec.
    //
    // Copyright (c) 2012 Texas Instruments Incorporated.  All rights reserved.
    // Software License Agreement
    //
    // Texas Instruments (TI) is supplying this software for use solely and
    // exclusively on TI's microcontroller products. The software is owned by
    // TI and/or its suppliers, and is protected under applicable copyright
    // laws. You may not combine this software with "viral" open-source
    // software in order to form a larger program.
    //
    // THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
    // NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
    // NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    // A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
    // CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
    // DAMAGES, FOR ANY REASON WHATSOEVER.
    //
    //
    //*****************************************************************************
    
    #include <stdbool.h>
    #include <stdint.h>
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "inc/hw_ints.h"
    #include "inc/hw_i2c.h"
    #include "inc/hw_ssi.h"
    #include "driverlib/ssi.h"
    #include "driverlib/debug.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/rom_map.h"
    #include "driverlib/systick.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/udma.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/timer.h"
    #include "drivers/dac.h"
    
    #include "BTPSKRNL.h"
    
    //*****************************************************************************
    //
    //! \addtogroup dac_api
    //! @{
    //
    //*****************************************************************************
    
    //*****************************************************************************
    //
    // The system clock frequency in Hz. it must be supplied by the application.
    //
    //*****************************************************************************
    extern uint32_t g_ui32SysClock;
    
    //*****************************************************************************
    //
    // The I2C and reset pins that are used by this application.
    //
    //*****************************************************************************
    #define DAC_I2C_PERIPH                  (SYSCTL_PERIPH_I2C7)
    #define DAC_I2C_GPIO_PERIPH             (SYSCTL_PERIPH_GPIOA)
    #define DAC_I2CSCL_GPIO                 (GPIO_PA4_I2C7SCL)
    #define DAC_I2CSDA_GPIO                 (GPIO_PA5_I2C7SDA)
    #define DAC_I2C_BASE                    (GPIO_PORTA_BASE)
    #define DAC_I2CSDA_PIN                  (GPIO_PIN_5)
    #define DAC_I2CSCL_PIN                  (GPIO_PIN_4)
    #define DAC_I2C_MASTER_BASE             (I2C7_BASE)
    
    #define DAC_RESET_GPIO_PERIPH           (SYSCTL_PERIPH_GPIOJ)
    #define DAC_RESET_GPIO_PORT             (GPIO_PORTJ_BASE)
    #define DAC_RESET_PIN                   (GPIO_PIN_7)
    
    //*****************************************************************************
    //
    // The amount of time (in milliseconds) allowed to elapse before an I2C
    // transaction is considered timed out.
    //
    //*****************************************************************************
    #define I2C_TRANSMISSION_TIMEOUT        (2000)
    
    //*****************************************************************************
    //
    //! Writes a register in the TLV320AIC3262 codec.
    //!
    //! \param ucRegister is the codec's register address to be written.
    //! \param ucData is the data to be written to the codec's register address.
    //!
    //! This function will write to the address passed in ucRegister with the
    //! value passed in to ulData. The data in ucData is 8 bits and the value in
    //! ucRegister is interpreted as 7 bits (LSBs).
    //!
    //! \return True on success or false on error.
    //
    //*****************************************************************************
    bool
    DACWriteRegister(unsigned char ucRegister, unsigned char ucData)
    {
        unsigned long TickCount;
    
        //
        // Add a delay before writing. This delay is around 50us.
        //
        SysCtlDelay((50 * (g_ui32SysClock / 1000000) + 1 ) / 3);
    
        //
        // Set the slave address and "WRITE".
        //
        ROM_I2CMasterSlaveAddrSet(DAC_I2C_MASTER_BASE, TI_TLV320AIC3262_ADDR,
                              false);
    
        //
        // Place the register address to be sent the codec.
        //
        ROM_I2CMasterDataPut(DAC_I2C_MASTER_BASE, ucRegister);
    
        //
        // Send the register address to the codec.
        //
        ROM_I2CMasterControl(DAC_I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);
    
        //
        // Check for errors.
        //
        if(ROM_I2CMasterErr(DAC_I2C_MASTER_BASE) != I2C_MASTER_ERR_NONE)
        {
            ROM_I2CMasterIntClear(DAC_I2C_MASTER_BASE);
            return(false);
        }
    
        //
        // Wait for transmission completion.
        //
        TickCount = BTPS_GetTickCount();
        while(ROM_I2CMasterBusy(DAC_I2C_MASTER_BASE))
        {
            if((BTPS_GetTickCount() - TickCount) > I2C_TRANSMISSION_TIMEOUT)
                return(false);
        }
    
        //
        // Place the register data to be sent the codec.
        //
        ROM_I2CMasterDataPut(DAC_I2C_MASTER_BASE, ucData);
    
        //
        // Indicate the end of the transfer.
        //
        ROM_I2CMasterControl(DAC_I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
    
        //
        // Check for errors.
        //
        if(ROM_I2CMasterErr(DAC_I2C_MASTER_BASE) != I2C_MASTER_ERR_NONE)
        {
            I2CMasterIntClear(DAC_I2C_MASTER_BASE);
            return(false);
        }
    
        //
        // Wait for transmission completion.
        //
        TickCount = BTPS_GetTickCount();
        while(ROM_I2CMasterBusy(DAC_I2C_MASTER_BASE))
        {
            if((BTPS_GetTickCount() - TickCount) > I2C_TRANSMISSION_TIMEOUT)
                return(false);
        }
    
        return(true);
    }
    
    //*****************************************************************************
    //
    //! Configures the TLV320AIC3262 codec.
    //!
    //! \param uiFrequency is the sampling frequency of the audio to be played.
    //!  e.g FSS or WCLK
    //!
    //! This function configures the TLV320AIC3262 Codec according to the sampling
    //! frequency and enables audio output at the headset. DACInit() must be called
    //! prior to this function.
    //!
    //! \return Returns \b true on success or \b false on failure.
    //
    //*****************************************************************************
    bool
    DACConfigure(unsigned int uiFrequency)
    {
        bool bRetcode = true;
    /*
        //
        // Page Select Register
        // Bx, Px, R0
        // b0-7 = 0x00, Switch to Page 0
        //
        bRetcode = bRetcode && DACWriteRegister(TI_PAGE_SELECT_R, 0x00);
    
        //
        // Book Selection Register
        // Bx, P0, R127
        // b0-7 = 0x00, Switch to Book 0
        //
        bRetcode = bRetcode && DACWriteRegister(TI_BOOK_SELECT_R, 0x00);
    
        //
        // Software Reset Register - Apply a Software Reset to the Codec.
        // B0, P0, R1
        // b0 = 1, SW Reset - Internal Registers
        //
        bRetcode = bRetcode && DACWriteRegister(TI_SOFTWARE_RESET_R, 0x01);
    
        //*************************************************************************
        //
        // Clock Configuration
        //
        //*************************************************************************
    
        //
        // Clock Control Register 1, Clock Input Multiplexers
        // B0, P0, R4
        // b4-7 = 0011, Select PLL_CLK for DAC_CLKIN clock input
        // b3-0 = 0011, Select PLL_CLK for ADC_CLKIN clock input
        //
        bRetcode = bRetcode && DACWriteRegister(0x04, 0x33);
    
        //
        // Clock Control Register 2, PLL Input Multiplexer
        // B0, P0, R5
        // b6 = 1, Select high PLL clock range - TBC
        // b5-2 = 0000, Select MCLK1 for PLL_CLKIN clock input
        //
        bRetcode = bRetcode && DACWriteRegister(0x05, 0x40);
    
        //
        // Clock Control Register 4, PLL J Value
        // B0, P0, R7
        // b5-0 = 000100, Set PLL J value to 4
        // PLL_CLK = (PLL_CLKIN x R x J.D)/P
        //
        bRetcode = bRetcode && DACWriteRegister(0x07, 0x04);
    
        //
        // Configure the codec to sample the audio (set SSI clk) at the frequency
        // specified by uiFrequency. Supported sampling frequencies are:
        // 44.1KHz and 48KHz
        //
    
        //
        // 44.1KHz
        //
        if ( uiFrequency == 44100 )
        {
            //
            // Clock Control Register 5, PLL D Value (MSB)
            // B0, P0, R8
            // b5-0 = 0x09, Set PLL D value to 2336
            // Clock Control Register 6, PLL D Value (LSB)
            // B0, P0, R9
            // b7-0 = 0x20, Set PLL D value to 2336
            // PLL_CLK = (PLL_CLKIN x R x J.D)/P, for PRB_R1-R3:
            // 84.672MHz = (20MHz  x 1 x 4.2336)/1 --> For Fs = 44.1KHz
            //
            bRetcode = bRetcode && DACWriteRegister(0x08, 0x09);
            bRetcode = bRetcode && DACWriteRegister(0x09, 0x20);
        }
        //
        // 48KHz
        //
        else if ( uiFrequency == 48000 )
        {
            //
            // Clock Control Register 5, PLL D Value (MSB)
            // B0, P0, R8
            // b5-0 = 0x17, Set PLL D value to 6080
            // Clock Control Register 6, PLL D Value (LSB)
            // B0, P0, R9
            // b7-0 = 0xC0, Set PLL D value to 6080
            // PLL_CLK = (PLL_CLKIN x R x J.D)/P, for PRB_R1-R3:
            // 92.160MHz = (20MHz  x 1 x 4.6080)/1 --> For Fs = 48KHz
            //
            bRetcode = bRetcode && DACWriteRegister(0x08, 0x17);
            bRetcode = bRetcode && DACWriteRegister(0x09, 0xC0);
        }
        //
        // Error - Unsupported sampling rate.
        //
        else
        {
            return(false);
        }
    
        //
        // Clock Control Register 3, PLL P and R Values
        // B0, P0, R6
        // b7 = 1, Power up PLL
        // b6-4 = 001, Set PLL P value to 1
        // b3-0 = 0001, Set PLL R value to 1
        // PLL_CLK = (PLL_CLKIN x R x J.D)/P
        //
        bRetcode = bRetcode && DACWriteRegister(0x06, 0x91);
    
        //
        // Clock Control Register 8, NDAC Divider Values
        // B0, P0, R11
        // b7 = 1, Power up NDAC
        // b5-0 = 000 0011, Set NDAC value to 3
        // DAC_Fs = CODEC_CLK_IN / (NDAC.MDAC.AOSR)
        // 44.1KHz = 84.672MHz / (3.5.128)
        // 48KHz = 92.160 MHz / (3.5.128)
        //
        bRetcode = bRetcode && DACWriteRegister(0x0B, 0x83);
    
        //
        // Clock Control Register 9, MDAC Divider Values
        // B0, P0, R12
        // b7 = 1, Power up MDAC
        // b5-0 = 000 0101, Set NDAC value to 5
        // DAC_Fs = CODEC_CLK_IN / (NDAC.MDAC.DAC_OSR)
        // 44.1KHz = 84.672MHz / (3.5.128)
        // 48KHz = 92.160 MHz / (3.5.128)
        //
        bRetcode = bRetcode && DACWriteRegister(0x0C, 0x85);
    
        //
        // DAC OSR Control Register 1, MSB Value
        // B0, P0, R13
        // b1-0 = 00, Set DAC_OSR value to 128
        // DAC OSR Control Register 2, LSB Value
        // B0, P0, R14
        // b7-0 = 0x80, Set DAC_OSR value to 128
        // DAC_Fs = CODEC_CLK_IN / (NDAC.MDAC.DAC_OSR)
        // 44.1KHz = 84.672MHz / (3.5.128)
        // 48KHz = 92.160 MHz / (3.5.128)
        //
        bRetcode = bRetcode && DACWriteRegister(0x0D, 0x00);
        bRetcode = bRetcode && DACWriteRegister(0x0E, 0x80);
    
        //*************************************************************************
        //
        // Initialize the Codec
        //
        //*************************************************************************
    
        //
        // Page Select Register
        // Bx, Px, R0
        // b0-7 = 0x01, Switch to Page 1
        //
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x01);
    
        //
        // Power Configuration Register
        // B0, P1, R1
        // b3 = 0, Disable weak connection of AVDD with DVDD
        // b2 = 0, All the external analog supplies are available
        //
        bRetcode = bRetcode && DACWriteRegister(0x01, 0x00);
    
        //
        // Reference Powerup Delay Register
        // B0, P1, R122
        // b3 = 0, At the fine charge time
        // b1-0 = 01, VREF Fast Charge, Fine Charge Time = 30ms
        //
        bRetcode = bRetcode && DACWriteRegister(0x7A, 0x01);
    
        //*************************************************************************
        //
        // Audio Serial Interface #1 Configuration
        //
        //*************************************************************************
    
        //
        // Page Select Register
        // Bx, Px, R0
        // b0-7 = 0x04, Switch to Page 4
        //
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x04);
    
        //
        // Audio Serial Interface 1, Audio Bus Format Control Register
        // B0, P4, R1
        // b7-5 = 000, Set I2S Format (011, Set LJF Format)
        // b4-3 = 00, 16 bits data length
        // b0 = 0, DOUT1 will not be high impedance while ASI1 is active
        // See SLAU309 - Figure 2-65 and/or Figure 2-62
        //
        bRetcode = bRetcode && DACWriteRegister(0x01, 0x00);
    
        //
        // Audio Serial Interface 1, WCLK and BCLK Control Register
        // B0, P4, R10
        // b7-5 = 001, WCLK1 pin is output from Audio Serial Interface 1
        // b4-2 = 001, BCLK1 pin is output from Audio Serial Interface 1
        // b1 = 1, Bit Clock Inverted (0, Default Bit Clock polarity)
        // b0 = 0, Primary BCLK and Primary WCLK buffers are powered down when the
        //         codec is powered down or Audio Serial Interface 1 is inactive.
        // See SLAU309 - Figure 2-65 and/or Figure 2-62
        //
        bRetcode = bRetcode && DACWriteRegister(0x0A, 0x26);
    
        //
        // Audio Serial Interface 1, Bit Clock N Divider Input Control Register
        // B0, P4, R11
        // b1-0 = 00, ASI1_BDIV_CLKIN = DAC_CLK
        // DAC_CLK = CODEC_CLK_IN / NDAC
        // 28.224 MHz = 84.672MHz / 3
        // 30.72 MHz = 92.160 MHz / 3
        //
        bRetcode = bRetcode && DACWriteRegister(0x0B, 0x00);
    
        //
        // Audio Serial Interface 1, Bit Clock N Divider Register
        // B0, P4, R12
        // b7 = 1, Power Up BCLK N Divider
        // b6-0 = 0x0A, BCLK N Divider = 20
        //
        bRetcode = bRetcode && DACWriteRegister(0x0C, 0x94); // BCLK_DIV ON and Div = 20
    
        //
        // Audio Serial Interface 1, BCLK and WCLK Output Register
        // B0, P4, R14
        // b6-4 = 000, BCLK_OUT = BCLK_DIV_OUT
        // b2-0 = 000, WCLK_OUT = DAC_Fs
        // BCLK_DIV_OUT = ASI1_BDIV_CLKIN / BCLK N Divider
        // 2.8224 MHz = 28.224 MHz / 10 -> 44.1KHz
        // 3.072 = 30.72 MHz / 10 -> 48KHz
        // DAC_Fs = 44.1 KHz
        // DAC_Fs = 48 KHz
        //
        bRetcode = bRetcode && DACWriteRegister(0x0E, 0x00);
    
        //*************************************************************************
        //
        // Signal Processing
        //
        //*************************************************************************
    
        // Select page 0
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x00);
        // Set DAC PRB mode to PRB_P1
        bRetcode = bRetcode && DACWriteRegister(0x3C, 0x01);
    
        //*************************************************************************
        //
        // DAC Configuration
        //
        //*************************************************************************
    
        // Select page 1
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x01);
        // Set DAC PTM mode to PTM_P3
        bRetcode = bRetcode && DACWriteRegister(0x03, 0x00);
        bRetcode = bRetcode && DACWriteRegister(0x04, 0x00);
        // Route LDAC to HPL and route RDAC to HPR, don't power up HP driver yet
        bRetcode = bRetcode && DACWriteRegister(0x1B, 0x30);
        // Select page 0
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x00);
        // Power up LDAC and RDAC
        bRetcode = bRetcode && DACWriteRegister(0x3F, 0xC0);
        // Select page 0
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x00);
        // Power up LDAC and RDAC
        bRetcode = bRetcode && DACWriteRegister(0x3F, 0xC0);
        // Select page 1
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x01);
        // Set Headphone in Ground-centered Mode, HPL is still muted
        bRetcode = bRetcode && DACWriteRegister(0x1F, 0xB9);
        // HPR has the same gain as HPL, HPR is still muted
        bRetcode = bRetcode && DACWriteRegister(0x20, 0xB9);
        // Charge pump runs of Oscillator clock with divider = 4
        bRetcode = bRetcode && DACWriteRegister(0x21, 0x28);
        // Headphone output offset correction (default setting)
        bRetcode = bRetcode && DACWriteRegister(0x22, 0x3E);
        // Charge pump to power up on the headphone power up in Ground-centered Mode
        bRetcode = bRetcode && DACWriteRegister(0x23, 0x30);
        // Unmute HPL driver, set gain = 0 dB
        bRetcode = bRetcode && DACWriteRegister(0x1F, 0x80);
        // Unmute HPR driver, set gain = 0 dB
        bRetcode = bRetcode && DACWriteRegister(0x20, 0x80);
        // Select page 0
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x00);
        // Unmute LDAC and RDAC digital volume
        bRetcode = bRetcode && DACWriteRegister(0x40, 0x40);
        // Select page 1
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x01);
        // Reduce HP power to 25 percent
        bRetcode = bRetcode && DACWriteRegister(0x09, 0x70);
        // Power up HPL/HPR
        bRetcode = bRetcode && DACWriteRegister(0x1B, 0x33);
    
        // Select page 1
        bRetcode = bRetcode && DACWriteRegister(0x00, 0x01);
        // Increase HP power to 100 percent
        bRetcode = bRetcode && DACWriteRegister(0x09, 0x10);
    */
        return(bRetcode);
    }
    
    //*****************************************************************************
    //
    //! Initializes the TLV320AIC3262 Codec.
    //!
    //! This function initializes the I2C interface and the TLV320AIC3262 Codec. It
    //! must be called prior to any other API in the DAC module.
    //!
    //! \return Returns \b true on success or \b false on failure.
    //
    //*****************************************************************************
    bool
    DACInit(void)
    {
        bool bRetcode = true;
    
        //
        // Enable the GPIO port containing the I2C pins and set the SDA pin as a
        // GPIO input for now and engage a weak pull-down.  If the daughter board
        // is present, the pull-up on the board should easily overwhelm
        // the pull-down and we should read the line state as high.
        //
        //SysCtlPeripheralReset(DAC_I2C_PERIPH);
        ROM_SysCtlPeripheralEnable(DAC_I2C_PERIPH);
    
        //
        // Enable the I2C peripheral.
        //
        //SysCtlPeripheralReset(DAC_I2C_GPIO_PERIPH);
        ROM_SysCtlPeripheralEnable(DAC_I2C_GPIO_PERIPH);
    
        //
        // Configure the pin mux.
        //
        ROM_GPIOPinConfigure(DAC_I2CSCL_GPIO);
        ROM_GPIOPinConfigure(DAC_I2CSDA_GPIO);
    
        //
        // Configure the I2C SCL and SDA pins for I2C operation.
        //
        ROM_GPIOPinTypeI2CSCL(DAC_I2C_BASE, DAC_I2CSCL_PIN);
        ROM_GPIOPinTypeI2C(DAC_I2C_BASE, DAC_I2CSDA_PIN);
    
        //
        // Initialize the I2C master.
        //
        ROM_I2CMasterInitExpClk(DAC_I2C_MASTER_BASE, g_ui32SysClock, true);
    
        //
        // Enable the I2C peripheral.
        //
        ROM_SysCtlPeripheralEnable(DAC_RESET_GPIO_PERIPH);
    
        //
        // Configure the PD0 as a GPIO output.
        //
        ROM_GPIOPinTypeGPIOOutput(DAC_RESET_GPIO_PORT, DAC_RESET_PIN);
    
        //
        // Reset the Codec.
        //
        ROM_GPIOPinWrite(DAC_RESET_GPIO_PORT , DAC_RESET_PIN, 0);
        ROM_GPIOPinWrite(DAC_RESET_GPIO_PORT , DAC_RESET_PIN, DAC_RESET_PIN);
    
        //
        // Delay a while to ensure that the codec is ready. If we read too quickly,
        // the result is unpredictable. This delay is around 10mS.
        //
        SysCtlDelay((10000 * (g_ui32SysClock / 1000000) + 1 ) / 3);
    
        //
        // Select Codec's Page 0.  Check the return code on this call since
        // we use it to indicate whether or not the Codec is present.  If the
        // register write fails, we assume the Codec EVM is not present and
        // return false.
        //
        bRetcode = bRetcode && DACWriteRegister(TI_PAGE_SELECT_R, 0x00);
    
        return(bRetcode);
    }
    
    //*****************************************************************************
    //
    // Close the Doxygen group.
    //! @}
    //
    //*****************************************************************************
    

    2335.sound_ssi.c
    //*****************************************************************************
    //
    // sound.c - Functions supporting playback of .wav audio files on LM4F232.
    //
    // Copyright (c) 2012 Texas Instruments Incorporated.  All rights reserved.
    // Software License Agreement
    //
    // Texas Instruments (TI) is supplying this software for use solely and
    // exclusively on TI's microcontroller products. The software is owned by
    // TI and/or its suppliers, and is protected under applicable copyright
    // laws. You may not combine this software with "viral" open-source
    // software in order to form a larger program.
    //
    // THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
    // NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
    // NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    // A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
    // CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
    // DAMAGES, FOR ANY REASON WHATSOEVER.
    //
    // This is part of revision 8049 of the Stellaris Firmware Development Package.
    //
    //*****************************************************************************
    
    //*****************************************************************************
    //
    //! \addtogroup sound_api
    //! @{
    //
    //*****************************************************************************
    
    #include <stdbool.h>
    #include <stdint.h>
    #include <string.h>
    #include "inc/hw_ssi.h"
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_sysctl.h"
    #include "inc/hw_types.h"
    #include "driverlib/debug.h"
    #include "driverlib/gpio.h"
    #include "driverlib/ssi.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/udma.h"
    #include "driverlib/timer.h"
    #include "utils/ustdlib.h"
    #include "dac.h"
    #include "sound_ssi.h"
    
    //*****************************************************************************
    //
    // The system clock frequency in Hz. it must be supplied by the application.
    //
    //*****************************************************************************
    extern uint32_t g_ui32SysClock;
    
    //*****************************************************************************
    //
    // Configure the Timer5B as a 16-bit PWM with a duty cycle of 50%,
    // 20MHz output, to drive the Codecs's MCLK.
    //
    //*****************************************************************************
    void
    setupCCP()
    {
        unsigned long ulTimerMatchValue;
    
        //
        // Enable the Timer5 peripheral for use.
        //
        ROM_SysCtlPeripheralEnable(I2S_MASTERCLK_PERIPH);
    
        //
        // Timer CCP5 uses GPIO port S, pin 7.
        // GPIO port S needs to be enabled so these pins can be used.
        //
        ROM_SysCtlPeripheralEnable(I2S_MASTERCLK_GPIO);
    
        //
        // Configure the GPIO pin muxing for the Timer/CCP function.
        //
        ROM_GPIOPinConfigure(I2S_MASTERCLK_PORT);
    
        //
        // Configure the CCP settings for the CCP pin. This function also gives
        // control of these pins to the SSI hardware.
        //
        ROM_GPIOPinTypeTimer(I2S_MASTERCLK_BASE, I2S_MASTERCLK_PIN);
    
        //
        // Configure Timer5B as a 16-bit periodic timer.
        //
        ROM_TimerConfigure(I2S_MASTERCLK_TMR_B, (TIMER_CFG_SPLIT_PAIR |
                        TIMER_CFG_A_PERIODIC | TIMER_CFG_B_PWM));
    
        //
        // Set the Timer5B Frequency to a divisor to generate 20MHz.
        // Set the Timer5B duty cycle to 50%.
        // TM4C129x DS - Figure 16-7
        //
    
        ulTimerMatchValue = (g_ui32SysClock / 20000000) - 1;
    
        ROM_TimerLoadSet(I2S_MASTERCLK_TMR_B, I2S_MASTERCLK_TMR, ulTimerMatchValue);
        ROM_TimerMatchSet(I2S_MASTERCLK_TMR_B, I2S_MASTERCLK_TMR, 1);
    
        ROM_TimerEnable(I2S_MASTERCLK_TMR_B, I2S_MASTERCLK_TMR);
    }
    
    //*****************************************************************************
    //
    //! Initialize the sound driver.
    //!
    //! This function initializes the hardware components of the LM4F board,
    //! necessary for audio playback.
    //!
    //! \return None.
    //
    //*****************************************************************************
    bool
    SoundInit(void)
    {
    /*
        bool bRetcode;
    
        //
        // Enable and reset the necessary peripherals.
        //
        SysCtlPeripheralEnable(I2S_CHAN1_PERIPH);
        SysCtlPeripheralEnable(I2S_CHAN0_PERIPH);
        SysCtlPeripheralEnable(I2S_UDMA_PERIPH);
        SysCtlPeripheralEnable(I2S_CHAN0_GPIO_C);
        SysCtlPeripheralEnable(I2S_CHAN0_GPIO_D);
        SysCtlPeripheralEnable(I2S_CHAN1_GPIO);
    
        //
        // Set up the pin mux for  both SSI ports.
        //
        GPIOPinConfigure(I2S_CHAN1_CLK);
        GPIOPinConfigure(I2S_CHAN1_FSS);
        GPIOPinConfigure(I2S_CHAN1_RX);
        GPIOPinConfigure(I2S_CHAN1_TX);
        GPIOPinConfigure(I2S_CHAN0_RX);
        GPIOPinConfigure(I2S_CHAN0_TX);
        GPIOPinConfigure(I2S_CHAN0_CLK);
        GPIOPinConfigure(I2S_CHAN0_FSS);
    
        //
        // Select alternate functions for all of the SSI pins.
        //
        GPIOPinTypeSSI(I2S_CHAN0_PORT_C, GPIO_PIN_4 | GPIO_PIN_5);
        GPIOPinTypeSSI(I2S_CHAN0_PORT_D, GPIO_PIN_4 | GPIO_PIN_5);
        GPIOPinTypeSSI(I2S_CHAN1_PORT, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 |
                       GPIO_PIN_7);
    
        //
        // Initialize the DAC.
        //
        bRetcode = DACInit();
    
        //
        // Configure the SSI clock (CCP timer).
        //
        setupCCP();
    
        //
        // Clear out all pending interrupts.
        // SSI_TXFF: TX FIFO half full or less
        //
        SSIIntClear(I2S_CHAN0_BASE, SSI_TXFF);
        SSIIntClear(I2S_CHAN1_BASE, SSI_TXFF);
    
        //
        // Enables individual SSI interrupt source.
        // We'll use SSI1's empty interrupt status to drive the FIFO transfers
        // for both SSI1 and SSI3. This interrupt occurs whenever the TX FIFO
        // is half full or less.
        //
        SSIIntEnable(I2S_CHAN0_BASE, SSI_TXFF);
    
        //
        // Configure the synchronous serial interfaces, SSI1 and SSI3.
        // Clock each at the system clock rate of 80MHz.
        // Use the Motorola Mode 2 protocol.
        // As the DAC is the master, set the SSI as a slave device.
        // Set the bit rate to 1.4112 MHz -> 44.1KHz
        // Set the width of the data transfers to the maximum : 16bits.
        //
        SSIConfigSetExpClk(I2S_CHAN0_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_2,
                           SSI_MODE_SLAVE, 44100 * 32, 16);
        SSIConfigSetExpClk(I2S_CHAN1_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_2,
                           SSI_MODE_SLAVE, 44100 * 32, 16);
    
        //
        // Enable the SSI controller.
        //
        SSIEnable(I2S_CHAN0_BASE);
        SSIEnable(I2S_CHAN1_BASE);
    
        return(bRetcode);
    */
        return(true);
    }
    
    //*****************************************************************************
    //
    // Configures the SSI peripheral to play audio in a given format.
    //
    // ulSampleRate is the sample rate of the audio to be played in
    // samples per second.
    // usBitsPerSample is the number of bits in each audio sample.
    // usChannels is the number of audio channels, 1 for mono, 2 for
    // stereo.
    //
    // This function configures the SSI peripheral in preparation for playing
    // or recording audio data in a particular format.
    //
    //*****************************************************************************
    bool
    SoundSetFormat(unsigned long ulSampleRate, unsigned short usBitsPerSample, unsigned short usChannels)
    {
        unsigned long ulBitClockRate;
        bool          bRetcode;
    /*
        //
        // Configure for Mono or Stereo, 16 bit formats.
        // Determine the MCLK rate & DAC PLL for each format.
        //
    
        if((usBitsPerSample == 16) && ((ulSampleRate == 44100) || (ulSampleRate == 48000)))
        {
            ulBitClockRate = ulSampleRate * 32;
    
            SSIDisable(I2S_CHAN0_BASE);
            SSIDisable(I2S_CHAN1_BASE);
    
            SSIConfigSetExpClk(I2S_CHAN0_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_2,
                               SSI_MODE_SLAVE, ulBitClockRate, 16);
    
            SSIConfigSetExpClk(I2S_CHAN1_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_2,
                               SSI_MODE_SLAVE, ulBitClockRate, 16);
    
            SSIEnable(I2S_CHAN0_BASE);
    
            if(usChannels == 2)
                SSIEnable(I2S_CHAN1_BASE);
    
            bRetcode = DACConfigure(ulSampleRate);
        }
        else
            bRetcode = false;
    
        return(bRetcode);
    */
        return(true);
    }
    
    //*****************************************************************************
    //
    // Close the Doxygen group.
    //! @}
    //
    //*****************************************************************************
    

    Still you can see timings among below threads/events and see ProcessAudioData speed is not fast enough.
    - SBC_decode()
    - ProcessAuioData()
    - etc.

    Thanks and regards,
    Koichiro Tashiro

  • Hi Michael,

    There was one incorrect information in previous excel.
    As the incoming audio data is 44.1kHz stereo, I2S format is also configured for 44.1kHz(not 48kHz).
    Sorry if this may cause some confusion at your side.
    Please find below updated excel with correction.
    A2DP SSI stop_Oct17.xlsx

    The basic issue is the same.
    For 44.1kHz audio playback, ProcessAudioData() needs to come with ~23msec interval, but it does not.

    Thanks and regards,
    Koichiro Tashiro

  • Hi Tashiro,

    Thanks for providing the dac.c and sound_ssi.c. I don't have the exact codec supported by the Tiva Bluetopia SDK so I would have needed to modify that to use the CC3200AUDBOOST that I use for the MSP432 Bluetopia SDK.

    So, I ran the code in sink mode and connected my phone and so far it is difficult to tell if I encounter the same issue you are seeing. While I have implemented the LED toggles that you seem to have in your capture, I cannot easily tell whether or not the I2S is stalled or not. What codec are you using to receive the I2S audio?

    Also, is there some reason why A3DP cannot be used by your customer? Assuming that the bottleneck is in the playbackThread(), using A3DP will eliminate the possibility of the I2S audio being stalled due to audio decode not being fast enough.


    Regards,
    Michael

  • Hi Michael,

    Michael Reymond said:

    So, I ran the code in sink mode and connected my phone and so far it is difficult to tell if I encounter the same issue you are seeing. While I have implemented the LED toggles that you seem to have in your capture, I cannot easily tell whether or not the I2S is stalled or not. What codec are you using to receive the I2S audio?

    Could you monitor I2S signals with oscilloscope?
    If the issue happens, BCLK is stopped, so it can be easily detected.
    Or you can toggle GPIO at the beginning of ProcessAudioData() as customer does.
    Ideally the GPIO pulse should come ~21msec for 44.1kHz stereo.

    I will send the codec customer uses by separate e-mail.

    Michael Reymond said:

    Also, is there some reason why A3DP cannot be used by your customer? Assuming that the bottleneck is in the playbackThread(), using A3DP will eliminate the possibility of the I2S audio being stalled due to audio decode not being fast enough.


    Assisted modes and BLE use the same hardware resources inside CC2564B.
    Customer wants to use BLE (SPPLE) while music is played, so A3DP cannot be used. A2DP is only the option.




    I do not think playbackThread() is the bottleneck, because the thread runs as soon as new data are received and the data decoding finishes before next data are received.
    The problem is the data do not come periodically with ~21msec interval.
    In below waveform as an example, it takes more than 27msec interval and it continues 4 times. Then it results in decoded data shortage.



    I found there is Sample Rate Adjustment mechanism in AudioDecoder.c, but it seems it increases playback speed.
    Is it possible to slow down playback speed based on decoded data inside buffer ?

             /* Check to state of the SBC buffer to see if we need to adjust*/
             /* the playback speed.                                         */
             if((!PlaybackContext.SampleRateAdjustment) && (PlaybackContext.SBCUsed >= PlaybackContext.BufferHighLimit))
             {
                /* We have too many samples so we will increase the playback*/
                /* speed to free up storage.                                */
                PlaybackContext.SampleRateAdjustment = SAMPLE_RATE_ADJUSTMENT_VALUE;
    
                Display(("Up %d %d\r\n", (unsigned long)PlaybackContext.CurrentSampleRate, PlaybackContext.SBCUsed));
             }
    
             if((PlaybackContext.SampleRateAdjustment) && (PlaybackContext.SBCUsed <= PlaybackContext.BufferLowLimit))
             {
                /* We have too many samples so we will increase the playback*/
                /* speed to reduce the storage.                             */
                PlaybackContext.SampleRateAdjustment = 0;
    
                Display(("Down %d %d\r\n", (unsigned long)PlaybackContext.CurrentSampleRate, PlaybackContext.SBCUsed));
             }



    Thanks and regards,
    Koichiro Tashiro

  • Hi Tashiro,

    Thanks for your explanation on why you're not using A3DP as well as your notes on the I2S behavior you are seeing.

    I have a couple more questions:

    1. Are you using the A2DPDemo from the SDK without modifications other than the SSI master change? Or are you also running the SPPLE code as well at the same time as the A2DP code?
    2. Can you please provide the SSI code changes that you made? While I don't have the exact codec the customer is using, I will be able to replicate your SSI changes and see the I2S output on my logic analyzer.

    Regards,

    Michael

  • Hi Michael,

    #1:
    Customer uses only A2DP demo with minimal modifications;
    - Configure single SSI as master
    - Use their codec
    No SPPLE demo is running for now.

    #2:
    I will get the code and send it offline.

    BTW, when you use your codec with A2DP demo, the audio data are received exactly 21msec interval?
    (i.e. ProcessAudioData() is called once in 21msec)

    Thanks and regards,
    Koichiro Tashiro

  • Hi Michael,

    I did some additional tests with customer project and updated the excel sheet.
    Please see below additional sheet.
    - Bitpool change
    - HCI Uart
    - ListLock check
    (ProcessAudioData sheet is not changed)
    A2DP SSI stop_Oct31.xlsx
    So far no success to avoid the BCLK stop issue.

    Thanks and regards,
    Koichiro Tashiro

  • Hi Tashiro,

    I am able to replicate your issue now, and will keep investigating this with the notes you have provided in your previous posts. I appreciate your patience and hope to be able to resolve this quickly now that I can observe the same I2S behavior.

    Regards,

    Michael

  • Hi Tashiro,

    As I mentioned in my email, I have been able to replicate the issue the customer is seeing. However, debugging this has taken me longer than anticipated. I nevertheless hope to resolve this soon, especially given the continued effort of you and your customer in providing debug data, and I appreciate your patience in this issue.

    Regards,

    Michael

  • Hi Tashiro,

    As I mentioned in my latest email, I am still investigating this issue and have a few possible approaches I am looking at, such as with the Tiva I2S bus as well the compiler optimization. I'll keep you updated with my progress, and I appreciate your patience.

    Regards,

    Michael

  • Hi Tashiro,

    I'm still looking at this issue. As of right now I do not have an update since my last post.

    Regards,

    Michael

  • Tashiro,

    I'll be taking a look at this this week and will get back to you as soon as I have a update.

    BR,

    Vince

  • Tashiro,

    As discussed, I'll provide a follow up to this over email. I am going to close this thread, but will post the solution once it is found.

    BR,

    Vince 

  • Solution:

    The root cause of the issue is that the TIVA C SSI peripheral is not able to Clock out a precise BLCK for a 44.1KHz Stereo Signal. Diving into the SSI Driverlib, you’ll see the formula for SSInClk is:

     

    SSInClk = SysClk / (CPSDVSR * (1 + SCR)

     

    When you then calculate what the possible BCLKs are for a 16 bit, 44.1KHz Stereo signal, it looks like this:

    Ideal Freq=1411200Hz

    SSinClk (BCLK)

    sysclk

    cpsdvsr

    SCR

    1428571.429

    120000000

    2

    41

    1395348.837

    120000000

    2

    42

    The correct clock source should be 1411200 Hz. As you can see the SSI peripheral does not have the granularity to accomplish this, so it gets the closest BLCK it can, which is 1.42MHz (SCR = 41). This rate is faster than the correct frequency, thus the TIVA device sends samples at a faster rate than it receives them (Underflow condition). If you were to change your target bit rate to second one (SCR=42), then the underflow events are eliminated, but now you will get overflow events represented by the dropped packet message.

     

    To complicate this further, you need to bit stuff your 16 bit data by 16 bits, thus doubling the sampling rate acquired. This makes the issue worse because the effective delta of frequency is now doubled.

     

    Ideal Freq = 2822400Hz

    SSinClk (BCLK)

    sysclk

    cpsdvsr

    SCR

    2857142.857

    120000000

    2

    20

    2727272.727

    120000000

    2

    21

     

    A way to overcome this issue is by shifting the set sampling frequency between these two values so that the average frequency equates to your goal sampling frequency. The equation for that looks like this:

     

    (X * Freq1) + (Y * Freq2) / (X+Y) = Sampling Frequency

     

    The numbers I calculated for your use case are X = 500, and Y=183 . You can play around with these numbers to see the effects. The lower the values of the X and Y, the quicker the transitions but lower the granularity. The code I added is the in the audio I2S IRQ.

    This is a workaround for a hardware limitation, so results may vary.

    Best Regards,
    Vince