cancel
Showing results for 
Search instead for 
Did you mean: 

Wrong (duplicated) PWM pulse

cristianosar
Associate

I'm a software developer and love electronics, specifically making metal detectors. I'm trying to develop a new Pulse Induction metal detector and I'm using this development board (STM32F407VET6).

Although I'm a software developer, I work with high level programming languages like Javascript and Python, so programming in plain C (register manipulation) is not easy for me, but I want to learn, so, I'm building the code with the help of AI (ChatGpt).

The code below is supposed to generate 2 pulses with PWM inside a 1ms pulse period. One pulse is 100us (or the metal detector TX pulse) generated to PE13 and another is 500us (for the metal detector power supply charge pump) generated to PA8.

I tested the pulse generation with an oscilloscope 100uS/Div and the first pulse is correct (100us) but it appears one time at the start and another at the end of the and it should appear only one time at the start of the period. I tried everything with the IA but I can't solve this problem and don't understand why it happens.

So, can someone look at my code and give an idea of the problem source?

#include "stm32f4xx.h"

// Function Prototypes
void SystemClock_Config(void);
void GPIO_Init(void);
void TIM1_PWM_Init(void);

int main(void) {
    // Configure system clock
    SystemClock_Config();

    // Initialize GPIO (PE13 as TIM1_CH3 Alternate Function)
    GPIO_Init();

    // Initialize TIM1 for PWM on PE13
    TIM1_PWM_Init();

    while (1) {
        // PWM runs automatically in hardware
    }
}

void SystemClock_Config(void) {
    // Enable HSE (High-Speed External Clock)
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY));  // Wait for HSE to be ready

    // Configure PLL: PLL_M = 8, PLL_N = 336, PLL_P = 2, PLL_Q = 7
    RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos) |  // PLLM = 8
                   (336 << RCC_PLLCFGR_PLLN_Pos) | // PLLN = 336
                   (0 << RCC_PLLCFGR_PLLP_Pos) |   // PLLP = 2 (00)
                   (7 << RCC_PLLCFGR_PLLQ_Pos) |   // PLLQ = 7
                   RCC_PLLCFGR_PLLSRC_HSE;         // Use HSE as PLL source

    // Enable PLL
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR & RCC_CR_PLLRDY));  // Wait for PLL to lock

    // Set Flash Latency and Enable Prefetch Buffer
    FLASH->ACR |= FLASH_ACR_LATENCY_5WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;

    // Set system clock to PLL
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL));  // Wait for PLL to be used as SYSCLK

    // Set AHB = 168 MHz, APB1 = 42 MHz, APB2 = 84 MHz
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;    // AHB Prescaler = 1
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;   // APB1 Prescaler = 4 (42 MHz)
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;   // APB2 Prescaler = 2 (84 MHz)
}

void GPIO_Init(void) {
    // Habilitar clock dos GPIOs A e E
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOEEN;

    // Configurar PE13 como função alternativa (TIM1_CH3)
    GPIOE->MODER &= ~(3 << (13 * 2));
    GPIOE->MODER |= (2 << (13 * 2));
    GPIOE->OSPEEDR |= (3 << (13 * 2));  // Alta velocidade
    GPIOE->AFR[1] &= ~(0xF << ((13 - 8) * 4));
    GPIOE->AFR[1] |= (1 << ((13 - 8) * 4)); // AF1 - TIM1_CH3

    // Configurar PA8 como função alternativa (TIM1_CH1)
    GPIOA->MODER &= ~(3 << (8 * 2));
    GPIOA->MODER |= (2 << (8 * 2));
    GPIOA->OSPEEDR |= (3 << (8 * 2));  // Alta velocidade
    GPIOA->AFR[1] &= ~(0xF << ((8 - 8) * 4));
    GPIOA->AFR[1] |= (1 << ((8 - 8) * 4)); // AF1 - TIM1_CH1
}

