cancel
Showing results for 
Search instead for 
Did you mean: 

I2C DMA is Busy

mihnea95rusu
Associate II
Posted on June 11, 2015 at 06:52

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 amaster 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 followingthat nothing happens andthe I2C lines just very slowlyexponentially 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.
2 REPLIES 2
Posted on June 11, 2015 at 09:05

Do you have a debugger? Place a breakpoint in the respective DMA channel interrupt service routine.

JW

mihnea95rusu
Associate II
Posted on June 11, 2015 at 19:29

Thanks for your response. It appears that the entire issue was due to me not having initialised the DMA NVIC. I thought that since I didn't need the interrupt in my program I could get away without initialising it. I clearly need to read up more on how DMA works. 🙂