Issue with MCPWM Prescaler

jmcornil
Posts: 12
Joined: Mon Feb 10, 2020 1:42 pm

Issue with MCPWM Prescaler

Postby jmcornil » Mon Feb 10, 2020 5:44 pm

Hello everybody and thank you for reading my post.

I am beginning to play with MCPWM and I would change on the fly some values such as the prescaler which are in MCPWM_CLK_CFG_REG and in MCPWM_TIMER0_CFG0_REG. For the moment I have no oscillo and I test with leds.
  • For the prescaler in MCPWM_CLK_CFG_REG register, I see a change when I modify its value.
  • But for the one in MCPWM_TIMER0_CFG0_REG, it seems to me that there is no change when I modify its value. what am I doing wrong in the following program ?
Other question : if in the following program I replace
pwm_config.frequency = 20;
by
pwm_config.frequency = 10;
without executing the lines 92-101, I see a strange value in the MCPWM_TIMER0_PERIOD part of the PWM_TIMER0_CFG0_REG :
I find there 34464 = 0x86A0 when the right value would be 100000 = 0x186A0.
Obviously it is impossible to put this value into the 16 bits provided and it looks like the extra digit was cut.

Thank you very much for any clue

Best regards

J.M. CORNIL


Code: Select all

#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_attr.h"
#include "soc/rtc.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_periph.h"


#define GPIO_PWM0A_OUT 17   //Set GPIO 17 as PWM0A
#define GPIO_PWM0B_OUT 21   //Set GPIO 21 as PWM0B

static void mcpwm_example_gpio_initialize(void)
{
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);
}

		// general prescaler
		uint8_t get_MCPWM_CLK_CFG_REG(){ 
			return *((uint32_t *) MCPWM_CLK_CFG_REG(0)) & 0xFF;
			}		
			
		void set_MCPWM_CLK_CFG_REG(uint8_t val){
			 *((uint32_t *) MCPWM_CLK_CFG_REG(0)) = (MCPWM_CLK_CFG_REG(0) & 0xFFFFFF00) | val ;
			 }
			 
		// Timer0 prescaler
		uint8_t get_MCPWM_TIMER0_PRESCALE(){ 
			return *((uint32_t *) MCPWM_TIMER0_CFG0_REG(0)) & 0xFF;
			}

		void set_MCPWM_TIMER0_PRESCALE(uint8_t val){ 
			uint32_t* p = (uint32_t *) MCPWM_TIMER0_CFG0_REG(0);
			*p = (*p & 0xFFFFFF00) | val;
			}
    
    // Timer0 period
		uint16_t get_MCPWM_TIMER0_PERIOD(){ 
			return ((*((uint32_t *) MCPWM_TIMER0_CFG0_REG(0))) >>8) & 0xFFFF;
			}

		void set_MCPWM_TIMER0_PERIOD(uint16_t val){ 
			uint32_t* p = (uint32_t *) MCPWM_TIMER0_CFG0_REG(0);
			*p = (*p & 0xFF0000FF) | (val<<8);
			}

    // A cmpr register
		uint16_t get_MCPWM_GEN0_TSTMP_A_REG(){ 
			return (*(uint32_t *)MCPWM_GEN0_TSTMP_A_REG(0)) & 0xFFFF;
			}

		void set_MCPWM_GEN0_TSTMP_A_REG(uint16_t val){ 
			uint32_t* p = (uint32_t *)MCPWM_GEN0_TSTMP_A_REG(0);
			*p = (*p & 0xFFFF0000) | val;
			}

    // B cmpr register
		uint16_t get_MCPWM_GEN0_TSTMP_B_REG(){ 
			return (*(uint32_t *)MCPWM_GEN0_TSTMP_B_REG(0)) & 0xFFFF;
			}

		void set_MCPWM_GEN0_TSTMP_B_REG(uint16_t val){ 
			uint32_t* p = (uint32_t *)MCPWM_GEN0_TSTMP_B_REG(0);
			*p = (*p & 0xFFFF0000) | val;
			}			
		void timer_Start(){
			uint32_t* p = (uint32_t *) MCPWM_TIMER0_CFG1_REG(0);
			*p = (*p & ~0b111) | 0b10;
			}
		void timer_Stop(){
			uint32_t* p = (uint32_t *) MCPWM_TIMER0_CFG1_REG(0);
			*p = *p & ~0b111;
			}

