cancel
Showing results for 
Search instead for 
Did you mean: 

How to use DMA to update the PWM setting on a timer

SSchu.4
Associate II

Hi all,

I am using a PWM timer. That is initialized and works fine.

Code for init below:

static void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 39;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 13;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
 
  /* USER CODE END TIM2_Init 2 */
  HAL_TIM_MspPostInit(&htim2);
 
}

I start it with this call:

	if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK) {
		Error_Handler();
	}
	if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK) {
		Error_Handler();
	}

So far, so good, all works as expected. Now, I want to change the PWM pulse lenght dynamically

(I have an array of values for sConfigOC.Pulse). I found examples tha use DMA, but I really dont understand how they are working (and in my case, they dont).

So my question is: Can someone explain me in detail what needs to happen.

Questions are:

1) I can set up an DMA channel. CubeMX allows me to have TIM2_CH1 or TIM2_UP. What is the difference?

2) When I select TIM2_CH1, i can select the width of the requests on both sides. Not sure what to choose and more imporant why? The value I want to set in the register is uint32_t, so i assume, I go with word on periphal side and byte on memory side (using only values <256)?

3) How does DMA know it should copy the value in the CCR1 register? The link function that is generated by CubeMX points to the base adress of the registers.

I understand these are hard questions, but I really want to understand that inner workings, and any documentation if found left me with even more questionmarks.

Any help appreciated.

1 REPLY 1

Which STM32?

These are hard questions mainly because you have an unnecessary extra layer between hardware and software. Cube/HAL is intended for those who wish to achieve a working program by clicking in CubeMX, which for the "usual configurations" obviously works well. Anything beyond what is deemed by Cube authors as usual means, that Cube/HAL gets into way.

I don't use Cube/HAL, I'm going to use the RM's terminology and assume, you've already read the TIM and DMA chapters in RM.

  1. DMA performs a transfer (moves some data from source to destination) when a signal (trigger, request) is set. You can chose, whether this signal is the Compare event of CH1, or the Update event. To change TIMx_CCR1 using DMA, the Compare event is not very suitable - if you don't set CCR1 preload, and the newly written CCR1 value is above the previous one, another Compare event will happen within the same PWM cycle, which is not what you want. If you have set CCR1 preload and the old CCR1 value is slightly below top (ARR), it may happen that until the DMA machine succeeds in loading the new CCR1 value, Update has already passed, so the old CCR1 value is used in the new period, too; which is again not what you want. The usual way is to set CCR1 preload and use the Update event as DMA trigger/request, that ensures that the newly written CCR1 value gets always active exactly at the beginning of the next PWM cycle.
  2. The answer depends on the STM32 model. There are two different DMA models used in the various STM32 models (and a third one, together with the former two, in 'H7) and they behave differently in this regard. A failsafe setting suitable for all DMA variants is to set both sides to the same data width, according to the peripheral in question (32-bit in case of TIM2), and use values in memory accordingly, even if it's some waste of memory.
  3. The source and destination addresses are set in the (C)MAR/(C)PAR address registers for the given DMA channel/stream; which one is then source and which one is destination is given by the direction set in the control register. If you are asking about how to set this in Cube, well, refer to Cube's documentation, or maybe better, look it up, Cube is open source.

JW