cancel
Showing results for 
Search instead for 
Did you mean: 

Using STM32H7 I2C master transmit in DMA circular mode (I2C_CR2 NBYTES/RELOAD)

Georgy Moshkin
Senior II

I wanted to send looped signal pattern to I2C DAC at 1 MBit/s (around 100kHz sampling rate) and eventually found a solution.

Original problem description: Because DAC is accepting 16bit frames I set NBYTES to 254 (254/2=127 DAC samples) and RELOAD bit =1. This approach works but requires to call HAL_DMA_Start_IT and deal with TCR flag for each 254 bytes sent over I2C in XferCpltCallback. Is it possible to make interrupt-less I2C DMA circular buffer transfer with a single call to HAL_DMA_Start_IT (similar to built-in DAC peripheral)? Preferably with size>255 bytes. For high sampling rates I wanted to minimize overhead from repeated calls of HAL_DMA_Start_IT and I2C_TransferConfig functions.

Current solution: I2C + DMA circular buffer without repeated calls of HAL_DMA_Start_IT ( RELOAD bit still need to be updated each 254 samples). This solution is based on code from HAL libraries. Idea is to start transfer the same way as in HAL_I2C_Master_Transmit_DMA function when hi2c->XferCount > MAX_NBYTE_SIZE:

if (hi2c->XferCount > MAX_NBYTE_SIZE)
    {
      hi2c->XferSize = MAX_NBYTE_SIZE;
      xfermode = I2C_RELOAD_MODE;
    }

By default reaching XferSize will result in I2C_DMAMasterTransmitCplt call where DMA is stopped and restarted

/* Disable DMA Request */
  hi2c->Instance->CR1 &= ~I2C_CR1_TXDMAEN;
...
    if (HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->TXDR, hi2c->XferSize) != HAL_OK)

and I2C_Master_ISR_DMA call where I2C registers are updated

I2C_TransferConfig(hi2c, devaddress, (uint8_t)hi2c->XferSize, xfermode, I2C_NO_STARTSTOP);

If DMA is configured in circular mode, repeated calls of HAL_DMA_Start_IT may be avoided. Here is working example:

#define I2C_BUFFER_SIZE 1024
uint8_t i2cBuffer[I2C_BUFFER_SIZE] __attribute__((section(".ram_d1_axi_sram")));
static HAL_StatusTypeDef MY_I2C_Master_ISR_DMA(struct __I2C_HandleTypeDef *hi2c, uint32_t ITFlags, uint32_t ITSources)
{
	if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_TCR) == SET)
	{
		MY_I2C_TransferConfig(&hi2c1,  (uint16_t)DAC_ADDR, 254, I2C_RELOAD_MODE, I2C_NO_STARTSTOP); // in repeated calls using I2C_NO_STARTSTOP
 
	}
	return HAL_OK;
}
// DMA enabled single time
hi2c1.hdmatx->XferCpltCallback =  MY_I2C_DMAMasterTransmitCplt;
HAL_DMA_Start_IT(hi2c1.hdmatx, (uint32_t)&i2cBuffer, (uint32_t)&hi2c1.Instance->TXDR, I2C_BUFFER_SIZE);
MY_I2C_TransferConfig(&hi2c1,  (uint16_t)DAC_ADDR, 254, I2C_RELOAD_MODE, I2C_GENERATE_START_WRITE); // in first call using I2C_GENERATE_START_WRITE
uint32_t tmpisr = I2C_IT_TCI; 
__HAL_I2C_ENABLE_IT(&hi2c1, tmpisr);
hi2c1.Instance->CR1 |= I2C_CR1_TXDMAEN;

MY_I2C_TransferConfig is copy pasted from original I2C_TransferConfig

DMA src/dst data width 8 bytes, circular mode. DMA halfCplt and Cplt callbacks trigger after 512 and 1024 bytes. I2C_FLAG_TCR flag triggers every 254 bytes and need to be cleared using I2C_TransferConfig, which also sets NBYTES to 254. If there are a lot of other interrupts, then one solution to avoid I2C clock stretching would be setting higher I2C interrupt priority. Still interested in solution without updating I2C_FLAG_TCR each 254 bytes.

p.s. Interestingly, I've found one blog where author uses I2C with DMA in circular mode on STM32F103. Because I2C is "less advanced" and does not have reload in control registers it may work without using any interrupts at all: STM32 MIDI Controller Part 3: DMA Driven I2C LED Matrix

0 REPLIES 0