diff --git a/Core/Src/Gui/app_gui.h b/Core/Src/Gui/app_gui.h index e3a68a2..5f71a7a 100644 --- a/Core/Src/Gui/app_gui.h +++ b/Core/Src/Gui/app_gui.h @@ -41,6 +41,7 @@ uint32_t push_time(); void screen_home(GuiEvent event); void screen_manual(GuiEvent event); void screen_manual_menu(GuiEvent event); +void screen_calibration(GuiEvent event); struct State { /// Latest oven temp readout @@ -78,24 +79,6 @@ struct State { /// Pointer to the currently active screen func screen_t screen; - - /// 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 index 4b2d22c..64c90c1 100644 --- a/Core/Src/Gui/screen_calibration.c +++ b/Core/Src/Gui/screen_calibration.c @@ -4,6 +4,7 @@ #include +#include #include "app_gui.h" #include "app_heater.h" #include "screen_menu.h" @@ -12,6 +13,28 @@ #include "snprintf.h" #include "FreeRTOS.h" +struct calib_state { + int phase; + float sample1; + float sample2; + int temp1; + int temp2; +} s_calib; + +enum Phase { + PH_HELLO = 0, + PH_SAMPLE1, + PH_TEMP1, + PH_SAMPLE2, + PH_TEMP2, + PH_DONE, +}; + +static void next_phase() { + PUTS("Phase++\r\n"); + s_calib.phase++; +} + static const char* calib0_opts[] = { "Pokračovat", "Zrušit", @@ -22,7 +45,7 @@ static void calib0_cb(int opt) { switch (opt) { case 0: // Continue - s_app.calib.phase++; + next_phase(); request_paint(); app_heater_manual_override(100); break; @@ -41,13 +64,15 @@ static const char* calib1_opts[] = { }; + + static void calib1_cb(int opt) { switch (opt) { case 0: // Continue - s_app.calib.phase++; + next_phase(); request_paint(); - s_app.calib.sample1 = app_temp_read_oven_raw(); + s_calib.sample1 = app_temp_read_oven_raw(); app_heater_manual_override(0); break; @@ -69,9 +94,9 @@ static void calib3_cb(int opt) { switch (opt) { case 0: // Continue - s_app.calib.phase++; + next_phase(); request_paint(); - s_app.calib.sample2 = app_temp_read_oven_raw(); + s_calib.sample2 = app_temp_read_oven_raw(); app_heater_manual_override(0); break; @@ -82,54 +107,41 @@ static void calib3_cb(int opt) { } } -void screen_manual_menu(GuiEvent event) +void screen_calibration(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; + memset(&s_calib, 0, sizeof(s_calib)); + // continue to the rest - so the menu can be inited } - int phase = s_app.calib.phase; int *pT; - switch (phase) { - case 0: + switch (s_calib.phase) { + case PH_HELLO: if (event == GUI_EVENT_PAINT) { - fb_text(FBW/2, 14, "Vychlaďte", TEXT_CENTER, 1); + fb_text(FBW/2, 14, "Vychlaď", TEXT_CENTER, 1); fb_text(FBW/2, 24, "troubu", TEXT_CENTER, 1); } - screen_menu(event, calib1_opts, calib1_cb); + screen_menu(event, calib0_opts, calib0_cb); break; - case 1: // Heater is active, waiting for mark - case 3: + case PH_SAMPLE1: // Heater is active, waiting for mark + case PH_SAMPLE2: if (event == GUI_EVENT_PAINT) { fb_text(FBW/2, 14, "Zapiš teplotu", TEXT_CENTER, 1); } - if (phase == 1) { + if (s_calib.phase == PH_SAMPLE1) { 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; + case PH_TEMP1: + case PH_TEMP2: + if (s_calib.phase == PH_TEMP1) { + pT = &s_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; + pT = &s_calib.temp2; } if (push_time() > pdMS_TO_TICKS(500)) { @@ -139,40 +151,62 @@ void screen_manual_menu(GuiEvent event) return; } - if (event == GUI_EVENT_KNOB_PLUS) { - if (*pT < 500) { - *pT += 1; - input_sound_effect(); - request_paint(); + switch (event) { + case GUI_EVENT_PAINT: { + fb_text(FBW/2, 14, s_calib.phase == PH_TEMP1 ? "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; } - } - else if (event == GUI_EVENT_KNOB_MINUS) { - if (*pT > 0) { - input_sound_effect(); - *pT -= 1; - request_paint(); + + case GUI_EVENT_KNOB_PLUS: { + if (*pT < 500) { + *pT += 1; + input_sound_effect(); + request_paint(); + } + break; } - } 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); + case GUI_EVENT_KNOB_MINUS: { + if (*pT > 0) { + input_sound_effect(); + *pT -= 1; + request_paint(); + } + break; + } + + case GUI_EVENT_KNOB_RELEASE: { + next_phase(); + request_paint(); - float corrected1 = c_to_val((float) s_app.calib.temp1); - float corrected2 = c_to_val((float) s_app.calib.temp2); + if (s_calib.phase == PH_DONE) { + app_heater_manual_override(-1); + // TODO do the math + PRINTF("Sample 1 %f, T1 %d\r\n", s_calib.sample1, s_calib.temp1); + PRINTF("Sample 2 %f, T2 %d\r\n", s_calib.sample2, s_calib.temp2); - float a = ; - float b = ; + float corrected1 = c_to_val((float) s_calib.temp1); + float corrected2 = c_to_val((float) s_calib.temp2); - // TODO set and persist calibration + float a = (corrected1 - corrected2) / (s_calib.sample1 - s_calib.sample2); + float b = corrected1 - a * s_calib.sample1; + + app_temp_set_calib(a, b); + } else { + app_heater_manual_override(100); + } + break; } } break; - case 5: + case PH_DONE: if (event == GUI_EVENT_PAINT) { fb_text(FBW/2, 14, "Hotovo", TEXT_CENTER, 1); fb_text(FBW/2, 36, "→Hlavní menu", TEXT_CENTER, 1); @@ -182,5 +216,4 @@ void screen_manual_menu(GuiEvent event) } break; } - } diff --git a/Core/Src/Gui/screen_home.c b/Core/Src/Gui/screen_home.c index 6acf398..f4b5c2f 100644 --- a/Core/Src/Gui/screen_home.c +++ b/Core/Src/Gui/screen_home.c @@ -12,8 +12,8 @@ static const char* main_menu_opts[] = { "Ruční režim", "Kalibrace", - "Programy", - "Diagnostika", +// "Programy", +// "Diagnostika", NULL }; @@ -23,7 +23,7 @@ static void main_menu_cb(int opt) { switch_screen(screen_manual, true); break; case 1: - // TODO + switch_screen(screen_calibration, true); break; } } diff --git a/Core/Src/Gui/screen_menu.c b/Core/Src/Gui/screen_menu.c index 87d6afa..d92e312 100644 --- a/Core/Src/Gui/screen_menu.c +++ b/Core/Src/Gui/screen_menu.c @@ -3,6 +3,7 @@ // #include +#include #include "screen_menu.h" #include "app_gui.h" #include "FreeRTOS.h" @@ -12,40 +13,53 @@ #include "ufb/framebuffer.h" #include "ufb/fb_text.h" +struct menu_state { + int pos; + int len; + uint32_t change_time; + uint32_t slide_end_time; + uint16_t text_slide; + void * last_opts; +} s_menu; void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { + if (event != GUI_EVENT_SCREEN_INIT && s_menu.last_opts != (void*) options) { + // ensure the menu is properly inited when the options list changes. + screen_menu(GUI_EVENT_SCREEN_INIT, options, cb); + } + bool menu_changed = false; const uint32_t tickNow = xTaskGetTickCount(); - struct menu_state *menu = &s_app.menu; - switch (event) { case GUI_EVENT_SCREEN_INIT: - menu->pos = 0; - menu->len = 0; - menu->change_time = tickNow; - menu->text_slide = 0; + memset(&s_menu, 0, sizeof(s_menu)); + s_menu.last_opts = (void*) options; + + s_menu.change_time = tickNow; + + // count options const char **opt = options; while (*opt) { - menu->len++; + s_menu.len++; opt++; } break; case GUI_EVENT_SCREEN_TICK: // long text sliding animation - if (tickNow - menu->change_time >= pdMS_TO_TICKS(500)) { - const uint32_t textlen = utf8_strlen(options[menu->pos]) * 6; + if (tickNow - s_menu.change_time >= pdMS_TO_TICKS(500)) { + const uint32_t textlen = utf8_strlen(options[s_menu.pos]) * 6; if (textlen >= FBW - 2) { - if (textlen - menu->text_slide > FBW - 1) { - menu->text_slide += 1; - if (textlen - menu->text_slide >= FBW - 1) { - menu->slide_end_time = tickNow; + if (textlen - s_menu.text_slide > FBW - 1) { + s_menu.text_slide += 1; + if (textlen - s_menu.text_slide >= FBW - 1) { + s_menu.slide_end_time = tickNow; } - } else if (tickNow - menu->slide_end_time >= pdMS_TO_TICKS(500)) { - menu->change_time = tickNow; - menu->slide_end_time = 0; - menu->text_slide = 0; + } else if (tickNow - s_menu.slide_end_time >= pdMS_TO_TICKS(500)) { + s_menu.change_time = tickNow; + s_menu.slide_end_time = 0; + s_menu.text_slide = 0; } request_paint(); } @@ -53,13 +67,13 @@ void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { break; case GUI_EVENT_PAINT: - for (int i = 0; i < menu->len; i++) { + for (int i = 0; i < s_menu.len; i++) { // is the row currently rendered the selected row? - const bool is_selected = menu->pos == i; + const bool is_selected = s_menu.pos == i; const fbcolor_t color = !is_selected; // text color - black if selected, because it's inverted const fbpos_t y = 27 + i * 10; fb_rect(0, y, FBW, 10, !color); - fb_text(1 - (is_selected ? menu->text_slide : 0), y + 1, options[i], FONT_5X7, color); + fb_text(1 - (is_selected ? s_menu.text_slide : 0), y + 1, options[i], FONT_5X7, color); // ensure the text doesn't touch the edge (looks ugly) fb_vline(FBW - 1, y, 10, !color); fb_vline(0, y, 10, !color); @@ -69,28 +83,28 @@ void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { // the button is held! release is what activates the button case GUI_EVENT_KNOB_RELEASE: input_sound_effect(); - cb(menu->pos); + cb(s_menu.pos); break; case GUI_EVENT_KNOB_PLUS: - if (menu->pos < menu->len - 1) { - menu->pos++; + if (s_menu.pos < s_menu.len - 1) { + s_menu.pos++; menu_changed = true; } break; case GUI_EVENT_KNOB_MINUS: - if (menu->pos > 0) { - menu->pos--; + if (s_menu.pos > 0) { + s_menu.pos--; menu_changed = true; } break; } if (menu_changed) { - menu->change_time = tickNow; - menu->text_slide = 0; - menu->slide_end_time = 0; + s_menu.change_time = tickNow; + s_menu.text_slide = 0; + s_menu.slide_end_time = 0; input_sound_effect(); request_paint(); } diff --git a/Core/Src/app_heater.c b/Core/Src/app_heater.c index 9d15a03..9449330 100644 --- a/Core/Src/app_heater.c +++ b/Core/Src/app_heater.c @@ -66,6 +66,8 @@ static inline void heaterExitCritical() { } void app_heater_manual_override(int percent) { + PRINTF("Set manual override: %d\r\n", percent); + heaterEnterCritical(); PID_SetCtlMode(&state.pid, PID_MANUAL); state.manual_override = percent; diff --git a/Core/Src/app_temp.c b/Core/Src/app_temp.c index fbd0b9f..0f5aebc 100644 --- a/Core/Src/app_temp.c +++ b/Core/Src/app_temp.c @@ -26,17 +26,17 @@ const float V_REFINT = 1.23f; static struct App { float oven_temp; + float oven_temp_raw; float soc_temp; - float v_sensor; float cal_a; float cal_b; + float oventemp_history[OVENTEMP_HISTORY_DEPTH]; // raw temp uint16_t adc_averagebuf[AVERAGEBUF_DEPTH * 4]; uint8_t averagebuf_ptr; - float adc_averages[4]; - float oventemp_history[OVENTEMP_HISTORY_DEPTH]; uint8_t oventemp_history_ptr; } s_analog = { - .cal_a = 1.0f, + // Ax + B = y ... X = raw sample (ratio 0-1 of 3.3), Y = corrected sample + .cal_a = 1.0f, // safe default calibration constants .cal_b = 0.0f, }; @@ -148,6 +148,35 @@ static const float TSENSE_LOOKUP[TSENSE_LOOKUP_LEN] = { 0.219346163379138f, }; +/// if the calibration constants are zero, reset to defaults +static void correct_invalid_calib() { + if (s_analog.cal_a == 0.0f || s_analog.cal_b == 0.0f) { + PRINTF("ADC invalid calib, reset\r\n"); + s_analog.cal_a = 1.0f; + s_analog.cal_b = 0.0f; + } +} + +/// Set and persist calibration constants +void app_temp_set_calib(float a, float b) { + s_analog.cal_a = a; + s_analog.cal_b = b; + correct_invalid_calib(); + + EE_Status st = EE_WriteVariable32bits(EE_ADDR_CAL_A, ((x32_t) { .f = a }).u); + if (st == EE_CLEANUP_REQUIRED) { + EE_CleanUp(); + } else if (st != EE_OK) { + PRINTF("EE write err %d!\r\n", st); + } + st = EE_WriteVariable32bits(EE_ADDR_CAL_B, ((x32_t) { .f = b }).u); + if (st == EE_CLEANUP_REQUIRED) { + EE_CleanUp(); + } else if (st != EE_OK) { + PRINTF("EE write err %d!\r\n", st); + } +} + void app_analog_init() { // read calibration constants @@ -161,11 +190,7 @@ void app_analog_init() PRINTF("ADC calib b read from EE: %f\r\n", s_analog.cal_b); } - if (s_analog.cal_a == 0.0f || s_analog.cal_b == 0.0f) { - PRINTF("ADC invalid calib, reset\r\n"); - s_analog.cal_a = 1.0f; - s_analog.cal_b = 0.0f; - } + correct_invalid_calib(); LL_ADC_Enable(ADC_TEMP); @@ -192,7 +217,7 @@ void app_analog_init() LL_TIM_EnableCounter(TIM1); } -static float val_to_c(float val) +float val_to_c(float val) { // TODO use binary search.. lol for (int i = 1; i < TSENSE_LOOKUP_LEN; i++) { @@ -207,6 +232,22 @@ static float val_to_c(float val) return TSENSE_T_MAX; } +float c_to_val(float cf) +{ + int lower = (int) (cf / TSENSE_T_STEP); + + if (lower < 0) { + lower = 0; + } + if (lower >= TSENSE_LOOKUP_LEN - 1) { + lower = TSENSE_LOOKUP_LEN - 2; + } + int upper = lower + 1; + + float ratio = (cf - ((float)lower * TSENSE_T_STEP)) / 5.0f; + return TSENSE_LOOKUP[lower] + (TSENSE_LOOKUP[upper] - TSENSE_LOOKUP[lower]) * ratio; +} + void app_temp_sample() { uint32_t sums[4] = {}; @@ -224,39 +265,38 @@ void app_temp_sample() return; } - s_analog.adc_averages[0] = (float) sums[0] / count; - s_analog.adc_averages[1] = (float) sums[1] / count; - s_analog.adc_averages[2] = (float) sums[2] / count; - s_analog.adc_averages[3] = (float) sums[3] / count; + float adc_averages[4]; + + adc_averages[0] = (float) sums[0] / count; + adc_averages[1] = (float) sums[1] / count; + adc_averages[2] = (float) sums[2] / count; + adc_averages[3] = (float) sums[3] / count; PRINTF("%f\t%f\t%f\t%f\r\n", - s_analog.adc_averages[0], - s_analog.adc_averages[1], - s_analog.adc_averages[2], - s_analog.adc_averages[3] + adc_averages[0], + adc_averages[1], + adc_averages[2], + adc_averages[3] ); /* r_pt100, r_ref, internal_temp, v_ref_int */ - float refint = s_analog.adc_averages[3]; + float refint = adc_averages[3]; float scale = V_REFINT / refint; const float avg_slope = 4.3f * scale; const float v25 = 1.43f; - const float v_tsen = s_analog.adc_averages[2] * scale; + const float v_tsen = adc_averages[2] * scale; s_analog.soc_temp = (v25 - v_tsen) / avg_slope + 25.f; - s_analog.v_sensor = s_analog.adc_averages[0] * scale; // good for debug/tuning + //s_analog.v_sensor = adc_averages[0] * scale; // good for debug/tuning // using a voltage divider, so assuming the reference resistor is measured well, // we can just use the ratio and the exact voltage value is not important. - float x = s_analog.adc_averages[0] / s_analog.adc_averages[1]; - float y = s_analog.cal_a * x + s_analog.cal_b; - float actual_temp = val_to_c(y); - PRINTF("Compensated x %f -> y %f, temp %f C\r\n", x, y, actual_temp); + float oventemp_sample = adc_averages[0] / adc_averages[1]; - s_analog.oventemp_history[s_analog.oventemp_history_ptr] = actual_temp; + s_analog.oventemp_history[s_analog.oventemp_history_ptr] = oventemp_sample; s_analog.oventemp_history_ptr = (s_analog.oventemp_history_ptr + 1) % OVENTEMP_HISTORY_DEPTH; float sum = 0; @@ -270,15 +310,21 @@ void app_temp_sample() if (depth > 0) { sum /= depth; } - s_analog.oven_temp = sum; + + float y = s_analog.cal_a * sum + s_analog.cal_b; + float actual_temp = val_to_c(y); + PRINTF("Compensated x %f -> y %f, temp %f C\r\n", sum, y, actual_temp); + + s_analog.oven_temp = actual_temp; + s_analog.oven_temp_raw = sum; app_safety_pass_temp_calculation(); - if (s_analog.oven_temp >= 5.0 && s_analog.oven_temp <= 455.0) { + if (s_analog.oven_temp_raw >= 0.05 && s_analog.oven_temp_raw <= 0.22) { app_safety_pass_temp_normal(); } - if (s_analog.soc_temp >= 5.0 && s_analog.soc_temp <= 80.0) { + if (s_analog.soc_temp >= 2.0 && s_analog.soc_temp <= 80.0) { app_safety_pass_soc_temp_ok(); } } @@ -288,6 +334,11 @@ float app_temp_read_oven() return s_analog.oven_temp; } +float app_temp_read_oven_raw() +{ + return s_analog.oven_temp_raw; +} + float app_temp_read_soc() { return s_analog.soc_temp; diff --git a/Core/Src/app_temp.h b/Core/Src/app_temp.h index b74e943..85425ee 100644 --- a/Core/Src/app_temp.h +++ b/Core/Src/app_temp.h @@ -7,6 +7,11 @@ void app_analog_init(); +float val_to_c(float val); +float c_to_val(float c); + +void app_temp_set_calib(float a, float b); + /** * Update temperature measurement. *