// testar abaixo (foi removido o charge pump pra teste tambem)
void TIM1_PWM_Init(void) {
    // Enable TIM1 clock
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    
    // Set TIM1 Prescaler for 1 MHz Timer Clock (168 MHz / 168 = 1 MHz)
    TIM1->PSC = (84 * 2) - 1;
    
    // Set TIM1 Period for 1 kHz PRF (1 ms period)
    TIM1->ARR = 999;
    
    // Set Duty Cycle to 100 µs Pulse Width (10% of 1 ms)
    TIM1->CCR3 = 100;
    
    // Configure PWM Mode 1 (edge-aligned)
    TIM1->CCMR2 &= ~TIM_CCMR2_OC3M;  
    TIM1->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos);  // PWM mode 1
    TIM1->CCMR2 |= TIM_CCMR2_OC3PE;            // Enable preload
    
    // Set output polarity (active high)
    TIM1->CCER &= ~TIM_CCER_CC3P;
    TIM1->CCER |= TIM_CCER_CC3E;               // Enable output
    
    // Force edge-aligned mode
    TIM1->CR1 &= ~TIM_CR1_CMS;                 // Up-counting mode
    TIM1->CR1 |= TIM_CR1_ARPE;                 // Auto-reload preload enable
    
    // Main output enable
    TIM1->BDTR |= TIM_BDTR_MOE;
    
    // Generate update event to load all settings
    TIM1->EGR |= TIM_EGR_UG;
    
    // Start timer
    TIM1->CR1 |= TIM_CR1_CEN;
}

Please don't mind the code comments.

Thanks!

10 REPLIES 10

Try to post a screenshot from the oscilloscope and explain (perhaps make a drawing) of how it is different from the expectations.

JW

Below are the actual pulses and the drawing is what I expect:

PE13PE13

PA8PA8

ExpectedExpected

gbm
Principal

The scope screen shows one full period and the beginning of another period. I can't see anything wrong.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Carl_G
Senior

As others have stated, it looks like you are getting a regular PWM sequence instead of a single pulse. Can you confirm that your pulses keep going? Or are their only two?

 

In general the way ChatGPT has setup this code is wrong. One should never assume an initial value of any register. Set the complete value yourself. As it is, there is no way to know exactly what is in register CR1.

    // Force edge-aligned mode
    TIM1->CR1 &= ~TIM_CR1_CMS;                 // Up-counting mode
    TIM1->CR1 |= TIM_CR1_ARPE;                 // Auto-reload preload enable

In any event, you could try

    TIM1->CR1 |= (TIM_CR1_ARPE | TIM_CR1_OPM);                 // Auto-reload preload enable, One Pulse Mode Enabled

 

Thanks, the pulses keep going, What I dont understand is why I see the 100uS pulse 2 times in the timeline ( of 1ms ) where in my understanding I should see it only one time in this time window. Or maybe I understand things wrong.

MM..1
Chief III

I think your understanding is correct. There should only be 1 pulse within the 1ms window based on the configuration I see. Obviously, something is not configured correctly, but its not jumping out at me. technically the duty cycle also gets a '-1' so

TIM1->CCR3 = 100-1;

But I don't see that as a contributing factor. Where is your configuration for the 500us pulse?

Are you sure your scope plots match the code? I don't see any way to get other than a 1/10 duty cycle with the ARR and CCR2 set as they are. However, you are showing 1/9. Your registers don't match your scope plot. Be sure you are setting up the correct CCR register for the signal you are plotting.

I'm sorry, maybe the code I have send was some experiments and was not the most updated. Please look at the code below that is the one that generated the oscilloscope images:

#include "stm32f4xx.h"

// Function Prototypes
void SystemClock_Config(void);
void GPIO_Init(void);
void TIM1_PWM_Init(void);

int main(void) {
    // Configure system clock
    SystemClock_Config();

    // Initialize GPIO (PE13 as TIM1_CH3 Alternate Function)
    GPIO_Init();

    // Initialize TIM1 for PWM on PE13
    TIM1_PWM_Init();

    while (1) {
        // PWM runs automatically in hardware
    }
}

void SystemClock_Config(void) {
    // Enable HSE (High-Speed External Clock)
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY));  // Wait for HSE to be ready

    // Configure PLL: PLL_M = 8, PLL_N = 336, PLL_P = 2, PLL_Q = 7
    RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos) |  // PLLM = 8
                   (336 << RCC_PLLCFGR_PLLN_Pos) | // PLLN = 336
                   (0 << RCC_PLLCFGR_PLLP_Pos) |   // PLLP = 2 (00)
                   (7 << RCC_PLLCFGR_PLLQ_Pos) |   // PLLQ = 7
                   RCC_PLLCFGR_PLLSRC_HSE;         // Use HSE as PLL source

    // Enable PLL
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR & RCC_CR_PLLRDY));  // Wait for PLL to lock

    // Set Flash Latency and Enable Prefetch Buffer
    FLASH->ACR |= FLASH_ACR_LATENCY_5WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;

    // Set system clock to PLL
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL));  // Wait for PLL to be used as SYSCLK

    // Set AHB = 168 MHz, APB1 = 42 MHz, APB2 = 84 MHz
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;    // AHB Prescaler = 1
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;   // APB1 Prescaler = 4 (42 MHz)
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;   // APB2 Prescaler = 2 (84 MHz)
}

