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.
Starting with real time control
-
MicroController
- Posts: 2669
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Starting with real time control
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.
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
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
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.
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();
}
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 (222.48 KiB) Viewed 98 times
-
- 1234.jpg (337.65 KiB) Viewed 98 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, Baidu [Spider] and 5 guests