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.

TMS320C5517: How implement master I2C write using DMA?

Part Number: TMS320C5517
Other Parts Discussed in Thread: DAC80501

Tool/software:

Hi everyone, I would like to perform I2c writes using the DMA from my TMS320C55517 DSP (master) to the DAC80501M peripheral (slave). I want to use DMA on the I2c bus because I have some threads with interrupts below that if  I used the simple I2c writer without DMA, they would interrupt the I2c write and not complete it.

To write I2C without DMA I am using the Texas library function I2C_write(Uint16 *i2cWrBuf, Uint16 dataLength, Uint16 slaveAddr, Bool masterMode, Uint16 startStopFlag, Uint16 timeout) and it works fine without threads (this function is take from C5517_scl library ). Instead to perform a write I2c with DMA there is no already made function that I can use.

Could anyone recommend a software library where I could find it , or at least if I have to write it by myself how to do it.

many thanks in advance!

  • Hi Matteo,

    If you download and install the C55XCSL-LOWPOWER CSL 03.08.01 from SPRC133 Driver or library | TI.com, there is an example of I2C DMA in

    c55_csl_3.08.01\ccs_v6.x_examples\i2c\csl_I2C_DMA_example

    Best regards,

    Ming

  • Hi Ming, 

    first of all thank you for answer. I see the example project that you that you suggested to me. It helped me a lot, but I have some doubts to clarify.

    The things I don't understand are the following:
    1) inside the CSL_i2cDmaTest() function, the DMA_start() function is called first (which starts the DMA transmission) and then the START bit is set. I would have expected the opposite...
    2) the STOP bit to end the i2c writing is not set anywhere in the function. Would the DMA_start() function do this automatically?

    Taking inspiration from this example I wrote the following function in order to implement I2c writing with DMA:

    short i2c_write_DMA(unsigned short* buffer, unsigned short length, unsigned short address)
    {
            CSL_Status status;
            unsigned short delay;

            ICMDR = 0;                                                                                             // reset I2C bus.
            ICMDR = I2C_MDR_MST | I2C_MDR_TRX;                                         // enable master transmitter mode.
             ICCNT = length;                                                                                    // set message size.
            ICSAR = address;                                                                                  // set I2C slave device address.
            ICSTR = 0xFFFF;                                                                                  // clear all interrupt flags.
            ICMDR = I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS;              // take I2C bus out of reset.
            while((ICSTR) & (I2C_STR_BB));                                                        // wait for I2C bus to be free.

            /* Start Dma transfer */
            status = DMA_start(dmaHandleI2C);
            if(status != CSL_SOK)
            {
                     printf("I2C Dma Write Failed!!\n");
                     return(status);
             }

             /* Set the start bit */ 
             ICMDR |= I2C_MDR_STT;

             /* Check transfer complete status */
             while(DMA_getStatus(dmaHandleI2C));

             printf("DMA Data Write to I2C Complete\n");

             /* Give some delay */
             for(delay = 0; delay < 0x1FFF; delay++)
             {
                     ;
              }

          return(status);
    }

    Do you think such a solution could work?

    Best reguards,

    Matteo

  • Hi Matteo,

    1) The  DMA_start() starts the DMA for I2C. The CSL_I2C_SETSTOP starts the I2C transmission. The order is correct, because the DMA has to be ready before the I2C transmit starts.

    2) STP bit of ICMDR has been set to 1 in "i2cConfig.icmdr  = CSL_I2C_ICMDR_WRITE_DEFVAL;", so the stop bit will be generated automatically when the internal data counter of the I2C counts down to 0.

    Best regards,

    Ming
     

  • Hi Ming,

    I configured the DMA for I2c as follows:

    dmaConfig.dmaEvt = CSL_DMA_EVT_I2C_TX;               // I2C TX event triggers DMA
    dmaConfig.burstLen = CSL_DMA_TXBURST_2WORD;  // One 32-bit transfer: 1-word burst (on I2C interface)
    dmaConfig.chanDir = CSL_DMA_WRITE;                          // Writing data to a device (GPIO EXP and DAC80501 device)
    dmaConfig.dmaInt = CSL_DMA_INTERRUPT_DISABLE;  // Interrupt Disable.
    dmaConfig.dataLen = 3;                                                       // number of bytes to transfer.
    dmaConfig.srcAddr = (unsigned long)dataI2cOut;                // address of Source I2c data buffer
    dmaConfig.destAddr = 0x00001A20;                                  // I2C ICDXR Register address (DMA writes transmit data from sorce buffer to the I2C data transmit register ICDXR)

    dmaHandleI2C = DMA_open(CSL_DMA_CHAN8,&dmaObj, &status); // Open DMA channel 8 and assigns a handler to it (dmaHandleI2C)
    status = DMA_config(dmaHandleI2C, &dmaConfig); 

    DMA2CH0TCRU |= 0x200; // Destination have constant addressing.
                                                // Source addressing have automatic post increment (source address byte is incremented by four each transfer)

    As regards the function for writing to I2c with DMA, I implemented it like this:

    short i2c_write_DMA(unsigned short* buffer, unsigned short length, unsigned short address)
    {
    CSL_Status status;
    unsigned short delay;

    ICMDR = 0;                                                              // reset I2C bus.
    ICMDR = I2C_MDR_MST | I2C_MDR_TRX;           // enable master transmitter mode.
    ICCNT = length;                                                       // set message size.
    ICSAR = address;                                                    // set I2C slave device address.
    ICSTR = 0xFFFF;                                                    // clear all interrupt flags.
    ICMDR = I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS; // take I2C bus out of reset.
    while((ICSTR) & (I2C_STR_BB));         // wait for I2C bus to be free.
    ICMDR = (I2C_MDR_STP | I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS); // schedule STOP bit.


    /* Start Dma transfer */
    status = DMA_start(dmaHandleI2C);
    if(status != CSL_SOK)
    {
    return status;
    }

    /* Set the start bit */
    ICMDR |= I2C_MDR_STT;

    while(ICMDR & I2C_MDR_MST);      // wait for STOP bit to be executed.

    /* Give some delay */
    for(delay = 0; delay < I2C_WAIT; delay++)
    {
          ;
    }

    return status;
    }

    La chiama The function call is as follows:

    Uint16 dataI2cOut[3];

    short Dac_On(void)
    {
    CSL_Status I2cStatus = CSL_SOK;

    ///////////////////////////////////////
    // DAC80501M_u3 (DAC_0) on 371 board //
    //////////////////////////////////////

    //Turn-on DAC0
    dataI2cOut[0] = DAC_REG_CONFIG;  // Command byte: Dac Configuration register address
    dataI2cOut[1] = 0x0000;                       // MSB_Data: DAC output MSB (net DAC_0 = 0)
    dataI2cOut[2] = 0x0000;                       // LSB_Data: DAC output LSB (net DAC_0 = 0)


    I2cStatus = i2c_write_DMA(&dataI2cOut[0], 3, SLAVE_ADDR_DAC0);
    if (I2cStatus != 0)
    return I2cStatus;

    //DAC0 Output = 0V
    dataI2cOut[0] = DAC_REG_DATA; //Command byte: Dac Data Output register address
    dataI2cOut[1] = 0x0000; // MSB_Data: update DAC output (MSB)
    dataI2cOut[2] = 0x0000; // LSB_Data: update DAC output (LSB)

    //I2cStatus = i2c_write_DMA(&dataI2cOut[0], 3, SLAVE_ADDR_DAC0);

    if (I2cStatus != 0)
    return I2cStatus;

    Unfortunately the stream I send to the DAC is not correct. The slave address is always sent correctly, but the three bytes that must follow it always have an incorrect value that I can't explain. I have done several tests, also changing the parameters in play but I have not yet arrived at a solution. Where could the error be in your opinion?

    Best reguards,

    Matteo

  • I Matteo,

    I saw two issues with your implementation:

    1. Uint16 dataI2cOut[3]; should be Uint32 dataI2cOut[3];

    2. while(ICMDR & I2C_MDR_MST);      // wait for STOP bit to be executed. in  i2c_write_DMA is incorrect. You should have used the while(DMA_getStatus(dmaRdHandle)); instead.

    Best regards,

    Ming

  • Hi Ming,

    today I made some progress even if I didn't arrive at the definitive solution.
    By writing the function in this way and making the dataI2cOut buffer a Uint32 buffer, the writes now work (even the bytes after the address are sent correctly) but it seems that the STOP condition condition is not generated at the end of the transmission. Theoretically MST bit is automatically changed from 1 to 0 when the I2C master generates a STOP condition. This, however, does not seem to happen.

    What could I do to make this happen and close the I2C transmission correctly?

    This is the last version of my i2c write function:

    short i2c_write_DMA(unsigned long* buffer, unsigned short length, unsigned short address)
    {
    CSL_Status status;

    dmaConfig.dmaEvt = CSL_DMA_EVT_I2C_TX;                 // I2C TX event triggers DMA
    dmaConfig.burstLen = CSL_DMA_TXBURST_1WORD;    // One 32-bit transfer: 1-word burst (on I2C interface)
    dmaConfig.chanDir = CSL_DMA_WRITE;                          // Writing data to a device (GPIO EXP and DAC80501 device)
    dmaConfig.dmaInt = CSL_DMA_INTERRUPT_DISABLE; // Interrupt Disable.
    dmaConfig.dataLen = 12;                                                   // number of bytes to transfer.
    dmaConfig.srcAddr = (unsigned long)dataI2cOut; // address of Source I2c data buffer
    dmaConfig.destAddr = 0x00001A20; // I2C ICDXR Register address (DMA writes transmit data from sorce buffer to the I2C data transmit register ICDXR)

    dmaHandleI2C = DMA_open(CSL_DMA_CHAN8,&dmaObjI2C, &status); // Open DMA channel 8 and assigns a handler to it (dmaHandleI2C)
    status = DMA_config(dmaHandleI2C, &dmaConfig); // configura il canale DMA 8 (il quale ha handler dmaHandleI2C)
    // con le impostazioni definite sopra nella struttura dmaConfig.

    DMA2CH0TCRU |= 0x200; // Destination have constant addressing.
    // Source addressing have automatic post increment (source address byte is incremented by four each transfer)
    DMA2CH0TCRU |= 0x8004; // enable DMA2CH0 channel for I2C Tx.

    ICMDR = 0; // reset I2C bus.
    ICMDR = I2C_MDR_MST | I2C_MDR_TRX; // enable master transmitter mode.
    ICCNT = length; // set message size.
    ICSAR = address; // set I2C slave device address.
    ICSTR = 0xFFFF; // clear all interrupt flags.
    ICMDR = I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS; // take I2C bus out of reset.
    while((ICSTR) & (I2C_STR_BB)); // wait for I2C bus to be free.
    //ICMDR = (I2C_MDR_STP | I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS); // schedule STOP bit.

    /* Start Dma transfer */
    status = DMA_start(dmaHandleI2C);
    if(status != CSL_SOK)
    {
        return status;
    }

    /* Set the start bit */
    ICMDR |= I2C_MDR_STT;

    while(DMA_getStatus(dmaHandleI2C));

    wait_us(2);         // wait guard period after STOP bit.

    return status;
    }

    Actual function call: 

    2cStatus = i2c_write_DMA2(&dataI2cOut[0], 12, SLAVE_ADDR_DAC0);

    Many Thanks

    Matteo

  • Hi Matteo,

    Why do you comment out the following line?

    //ICMDR = (I2C_MDR_STP | I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS); // schedule STOP bit.

    Best regards,

    Ming

  • It's a transcription error. That line is active during my tests...

    What would you suggest I do?

  • Hi Matteo,

    1. Can you add the following line?

    dmaConfig.trigger      = CSL_DMA_EVENT_TRIGGER;

    2. Can you check the content of ICMDR after ICMDR = (I2C_MDR_STP | I2C_MDR_MST | I2C_MDR_TRX | I2C_MDR_IRS);

    Best regards,

    Ming

  • Hi Ming,

    leaving the function unchanged and changing the function call in the following way, the communication now works and closes correctly! Slight smile

    New function call: 

    2cStatus = i2c_write_DMA(&dataI2cOut[0], 3, SLAVE_ADDR_DAC0);

    In my opinion there was an error in the length parameter of the function you wrote: the number of bytes sent must be 3. It must be different from the number that I set on the DMA, because the DMA reasons in 32-bit words, while the I2C reasons in bytes transmitted.
    This is why the stop bit was not being generated: the I2C peripheral expects to send another 9 bytes before generating it.

    That said, Thank you very much Ming for your support these past few days!

    Matteo

  • Hi Ming,

    regarding writings with I2c with DMA I noticed that in debug they work (as I also told you 2 days ago), while in real time they don't work. In real time I tried them with and without other treads but the result is the same, they have no effect.
    I rechecked all the DMA registers but they are fine in debug and I see the DAC output moving (which indicates correct functioning).
    I'll also tell you that the only situation in which everything works (even in real time) is when I put the two library functions DMA_open() and DMA_config() at the beginning of the i2c_write_DMA() function. So every time I must do an I2c write (with DMA) I have to redo the DMA configuration to make everything work, which the debug disproves when I don't insert them.

    What would you recommend in this case to make everything work ,without having to insert the two library functions inside the function?

    Many Thanks!

    Matteo

  • Hi Matteo,

    When you configure a DMA with DMA_config(), it will set up the DMA to read/write to/from I2C for certain amount of data. After the DMA is started and I2C is started, the DMA will finish the job and inform the CPU, then the DMA is done. If you want to let the DMA to do another job, you will either re-configure the DMA with DMA_config() or set the dmaConfig.autoMode     = CSL_DMA_AUTORELOAD_ENABLE (it will repeat the previous job). I do not think you need to call the DMA_open every time.

    Best regards,

    Ming