cancel
Showing results for 
Search instead for 
Did you mean: 

Need help with specific I2C DMA setup on STM32U5

ruddy17
Associate II

Hello,

I am trying to implement the following: a timer (tim3) is set to PWM generation, outputting a pulse periodically. Its TRGO triggers the DMA channel to request I2C to initiate a read transaction, read 8 bytes and generate an event interrupt (TC probably) to process these data bytes. On the next PWM pulse the next 8 bytes should be read. Data should be put to a circular buffer (ideally 8 bytes long, so that data is just overwritten into the same memory buffer. Cycle should continue indefinitely.

I feel like this scenario should be implementable, however I don't seem to grasp how to set it up correctly. I have the timer generating pulses correctly, but no tinkering with GPDMA and I2C settings yields a working procedure, at best I manage to make I2C to trigger, but only read 2 bytes once and then not reacting to consecutive pulses. I don't ask to do my job for me, but at least could you confirm that it is doable and outline the correct way to set it up? Thx.

Here is how everything is set up now (pics attached) and how I start the cycle in the code:

HAL_I2C_Master_Receive_DMA(&hallSensorI2C, 0xC0, tempBuf, 8);
HAL_TIM_PWM_Start_IT(&trayTimer, TIM_CHANNEL_2);
5 REPLIES 5
ruddy17
Associate II

After some meditating over the reference manual, I got an idea. I think, to initiate a new transaction, I need to update I2C CR2 register each time. So, I think I need to make a linked list DMA queue, with a node writing something into CR2, and the next node to actually request Rx. Does it sound correct?

BarryWhit
Senior III

According to AN5593:

I2C.jpg

A second issue is that, If you want to perform a DMA read from the I2C Bus to memory, you probably want to enable increment address on the destination.

 

 

- If someone's post helped resolve your issue, please thank them by clicking "Accept as Solution".
- Please post an update with details once you've solved your issue. Your experience may help others.

Is there any appnote or example that uses EVC and RX requests to control I2C with automatic transaction handling? I couldn't find any, all I found is transaction initiated from code, and only moving data via DMA.

BarryWhit
Senior III

Oh not at all, not at all. You're very welcome.

- If someone's post helped resolve your issue, please thank them by clicking "Accept as Solution".
- Please post an update with details once you've solved your issue. Your experience may help others.
kfruchtnis
Associate II

Hi. I have a similar project, where the DMA needs to change the BSRR GPIO register, on TIM15 TRGO.
I almost got it, but the DMA does not get enabled by the timer triggers.
There is a single TC event from the DMA that gets triggered, and that's it.
Only with a workaround, which re-enables the DMA on TC (transaction complete) event, does the project work.
without it, the DMA does not do any work.

with it, the DMA is triggered by TIM15 properly and toggles the GPIOs properly.
Here is the DMA init code

 

static void MX_GPDMA1_Init(void)
{

  /* USER CODE BEGIN GPDMA1_Init 0 */

  /* USER CODE END GPDMA1_Init 0 */

  /* Peripheral clock enable */
  __HAL_RCC_GPDMA1_CLK_ENABLE();

  /* GPDMA1 interrupt Init */
#ifdef DMA_INT_EN
  HAL_NVIC_SetPriority(GPDMA1_Channel4_IRQn, 14, 14);
  HAL_NVIC_EnableIRQ(GPDMA1_Channel4_IRQn);
  HAL_NVIC_SetPriority(GPDMA1_Channel5_IRQn, 13, 13);
  HAL_NVIC_EnableIRQ(GPDMA1_Channel5_IRQn);
#endif
  /* USER CODE BEGIN GPDMA1_Init 1 */

  /* USER CODE END GPDMA1_Init 1 */
  handle_GPDMA1_Channel5.Instance = GPDMA1_Channel5;
  handle_GPDMA1_Channel5.InitLinkedList.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
  handle_GPDMA1_Channel5.InitLinkedList.LinkStepMode = DMA_LSM_1LINK_EXECUTION;
  handle_GPDMA1_Channel5.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
  handle_GPDMA1_Channel5.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
  handle_GPDMA1_Channel5.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
  if (HAL_DMAEx_List_Init(&handle_GPDMA1_Channel5) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel5, DMA_CHANNEL_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }

  handle_GPDMA1_Channel4.Instance = GPDMA1_Channel4;
  handle_GPDMA1_Channel4.InitLinkedList.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
  handle_GPDMA1_Channel4.InitLinkedList.LinkStepMode = DMA_LSM_1LINK_EXECUTION;
  handle_GPDMA1_Channel4.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
  handle_GPDMA1_Channel4.InitLinkedList.TransferEventMode = DMA_TCEM_EACH_LL_ITEM_TRANSFER;
  handle_GPDMA1_Channel4.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
  if (HAL_DMAEx_List_Init(&handle_GPDMA1_Channel4) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel4, DMA_CHANNEL_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN GPDMA1_Init 2 */

#ifdef DMA_INT_EN
  // Register DMA callbacks

  HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel4, HAL_DMA_XFER_CPLT_CB_ID, HAL_DMA_ConvCpltCallback);
  HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel5, HAL_DMA_XFER_CPLT_CB_ID, HAL_DMA_ConvCpltCallback);
  // HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel4, HAL_DMA_XFER_ERROR_CB_ID, HAL_DMA_ErrorCallback);
  // HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel5, HAL_DMA_XFER_ERROR_CB_ID, HAL_DMA_ErrorCallback);

#endif

  /* USER CODE END GPDMA1_Init 2 */

}

 

Here is the ISR with the workaround code

