cancel
Showing results for 
Search instead for 
Did you mean: 

PWM/DMA interrupts when using HAL_TIM_PWM_Start_DMA(...)

aaron1111
Associate II

Hi all,

(I am using a STM32L031) I'm trying to design a system that receives a UART string and uses this data to drive a WS2812 LED strip at different colors depending on the string. So far, I've done a lot of googling and found that a lot of people are using HAL_TIM_PWM_Start_DMA() to vary duty cycles of PWM for similar purposes. I've done all the peripheral setup required I think and am able to get a PWM signal going (see attached .png) I have a couple of issues though - The PWM never stops even though HAL_TIM_PWM_Start_DMA() requires a length to be passed in. Now, I've checked that the DMA transfer completes, by setting a breakpoint in the IRQ handler like so:

void TIMx_DMA_IRQHandler(void)
{
	if (__HAL_DMA_GET_IT_SOURCE(TimHandle.hdma[TIM_DMA_ID_CC3],DMA_IT_TC)){
		//
		if (TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback != NULL){
			//other = other+1;
			//this following callback does nothing...
			TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback(TimHandle.hdma[TIM_DMA_ID_CC3]);
			HAL_TIM_PWM_Stop_DMA(&TimHandle, TIM_CHANNEL_3);
			//HAL_Delay(1000);
		}
	}
  HAL_DMA_IRQHandler(TimHandle.hdma[TIM_DMA_ID_CC3]);
}

And if I uncomment 'other' then I can breakpoint and see that it increments after each transfer completes. However in the IRQ_Handler, my callback is never called. Question 1 is: is there any setup/configuration for callbacks required besides throwing this in my main

TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback  = TransferComplete;

where the callback function is defined like this in main.c

static void TransferComplete(DMA_HandleTypeDef *DmaHandle){
	other = other+1;	//never reached
}

because TransferComplete never gets called...

Question 2: Now that I know that PWM is "working", how do I get it to stop completely after a single transfer occurs? As you can see, I have tried adding a HAL_TIM_PWM_Stop_DMA(...) in the if a transfer completes, but it has seemingly no affect. Or maybe it does, but HAL_TIM_PWM_Start_DMA(...) is called so soon after there is no perceptible pause. To fix this, I added a HAL_Delay(1000) in the interrupt handler (you can see I commented it out for now). Now the HAL user manual for the STM32L031 states that you need to be careful about using HAL_Delay when PWM is running, or in an interrupt. So I added this to give priority:

NVIC_SetPriority(SysTick_IRQn, 0);
  NVIC_EnableIRQ(SysTick_IRQn);

which I assumed would stop the process from hanging if a delay is added in the interrupt. But it doesn't.

Here is the loop I'm using to try and delay so that I can load the next buffer:

while (1)
  {
		if (HAL_TIM_PWM_Start_DMA(&TimHandle, TIM_CHANNEL_3, buffer_1, 24*8) != HAL_OK)
	    {
	      Error_Handler();
	    }
	  TogglePixelBufferAddress();
	  HAL_Delay(5000);
	  	  if (HAL_TIM_PWM_Start_DMA(&TimHandle, TIM_CHANNEL_3, buffer_2, 24*8) != HAL_OK)
		{
		  Error_Handler();
		}
		TogglePixelBufferAddress();
		HAL_Delay(5000);
  }
}

and here is TogglePixelBufferAddress:

void TogglePixelBufferAddress(){
	__HAL_DMA_DISABLE(TimHandle.hdma[TIM_DMA_ID_CC3]);
	if (flag==0){
		TimHandle.hdma[TIM_DMA_ID_CC3]->Instance->CMAR = (uint32_t)(buffer_1);
		__HAL_DMA_ENABLE(TimHandle.hdma[TIM_DMA_ID_CC3]);
		flag = 1;
	}else{
		TimHandle.hdma[TIM_DMA_ID_CC3]->Instance->CMAR = (uint32_t)(buffer_2);
		__HAL_DMA_ENABLE(TimHandle.hdma[TIM_DMA_ID_CC3]);
		flag = 0;
	}
}