void app_main(void)
{
    //1. mcpwm gpio initialization
    mcpwm_example_gpio_initialize();

    //2. initial mcpwm configuration
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 20;    // frequency 
    pwm_config.cmpr_a = 50.0;     // duty cycle of PWMxA = 50.0%
    pwm_config.cmpr_b = 50.0;     // duty cycle of PWMxb = 50.0%
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_1;
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A & PWM0B with above settings

		// Then I try playing with the two prescaler
		timer_Stop();
		// For this one I see that it operates on the result
		set_MCPWM_CLK_CFG_REG(255);
		// But for the following, I don't see any change, whatever the value
		set_MCPWM_TIMER0_PRESCALE(4); 
		set_MCPWM_TIMER0_PERIOD(60000);
		set_MCPWM_GEN0_TSTMP_B_REG(50000);
		set_MCPWM_GEN0_TSTMP_A_REG(50000);
		timer_Start();
		
		printf("MCPWM_CLK_CFG_REG adr = %d \r\n",get_MCPWM_CLK_CFG_REG());
		printf("MCPWM_TIMER0_CFG0_REG  presc = %d  period = %d \r\n",get_MCPWM_TIMER0_PRESCALE(),get_MCPWM_TIMER0_PERIOD());
		printf("MCPWM_GEN0_TSTMP_A_REG val = %d \r\n",get_MCPWM_GEN0_TSTMP_A_REG());
		printf("MCPWM_GEN0_TSTMP_B_REG val = %d \r\n",get_MCPWM_GEN0_TSTMP_B_REG());

}


Last edited by jmcornil on Tue Feb 11, 2020 7:42 pm, edited 1 time in total.

jmcornil
Posts: 12
Joined: Mon Feb 10, 2020 1:42 pm

Re: Issue with MCPWM Prescaler

Postby jmcornil » Tue Feb 11, 2020 5:53 pm

Hello

Since yesterday I have found the LL layer for ESP32 MCPWM register operations
(in the file components/soc/esp32/include/hal/mcpwm_ll.h).

I find that it provides an efficient method to initialize the MCPWM
(only one timer and one operator for the moment).
And so, I can choose freely my two prescaler (not only 15->16 and 9->10).
I put a sample program below (much shorter than the program of ysterday !)

Hope it will be helpful.

Code: Select all

#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_attr.h"
#include "soc/rtc.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_periph.h"
#include "hal/mcpwm_hal.h"
#include "driver/gpio.h"

#define GPIO_PWM0A_OUT 17   //Set GPIO 17 as PWM0A
#define GPIO_PWM0B_OUT 21   //Set GPIO 21 as PWM0B
//#define GPIO_PWM1A_OUT 18   //Set GPIO 18 as PWM1A
//#define GPIO_PWM1B_OUT 22   //Set GPIO 22 as PWM1B
//#define GPIO_PWM2A_OUT 19   //Set GPIO 19 as PWM2A
//#define GPIO_PWM2B_OUT 23   //Set GPIO 23 as PWM2B

void app_main(void)
{
    // MCPWM GPIO initialization
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);

		// MCPWM Initialisation
		// First The clock prescale
		mcpwm_ll_set_clock_prescale(&MCPWM0, 31);
		// Then Timer0 initialisation
		mcpwm_ll_timer_set_prescale(&MCPWM0, MCPWM_TIMER_0, 39);
		mcpwm_ll_timer_set_count_mode(&MCPWM0, MCPWM_TIMER_0, 1);
		mcpwm_ll_timer_set_period(&MCPWM0, MCPWM_TIMER_0, 50000);
		mcpwm_ll_operator_select_timer(&MCPWM0, 0,MCPWM_TIMER_0);
		// Now Operator 0 initialisation
		mcpwm_ll_operator_set_compare(&MCPWM0, 0, MCPWM0A, 25000);
		mcpwm_ll_operator_set_compare(&MCPWM0, 0, MCPWM0B, 25000);
		mcpwm_ll_gen_set_zero_action(&MCPWM0, 0,  MCPWM_GEN_A, MCPWM_ACTION_FORCE_HIGH);
		mcpwm_ll_gen_set_cmp_action( &MCPWM0, 0,  MCPWM_GEN_A, MCPWM0A, MCPWM_ACTION_FORCE_LOW,MCPWM_ACTION_NO_CHANGE);
		mcpwm_ll_gen_set_zero_action(&MCPWM0, 0,  MCPWM_GEN_B, MCPWM_ACTION_FORCE_LOW);
		mcpwm_ll_gen_set_cmp_action( &MCPWM0, 0,  MCPWM_GEN_B, MCPWM0B, MCPWM_ACTION_FORCE_HIGH,MCPWM_ACTION_NO_CHANGE);
		
		// Timer start
		mcpwm_ll_timer_start(&MCPWM0,MCPWM_TIMER_0);
  
		while(1){}

}