void HAL_DMA_ConvCpltCallback(DMA_HandleTypeDef *hdma)
{
  // Workaround - re-enable DMA
  // __HAL_DMA_ENABLE(hdma);
  __HAL_DMA_ENABLE(&handle_GPDMA1_Channel4);
  __HAL_DMA_ENABLE(&handle_GPDMA1_Channel5);
}

Here is TIM15 init code

 

static void MX_TIM15_Init(void)
{

  /* USER CODE BEGIN TIM15_Init 0 */

  /* USER CODE END TIM15_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM15_Init 1 */

  /* USER CODE END TIM15_Init 1 */
  htim15.Instance = TIM15;
  htim15.Init.Prescaler = 49999;
  htim15.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim15.Init.Period = 799;
  htim15.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim15.Init.RepetitionCounter = 0;
  htim15.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim15) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim15, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM15_Init 2 */


  // Enable the update DMA request
  __HAL_TIM_ENABLE_DMA(&htim15, TIM_DMA_UPDATE);

  // Enable the update interrupt (UIE)
#ifdef TIM_INT_EN
  HAL_NVIC_SetPriority(TIM15_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM15_IRQn);
#endif
  /* USER CODE END TIM15_Init 2 */

}

 


Here is the DMA queue code

 

 

HAL_StatusTypeDef MX_led_dma_queue_Config(DMA_QListTypeDef * p_led_dma_queue, const uint32_t dst_addr, uint16_t seq_len,
                                          const uint32_t seq[])
{
  HAL_StatusTypeDef ret = HAL_OK;
  /* DMA node configuration declaration */
  DMA_NodeConfTypeDef pNodeConfig = {0};

  /* Set node configuration ################################################*/
  pNodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
#ifdef USE_TIM15_AS_TRGO
  pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_RISING;
  pNodeConfig.TriggerConfig.TriggerSelection = GPDMA1_TRIGGER_TIM15_TRGO;
  pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM15_UP;
#else
  pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
#endif
  pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; // Single burst per trigger
  pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
  pNodeConfig.Init.SrcInc = DMA_SINC_INCREMENTED;
  pNodeConfig.Init.DestInc = DMA_DINC_FIXED;
  pNodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
  pNodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
  pNodeConfig.Init.SrcBurstLength = 1;
  pNodeConfig.Init.DestBurstLength = 1;
  pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
  pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
  pNodeConfig.TriggerConfig.TriggerMode = DMA_TRIGM_SINGLE_BURST_TRANSFER;
  pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
  pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
  pNodeConfig.DstAddress = dst_addr;
  pNodeConfig.DataSize = seq_len * sizeof(seq[0]);

  if ( (node_idx+1) == MAX_NODES)
    ret |= HAL_ERROR;

  /* Build node */
  pNodeConfig.SrcAddress = (uint32_t)seq;
  ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &dma_queue_node[node_idx]);

  /* Insert node to Queue */
  ret |= HAL_DMAEx_List_InsertNode_Tail(p_led_dma_queue, &dma_queue_node[node_idx]);

  /* Define the first circular node of the queue */
  ret |= HAL_DMAEx_List_SetCircularModeConfig(p_led_dma_queue, &dma_queue_node[node_idx]);
  node_idx++;
  return ret;
}

 

 here is main()

 

int main(void)
{

  /* USER CODE BEGIN 1 */
  /* STM32U5xx HAL library initialization:
       - Configure the Flash prefetch
       - Configure the Systick to generate an interrupt each 1 msec
       - Set NVIC Group Priority to 3
       - Low Level Initialization
     */
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
#warning uncomment to have logs back
  // SWO_Init();
  /* USER CODE END Init */


  /* Configure the System Power */
  SystemPower_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_GPDMA1_Init();
  /* Configure the system clock */
  SystemClock_Config();
  MX_ICACHE_Init();
#ifdef USE_TIM15_AS_TRGO
  MX_TIM15_Init();
#endif
  /* USER CODE BEGIN 2 */

  printf("hello\n");
  fflush(stdout);

  if ( MX_led_dma_queue_Config(&led_dma_queue_port_0, LED1_GPIO_BSRR, BUFFER_SIZE, led1_toggle_buffer) != HAL_OK )
  {
    Error_Handler();
  }
  if ( MX_led_dma_queue_Config(&led_dma_queue_port_1, LED2_GPIO_BSRR, BUFFER_SIZE, led2_toggle_buffer) != HAL_OK )
  {
    Error_Handler();
  }
  if ( HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel4, &led_dma_queue_port_0) != HAL_OK )
  {
    Error_Handler();
  }
  if ( HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel5, &led_dma_queue_port_1) != HAL_OK )
  {
    Error_Handler();
  }
   /* -1- Enable GPIO Clock (to be able to program the configuration registers) */
  LED1_GPIO_CLK_ENABLE();
  LED2_GPIO_CLK_ENABLE();

  /* -2- Configure IO in output push-pull mode to drive external LEDs */
  GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull  = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

  GPIO_InitStruct.Pin = LED1_PIN;
  HAL_GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = LED2_PIN;
  HAL_GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStruct);


  HAL_Delay(100);

  start_dma_transfer();

#ifdef USE_TIM15_AS_TRGO
  #ifdef TIM_INT_EN
    if (HAL_TIM_Base_Start_IT(&htim15) != HAL_OK)
  #else
    if (HAL_TIM_Base_Start(&htim15) != HAL_OK)
  #endif
  {
    Error_Handler();
  }
#endif
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_PIN);
    /* Insert delay 100 ms */
    // HAL_Delay(100);
    // HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_PIN);
    /* Insert delay 100 ms */
    HAL_Delay(1);

    // start_dma_transfer();
  }
  /* USER CODE END 3 */
}