I2C with DMA, strange byte counter behavior

cancel
Showing results for 
Search instead for 
Did you mean: 

I2C with DMA, strange byte counter behavior

Jump to solution
1,502 Views
age
Contributor II

Hello!

I'm working on a DMA-enabled I2C driver for MKL25Z (Freedom Board). My problem is this: first, I coufigure DMA to send two bytes; then I start a DMA/I2C transfer by writing the slave address to I2C Data register; after the transfer completes (DMA interrupt fires), only one byte of the two appears to have been sent (total 2/3: slave address and first data byte).

I verify this a) by watching I2C SCL with a scope; b) by comparing behavior with DMA-less I2C code -- I configure an accelerometer to generate interrupts.

However, if I write N + 1 to DMA BCR where N is the number of bytes to be transferred, the correct number of bytes is transferred.

Also, errata id 5746 for mask 1N97F seems remotely related: "When using the PIT to trigger DMA transfers using cycle steal mode, two data transfers per request are generated".

I'd much appreciate a solution to this.

The setup code is this:

      SIM->SCGC4 |= SIM_SCGC4_I2C0;

      SIM->SCGC4 |= SIM_SCGC5_PORTE;

// enable DMA and DMAMUX clocks

      SIM->SCGC7 |= 1 << 8;

      SIM->SCGC6 |= 1 << 1;

// I2C SDA and SCL

      PORTE->PCR[24] = PORTx_PCRn_MUX(5);

      PORTE->PCR[25] = PORTx_PCRn_MUX(5);

   

      I2C0->F = 0x20;

// enable interrupts and DMA, all interrupt conditions generate DMA requests

      I2C0->C1 = I2Cx_C1_IICEN | I2Cx_C1_DMAEN | I2Cx_C1_IICIE;

// enable channel 0 with 8 bit source and dest, cycle-steal mode (one byte per I2C request)

      DMA->ch[0].DCR = DMA_DCRn_SSIZE(1) | DMA_DCRn_DSIZE(1) | DMA_DCRn_CS

        | DMA_DCRn_EINT | DMA_DCRn_D_REQ;

// DMAMUX ch. 0 with I2C0 as source

      DMAMUX->CHCFG[0] = DMAMUX_CHCFGn_ENBL | DMAMUX_CHCFGn_SOURCE(22);

Transmission code:

    DMA->ch[0].DSR_BCR = DMA_DSR_BCRn_BCR(txbytes + 1); // <--- txbytes = 2; if I leave the +1 out, only one byte is transferred

    DMA->ch[0].SAR = txbuf; // a buffer with two bytes

    DMA->ch[0].DAR = &I2C0->D;

    DMA->ch[0].DCR |= DMA_DCRn_SINC | DMA_DCRn_ERQ; // increment source address, listen to requests from I2C

   

// transmission/master mode

    I2C0->C1 |= I2Cx_C1_TX;

    I2C0->C1 |= I2Cx_C1_MST;

// writing slave address; when this completes, TCF flag is set, IICIF is set, DMA transaction begins

    I2C0->D = (addr << 1);

// wait until DMA interrupt has fired

DMA interrupt handler code:

  DMA->ch[0].DSR_BCR |= DMA_DSR_BCRn_DONE; // clear all status flags

Tags (3)
1 Solution
121 Views
age
Contributor II

xiangjun.rong, Santiago_Lopezthank you for your responses. I have solved my problem, and quite subtle it was; I was disabling the I2C module on DMA interrupt, right after the DMA transfer to the data register was complete. However, at that time I2C hadn't yet sent the last byte (and nowhere it is said it would).

    I2C0->C1 |= I2Cx_C1_DMAEN;

    DMA->ch[0].DSR_BCR = DMA_DSR_BCRn_BCR(txbytes);

    DMA->ch[0].SAR     = (uint32_t) txbuf;

    DMA->ch[0].DAR     = (uint32_t) &(I2C0->D);

    DMA->ch[0].DCR    |= DMA_DCRn_SINC | DMA_DCRn_ERQ | DMA_DCRn_START;

   

    // wait for DMA here

    while ((I2C0->S & I2Cx_S_TCF) == 0); // <== this line. when DMA is done, I2C is not yet done!

    I2C0->C1 &= ~I2Cx_C1_DMAEN;

    if (rxbytes != 0)

      I2C0->C1 |= I2Cx_C1_RSTA;

    else

      I2C0->C1 &= ~(I2Cx_C1_MST | I2Cx_C1_TX); // <== disable I2C after a transfer is complete