Thanks for reading, and if anyone could provide any insight, with any of this, it would be very much appreciated

Best Regards,

Aaron

edit1: I was able to use HAL_Delay(...) within the interrupt by using

NVIC_SetPriority(DMA1_Channel1_IRQn, 1);

which I should have done initially. This now means that there is a delay between stopping and starting the DMA, however, my oscilloscope still shows that the PWM never stops. I find this odd...

edit2: I've managed to stop the PWM after completing one transaction: actually the while(1) loop isn't required at all. Instead I started the TIM_PWM_Start_DMA(...) outside of a loop, and then stopped it in the transfer complete interrupt. Probably I throw all of the logic inside of the interrupt and configure it to be at the lowest priority, which means it won't block anything else important

1 ACCEPTED SOLUTION

Accepted Solutions

I've managed to fix the callback - It seems as though by default, even by writing

    TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback  = TransferComplete;

the callback will be ignored or overwritten by the default weak pointer. The fix to this is to go to the ..._hal_<peripheral>.c and modify the function you want to trigger a callback. In my case, I am using HAL_TIM_PWM_Start_DMA(...) so I went to this function definition in the stm32l0xx_hal_tim.c and then registered the callback. Inside of the switch, I changed the callback for the particular channel I was using - the original reads like this:

htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback = TIM_DMADelayPulseCplt;

which I guess assigns the callback to the empty TIM_DMADelayPulseCplt. Instead, I wanted my custom callback to be invoked, so I changed it to this:

HAL_DMA_RegisterCallback( htim->hdma[TIM_DMA_ID_CC3],HAL_DMA_XFER_CPLT_CB_ID, htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback);

And make sure to actually assign to the callback function pointer contained within the DMA handler like mentioned above:

TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback  = TransferComplete;

Then it is just a matter of having a user-defined callback function in main.c or whatever that can be called. Hope this can help someone else who might come across this issue

View solution in original post

3 REPLIES 3

The basic thing is that timer is running and generating PWM infinitely, until you either stop it by writing 0 to TIMx_CR1.CEN, or by setting the timer to On-Pulse Mode, (but then you have to enable it after each period again).

The DMA just writes in the TIM registers upon triggers coming from the same TIM.

Perhaps read the related chapters in Reference Manual.

I don't Cube.

JW

Hi JW,

Thanks for the reply, I've read the reference manual chapters on DMA, to be honest I didn't read the chapters on TIM - I managed to get the PWM to stop by writing HAL_TIM_PWM_Stop_DMA(...) in my transfer complete IRQ. Internally, this function calls __HAL_DMA_DISABLE which in turn disables the DMA. The only issue I have now is invoking the callback...

I've managed to fix the callback - It seems as though by default, even by writing

    TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback  = TransferComplete;

the callback will be ignored or overwritten by the default weak pointer. The fix to this is to go to the ..._hal_<peripheral>.c and modify the function you want to trigger a callback. In my case, I am using HAL_TIM_PWM_Start_DMA(...) so I went to this function definition in the stm32l0xx_hal_tim.c and then registered the callback. Inside of the switch, I changed the callback for the particular channel I was using - the original reads like this:

htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback = TIM_DMADelayPulseCplt;

which I guess assigns the callback to the empty TIM_DMADelayPulseCplt. Instead, I wanted my custom callback to be invoked, so I changed it to this:

HAL_DMA_RegisterCallback( htim->hdma[TIM_DMA_ID_CC3],HAL_DMA_XFER_CPLT_CB_ID, htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback);

And make sure to actually assign to the callback function pointer contained within the DMA handler like mentioned above:

TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback  = TransferComplete;

Then it is just a matter of having a user-defined callback function in main.c or whatever that can be called. Hope this can help someone else who might come across this issue