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 = PORTx_PCRn_MUX(5); PORTE->PCR = 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.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 = DMAMUX_CHCFGn_ENBL | DMAMUX_CHCFGn_SOURCE(22);
DMA->ch.DSR_BCR = DMA_DSR_BCRn_BCR(txbytes + 1); // <--- txbytes = 2; if I leave the +1 out, only one byte is transferred DMA->ch.SAR = txbuf; // a buffer with two bytes DMA->ch.DAR = &I2C0->D; DMA->ch.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.DSR_BCR |= DMA_DSR_BCRn_DONE; // clear all status flags