View solution in original post

0 Kudos
4 Replies
122 Views
age
Contributor II

xiangjun.rong, Santiago_Lopezthank you for your responses. I have solved my problem, and quite subtle it was; I was disabling the I2C module on DMA interrupt, right after the DMA transfer to the data register was complete. However, at that time I2C hadn't yet sent the last byte (and nowhere it is said it would).

    I2C0->C1 |= I2Cx_C1_DMAEN;

    DMA->ch[0].DSR_BCR = DMA_DSR_BCRn_BCR(txbytes);

    DMA->ch[0].SAR     = (uint32_t) txbuf;

    DMA->ch[0].DAR     = (uint32_t) &(I2C0->D);

    DMA->ch[0].DCR    |= DMA_DCRn_SINC | DMA_DCRn_ERQ | DMA_DCRn_START;

   

    // wait for DMA here

    while ((I2C0->S & I2Cx_S_TCF) == 0); // <== this line. when DMA is done, I2C is not yet done!

    I2C0->C1 &= ~I2Cx_C1_DMAEN;

    if (rxbytes != 0)

      I2C0->C1 |= I2Cx_C1_RSTA;

    else

      I2C0->C1 &= ~(I2Cx_C1_MST | I2Cx_C1_TX); // <== disable I2C after a transfer is complete

View solution in original post

0 Kudos
121 Views
santiago_lopez
NXP Employee
NXP Employee

Hi age,

I think it might be a problem with the IIC DMA requests. The IIC module triggers the DMA in the following cases:

• While FACK = 0, a data byte is received, either address or data is transmitted. (ACK/NACK automatic)

• While FACK = 0, the first byte received matches the A1 register or is general call address.

If any address matching occurs, IAAS and TCF are set. If the direction of transfer is known from master to slave, then it is not required to check the SRW. With this assumption, DMA can also be used in this case. In other cases, if the master reads data from the slave, then it is required to rewrite the C1 register operation. With this assumption, DMA cannot be used.

When FACK = 1, an address or a data byte is transmitted.

I would need to check your whole project, but I think the IIC triggering might be causing some double write to the I2C0_D register before the last one was sent. Your DMA configuration is right. Attached you will find my test code. I used your DMA configuration, only replaced the I2C module with a TPM, but if you set a breakpoint in the TPM ISR you will see that the DMA does copy all the bytes in the buffer to the destination register.

Saludos

121 Views
xiangjun_rong
NXP TechSupport
NXP TechSupport

I have tested the BCR with the following code from memory to memory, the DMA transfer the BCR bytes exactly.

uint16_t A[100],B[100];

int main(void)
{
    int counter = 0;
    //transfer 100 data from A to B
    for(counter=0; counter<100; counter++)
    {
        A[counter]=250+counter;
    }
    SIM_SCGC4 |= 0xC0; 
    SIM_SCGC5 |= 0x3E00;  //enable Port all
    SIM_SCGC7 |= 1 << 8;  //DMA enable
    SIM_SCGC6 |= 1 << 1;  //DMA mux enable
   
    DMA_SAR0 =(uint32_t)&A[0];
    DMA_DAR0 =(uint32_t)&B[0];
    DMA_DCR0 =0x006C0000; //disable interrupt
    DMA_DSR_BCR0=200; //200 bytes
    DMA_DCR0 |=0x10000; //start DMA
    while(!(DMA_DSR_BCR0&0x1000000)) {}
    asm("nop"); //set break point to check the A and B array

     for(;;)

}

0 Kudos
121 Views
xiangjun_rong
NXP TechSupport
NXP TechSupport

Firstly, I think the PortE clock enable is incorrect, pls change as following:

SIM->SCGC4 |= SIM_SCGC5_PORTE;

TO
SIM->SCGC5 |= SIM_SCGC5_PORTE;

Regarding you question, it is complicated to use IIC to test. I suggest you transfer data between two memory address to test BCR function, and check if the actual data number transferred is less than 1.

0 Kudos