Skip to main content
Explorer II
September 5, 2025
Solved

Inexplicable extra pulses when using PWM + DMA

  • September 5, 2025
  • 4 replies
  • 678 views

I'm try to generate some pulses to PB0 using STM32F103C8T6 with the following code

#include <stdint.h>
// STM32F103C8 is a medium density device, hence we use the xB header
#include <stm32f103xb.h>

#if !defined(__SOFT_FP__) && defined(__ARM_FP)
#warning \
 "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif

void enable_clock() {
 RCC->CR |= RCC_CR_HSION;
 while ((RCC->CR & RCC_CR_HSIRDY) == 0);
}

// @formatter:off
#define HI 6
#define LO 3
uint16_t buffer[] = {
 0, LO, HI, LO
};
// @formatter:on

void blink_dma_to_gpio() {
 enable_clock();

 // GPIO B configuration
 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Enable IO port B

 GPIOB->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0); // GPIO B: Clear pin 6 configuration
 GPIOB->CRL |= GPIO_CRL_CNF0_1 | GPIO_CRL_MODE0_0; // GPIO B: Set pin 6 to alternate function push-pull 10MHz output

 // TIM3 configuration
 RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // Enable TIM3
 RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // Enable alternate function

 TIM3->PSC = 1 - 1;
 TIM3->ARR = 10 - 1;

 TIM3->CR1 |= TIM_CR1_ARPE;
 TIM3->BDTR |= TIM_BDTR_MOE;

 TIM3->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 // PWM mode 1
 | TIM_CCMR2_OC3PE // Enable preload
 ;
 TIM3->CCER |= TIM_CCER_CC3E; // Enable capture compare 3

 TIM3->CCMR2 |= TIM_CCMR2_OC4M_0; // Active on match
 TIM3->CCER |= TIM_CCER_CC4E; // Enable capture compare 4
 TIM3->DIER |= TIM_DIER_CC4DE; // Enable DMA1 request
 TIM3->CCR4 = 7;

 // DMA1 configuration
 RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Enable DMA1

 DMA1_Channel3->CPAR = (uint32_t) &TIM3->CCR3;
 DMA1_Channel3->CMAR = (uint32_t) &buffer;
 DMA1_Channel3->CNDTR = sizeof(buffer) / sizeof(buffer[0]);

 DMA1_Channel3->CCR = DMA_CCR_PL // Very high priority
 | DMA_CCR_MSIZE_0 // 16 bit peripheral
 | DMA_CCR_PSIZE_0 // 16 bit peripheral
 | DMA_CCR_MINC // Increment memory
 | DMA_CCR_CIRC // Circular memory
 | DMA_CCR_DIR // Memory to peripheral
 | DMA_CCR_HTIE // Half transfer interrupt
 | DMA_CCR_TCIE // Transfer complete interrupt
 ;

 NVIC_EnableIRQ(DMA1_Channel3_IRQn);

 // Enable peripherals
 DMA1_Channel3->CCR |= DMA_CCR_EN;
 TIM3->CR1 |= TIM_CR1_CEN;
}

int main() {
 blink_dma_to_gpio();
 for (;;);
}

void DMA1_Channel3_IRQHandler() {
 if (DMA1->ISR & DMA_ISR_HTIF3) {
 DMA1->IFCR = DMA_IFCR_CHTIF3;
 }
 if (DMA1->ISR & DMA_ISR_TCIF3) {
 DMA1->IFCR = DMA_IFCR_CTCIF3;
 }
}

I expect there to be only 3 pulses from the beginning to the end of the DMA buffer. However, when hooking to an oscilloscope, the pulses output are inconsistent.

pic_130_6.gif 

Some pulses are right with short, long, and short pulses, but some pulses just have random long or short pulse in them. I can't see what's wrong with my code. Please let me know if there is anything potentially wrong with my code.

    This topic has been closed for replies.
    Best answer by bmckenney

    The DMA is pretty fast, but it isn't instantaneous. If it can't transfer the new compare value within a single TIM cycle (10 clocks in your case) the next cycle will re-use the stale compare value, resulting in a duplicate pulse.

    I don't recall seeing a specification (for any STM32) for how many bus clocks a DMA transfer takes, but 6-10 is not unusual for other MCUs. Judging by the pattern in your trace I'm guessing it's about 12.

    A quick experiment would be to slow down the timer by e.g. doubling the PSC (->(2-1)) to see if the symptom still appears.

    4 replies

    bmckenneyAnswer
    Explorer II
    September 5, 2025

    The DMA is pretty fast, but it isn't instantaneous. If it can't transfer the new compare value within a single TIM cycle (10 clocks in your case) the next cycle will re-use the stale compare value, resulting in a duplicate pulse.

    I don't recall seeing a specification (for any STM32) for how many bus clocks a DMA transfer takes, but 6-10 is not unusual for other MCUs. Judging by the pattern in your trace I'm guessing it's about 12.

    A quick experiment would be to slow down the timer by e.g. doubling the PSC (->(2-1)) to see if the symptom still appears.

    BeartamaAuthor
    Explorer II
    September 5, 2025

    Yes, putting (12 -1) to the auto reload register actually makes it much more stable and fixes the issue. Thank you very much.

    BeartamaAuthor
    Explorer II
    September 5, 2025
    Explorer II
    September 5, 2025

    I hadn't seen this before. Thanks!