diff --git a/Core/Src/Gui/app_gui.c b/Core/Src/Gui/app_gui.c index 849f222..d4af5c7 100644 --- a/Core/Src/Gui/app_gui.c +++ b/Core/Src/Gui/app_gui.c @@ -124,7 +124,6 @@ void switch_screen(screen_t pScreen, bool init) { s_app.initial_pushed = s_app.pushed; s_app.screen = pScreen; // clear the union field - memset(&s_app.page, 0, sizeof(s_app.page)); request_paint(); if (init) { diff --git a/Core/Src/Gui/app_gui.h b/Core/Src/Gui/app_gui.h index fd03144..e3a68a2 100644 --- a/Core/Src/Gui/app_gui.h +++ b/Core/Src/Gui/app_gui.h @@ -79,18 +79,23 @@ struct State { /// Pointer to the currently active screen func screen_t screen; - /// data specific for each of the screens (not persistent across screen switching) - union { - /// Generic menu - struct menu_state { - int pos; - int len; - uint32_t change_time; - uint32_t slide_end_time; - uint16_t text_slide; - uint8_t tick_counter; - } menu; - } page; + /// Generic menu + struct menu_state { + int pos; + int len; + uint32_t change_time; + uint32_t slide_end_time; + uint16_t text_slide; + uint8_t tick_counter; + } menu; + + struct calib_state { + int phase; + float sample1; + float sample2; + int temp1; + int temp2; + } calib; }; extern struct State s_app; diff --git a/Core/Src/Gui/screen_calibration.c b/Core/Src/Gui/screen_calibration.c new file mode 100644 index 0000000..4b2d22c --- /dev/null +++ b/Core/Src/Gui/screen_calibration.c @@ -0,0 +1,186 @@ +// +// Created by MightyPork on 2023/04/09. +// + + +#include +#include "app_gui.h" +#include "app_heater.h" +#include "screen_menu.h" +#include "ufb/fb_text.h" +#include "app_temp.h" +#include "snprintf.h" +#include "FreeRTOS.h" + +static const char* calib0_opts[] = { + "Pokračovat", + "Zrušit", + NULL +}; + +static void calib0_cb(int opt) { + switch (opt) { + case 0: + // Continue + s_app.calib.phase++; + request_paint(); + app_heater_manual_override(100); + break; + + case 1: + switch_screen(screen_home, true); + break; + } +} + + +static const char* calib1_opts[] = { + "Vzorek 1", + "Zrušit", + NULL +}; + + +static void calib1_cb(int opt) { + switch (opt) { + case 0: + // Continue + s_app.calib.phase++; + request_paint(); + s_app.calib.sample1 = app_temp_read_oven_raw(); + app_heater_manual_override(0); + break; + + case 1: + app_heater_manual_override(-1); + switch_screen(screen_home, true); + break; + } +} + +static const char* calib3_opts[] = { + "Vzorek 2", + "Zrušit", + NULL +}; + + +static void calib3_cb(int opt) { + switch (opt) { + case 0: + // Continue + s_app.calib.phase++; + request_paint(); + s_app.calib.sample2 = app_temp_read_oven_raw(); + app_heater_manual_override(0); + break; + + case 1: + app_heater_manual_override(-1); + switch_screen(screen_home, true); + break; + } +} + +void screen_manual_menu(GuiEvent event) +{ + if (event == GUI_EVENT_SCREEN_INIT) { + s_app.calib.phase = 0; + s_app.calib.sample1 = s_app.calib.sample2 = 0.0f; + s_app.calib.temp1 = s_app.calib.temp2 = 0; + } + + int phase = s_app.calib.phase; + int *pT; + switch (phase) { + case 0: + if (event == GUI_EVENT_PAINT) { + fb_text(FBW/2, 14, "Vychlaďte", TEXT_CENTER, 1); + fb_text(FBW/2, 24, "troubu", TEXT_CENTER, 1); + } + screen_menu(event, calib1_opts, calib1_cb); + break; + + case 1: // Heater is active, waiting for mark + case 3: + if (event == GUI_EVENT_PAINT) { + fb_text(FBW/2, 14, "Zapiš teplotu", TEXT_CENTER, 1); + } + if (phase == 1) { + screen_menu(event, calib1_opts, calib1_cb); + } else { + screen_menu(event, calib3_opts, calib3_cb); + } + break; + + case 2: + case 4: + if (phase == 2) { + pT = &s_app.calib.temp1; + } else { + pT = &s_app.calib.temp2; + } + + if (event == GUI_EVENT_PAINT) { + fb_text(FBW/2, 14, phase == 2 ? "Teplota 1" : "Teplota 2", TEXT_CENTER, 1); + SPRINTF(stmp, "%d°C", *pT); + fb_text(FBW/2, 30, stmp, TEXT_CENTER | FONT_DOUBLE, 1); + + fb_text(2, FBH - 8 * 3, "←→Nastav", 0, 1); + fb_text(2, FBH - 8 * 2, "> Potvrdit", 0, 1); + fb_text(2, FBH - 8 * 1, "» Zrušit", 0, 1); + return; + } + + if (push_time() > pdMS_TO_TICKS(500)) { + input_sound_effect(); + app_heater_manual_override_clear(); + switch_screen(screen_home, true); + return; + } + + if (event == GUI_EVENT_KNOB_PLUS) { + if (*pT < 500) { + *pT += 1; + input_sound_effect(); + request_paint(); + } + } + else if (event == GUI_EVENT_KNOB_MINUS) { + if (*pT > 0) { + input_sound_effect(); + *pT -= 1; + request_paint(); + } + } else if (event == GUI_EVENT_KNOB_RELEASE) { + s_app.calib.phase++; + request_paint(); + + if (s_app.calib.phase == 5) { + // TODO do the math + PRINTF("Sample 1 %f, T1 %d\r\n", s_app.calib.sample1, s_app.calib.temp1); + PRINTF("Sample 2 %f, T2 %d\r\n", s_app.calib.sample2, s_app.calib.temp2); + + float corrected1 = c_to_val((float) s_app.calib.temp1); + float corrected2 = c_to_val((float) s_app.calib.temp2); + + float a = ; + float b = ; + + // TODO set and persist calibration + } + } + break; + + case 5: + if (event == GUI_EVENT_PAINT) { + fb_text(FBW/2, 14, "Hotovo", TEXT_CENTER, 1); + fb_text(FBW/2, 36, "→Hlavní menu", TEXT_CENTER, 1); + } + if (event == GUI_EVENT_KNOB_RELEASE) { + switch_screen(screen_home, 1); + } + break; + } + +} diff --git a/Core/Src/Gui/screen_manual.c b/Core/Src/Gui/screen_manual.c index e0e35e5..268b9fb 100644 --- a/Core/Src/Gui/screen_manual.c +++ b/Core/Src/Gui/screen_manual.c @@ -17,6 +17,8 @@ void screen_manual(GuiEvent event) { bool temp_changed = false; if (event == GUI_EVENT_SCREEN_INIT) { + app_heater_manual_override_clear(); + app_heater_enable(false); return; } diff --git a/Core/Src/Gui/screen_menu.c b/Core/Src/Gui/screen_menu.c index 8b89b78..87d6afa 100644 --- a/Core/Src/Gui/screen_menu.c +++ b/Core/Src/Gui/screen_menu.c @@ -17,7 +17,7 @@ void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { bool menu_changed = false; const uint32_t tickNow = xTaskGetTickCount(); - struct menu_state *menu = &s_app.page.menu; + struct menu_state *menu = &s_app.menu; switch (event) { case GUI_EVENT_SCREEN_INIT: diff --git a/Core/Src/app_heater.c b/Core/Src/app_heater.c index cf90b07..9d15a03 100644 --- a/Core/Src/app_heater.c +++ b/Core/Src/app_heater.c @@ -39,12 +39,15 @@ static struct { float tuning_p; float tuning_i; float tuning_d; + /// Manual PWM override (percent, -1 = override disable) + int manual_override; // PID state struct PID pid; } state = { .tuning_p = 10.0f, .tuning_i = 0.052f, .tuning_d = 100.0f, + .manual_override = -1, .pid = { .SampleTimeTicks = pdMS_TO_TICKS(1000), .outMax = 100.0f, @@ -62,6 +65,17 @@ static inline void heaterExitCritical() { osMutexRelease(heaterMutexHandle); } +void app_heater_manual_override(int percent) { + heaterEnterCritical(); + PID_SetCtlMode(&state.pid, PID_MANUAL); + state.manual_override = percent; + heaterExitCritical(); +} + +void app_heater_manual_override_clear() { + app_heater_manual_override(-1); +} + void app_heater_set_tuning(float p, float i, float d) { heaterEnterCritical(); PID_SetTunings(&state.pid, p, i, d); @@ -127,13 +141,19 @@ void app_task_heater(void *argument) heaterEnterCritical(); app_safety_pass_reg_loop_running(); - PID_Compute(&state.pid, state.oven_temp); - if (state.pid.ctlMode == PID_AUTOMATIC) { - PRINTF("temp %d, output %d\r\n", (int) state.oven_temp, (int) state.pid.Output); + + if (state.manual_override >= 0 && state.manual_override <= 100) { + PRINTF("manual override %d%%\r\n", state.manual_override); heater_pwm_set_perc(state.pid.Output); } else { - // turn it off - heater_pwm_set_perc(0); + PID_Compute(&state.pid, state.oven_temp); + if (state.pid.ctlMode == PID_AUTOMATIC) { + PRINTF("temp %d, output %d\r\n", (int) state.oven_temp, (int) state.pid.Output); + heater_pwm_set_perc(state.pid.Output); + } else { + // turn it off + heater_pwm_set_perc(0); + } } heaterExitCritical(); diff --git a/Core/Src/app_heater.h b/Core/Src/app_heater.h index e264ba7..edcc9ba 100644 --- a/Core/Src/app_heater.h +++ b/Core/Src/app_heater.h @@ -14,6 +14,13 @@ extern osThreadId_t heaterTskHandle; void app_task_heater(void *argument); +/// Clear manual override, disable heater for normal mode. +void app_heater_manual_override_clear(); + +/// Set manual override PWM 0-100%. +/// Also disables heater in the normal mode. +void app_heater_manual_override(int percent); + /// Set heater regulator tuning. /// Mutex is locked internally. void app_heater_set_tuning(float p, float i, float d); diff --git a/Core/Src/app_temp.h b/Core/Src/app_temp.h index 6972ba4..b74e943 100644 --- a/Core/Src/app_temp.h +++ b/Core/Src/app_temp.h @@ -20,6 +20,9 @@ void app_temp_sample(); /// The value is valid after calling app_temp_sample() float app_temp_read_oven(); +/// Get the raw ADC value (divider voltage) +float app_temp_read_oven_raw(); + /// Read current SOC temperature (celsius) /// The value is valid after calling app_temp_sample() float app_temp_read_soc(); diff --git a/Makefile b/Makefile index dd2ad3c..9bac69c 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ Core/Src/Gui/app_gui.c \ Core/Src/Gui/screen_menu.c \ Core/Src/Gui/screen_home.c \ Core/Src/Gui/screen_manual.c \ +Core/Src/Gui/screen_calibration.c \ Core/Src/Gui/screen_manual_menu.c \ Core/Src/app_temp.c \ Core/Src/app_knob.c \