henrik.basic
Posts: 3
Joined: Sat Mar 21, 2020 12:56 pm

Re: Issue with MCPWM Prescaler

Postby henrik.basic » Sat Mar 21, 2020 1:16 pm

I got the same problem here. But I'm in the Arduino environment, and the LL layer doesn't seem to exist there.
I started to write my own simple driver, using "soc/mcpwm_struct.h" and writing directly to the hw registers. And when I look at the LL layer, it seems to do the same thing as I do.

I can write to the PWM_TIMER0_CFG0_REG register where both PWM_TIMER0_PERIOD and PWM_TIMER0_PRESCALE is located. When I read the register I can see that the value is what I've written. I see that the output frequency changes according to PWM_TIMER0_PERIOD, but it's like if I've written 0x00 to PWM_TIMER0_PRESCALE, no matter what I write.

I've tried it on all the timers, tried both immediate and TEZ update method. It's always the same: PERIOD works, PRESCALE ignores the value and doesn't do any prescaling (as if PRESCALE was 0).

Have you checked that you actually get the requested frequency?

henrik.basic
Posts: 3
Joined: Sat Mar 21, 2020 12:56 pm

Re: Issue with MCPWM Prescaler

Postby henrik.basic » Mon Mar 23, 2020 5:50 pm

I've got it working, and I'm closing in on the cause of the problem, and a workaround.

The PERIOD and PRESCALE subregister is in the same register as the PERIOD_UPMETHOD subregister, which made me think that the PRESCALE shadow/active subregister would follow PERIOD_UPETHOD, but it doesn't. There's no immediate load for PRESCALE!
The only way I've managed to update the PRESCALE active register is if the TIMER_VALUE (and probably the hidden counter for PRESCALE) is zero, and the timer is turned off for a short while. This means that the PRESCALE active subregister will never be updated in free running mode.

One workaround:

Code: Select all

  mcpwm0.timer[0].mode.start        = 0;   // Stop on TEZ
  mcpwm0.timer[0].period.prescale   = newPrescale;
  mcpwm0.timer[0].sync.timer_phase  = 0;
  mcpwm0.timer[0].sync.sync_sw     ^= 1;
  delayMicroseconds(0);
  mcpwm0.timer[0].mode.start        = 2;   // Free running
Notice the tiny delay. Without it, the prescaler would still be stuck on the old value.
When I played around with different tiny delays, I noticed that with some delays the timer could be stuck turned off. That need more investigation to give safe delay values. My guess is that the needed delay is a function of timer prescale and pre-prescale.

A drawback of this way is that there will be a discontinuity in timer value. (That matters in some use cases.)
You could patch that by saving the old timer value, and reloading it through timer_phase after the prescale has been updated. But then you'd still need to make sure there's no generetor event at TEZ that mess things up. In short; a messy solution if you want the timing to be continuous. (But fast and simple if that doesn't matter.)


A different workaround:

Code: Select all

  set new period                    // update on TEZ 
  set new A/B time stamp values     // update on TEZ 
  set new prescale                  // timer mode to stop at TEZ
  Enable an ISR on TEZ that just restarts the timer.
This is rather close to what I initialy intended to do when changing pwm speed on the run. It's just the stop and restart that differs.


I will most likely end up with a mix of both workarounds. My current use case is to drive a stepper motor, and I need both to set the new speed when standing still (first workaround), and doing continuous accelleration (second workaround).

Who is online

Users browsing this forum: No registered users and 22 guests