Starting with real time control

PingwinVonJelen
Posts: 1
Joined: Thu Mar 05, 2026 8:29 am

Starting with real time control

Postby PingwinVonJelen » Thu Mar 05, 2026 11:13 am

Hi, i am beginner ESP-IDF programmer, my first goal-project is real time control of 6 phase thyristor rectifier.
So sequence of work is :
Interrupt Read
Start timer that need to count 3333us
After this, i need to start thyristor firing sequence (pwm on output)
6 steps, every step is spaced every 3333us, and then there is interrupt and so on..
I tried writing this code with gptimer and freertos but i have some problems to get it working.
So my question is, how code should look, best way to implement this.
Also in my first attempt, turns out in timer callback, or interrupt we dont want to execute big amount of code, so this automatically needed notifications to jump to freertos tasks, and whole thing gets pretty messy.
Don't want everything of course, just direct me in right way.

MicroController
Posts: 2669
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Starting with real time control

Postby MicroController » Sat Mar 07, 2026 8:07 am

I'm not sure what exactly you need, or what problem you're facing.
What you wrote seems pretty simple to do, but there may be some details missing.

I guess you want to control the output power, so variable duty cycle of the thyristors? Same duty cycle for all of them?

The flow could be something like this:
Interrupt -> reset firing sequence to fs=0, reset timer, start timer with X microseconds (0...3333 us, 3333-(3333*duty cycle))
In the timer interrupt, check fs; if fs==0, set timer to 3333us. Trigger thyristor fs, increment fs.
If you need 'longer' firing pulses, e.g. 100us, you can use another timer which you start each time a thyristor is fired and which just resets all outputs when triggered.

If you could share some code we may be better able to help.

PingwinVonJelen
Posts: 1
Joined: Thu Mar 05, 2026 8:29 am

Re: Starting with real time control

Postby PingwinVonJelen » Sun Mar 15, 2026 11:45 am

So core of the working principle is this (pic)
When AB voltage is slightly above zero, i get interrupt from optocoupler. ( Red dot)
Now we need to wait to α = 30 (3333us from interrupt)
We are now at blue dot.
Now we need to start firing thyristors, first is AB pair at α = 30, next is AC pair at α = 90, and so on to the CB at α = 330
Cycle ends and is started from the beggining.

Notice that, when CB conduction is still going on, we have already interrupt, so this cant block each other.
Curcuit is regulating output voltage by delaying thyristor fire ( for now lets focus on program core, this feature can be easily added later)
In real world big thyristors need to be fired in whole time of conduction to ensure proper operation, that's why im outputting pwm instead of pulses
current code

Code: Select all

#pragma region include libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_task_wdt.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "74HC595.h"
#include "driver/gptimer.h"
#include "tm1637.h"
#include <driver/ledc.h>
#include "button_gpio.h"
#include "iot_button.h"
#include "esp_system.h"
#include "esp_timer.h" 
#include <time.h>
#include "freertos/semphr.h"
#pragma endregion
#pragma region Pin Definitions
#define BUTTON1          1                //digital input
#define BUTTON2          2                //digital input
#define BUTTON3          42               //digital input
#define BUTTON4          41               //digital input
#define PhaseDetect      9                //digital input with interrupt
#define Data             4                //digital output
#define Clock            5                //digital output
#define Latch            6                //digital output
#define CLK2             38               //digital output
#define DIO1             39               //digital output
#define CLK1             40               //digital output
#define DIO2             45               //digital output
#define MotorPWM         10               //pwm output
#define TY1pin           47               //pwm output     
#define TY2pin           14               //pwm output
#define TY3pin           12               //pwm output
#define TY4pin           21               //pwm output
#define TY5pin           13               //pwm output
#define TY6pin           11               //pwm output
#define HallSignal       ADC_CHANNEL_5    //adc 2  analog input
#define Pot2             ADC_CHANNEL_6    //adc 1  analog input       
#define Pot1             ADC_CHANNEL_4    //adc 2  analog input
#define RectifierOutput  ADC_CHANNEL_6    //adc 2  analog input
#define T100             ADC_CHANNEL_7    //adc 2  analog input
#define T200             ADC_CHANNEL_7    //adc 1  analog input

