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
Solved! Go to Solution.
xiangjun.rong, Santiago_Lopez, thank 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
xiangjun.rong, Santiago_Lopez, thank 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
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
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(;;)
}
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.