cancel
Showing results for 
Search instead for 
Did you mean: 

I2C In Master Receive Mode, DMA transfer in circular mode not working

NMan.11
Associate II

Hi I'm using the STM32F446RE with the latest FW Package - SMT32Cube_FW_F4 V1.28.1, and i have a MCP3221 connected to an I2C port.
Its a strange ADC that returns a sample every 2 bytes when its setup to send data to the I2C master. There are no addresses like most I2C devices. 
Since it works in this way i can take advantage of this and I'd like to read it continuously in DMA mode, with no interruptions and so i tried DMA circular mode. The problem is that it runs 1 time trough the recv process and stops after the callback. I'm using CubeMX to setup the peripherals.

I2C init code. 

  /* USER CODE END I2C3_Init 1 */
  hi2c3.Instance = I2C3;
  hi2c3.Init.ClockSpeed = 400000;
  hi2c3.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c3.Init.OwnAddress1 = 0;
  hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c3.Init.OwnAddress2 = 0;
  hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c3) != HAL_OK)
  {
    Error_Handler();
  }

DMA Init code. 

    /* I2C3 DMA Init */
    /* I2C3_RX Init */
    hdma_i2c3_rx.Instance = DMA1_Stream1;
    hdma_i2c3_rx.Init.Channel = DMA_CHANNEL_1;
    hdma_i2c3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_i2c3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_i2c3_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_i2c3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_i2c3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_i2c3_rx.Init.Mode = DMA_CIRCULAR;
    hdma_i2c3_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_i2c3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_i2c3_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hi2c,hdmarx,hdma_i2c3_rx);

    /* I2C3 interrupt Init */
    HAL_NVIC_SetPriority(I2C3_EV_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(I2C3_EV_IRQn);
    HAL_NVIC_SetPriority(I2C3_ER_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(I2C3_ER_IRQn);
  /* USER CODE BEGIN I2C3_MspInit 1 */

 Call code.

static volatile uint8_t data_buff[200*2]; //Global variable.

HAL_I2C_Master_Receive_DMA(&hi2c3, I2C_ADDRESS_ADC, (uint8_t *)data_buff, 200/2);

 

currently my void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) is just blank. I just use it with a breakpoint to check, if it got there. it gets there once and then it stops. 

NMan11_0-1729455206683.png

Currently i have it bodged where i start a new transfer as soon as the previous one ends, but I'd prefer to have it with proper circular mode.
Any help would be highly appreciated.

 

4 REPLIES 4
Ghofrane GSOURI
ST Employee

Hello @NMan.11 

First let me thank you for posting.

The MCP3221 ADC sends data in a two-byte format without a traditional I2C address. Instead, it uses a specific I2C address for the master to initiate communication. The device operates in a way that requires reading two bytes at a time, which is essential for extracting the 12-bit ADC value.

DMA Configuration:

  • The MemDataAlignment should match the size of the data being received (in this case, DMA_MDATAALIGN_BYTE might be more appropriate since you're reading two bytes at a time)

I2C Configuration:

  • Verify that your I2C settings are compatible with the MCP3221. The clock speed (400 kHz) is typically fine, but ensure that pull-up resistors are correctly placed on the SDA and SCL lines

DMA Initialization

hdma_i2c3_rx.Instance = DMA1_Stream1;
hdma_i2c3_rx.Init.Channel = DMA_CHANNEL_1;
hdma_i2c3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_i2c3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_i2c3_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_i2c3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // Change to BYTE
hdma_i2c3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // Change to BYTE
hdma_i2c3_rx.Init.Mode = DMA_CIRCULAR;
hdma_i2c3_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_i2c3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

if (HAL_DMA_Init(&hdma_i2c3_rx) != HAL_OK) {
    Error_Handler();
}

__HAL_LINKDMA(hi2c, hdmarx, hdma_i2c3_rx);

Continuous Reading Logic

In your main application logic:

static volatile uint8_t data_buff[200]; // Adjust size based on actual needs

// Start receiving data in circular mode
HAL_I2C_Master_Receive_DMA(&hi2c3, I2C_ADDRESS_ADC << 1, (uint8_t *)data_buff, sizeof(data_buff));

// Callback function
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
    // Process received data from data_buff here if needed
    // No need to restart transfer in circular mode
}

 Also you can check this post , it could help you .

THX

Ghofrane

Hi @Ghofrane GSOURI 

Thank you for the quick response. 

Now as far as hardware and clock configuration go, they are all fine. I've checked the I2C fronts with a scope and they are steep enough to be read properly. I'm sorry i don't have a capture of the signal.
I've also using this hardware (pull-up resistors and I2C settings), I'm just retriggering the DMA transfer every time its done, so I'm imitating the circular DMA logic as well as it is possible. 

I2C_ADDRESS_ADC is actually already shifter.
Yes i agree about the buffer size, this was just a simple sanity check code and i was using to quickly test everything and i was noodling.

Unfortunately changing the data alignment, didn't fix the original issue. It just triggers the transfer once and stops. It's acting like normal mode basically.


Hello again @NMan.11 

If your DMA transfer for the MCP3221 ADC is only triggering once and then stopping, it suggests that might be an issue with the I2C communication itself. 

Make sure to follow the correct initialization sequence as per the MCP3221’s requirements:
Start communication with a Start condition.
Send the device address with the read bit set.
Read two bytes with appropriate ACK/NACK handling.
End communication with a Stop condition

Hi @Ghofrane GSOURI 

I've attached a simplified single transfer with just 7 bytes, so we can see it better - 1 byte is the MCP3221 address (its correct) and we have 3 pairs of 2 bytes that are 0x000E (that is also the correct value). 

NMan11_0-1729514987936.png

What i need is a way to not send NAK and the Stop condition after the last byte, because that terminates the transfer.

EDIT: I tried edition the I2C_DMAXferCplt function, by removing everything that disabled interrupts or changed the state. This removed the stop condition, but not the NAK and it ended the transfer.