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 07:24 PM - edited 2024-09-05 07:38 PM
Since you're starting your dma at the second sample, I assumed 2000 was a large value placed there just for debugging, and that you would the set the initial value to match the value of the first sample, to make things seamless (especially if this will become a circular DMA).
As for why it takes two update events to sync up, you could disable "autoload preload" in the timer's CubeMX config pane to make writes to ARR take effect immediately but, depending on your waveform and timing, this may create glitches in the output.
> 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.
I may be misunderstanding, but to make sure - you do realize that "Live Expressions" (unlike plain "Expressions") are used to display updates continuously while the program is running? there's no need to pause the debugger. It's a feature of CubeIDE.