#define TY_CHANNEL_1     LEDC_CHANNEL_0
#define TY_CHANNEL_2     LEDC_CHANNEL_1
#define TY_CHANNEL_3     LEDC_CHANNEL_2
#define TY_CHANNEL_4     LEDC_CHANNEL_3
#define TY_CHANNEL_5     LEDC_CHANNEL_4
#define TY_CHANNEL_6     LEDC_CHANNEL_5
#define TY_TIMER_NUM     LEDC_TIMER_0
#define TY_MODE          LEDC_LOW_SPEED_MODE
#define TY_DUTY_RES      LEDC_TIMER_4_BIT 
#define TY_FREQUENCY     10000             

#define MOT_CHANNEL      LEDC_CHANNEL_6
#define MOT_TIMER_NUM    LEDC_TIMER_1
#define MOT_MODE         LEDC_LOW_SPEED_MODE
#define MOT_DUTY_RES     LEDC_TIMER_10_BIT 
#define MOT_FREQUENCY    10000       

#define ADC_BITWIDTH     ADC_BITWIDTH_12   
#define ADC_ATTEN_T      ADC_ATTEN_DB_12  
#define ADC_ATTEN_OTHER  ADC_ATTEN_DB_0  
#pragma endregion

static gptimer_handle_t timer1 = NULL;
static gptimer_handle_t timer2 = NULL;
static adc_oneshot_unit_handle_t ADC1 , ADC2;
static tm1637_handle_t display1 , display2 ;
volatile uint32_t SEQUENCE = 0;
TaskHandle_t firing_task_handle = NULL;

void IRAM_ATTR Interrupt() 
{
gptimer_set_raw_count(timer1, 0);
gptimer_start(timer1);
}

bool IRAM_ATTR timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
gptimer_stop(timer1);
gptimer_stop(timer2);
gptimer_set_raw_count(timer2, 0);
gptimer_start(timer2);
SEQUENCE = 1;

return pdFALSE;
}



static inline void TY1_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_1, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_1);
}
static inline void TY2_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_2, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_2);
}
static inline void TY3_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_3, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_3);
}
static inline void TY4_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_4, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_4);
}
static inline void TY5_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_5, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_5);
}
static inline void TY6_SET(uint32_t duty)
{
    ledc_set_duty(TY_MODE, TY_CHANNEL_6, duty);
    ledc_update_duty(TY_MODE, TY_CHANNEL_6);
}


bool IRAM_ATTR timer_callback2(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
  switch (SEQUENCE)
    {
     case 0:
    TY1_SET(0);
    TY2_SET(0);
    TY3_SET(0);
    TY4_SET(0);
    TY5_SET(0);
    TY6_SET(0);
    break;
    case 1:
    TY1_SET(8);
    TY2_SET(0);
    TY3_SET(0);
    TY4_SET(0);
    TY5_SET(0);
    TY6_SET(8);
    break;
    case 2 :
    TY1_SET(8);
    TY2_SET(0);
    TY3_SET(0);
    TY4_SET(0);
    TY5_SET(8);
    TY6_SET(0);
    break;
    case 3:
    TY1_SET(0);
    TY2_SET(0);
    TY3_SET(8);
    TY4_SET(0);
    TY5_SET(8);
    TY6_SET(0);
    break;
    case 4 :
    TY1_SET(0);
    TY2_SET(0);
    TY3_SET(8);
    TY4_SET(8);
    TY5_SET(0);
    TY6_SET(0);
    break;
    case 5:
    TY1_SET(0);
    TY2_SET(8);
    TY3_SET(0);
    TY4_SET(8);
    TY5_SET(0);
    TY6_SET(0);
    break;
    case 6:
    TY1_SET(0);
    TY2_SET(8);
    TY3_SET(0);
    TY4_SET(0);
    TY5_SET(0);
    TY6_SET(8);
    break;

    }
    SEQUENCE++;
    gptimer_set_raw_count(timer2, 0);
return pdTRUE;
}

void TIMER1_CONFIG(void) { 
#pragma region TIMER CONF
    gptimer_config_t config = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_UP,
        .resolution_hz = 1 * 1000000, 
    };
    gptimer_new_timer(&config, &timer1);
    static gptimer_alarm_config_t alarm_config = {
    .alarm_count = 1,
    .reload_count = 0,
    .flags.auto_reload_on_alarm = false,
};
    gptimer_set_alarm_action(timer1, &alarm_config);
    gptimer_register_event_callbacks(timer1, &(gptimer_event_callbacks_t){
        .on_alarm = timer_callback
    }, NULL); 
    gptimer_enable(timer1);
    ESP_LOGI("TIMER1" , "TIMER 1 CONFIGURED");

