2024-09-05 09:48 AM - edited 2024-09-05 09:49 AM
Hi folks,
I'm trying to use a timer to generate a PWM output on a single output channel, but vary the frequency each cycle by updating the ARR register with DMA updates. I think I set everything up correctly, but when I call HAL_TIM_Base_Start_DMA(), I don't see any ARR updates [it remains at the value configured in CubeMX]. I also registered interrupts to trigger when the DMA is half-complete and fully-complete, and have them toggle GPIO pins. I see no triggering of the interrupts either. No errors reported when calling any of the HAL functions, so it looks like the updates are not being triggered for some reason. The U5 GPDMA has a lot more to configure than the DMA on the L5 I used previously, so quite possible I've misunderstood or misconfigured something.
Timer configuration:
The counter period of 2000 is just a placeholder I expected to get overwritten by the DMA update.
The GPDMA1 CH0 configuration:
I'm not sure if the trigger configuration is correct or even necessary. I've tried with it disabled, and with rising and falling edge trigger, with none of them having any obversable difference. Does TRGO set up UPDATE in the TIM2 configuration trigger this here? If so, does TIM2 trigger an update on start, or only after the first cycle?
My code is fairly simple: DMA buffer and interrupt handlers to toggle pins to see activity:
uint32_t samples[256];
void tim_dma_half(TIM_HandleTypeDef *htim) {
HAL_GPIO_TogglePin(TIMER_DMA_GPIO_Port, TIMER_DMA_Pin);
}
void tim_dma_full(TIM_HandleTypeDef *htim) {
HAL_GPIO_TogglePin(TIMER_UPDATE_GPIO_Port, TIMER_UPDATE_Pin);
}
and in the "USER CODE BEGIN 2" section of the CubeMX-generated main():
/* Compute sine waveform for varying the timer period */
float previous = asin(0.0f) * 2.0f / M_PI;
for (int i = 0; i < 256; ++i) {
float current = asin(((float)i)/255.0) * 2.0f / M_PI;
float diff = current - previous;
previous = current;
samples[i] = (uint32_t)(diff * 100000.0f);
}
HAL_StatusTypeDef status = HAL_DCACHE_InvalidateByAddr(&hdcache1, &samples[0], sizeof(samples));
if (status != HAL_OK) {
_NOP();
}
status = HAL_TIM_RegisterCallback(&STEP_TIMER, HAL_TIM_PERIOD_ELAPSED_HALF_CB_ID, tim_dma_half);
if (status != HAL_OK) {
__NOP();
}
status = HAL_TIM_RegisterCallback(&STEP_TIMER, HAL_TIM_PERIOD_ELAPSED_CB_ID, tim_dma_full);
if (status != HAL_OK) {
__NOP();
}
/* Toggle pins to indicate start on trace */
HAL_GPIO_TogglePin(TIMER_DMA_GPIO_Port, TIMER_DMA_Pin);
HAL_GPIO_TogglePin(TIMER_DMA_GPIO_Port, TIMER_DMA_Pin);
HAL_GPIO_TogglePin(TIMER_UPDATE_GPIO_Port, TIMER_UPDATE_Pin);
HAL_GPIO_TogglePin(TIMER_UPDATE_GPIO_Port, TIMER_UPDATE_Pin);
/* Enable channel 1 */
TIM_CCxChannelCmd(STEP_TIMER.Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
/* Start TIM2 with DMA reload of ARR */
status = HAL_TIM_Base_Start_DMA(&STEP_TIMER, &samples[1], (sizeof(samples)/sizeof(samples[0]))-1);
if (status != HAL_OK) {
__NOP();
}
With a logic analyser, I see a steady frequency matching the 2ms period from the CubeMX configuration, not the varying frequency I expected:
I didn't find any examples of how to do this for the U5. If anyone has any pointers or suggestions that would be greatly appreciated.
Many thanks,
Roger
2024-09-05 10:23 AM - edited 2024-09-05 10:30 AM
TIP: It may be more convenient to initially monitor ARR using live expressions instead of relying on a logic analyzer, if all you want to do is verify that its value jumps around. An expression like htim2->Instance->ARR should work.
note that prescaler values are always off by one. so a value of 160 actually divides by 161 - is that what you intended?
I think this is true for ARR as well (ARR=0 does nothing, while ARR=1 divides by 2).
TIM_CCxChannelCmd implies use as capture/compare, is that the right HAL function to call here?
I'd use "Live Expressions" to check whether the timer's CNT is counting up as expected after the call to HAL_TIM_Base_Start_DMA.
Have you manually verified that the calculates values stored in samples are reasonable?
You already have TIM2_UP set up as the request source for the DMA channel. I think defining a trigger for the DMA channel acts here as additional "gating" of the request, which is not what you want. The docs are very undisciplined about mixing these two terms ("request" and "trigger").
Also, check the value of TIM2_DIER.UDE. The generated code should be setting it for you, based on your configuration, but you should double-check.
2024-09-05 10:47 AM
> HAL_DCACHE_InvalidateByAddr
Surely you should clean the cache instead of invalidating it directly after you wrote values. Probably not the primary issue here.
When you debug, is the timer started? Is the hdma structure started? Does NDTR change values?
TIM_CCxChannelCmd should not be called directly, but also probably isn't the primary issue here.
2024-09-05 11:57 AM
Hi,
Thanks for replying. You're absolutely right about the PSC and ARR being off-by-one, my oversight there.
TIM_CCxChannelCmd might well be wrong; but I couldn't see how to enable the base timer with DMA ARR updates, and have the channel enabled at the same time unless I enabled it first. If you enable the channel it will start the timer without DMA ARR updates, and that will likely generate unwanted pulses. If there's a better way to do this, I'd be very interested to know how to do it.
I'll look at using live expressions or data watchpoints. I was pausing the debugger and looking at the live expression after a certain time. But the values are all the initial values, I see no evidence of them changing if I start and stop again. Looking at the GPDMA registers, it looks like it has all of the initial start addresses for source and destination and the number of values to transfer is the initial value--it seems it's all set up to go but hasn't actually been triggered to do anything!
"samples" is filled with usable values, checked in the debugger.
I'll disable the "trigger" in the GPDMA channel configuration, and see if I can find better documentation of what it's intended for; the GPDMA docs I've seen so far don't really go into any detail about this.
TIM2_DIER.UDE is definitely enabled.
Regards,
Roger
2024-09-05 12:14 PM
Hi,
Thanks for the reply. You're certainly right about the cache; I switched it to using HAL_DCACHE_CleanByAddr() instead.
The timer is definitely started; I can see CNT incrementing, and I can also see the PWM output from the channel. By the way I only used TIM_CCxChannelCmd here to enable the channel output before starting the base timer with DMA updates. If there's a better way of achieving this with the HAL API, I would be interested to know what is the recommended way to do this.
I couldn't see "NDTR" in the registers or the HAL structures. Is this the hdma ready state?
After HAL initialisation, I see this:
And then after starting the timer with DMA:
All of the DMA source and destination addresses and sample count are correct as the initial values, but it looks clear that no transfers took place.
I see that there is an error code of 4 in the hdma structure, so that's likely an indicator of the fault.
Thanks,
Roger
2024-09-05 12:24 PM
> I see that there is an error code of 4 in the hdma structure, so that's likely an indicator of the fault.
Indicates a user setting error:
#define HAL_DMA_ERROR_USE (0x0004U) /*!< User setting error */
In that case, HAL has determined something is amiss. Probably stepping through HAL_TIM_Base_Start_DMA would show where this happens and why. Possibly a CubeMX generation issue.