void GPIO_Init(void) {
    // Habilitar clock dos GPIOs A e E
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOEEN;

    // Configurar PE13 como função alternativa (TIM1_CH3)
    GPIOE->MODER &= ~(3 << (13 * 2));
    GPIOE->MODER |= (2 << (13 * 2));
    GPIOE->OSPEEDR |= (3 << (13 * 2));  // Alta velocidade
    GPIOE->AFR[1] &= ~(0xF << ((13 - 8) * 4));
    GPIOE->AFR[1] |= (1 << ((13 - 8) * 4)); // AF1 - TIM1_CH3

    // Configurar PA8 como função alternativa (TIM1_CH1)
    GPIOA->MODER &= ~(3 << (8 * 2));
    GPIOA->MODER |= (2 << (8 * 2));
    GPIOA->OSPEEDR |= (3 << (8 * 2));  // Alta velocidade
    GPIOA->AFR[1] &= ~(0xF << ((8 - 8) * 4));
    GPIOA->AFR[1] |= (1 << ((8 - 8) * 4)); // AF1 - TIM1_CH1
}

void TIM1_PWM_Init(void) {
    // Enable TIM1 clock
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    
    // Set TIM1 Prescaler for 1 MHz Timer Clock (168 MHz / 168 = 1 MHz)
    TIM1->PSC = (84 * 2) - 1;
    
    // Set TIM1 Period for 1 kHz PRF (1 ms period)
    // TIM1->ARR = 1000 - 1;
    TIM1->ARR = 999; // como começa em zero, tem que ser 999 e nao 1000

    // Set Duty Cycle to 100 µs Pulse Width (10% of 1 ms) - TX pulse
    // TIM1->CCR3 = 100;
    TIM1->CCR3 = (10 * (TIM1->ARR + 1)) / 100;

    // Configura PWM da Charge Pump (PA8 - CH1) para 500us (50% de 1ms)
    // TIM1->CCR1 = (TIM1->ARR + 1) / 2;  // Charge Pump (PA8) - Exatamente 50%, poderia ser 500 diretamente
    TIM1->CCR1 = TIM1->ARR / 2;
    // mas dessa forma garante os 50% exatamente (senão nao ficava exato, por causa de arredondamentos)
    
    // Configura PWM Mode 1 para os dois canais
    TIM1->CCMR1 &= ~TIM_CCMR1_OC1M;  
    TIM1->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos);  // PA8 - CH1 (Charge Pump)
    TIM1->CCMR1 |= TIM_CCMR1_OC1PE; 

    TIM1->CCMR2 &= ~TIM_CCMR2_OC3M;  
    TIM1->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos);  // PE13 - CH3 (TX)
    TIM1->CCMR2 |= TIM_CCMR2_OC3PE; 
    
    // Habilita os canais de saída
    TIM1->CCER |= TIM_CCER_CC1E;  // Habilita CH1 (PA8)
    TIM1->CCER |= TIM_CCER_CC3E;  // Habilita CH3 (PE13)

    // Set Active Low Polarity for Channel 3 (to invert logic)
    //TIM1->CCER |= TIM_CCER_CC3P;

    // Ensure correct polarity (active high)
    TIM1->CCER &= ~TIM_CCER_CC3P;  // Clear polarity bit (active high)

    // Set Repetition Counter para evitar múltiplos pulsos
    TIM1->RCR = 0;  // Apenas 1 pulso por ciclo de 1ms

    // Enable TIM1 Main Output (MOE)
    TIM1->BDTR |= TIM_BDTR_MOE;

    // Garante que o contador do timer reinicie corretamente (senao o pulso podia nao começar no inicio)
    TIM1->EGR |= TIM_EGR_UG;

    // Set Repetition Counter for Adjustable PRF (incluir apenas se precisar usar interrupts)
    // TIM1->RCR = 9;  // Repeat every 10 cycles (Adjust for PRF)

    // Ensure edge-aligned mode (up-counting)
    TIM1->CR1 &= ~TIM_CR1_CMS;  // Clear center-aligned mode bits

    // Start TIM1
    TIM1->CR1 |= TIM_CR1_CEN;
}
gbm
Principal

You are seeing MORE THAN ONE PERIOD of your PWM on the oscilloscope screen.The pulse at the right side is the next period pulse.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice