Skip to content

Commit

Permalink
Implement PWM Power ramping
Browse files Browse the repository at this point in the history
As reported in Eddddddddy/Songguo-PTS200#18 some PSUs could trigger high current protection on switch on.

Here implement PWM Power ramping, heater will use LEDC's hw fader to gradualy increase PWM duty from 0 to max_duty.
While the scaling itself is non-blocking and performed in hardware, ledc engine is blocked for the time of fading and no temperature measurments could be done.
So heater controlling thread must be suspended for the period and awoken back with LEDC interrupt.
  • Loading branch information
vortigont committed May 26, 2024
1 parent 36afe33 commit 43cf277
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 63 deletions.
4 changes: 2 additions & 2 deletions ESPIron/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ enum class ironState_t {
suspend, // hibernation mode
boost, // heater temperature is increased for a short period of time
setup, // iron is configuration mode, i.e. working with screen menu, heater switches off
notip // tip is missing or failed

notip, // tip is missing or failed
PWRramp // controller is performing Power Ramping
};

// working temperature values
Expand Down
14 changes: 3 additions & 11 deletions ESPIron/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
#define TEMP_MIN 150 // 最小温度
#define TEMP_MAX 450 // 最大温度
#define TEMP_STEP 5 // temperature change step / 旋转编码器温度变化步进
#define TEMP_NOTIP 500 // virtual temperature threshold when tip is not installed
#define TEMP_NOTIP 500 // virtual temperature threshold when tip is not installed (op amp max ~650 C)
#define TEMP_STANDBY 120 // 休眠温度
#define TEMP_STANDBY_MIN 100
#define TEMP_STANDBY_MAX 180
Expand All @@ -53,10 +53,6 @@
#define TEMP_BOOST_MAX 100
#define TEMP_BOOST_STEP 10

#define POWER_LIMIT_15 170 // 功率限制
#define POWER_LIMIT_20 255 // 功率限制
#define POWER_LIMIT_20_2 127 // 功率限制

// Default tip temperature calibration value / 默认的T12烙铁头温度校准值
#define TEMP200 200 // temperature at ADC = 200
#define TEMP280 280 // temperature at ADC = 280
Expand Down Expand Up @@ -89,12 +85,8 @@
#define WAKEUP_THRESHOLD 10

// Control values
#define TIME2SETTLE 5000 // The time in microseconds allowed for the OpAmp output to stabilize / 以微秒为单位的时间允许OpAmp输出稳定
#define TIME2SETTLE_20V 2000 // The time in microseconds allowed for the OpAmp output to stabilize / 以微秒为单位的时间允许OpAmp输出稳定
#define SMOOTHIE 0.2 // OpAmp output smoothing coefficient (1=no smoothing; default: 0.05) / OpAmp输出平滑系数 (1=无平滑; 默认:0.05)
//#define PID_ENABLE true // enable PID control
#define PID_ENGAGE_DIFF 30 // temperature difference when PID algo should be engaged
#define BEEP_ENABLE true // enable/disable buzzer
#define SMOOTHIE 0.2 // OpAmp output smoothing coefficient (1=no smoothing; default: 0.05) / OpAmp输出平滑系数 (1=无平滑; 默认:0.05)
#define BEEP_ENABLE true // enable/disable buzzer


// MOSFET control definitions
Expand Down
1 change: 1 addition & 0 deletions ESPIron/evtloop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ESP_EVENT_DEFINE_BASE(IRON_GET_EVT);
ESP_EVENT_DEFINE_BASE(IRON_NOTIFY);
ESP_EVENT_DEFINE_BASE(IRON_STATE);
ESP_EVENT_DEFINE_BASE(IRON_VISET);
ESP_EVENT_DEFINE_BASE(IRON_HEATER);


