1
\$\begingroup\$

I want to use an ESP32 microcontroller to power a 3600 W load with a triac. Using phase control requires accurate timing and that is nearly impossible with a lot of code already running in the ESP32. I have created a schematic to do most of the timing in hardware but this needs synchronisation with the mains frequency.

I had a look at both 'ledc' and 'mcpwm' in the ESP32 documentation but so far I can only find syncing with a pin at the start of the pwm signal or syncing two pwm signals together. I cannot find info about preloading the pwm counter register with a calculated value (a little bit later than the start in the pwm cycle). My zero crossing puls starts approximately 300 microseconds before the crossing and it ends 300 us after that. The plan is to set the pwm counter to the equivalent of 300us on the rising edge (see schematic). If I only knew what register/variable/class_member to use.

I expect that 'ledc' is much to simple and will never need synchronisation. Why would it for just dimming a LED. 'mcpwm' is such a complicated piece of code that I don't know where to start.

enter image description here

What is the right way to preset the pwm counter in mcpwm?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Please edit to make it clear what exactly you are asking. You have no question mark anywhere in your post. \$\endgroup\$
    – Transistor
    Commented Aug 25, 2022 at 12:14
  • 2
    \$\begingroup\$ You want capture and compare. Capture for the zero cross input then calc the reqd delay and that is your compare value. You don’t want pwm. \$\endgroup\$
    – Kartman
    Commented Aug 25, 2022 at 12:45
  • \$\begingroup\$ Yes I do want pwm. I don't want to fire the gate every half period of the sine, CPU time is needed for something else. The ESP32 should synchronise the pwm once in a few minutes at most. \$\endgroup\$
    – hennep
    Commented Aug 25, 2022 at 12:56
  • \$\begingroup\$ You want your output to be PWM, but unless the mcpwm library will let you sync to an external signal, you can't use it. \$\endgroup\$
    – Mattman944
    Commented Aug 25, 2022 at 16:12
  • \$\begingroup\$ You can't spare a two or three dozen instruction cycles every 7-8ms? Because that's all you would need to set the output capture and reset the input capture timer. You could reduce that by a lot that if can you use an event trigger on the output compare to produce pulses but measure the measure and update period only once per second. \$\endgroup\$
    – DKNguyen
    Commented Sep 8, 2022 at 14:02

1 Answer 1

1
\$\begingroup\$

I succeeded in creating code to synchronise the PWM with the mains frequency, it took me a while to make it work.
With the above circuit the offset should, in my case, be set to 950. The dutycycle can be changed with the 'mcpwm_set_duty' function. See the code for the parameters.

I noticed that some constants that work correct in Linux cause a syntax error in Windows. I commented that in code. I guess that I used different versions of the Espressiv firmware.

If someone wants to give it a try, below is my code.

#include "driver/mcpwm.h"

char action = 'D';
int value = -1;
int incomingByte = 0; // for incoming serial data

#define ENTER 10
#define GPIO_PWM0A_OUT          19   // Pin output - connect to 'PWM_from_uC' in schematic
#define GPIO_SYNC0_IN   GPIO_NUM_2   // Pin input - connect to 'zero_cross_puls_to_uC' in schematic  (using a PC817)

static void mcpwm_config( void ) {
    // mcpwm gpio initialisation
    mcpwm_gpio_init( MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT );
    mcpwm_gpio_init( MCPWM_UNIT_0, MCPWM_SYNC_0, GPIO_SYNC0_IN ); // need to modify esp_rom_gpio_connect_in_signal in components/drivers/mcpwm.c

    // initialise mcpwm configuration
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 100;    //frequency = 100Hz, double of AC 50Hz since we drive for two halves of the waveform
    pwm_config.cmpr_a = 0.0;
    pwm_config.counter_mode = MCPWM_DOWN_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_1;  
    mcpwm_init( MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config );

    // Synchronisation configuration
    // @param4 = phase value in 1/1000 (for 86.7%, phase_val = 867) which timer moves to on sync signal 
    // mcpwm_sync_enable( MCPWM_UNIT_0, MCPWM_TIMER_0,  MCPWM_SELECT_SYNC0, 950 ); //  Windows
       mcpwm_sync_enable( MCPWM_UNIT_0, MCPWM_TIMER_0,  MCPWM_SELECT_GPIO_SYNC0, 950 ); // Linux    
}

void setup( void ) {
    Serial.begin(115200);
    delay(200);
    mcpwm_config();
}

void loop( void ) {
    // Code to change the dutycycle and offset in the serial monitor
    // typ D or O in the serial monitor with a valid number
    // the value changes when you press enter
    if( Serial.available() > 0 ) {
        incomingByte = Serial.read();
        if( incomingByte == ENTER ) {
            if( action == 'O' && value >= 0 && value <= 999 ) {
                Serial.print( "offset: " );
                Serial.println( value, DEC );
                // mcpwm_sync_enable( MCPWM_UNIT_0, MCPWM_TIMER_0,  MCPWM_SELECT_SYNC0, value ); //  Windows
                mcpwm_sync_enable( MCPWM_UNIT_0, MCPWM_TIMER_0,  MCPWM_SELECT_GPIO_SYNC0, value ); // Linux
                value = 0;
            } else if( action == 'D' && value >= 0 && value <= 100 ) {
                Serial.print( "dutycycle: " );
                Serial.println( value, DEC );
                mcpwm_set_duty( MCPWM_UNIT_0, MCPWM_TIMER_0,  MCPWM_OPR_A /* MCPWM_GEN_A */, value );
                value = 0;
            } else if( action == 'D' && value > 100 ) {
                Serial.println( "dutycycle range: 0-100" );
                value = 0;
            } else if( action == 'O' && value > 999 ) {
                Serial.println( "offset range: 0-999" );
                value = 0;
            }
        } else if( incomingByte >= '0' && incomingByte <= '9' ) {
            if( value == -1 ) {
                value = 0;
            }
            value = value * 10 + ( incomingByte - '0' );
        } else if( incomingByte == 'd' || incomingByte == 'D' ) {
            if( Serial.peek() == ENTER ) {
                Serial.read();
            }
            action = 'D';
            Serial.println( "waiting for dutycycle" );
        } else if( incomingByte == 'o' || incomingByte == 'O' ) {
            if( Serial.peek() == ENTER ) {
                Serial.read();
            }
            action = 'O';
            Serial.println( "waiting for offset" );
        } else {
            if( incomingByte < 32 ) {
                Serial.print( "\0x" );
                Serial.println( incomingByte, DEC );
            } else {
                char buf[4];
                sprintf( buf, "'%c'", incomingByte );
                Serial.println( buf );
            }
            value = -1;
        }
    }
}

blue = zero crossing
pink = PWM
yellow = gate trigger

Oscilloscope image

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.