#pragma endregion
}
void TIMER2_CONFIG(void) { 
#pragma region TIMER CONF
    gptimer_config_t config = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_UP,
        .resolution_hz = 1 * 1000000, 
    };
    gptimer_new_timer(&config, &timer2);
    static gptimer_alarm_config_t alarm_config = {
    .alarm_count = 3333,
    .reload_count = 0,
    .flags.auto_reload_on_alarm = true,
};
    gptimer_set_alarm_action(timer2, &alarm_config);
    gptimer_register_event_callbacks(timer2, &(gptimer_event_callbacks_t){
        .on_alarm = timer_callback2
    }, NULL); 
    gptimer_enable(timer2);
    ESP_LOGI("TIMER2" , "TIMER 2 CONFIGURED");

#pragma endregion
}
void ADC_CONFIG(void) {
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
        .clk_src = ADC_RTC_CLK_SRC_DEFAULT,
    };
    adc_oneshot_new_unit(&init_config1, &ADC1);
    adc_oneshot_unit_init_cfg_t init_config2 = {
        .unit_id = ADC_UNIT_2,
        .clk_src = ADC_RTC_CLK_SRC_DEFAULT,
    };
    adc_oneshot_new_unit(&init_config2, &ADC2);

    adc_oneshot_chan_cfg_t config1 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_OTHER,
    };
    adc_oneshot_config_channel(ADC2, HallSignal ,&config1);
    adc_oneshot_chan_cfg_t config2 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_OTHER,
    };
    adc_oneshot_config_channel(ADC1, Pot2 ,&config2);
    adc_oneshot_chan_cfg_t config3 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_OTHER,
    };
    adc_oneshot_config_channel(ADC2, Pot1 ,&config3);
    adc_oneshot_chan_cfg_t config4 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_OTHER,
    };
    adc_oneshot_config_channel(ADC2, RectifierOutput ,&config4);
    adc_oneshot_chan_cfg_t config5 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_T,
    };
    adc_oneshot_config_channel(ADC2, T100,&config5);
    adc_oneshot_chan_cfg_t config6 = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN_OTHER,
    };
    adc_oneshot_config_channel(ADC1, T200 ,&config6);
    ESP_LOGI("ADC" , "ADC Configured");
   // adc_oneshot_read(ADC2, HallSignal, &HALLSIGNAL_VALUE);

}
void SCREEN_CONFIG(void)
{ 
    // SCREENS CONFIG
    tm1637_config_t config10 = {
        .clk_pin = CLK1,
        .dio_pin = DIO1,
        .bit_delay_us = 100
    };
    tm1637_config_t config20 = {
        .clk_pin = CLK2,
        .dio_pin = DIO2,
        .bit_delay_us = 100
    };
    tm1637_init(&config10, &display1);
    tm1637_init(&config20, &display2);
    tm1637_set_brightness(display1, 7, true);
    tm1637_set_brightness(display2, 7, true);
    tm1637_show_number(display1, 0000, true, 4, 0);
    tm1637_show_number(display2, 0000, true, 4, 0);
    vTaskDelay(1000/ portTICK_PERIOD_MS);
    tm1637_clear(display1);
    tm1637_clear(display2);
    ESP_LOGI("SCREENS" , "Screens Configured");
    int value = 0;
     tm1637_show_number(display1, value , true, 4, 0);
}
void HC595_CONFIG(void) {
//    HC595 pcb output schematic
//     24V 0 1 2 3 4 5 6
//         I I I I I I I
    HC595_INIT();
    close_led_all();
    ESP_LOGI("HC595" , "HC595 Configured");
}
void IO_CONFIG(void) {
    gpio_config_t BUTTON1_CONF = {
    .pin_bit_mask = (1ULL << BUTTON1 ),      // Select GPIO 
    .mode = GPIO_MODE_INPUT,            // Set as output
    .pull_up_en = GPIO_PULLUP_DISABLE,  // Disable pull-up
    .pull_down_en = GPIO_PULLDOWN_DISABLE,  // Disable pull-down
    .intr_type = GPIO_INTR_DISABLE             // Disable interrupts
    };
    gpio_config_t BUTTON2_CONF = {
    .pin_bit_mask = (1ULL << BUTTON2 ),      // Select GPIO 
    .mode = GPIO_MODE_INPUT,            // Set as output
    .pull_up_en = GPIO_PULLUP_DISABLE,  // Disable pull-up
    .pull_down_en = GPIO_PULLDOWN_DISABLE,  // Disable pull-down
    .intr_type = GPIO_INTR_DISABLE             // Disable interrupts
    };
    gpio_config_t BUTTON3_CONF = {
    .pin_bit_mask = (1ULL << BUTTON3 ),      // Select GPIO 
    .mode = GPIO_MODE_INPUT,            // Set as output
    .pull_up_en = GPIO_PULLUP_DISABLE,  // Disable pull-up
    .pull_down_en = GPIO_PULLDOWN_DISABLE,  // Disable pull-down
    .intr_type = GPIO_INTR_DISABLE             // Disable interrupts
    };
    gpio_config_t BUTTON4_CONF = {
    .pin_bit_mask = (1ULL << BUTTON4 ),      // Select GPIO 
    .mode = GPIO_MODE_INPUT,            // Set as output
    .pull_up_en = GPIO_PULLUP_DISABLE,  // Disable pull-up
    .pull_down_en = GPIO_PULLDOWN_DISABLE,  // Disable pull-down
    .intr_type = GPIO_INTR_DISABLE             // Disable interrupts
    };
    gpio_config(&BUTTON1_CONF);
    gpio_config(&BUTTON2_CONF);
    gpio_config(&BUTTON3_CONF);
    gpio_config(&BUTTON4_CONF);
    ESP_LOGI("GPIO" , "GPIO Configured");
}
void PWM_CONFIG(void) {
     ledc_timer_config_t TY_TIMER = {
        .speed_mode = TY_MODE,
        .duty_resolution = TY_DUTY_RES,
        .timer_num = TY_TIMER_NUM,
        .freq_hz = TY_FREQUENCY
    };
    ledc_timer_config(&TY_TIMER);
    ledc_channel_config_t TYconfig1 = {
        .gpio_num = TY1pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_1,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig1);
    ledc_channel_config_t TYconfig2 = {
        .gpio_num = TY2pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_2,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig2);
    ledc_channel_config_t TYconfig3 = {
        .gpio_num = TY3pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_3,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig3);
    ledc_channel_config_t TYconfig4 = {
        .gpio_num = TY4pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_4,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig4);
    ledc_channel_config_t TYconfig5 = {
        .gpio_num = TY5pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_5,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig5);
    ledc_channel_config_t TYconfig6 = {
        .gpio_num = TY6pin,
        .speed_mode = TY_MODE,
        .channel = TY_CHANNEL_6,
        .timer_sel = TY_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&TYconfig6);

    ledc_timer_config_t MOT_TIMER = {
        .speed_mode = MOT_MODE,
        .duty_resolution = MOT_DUTY_RES,
        .timer_num = MOT_TIMER_NUM,
        .freq_hz = MOT_FREQUENCY
    };
    ledc_timer_config(&MOT_TIMER);
    ledc_channel_config_t MOTconfig = {
        .gpio_num = MotorPWM,
        .speed_mode = MOT_MODE,
        .channel = MOT_CHANNEL,
        .timer_sel = MOT_TIMER_NUM,
        .duty = 0
    };
    ledc_channel_config(&MOTconfig);
    ESP_LOGI("PWM" , "PWM configured");
}
void ISR_CONFIG(void) {
    gpio_set_direction(PhaseDetect, GPIO_MODE_INPUT);
    gpio_set_pull_mode(PhaseDetect, GPIO_PULLUP_ENABLE);
    gpio_set_intr_type(PhaseDetect, GPIO_INTR_POSEDGE);
    gpio_install_isr_service(0);
    gpio_isr_handler_add(PhaseDetect, Interrupt, NULL);
    ESP_LOGI("ISR" , "ISR CONFIGURED");
}

void app_main(void)
{
    TIMER1_CONFIG();
    TIMER2_CONFIG();
    ADC_CONFIG();
    SCREEN_CONFIG();
    HC595_CONFIG();
    IO_CONFIG();
    PWM_CONFIG();
    ISR_CONFIG();
}
The problem
When measuring with oscilloscope it sometimes skips a sequence, or two, or one pulse is shorter than it should be, i can even hear this because whole thing works in audible frequency.

So i tried multiple takes to write this program, ai help, and nothing worked and i'm stuck at this.
Attachments
Bez tytułu.jpg
Bez tytułu.jpg (222.48 KiB) Viewed 99 times
1234.jpg
1234.jpg (337.65 KiB) Viewed 99 times
Last edited by PingwinVonJelen on Sun Mar 15, 2026 11:51 am, edited 3 times in total.

Who is online

Users browsing this forum: akashgaur0001, ChatGPT-User and 6 guests