namespace evt {
Expand Down
14 changes: 10 additions & 4 deletions ESPIron/evtloop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ESP_EVENT_DECLARE_BASE(IRON_GET_EVT); // ESPIron getter Commands events ba
ESP_EVENT_DECLARE_BASE(IRON_NOTIFY); // ESPIron notification events base (those events are published when some state or mode changes due to any commands or component's logic)
ESP_EVENT_DECLARE_BASE(IRON_STATE); // ESPIron State publishing events base (those events are published on IRON_GET_EVT requests on demand)
ESP_EVENT_DECLARE_BASE(IRON_VISET); // ESPIron VisualSet HID events
ESP_EVENT_DECLARE_BASE(IRON_HEATER); // ESPIron heater control events

// cast enum to int
template <class E>
Expand All @@ -48,20 +49,25 @@ enum class iron_t:int32_t {

// Commands
sensorsReload = 200, // reload configuration for any sensors available
heaterTargetT, // set heater target temperature, parameter int32_t
workTemp, // set working temperature, parameter int32_t
workModeToggle, // toggle working mode on/off
boostModeToggle, // toggle boost mode on/off

// Heater Commands
heaterTargetT = 250, // set heater target temperature, parameter int32_t
heaterEnable,
heaterDisable,
heaterRampUp, // start PWM ramp heating, switch to enabled mode

reloadTemp, // reload temperature configuration
reloadTimeouts, // reload timeouts configuration

// Commands - power control
pdVoltage, // switch PD trigger, arg uint32_t in V
qcVoltage, // switch QC trigger, arg uint32_t in V
qc2enable, // activate QC trigger in QC2 mode
qc3enable, // activate QC trigger in QC3 mode
qcDisable, // disable QC trigger
// qc2enable, // activate QC trigger in QC2 mode
// qc3enable, // activate QC trigger in QC3 mode
// qcDisable, // disable QC trigger

// State notifications
stateWorking = 300,
Expand Down
118 changes: 81 additions & 37 deletions ESPIron/heater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@
#endif
#define HEATER_TASK_NAME "HEATER"

#define LEDC_MODE LEDC_LOW_SPEED_MODE // S2 supports only low speed mode
#define LEDC_DUTY_RES HEATER_RES
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_FREQUENCY HEATER_FREQ
#define HEATER_LEDC_SPEEDMODE LEDC_LOW_SPEED_MODE // S2 supports only low speed mode
#define HEATER_LEDC_DUTY_RES HEATER_RES
#define HEATER_LEDC_TIMER LEDC_TIMER_0
#define HEATER_LEDC_FREQUENCY HEATER_FREQ
#define HEATER_LEDC_RAMPUP_TIME 3000 // time duration to fade in-for PWM ramping, ms

#define HEATER_OPAMP_STABILIZE_MS 5 // how long to wait after disabling PWM to let OpAmp stabilize

#define HEATER_ADC_SAMPLES 8 // number of ADC reads to averate tip tempearture

#define PID_ENGAGE_DIFF_LOW 30 // lower temperature difference when PID algo should be engaged
#define PID_ENGAGE_DIFF_HIGH 10 // higher temperature difference when PID algo should be engaged


// normal interval between temp measurments
constexpr TickType_t measure_delay_ticks = pdMS_TO_TICKS(1000 / HEATER_MEASURE_RATE);
// long interval between temp measurments
Expand All @@ -35,12 +40,12 @@ TipHeater::~TipHeater(){
esp_event_handler_instance_unregister_with(evt::get_hndlr(), IRON_SET_EVT, ESP_EVENT_ANY_ID, _evt_cmd_handler);
_evt_cmd_handler = nullptr;
}

/*
if (_evt_ntf_handler){
esp_event_handler_instance_unregister_with(evt::get_hndlr(), IRON_NOTIFY, ESP_EVENT_ANY_ID, _evt_ntf_handler);
_evt_ntf_handler = nullptr;
}

*/
_stop_runner();
}

Expand All @@ -54,25 +59,28 @@ void TipHeater::init(){
if (!_evt_cmd_handler){
esp_event_handler_instance_register_with(
evt::get_hndlr(),
IRON_SET_EVT,
ESP_EVENT_ANY_ID, // subscribe to 'sensorsReload command'
IRON_HEATER,
ESP_EVENT_ANY_ID,
[](void* self, esp_event_base_t base, int32_t id, void* data) { static_cast<TipHeater*>(self)->_evt_picker(base, id, data); },
this,
&_evt_cmd_handler
);
}

/*
if (!_evt_ntf_handler){
esp_event_handler_instance_register_with(
evt::get_hndlr(),
IRON_NOTIFY,
ESP_EVENT_ANY_ID, // subscribe to 'sensorsReload command'
ESP_EVENT_ANY_ID,
[](void* self, esp_event_base_t base, int32_t id, void* data) { static_cast<TipHeater*>(self)->_evt_picker(base, id, data); },
this,
&_evt_ntf_handler
);
}
*/

// init HW fader
ledc_fade_func_install(0);
// create RTOS task that controls heater PWM
_start_runner();
}
Expand All @@ -87,45 +95,54 @@ void TipHeater::_evt_picker(esp_event_base_t base, int32_t id, void* data){
return;
}

case evt::iron_t::stateWorking : {
case evt::iron_t::heaterEnable : {
// enable heater
enable();
return;
}

case evt::iron_t::stateIdle : {
case evt::iron_t::heaterDisable : {
// disable heater in standby mode
disable();
return;
}

case evt::iron_t::heaterRampUp : {
// disable heater in standby mode
rampUp();
return;
}

/*
// obsolete, use deep-sleep in Suspend
case evt::iron_t::stateSuspend : {
// disable worker task in standby mode
_stop_runner();
return;
}
*/
}
}


void TipHeater::_start_runner(){
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.duty_resolution = LEDC_DUTY_RES,
.timer_num = LEDC_TIMER,
.freq_hz = LEDC_FREQUENCY,
.speed_mode = HEATER_LEDC_SPEEDMODE,
.duty_resolution = HEATER_LEDC_DUTY_RES,
.timer_num = HEATER_LEDC_TIMER,
.freq_hz = HEATER_LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {
.gpio_num = _pwm.gpio,
.speed_mode = LEDC_MODE,
.speed_mode = HEATER_LEDC_SPEEDMODE,
.channel = _pwm.channel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER,
.timer_sel = HEATER_LEDC_TIMER,
.duty = 0,
.hpoint = 0,
.flags = {.output_invert = _pwm.invert }
Expand All @@ -140,18 +157,28 @@ void TipHeater::_start_runner(){
HEATER_TASK_PRIO,
&_task_hndlr,
tskNO_AFFINITY ) == pdPASS;

// fader iterrupt handler, it will resume heaterControl task on fade end event
ledc_cbs_t fade_callback = {
.fade_cb = TipHeater::_cb_ledc_fade_end_event
};
// register fader interrupt handler
ledc_cb_register(HEATER_LEDC_SPEEDMODE, _pwm.channel, &fade_callback, this);
}

void TipHeater::_stop_runner(){
_state = HeaterState_t::shutoff;
ledc_stop(LEDC_MODE, _pwm.channel, _pwm.invert);
ledc_stop(HEATER_LEDC_SPEEDMODE, _pwm.channel, _pwm.invert);
if(_task_hndlr)
vTaskDelete(_task_hndlr);
_task_hndlr = nullptr;

// unregister cb interrupt handler
ledc_fade_func_uninstall();
}

void TipHeater::_heaterControl(){
TickType_t xLastWakeTime = xTaskGetTickCount ();
TickType_t xLastWakeTime = xTaskGetTickCount();
TickType_t delay_time = measure_delay_ticks;
for (;;){
// sleep to accomodate specified measuring rate
Expand All @@ -165,12 +192,15 @@ void TipHeater::_heaterControl(){
delay_time = idle_delay_ticks;
break;
case HeaterState_t::active : {
if (ledc_get_duty(LEDC_MODE, _pwm.channel)){
// use while to avoid concurency issues with hw fader interrupt
while (ledc_get_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel)){
// shut off heater in order to measure temperature 关闭加热器以测量温度
ledc_set_duty(LEDC_MODE, _pwm.channel, 0);
ledc_update_duty(LEDC_MODE, _pwm.channel);
ledc_set_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel, 0);
ledc_update_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel);
// idle while OpAmp stabilizes
vTaskDelay(pdMS_TO_TICKS(HEATER_OPAMP_STABILIZE_MS));
// reset run time (need to avoid extra consecutive runs with xTaskDelayUntil if thread was suspended from the outside)
xLastWakeTime = xTaskGetTickCount();
}
break;
}
Expand All @@ -189,10 +219,10 @@ void TipHeater::_heaterControl(){
if (_state != HeaterState_t::notip && t > TEMP_NOTIP){
// we have just lost connection with a tip sensor
// disable PWM
ledc_set_duty(LEDC_MODE, _pwm.channel, 0);
ledc_update_duty(LEDC_MODE, _pwm.channel);
ledc_set_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel, 0);
ledc_update_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel);
_state = HeaterState_t::notip;
LOGI(T_HEAT, printf, "Iron Tip ejected, T:%d\n", static_cast<int32_t>(t));
LOGW(T_HEAT, printf, "Iron Tip ejected, T:%d\n", static_cast<int32_t>(t));
EVT_POST(SENSOR_DATA, e2int(evt::iron_t::tipEject));
continue;
}
Expand All @@ -201,7 +231,7 @@ void TipHeater::_heaterControl(){
if (_state == HeaterState_t::notip && t < TEMP_NOTIP){
_state = HeaterState_t::inactive;
EVT_POST(SENSOR_DATA, e2int(evt::iron_t::tipInsert));
LOGI(T_HEAT, println, "Iron Tip inserted");
LOGW(T_HEAT, println, "Iron Tip inserted");
continue;
}

Expand All @@ -224,24 +254,23 @@ void TipHeater::_heaterControl(){
// note: this is ugly external function, I will rework it later
//_t.calibrated = calculateTemp(_t.avg);
_t.calibrated = _t.avg;
LOGV(T_HEAT, printf, "avg T: %5.1f, calibrated T: %d\n", _t.avg, _t.calibrated);
ADC_LOGD(printf, "avg T: %5.1f, cal T: %d, tgt T:%d\n", _t.avg, _t.calibrated, _t.target);
EVT_POST_DATA(SENSOR_DATA, e2int(evt::iron_t::tiptemp), &_t.calibrated, sizeof(_t.calibrated));

auto diff = abs(_t.target - _t.calibrated);
// if PID algo should be engaged
if (diff < PID_ENGAGE_DIFF){
if (_t.calibrated > (_t.target - PID_ENGAGE_DIFF_LOW) && _t.calibrated < (_t.target + PID_ENGAGE_DIFF_HIGH)){
_pwm.duty = _pid.step(_t.target, _t.calibrated);
delay_time = measure_delay_ticks;
} else {
// heater must be either turned on or off
// heater must be either turned full on or off
_pwm.duty = _t.calibrated < _t.target ? 1<<HEATER_RES : 0;
_pid.clear(); // reset pid algo
// give heater more time to gain/loose temperature
delay_time = long_measure_delay_ticks;
}

ledc_set_duty(LEDC_MODE, _pwm.channel, _pwm.duty);
ledc_update_duty(LEDC_MODE, _pwm.channel);
ledc_set_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel, _pwm.duty);
ledc_update_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel);
PWM_LOGV(printf, "Duty:%u\n", _pwm.duty);
}
// Task must self-terminate (if ever)
Expand All @@ -265,21 +294,36 @@ void TipHeater::enable(){
return;
}

LOGI(T_HEAT, printf, "Enabling, target T:%d\n", _t.target);
LOGI(T_HEAT, printf, "Enable, target T:%d\n", _t.target);
};

void TipHeater::disable(){
switch (_state){
case HeaterState_t::active :
ledc_set_duty(LEDC_MODE, _pwm.channel, 0);
ledc_update_duty(LEDC_MODE, _pwm.channel);
ledc_set_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel, 0);
ledc_update_duty(HEATER_LEDC_SPEEDMODE, _pwm.channel);
_state = HeaterState_t::inactive;
LOGI(T_PWM, println, "Disable heater");
LOGI(T_PWM, println, "Disable");
break;
// in all other cases this call could be ignored
}
}

void TipHeater::rampUp(){
// can only ramp-up from inactive state
if (_state != HeaterState_t::inactive) return;

// first suspend heaterControl task for the duration of fade-in, 'cause no changes to PWM duty could be made for that period,
// fader interrupt will resume it
vTaskSuspend( _task_hndlr );
// initiate HW fader
ledc_set_fade_time_and_start(HEATER_LEDC_SPEEDMODE, _pwm.channel, 1<<HEATER_RES, HEATER_LEDC_RAMPUP_TIME, LEDC_FADE_NO_WAIT);
// from now on consider heater in active state
_state = HeaterState_t::active;
}



// 对32个ADC读数进行平均以降噪
// VP+_Ru = 100k, Rd_GND = 1K
float TipHeater::_denoiseADC(){
Expand Down
Loading

0 comments on commit 43cf277

Please sign in to comment.