AnsweredAssumed Answered

I2C DMA is Busy

Question asked by rusu.mihnea on Jun 11, 2015
Latest reply on Jun 11, 2015 by rusu.mihnea
Hey all,

Firstly I've tried the following on STM32F303 and STM32F072 and it's the same result on both. The MCU is configured as a master and I'm trying to send I2C commands to an SSD1306 based OLED display.

I've tried using DMA to do this but the controller becomes HAL_BUSY after the first packet transfer. I can initialize the OLED via polling mode operation, and then use DMA to send one display buffer (192 bytes + 1 control byte). This locks the DMA in the HAL_BUSY state and it becomes unusable until the MCU is reset. I've also tried initializing with DMA, and the same thing happens, after just one command (sent as a single DMA packet) i.e. 1+1 bytes of data.

I've checked this on the scope, and as I mentioned: the first DMA packet transfers fine, be it 193 bytes or 2 bytes, but following that nothing happens and the I2C lines just very slowly exponentially decrease down to 0. I've tried these operations both at 100kHz and 400kHz without any difference.

My code is generated from CubeMX and is shown below:
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
  
  GPIO_InitTypeDef GPIO_InitStruct;
  if(hi2c->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */
  
  /* USER CODE END I2C1_MspInit 0 */
    /* Peripheral clock enable */
    __I2C1_CLK_ENABLE();
    
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  
    __HAL_SYSCFG_FASTMODEPLUS_ENABLE(HAL_SYSCFG_FASTMODEPLUS_I2C_PB6);
  
    __HAL_SYSCFG_FASTMODEPLUS_ENABLE(HAL_SYSCFG_FASTMODEPLUS_I2C_PB7);
  
    /* Peripheral DMA init*/
    
    hdma_i2c1_tx.Instance = DMA1_Channel2;
    hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_i2c1_tx.Init.Mode = DMA_NORMAL;
    hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    HAL_DMA_Init(&hdma_i2c1_tx);
  
    __HAL_REMAPDMA_CHANNEL_ENABLE(HAL_REMAPDMA_I2C1_TX_DMA1_CH2);
  
    __HAL_LINKDMA(hi2c,hdmatx,hdma_i2c1_tx);
  
    hdma_i2c1_rx.Instance = DMA1_Channel5;
    hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_i2c1_rx.Init.Mode = DMA_NORMAL;
    hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    HAL_DMA_Init(&hdma_i2c1_rx);
  
    __HAL_REMAPDMA_CHANNEL_ENABLE(HAL_REMAPDMA_I2C1_RX_DMA1_CH5);
  
    __HAL_LINKDMA(hi2c,hdmarx,hdma_i2c1_rx);
  
  /* USER CODE BEGIN I2C1_MspInit 1 */
  
  /* USER CODE END I2C1_MspInit 1 */
  }
  
}

/** 
  * Enable DMA controller clock
  */
void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __DMA1_CLK_ENABLE();
  
  /* DMA interrupt init */
  
}

/* I2C1 init function */
void MX_I2C1_Init(void)
{
  
  hi2c1.Instance = I2C1;
  hi2c1.Init.Timing = 0x2000090E;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLED;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLED;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLED;
  HAL_I2C_Init(&hi2c1);
  
    /**Configure Analogue filter 
    */
  HAL_I2CEx_AnalogFilter_Config(&hi2c1, I2C_ANALOGFILTER_ENABLED);
  
}

A display buffer is sent as such:
    /* Use DMA to send the buffer, in order to save processor cycles; 
    NOTE: We embed the control byte (0x40) in the DMA command by having it write to a memory location */
    if (HAL_I2C_Mem_Write_DMA(OLED_Struct->hi2c, SSD1306_I2C_ADDRESS, (uint16_t)0x40, I2C_MEMADD_SIZE_8BIT, DisplayBuffer, DISPLAY_BUFFER_SIZE) != HAL_OK)
        {
            // Writing process Error 
            OLED_Error_Handler();
        
          
//  /* Polling mode operation */
//  if(HAL_I2C_Mem_Write(OLED_Struct->hi2c, SSD1306_I2C_ADDRESS, (uint16_t)0x40, I2C_MEMADD_SIZE_8BIT, DisplayBuffer, DISPLAY_BUFFER_SIZE, 100) != HAL_OK) {
//      OLED_Error_Handler();
//  }

Commands are sent in a similar fashion, so I won't show those as well. If anyone has any insight on why the DMA controller always becomes busy after the first packet, I'd really appreciate it. I could seriously use the extra speed.

Thanks.

Outcomes