/* menu.c */ //#include #include #include #include #include #include #include "common.h" #include "menu.h" #include "display.h" #include "timer.h" #include "therm.h" #include "config.h" #include "beep.h" #include "fields.h" #include "profile.h" #include "control.h" uint8_t menu_current; uint8_t menu_row; uint8_t field_row; uint8_t menu_next; uint8_t menu_redraw; bool edit_dirty; #define REDRAW_NONE 0 #define REDRAW_FIELDS 1 #define REDRAW_ALL 2 uint8_t run_status; bool menu_editing; uint8_t menu_editing_field; menu_t *menu_current_p; const char str_b[] PROGMEM = "B"; const char str_e[] PROGMEM = "E"; const char str_j[] PROGMEM = "J"; const char str_k[] PROGMEM = "K"; const char str_n[] PROGMEM = "N"; const char str_r[] PROGMEM = "R"; const char str_s[] PROGMEM = "S"; const char str_t[] PROGMEM = "T"; PGM_P const enum_thermocouple_types[] PROGMEM = { str_b, str_e, str_j, str_k, str_n, str_r, str_s, str_t }; const char str_negative[] PROGMEM = "-"; const char str_positive[] PROGMEM = "+"; PGM_P const enum_posneg[] PROGMEM = { str_negative, str_positive }; const char str_off[] PROGMEM = "Off"; const char str_on[] PROGMEM = "On"; PGM_P const enum_boolean[] PROGMEM = { str_off, str_on }; const char str_50hz[] PROGMEM = "50Hz"; const char str_60hz[] PROGMEM = "60Hz"; PGM_P const enum_frequency[] PROGMEM = { str_50hz, str_60hz }; const char str_degc[] PROGMEM = "\047C"; const char str_degf[] PROGMEM = "\047F"; /* str_k already defined */ const char str_degde[] PROGMEM = "\047De"; const char str_degn[] PROGMEM = "\047N"; const char str_degr[] PROGMEM = "\047R"; const char str_degre[] PROGMEM = "\047Re"; const char str_degro[] PROGMEM = "\047Ro"; const char str_mev[] PROGMEM = "meV"; #define TEMPERATURE_CELSIUS 0 #define TEMPERATURE_FAHRENHEIT 1 #define TEMPERATURE_KELVIN 2 #define TEMPERATURE_DELISLE 3 #define TEMPERATURE_NEWTON 4 #define TEMPERATURE_RANKINE 5 #define TEMPERATURE_REAUMUR 6 #define TEMPERATURE_ROMER 7 #define TEMPERATURE_MEV 8 PGM_P const enum_units[] PROGMEM = { str_degc, str_degf, str_k, str_degde, str_degn, str_degr, str_degre, str_degro, str_mev }; const char str_seconds[] PROGMEM = "s"; const char str_minutes[] PROGMEM = "m"; const char str_hours[] PROGMEM = "h"; PGM_P const enum_time_units[] PROGMEM = { str_seconds, str_minutes, str_hours }; const char str_0[] PROGMEM = "0"; const char str_1[] PROGMEM = "1"; const char str_2[] PROGMEM = "2"; const char str_3[] PROGMEM = "3"; const char str_4[] PROGMEM = "4"; const char str_5[] PROGMEM = "5"; const char str_6[] PROGMEM = "6"; const char str_7[] PROGMEM = "7"; const char str_8[] PROGMEM = "8"; const char str_9[] PROGMEM = "9"; PGM_P const enum_digits[] PROGMEM = { str_0, str_1, str_2, str_3, str_4, str_5, str_6, str_7, str_8, str_9, }; const char str_start[] PROGMEM = "Start?"; const char str_running[] PROGMEM = "Run"; const char str_finished[] PROGMEM = "Done"; //const char str_error[] PROGMEM = "Error"; PGM_P const enum_status[] PROGMEM = { str_start, str_running, str_finished // str_error }; #define RUN_STATUS_READY 0 #define RUN_STATUS_RUNNING 1 #define RUN_STATUS_FINISHED 2 #define RUN_STATUS_ERROR 3 const char str_empty[] PROGMEM = ""; const char str_fault[] PROGMEM = "FAULT"; //const char str_open[] PROGMEM = "Connect thermocouple"; //const char str_chiprange[] PROGMEM = "Cold junction range"; //const char str_temprange[] PROGMEM = "Thermocouple range"; PGM_P const enum_faultstatus[] PROGMEM = { str_empty, str_fault // str_open, // str_chiprange, // str_temprange }; const char str_run_profile[] PROGMEM = "Run profile"; const char str_edit_profile[] PROGMEM = "Edit profile"; const char str_configure[] PROGMEM = "Configure"; const char str_faultstatus[] PROGMEM = "\x0a"; const char str_oven[] PROGMEM = "Oven \x08\x02\x02\x02\x02.\x02\x04\x04\x04"; const char str_ambient[] PROGMEM = "Ambient \x08\x02\x02\x02\x02.\x02\x04\x04\x04"; PGM_P const menu_main[] PROGMEM = { str_run_profile, str_edit_profile, str_configure, NULL, str_faultstatus, NULL, str_oven, str_ambient }; const char str_placeholder[] PROGMEM = "Placeholder"; const char str_selecting_profile[] PROGMEM = "Selecting profile"; const char str_str[] PROGMEM = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"; const char str_editstr[] PROGMEM = "\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11"; PGM_P const menu_select_profile[] PROGMEM = { str_str, str_str, str_str, str_str, str_str, str_str, str_str, str_str }; const char str_editing_profile[] PROGMEM = "Editing profile"; const char str_name_field[] PROGMEM = "> Name field <"; const char str_start_temperature[] PROGMEM = "Start temp \x18\x12\x12\x12\x12\x04<<"; const char str_edit_field[] PROGMEM = "\x02\x02.\x02\x02/ \x12\x12\x12\x12\x13 \x18\x12\x12\x12\x12\x04<<"; PGM_P const menu_edit_profile[] PROGMEM = { str_editstr, str_start_temperature, str_edit_field, str_edit_field, str_edit_field, str_edit_field, str_edit_field, str_edit_field, }; const char str_contrast[] PROGMEM = "Contrast \x12\x12\x12"; const char str_beep[] PROGMEM = "Beep \x15>>"; const char str_thermocouple[] PROGMEM = "Thermocouple \x16"; const char str_frequency[] PROGMEM = "Frequency \x17>>>"; const char str_units[] PROGMEM = "Units \x14>>"; const char str_p[] PROGMEM = "P \x18\x12.\x12\x12"; const char str_i[] PROGMEM = "I \x18\x12.\x12\x12"; const char str_d[] PROGMEM = "D \x18\x12.\x12\x12"; PGM_P const menu_configuration[] PROGMEM = { str_contrast, str_beep, str_thermocouple, str_frequency, str_units, str_p, str_i, str_d }; const char str_running_profile[] PROGMEM = "\x09\x09\x09\x09\x09\x09\x12\x12\x12\x12\x13\x08\x02\x02\x02\x02.\x02\x04<<"; PGM_P const menu_run_profile[] PROGMEM = { str_running_profile, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static void main_menu_fields(uint8_t row); static void configuration_fields(uint8_t row); static void save_configuration_fields(uint8_t row); static void select_profile_fields(uint8_t row); static void edit_profile_fields(uint8_t row); static void save_profile_fields(uint8_t row); static void update_run_status(uint8_t row); menu_t menus[] = { { menu_main, 0, 2, main_menu_fields, NULL }, { menu_select_profile, 0, 7, select_profile_fields, NULL }, { menu_configuration, 0, 7, configuration_fields, save_configuration_fields }, { menu_edit_profile, 0, 7, edit_profile_fields, save_profile_fields }, { menu_run_profile, 7, 7, update_run_status, NULL } }; #define TEMPERATURE_UNIT 0x1000L int8_t temp_n[] = {1, 9, 1, -3, 33, 9, 4, 21, 10}; int8_t temp_d[] = {1, 5, 1, 2, 100, 5, 5, 40, 116}; int32_t temp_off[] = {0, 32*TEMPERATURE_UNIT, 1118822, -614400, 0, 2013880, 0, 30720, 96450}; int32_t temperature_from_kelvin(temp_t temp) { uint8_t units = config.units; int32_t temp32 = temp; int32_t source = ((temp32-2732)<<12) / 10; int32_t result; result = source * temp_n[units] / temp_d[units] + temp_off[units]; // printf("temp = %d, source = %d (%x), result = %d (%x)\n", temp, source, source, result, result); return result; } temp_t temperature_to_kelvin(int32_t source) { uint8_t units = config.units; int32_t result = (source - temp_off[units]) * temp_d[units] / temp_n[units]; return therm_reduce(result); } static void update_run_status(uint8_t row) { temp_t temp = therm_temp(); write_field_enum(0, run_status); write_field_uint16(1, control_now()); write_field_enum(2, 0); write_field_temperature(3, TRUE, temp); } static void main_menu_fields(uint8_t row) { temp_t temp; switch (row) { case 4: write_field_enum(0, therm_fault()?1:0); return; case 6: temp = therm_temp(); break; case 7: temp = therm_coldtemp(); break; default: return; } write_field_temperature(0, TRUE, temp); } static void configuration_fields(uint8_t row) { if (row == 0) { write_field_uint8(0, config.contrast); return; } if (row > 4) { int16_t value16; int32_t val; switch (row) { case 5: value16 = config.p; break; case 6: value16 = config.i; break; case 7: value16 = config.d; break; default: return; } val = (int32_t)value16 << 5; write_field_fracint(0, TRUE, TRUE, val); } else { uint8_t value8; switch (row) { case 1: value8 = config.beep; break; case 2: value8 = config.thermocouple_type; break; case 3: value8 = config.frequency; break; case 4: value8 = config.units; break; } write_field_enum(0, value8); } } static void save_configuration_fields(uint8_t row) { if (row == 0) { config.contrast = read_field_uint8(0); return; } if (row > 4) { int16_t value16 = read_field_fracint(0, TRUE) >> 5; switch (row) { case 5: config.p = value16; break; case 6: config.i = value16; break; case 7: config.d = value16; break; } } else { uint8_t value8 = read_field_enum(0); switch (row) { case 1: config.beep = value8; break; case 2: config.thermocouple_type = value8; break; case 3: config.frequency = value8; break; case 4: config.units = value8; break; } } } static void select_profile_fields(uint8_t row) { profile_select(row); memcpy(field_values, &profile_p->name, PROFILE_NAME_LENGTH); } static void save_profile_fields(uint8_t row) { int32_t temp; temp_t k_temp; uint8_t n; if (row == 0) { memcpy(profile_p->name, field_values, PROFILE_NAME_LENGTH); return; } n = find_field_number(find_editable_field(0, FALSE)); temp = read_field_fracint(n+((row == 1)?0:2), FALSE); k_temp = temperature_to_kelvin(temp); set_profile_temperature(k_temp); if (row == 1) return; set_profile_time(row-2, read_field_uint16(n), read_field_enum(n+1)); } static void edit_profile_fields(uint8_t row) { int32_t unit_temp, prev_temp, step; temp_t temp; uint16_t time; uint8_t time_units; if (row == 0) { memcpy(field_values, profile_p->name, PROFILE_NAME_LENGTH); return; } temp = get_profile_temperature(row); write_field_temperature(((row == 1)?0:4), FALSE, temp); if (row == 1) return; unit_temp = temperature_from_kelvin(temp); prev_temp = temperature_from_kelvin(get_profile_temperature(row-1)); time = profile_get_time(row-2); time_units = profile_get_time_units(row-2); if (time == 0) step = 0; else step = (unit_temp - prev_temp) / time; write_field_fracint(0, FALSE, TRUE, step); write_field_uint16(2, time); write_field_enum(3, time_units); } #define MENU_MAIN 0 #define MENU_SELECT_PROFILE 1 #define MENU_CONFIGURATION 2 #define MENU_EDIT_PROFILE 3 #define MENU_RUN_PROFILE 4 char menu_getchar(uint8_t col) { PGM_P ptr = get_string(&menu_current_p->text[field_row]); if (!ptr) return '\0'; if (col > 20) return '\0'; return pgm_read_byte(ptr + col); } field_t fields[] = { {NULL /* ASCII */, 127-32, 1}, {enum_digits, ARRAY_SIZE(enum_digits), 1}, {enum_time_units, ARRAY_SIZE(enum_time_units), 1}, {enum_units, ARRAY_SIZE(enum_units), 3}, {enum_boolean, ARRAY_SIZE(enum_boolean), 3}, {enum_thermocouple_types, ARRAY_SIZE(enum_thermocouple_types), 1}, {enum_frequency, ARRAY_SIZE(enum_frequency), 4}, {enum_posneg, ARRAY_SIZE(enum_posneg), 1}, {enum_status, ARRAY_SIZE(enum_status), 6}, {enum_faultstatus, ARRAY_SIZE(enum_faultstatus), 21} }; static void display_field(char c, uint8_t col) { uint8_t i; if (field_is_text(c)) { if ((field_values[col] >= 32) && (field_values[col] < 32+field_display_entries(c))) display_putchar(field_values[col]); else display_putchar('?'); return; } if (field_values[col] >= field_display_entries(c)) { for (i = 0; i < field_length(c); i++) display_putchar('?'); } else { PGM_P str = field_text(c, field_values[col]); i = 0; if (field_length(c) > 1) { i = field_length(c) - strlen_P(str); if (menu_getchar(col+1) == '>') for (; i; i--) display_putchar(' '); } display_putstr_P(str); for (; i; i--) display_putchar(' '); } } #define menu_draw_current_row() menu_draw_row(menu_row) static void menu_set_fields(uint8_t row) { menu_t *menu = menu_current_p; field_row = row; if ((!menu_editing) && menu->get_field) menu->get_field(row); } static void menu_draw_row(uint8_t row) { uint8_t col; display_settextpos(0, row); display_setinverse((row == menu_row) && !menu_editing); if (get_string(&menu_current_p->text[row])) { char c; menu_set_fields(row); col = 0; while ((c = menu_getchar(col)) != '\0') { if (menu_editing) display_setinverse(col == menu_editing_field); if (is_field(c)) { display_field(c, col); col += field_length(c); } else { display_putchar(c); col++; } } } display_clearline(); } #if 1 static void display_menu(bool all) { uint8_t row; for (row = 0; row < 8; row++) { field_row = row; if (all || (find_field(0) != 255)) menu_draw_row(row); } } #endif void set_menu(uint8_t new_menu) { if (new_menu == menu_current) return; menu_current = new_menu; menu_current_p = &menus[new_menu]; menu_redraw = TRUE; // memcpy_P(&menu_row, &menu_current_p->first_line, sizeof(menu_row)); menu_row = 0; menu_editing = FALSE; } void menu_init(void) { /* Make sure set_menu does its work */ menu_current = MENU_MAIN+1; set_menu(MENU_MAIN); display_init(); } #define BUTTON_UP 1 #define BUTTON_DOWN 2 #define BUTTON_LEFT 4 #define BUTTON_RIGHT 3 static void menu_end_edit(void) { menu_t *menu = menu_current_p; menu_editing = FALSE; if (menu->put_field) menu->put_field(menu_row); menu_redraw = TRUE; } static void menu_edit_new_field(uint8_t field, bool left) { field = find_editable_field(field, left); menu_editing_field = field; menu_editing = TRUE; edit_dirty = FALSE; if (field == 255) menu_end_edit(); menu_draw_row(menu_row); } static void increment_value(int8_t inc) { uint8_t val = field_values[menu_editing_field]; uint8_t c = menu_getchar(menu_editing_field); uint8_t min = 0; uint8_t max = field_display_entries(c) - 1; if (field_is_text(c)) { min += 32; max += 32; } if (inc < 0 && val == min) val = max; else if (inc > 0 && val == max) val = min; else val += inc; field_values[menu_editing_field] = val; edit_dirty = TRUE; } static void menu_navigate_up(void) { if (menu_row == menu_current_p->first_line) return; menu_row--; menu_draw_row(menu_row+1); menu_draw_row(menu_row); } static void menu_navigate_down(void) { if (menu_row == menu_current_p->last_line) return; menu_row++; menu_draw_row(menu_row-1); menu_draw_row(menu_row); } static void menu_navigate_into(void) { uint8_t menu = menu_current; switch (menu) { case MENU_MAIN: switch (menu_row) { case 0: menu = MENU_SELECT_PROFILE; menu_next = MENU_RUN_PROFILE; break; case 1: menu = MENU_SELECT_PROFILE; menu_next = MENU_EDIT_PROFILE; break; case 2: menu = MENU_CONFIGURATION; break; } break; case MENU_SELECT_PROFILE: menu = menu_next; profile_select(menu_row); break; case MENU_RUN_PROFILE: control_start(); run_status = RUN_STATUS_RUNNING; break; default: menu_edit_new_field(21, TRUE); break; } set_menu(menu); } static void menu_navigate_back(void) { uint8_t menu = menu_current; switch (menu) { case MENU_EDIT_PROFILE: profile_save(); case MENU_RUN_PROFILE: menu = MENU_SELECT_PROFILE; break; default: menu = MENU_MAIN; break; } set_menu(menu); } static void menu_process_leftright(bool left) { int8_t increment = left?(-1):1; if (menu_editing) menu_edit_new_field(menu_editing_field+increment, left); else { if (left) menu_navigate_back(); else menu_navigate_into(); } } static void menu_process_updown(bool up) { int8_t increment = up?1:(-1); if (menu_editing) { increment_value(increment); menu_draw_row(menu_row); } else { if (up) menu_navigate_up(); else menu_navigate_down(); } } #define GRAPH_POINTS 128 //#define GRAPH_POINTS 128 //#define GRAPH_POINTS 12 #define GRAPH_START_X 0 #define GRAPH_HEIGHT (GRAPH_PAGES * 8) #define GRAPH_PAGES 7 #define GRAPH_START_Y 1 //uint8_t graph_desired[GRAPH_POINTS+1]; //uint8_t graph_actual[GRAPH_POINTS+1]; uint8_t graph_actual[4]; static void minmax_mark(uint8_t *min_p, uint8_t *max_p, uint8_t mid) { uint8_t min = *min_p; uint8_t max = *max_p; if (mid == 255) { *min_p = 255; return; } if (min == 255) min = mid; if (max == 255) max = mid; if (max < min) { uint8_t tmp = min; min = max; max = tmp; } *min_p = mid - (mid-min)/2; *max_p = (max-mid)/2 + mid; } temp_t graph_temp_min, graph_temp_range; uint32_t time_per_unit; uint8_t graph_cursor; static uint8_t graph_value(temp_t temp); static uint8_t graph_at(uint8_t col) { return graph_value(temperature_at_time(col*time_per_unit)); } static void graphics_draw_column(uint8_t col) { uint8_t data; uint8_t pr_mid, pr_min, pr_max; uint8_t act_mid, act_min, act_max; uint8_t i; uint8_t page; uint8_t act_col; if (col >= GRAPH_POINTS) return; if (col == graph_cursor) act_col = 1; else act_col = 0; pr_mid = graph_at(col); pr_min = graph_at(col-1); pr_max = graph_at(col+1); minmax_mark(&pr_min, &pr_max, pr_mid); act_min = graph_actual[act_col]; act_mid = graph_actual[act_col+1]; act_max = graph_actual[act_col+2]; minmax_mark(&act_min, &act_max, act_mid); if (col == 0) { pr_min = 0; pr_max = GRAPH_HEIGHT-1; } page = GRAPH_PAGES-1; for (i = 0; i < GRAPH_HEIGHT; i++) { data <<= 1; if ((i >= pr_min && i <= pr_max) || (i >= act_min && i <= act_max) || (i == 0)) { data |= 1; } if ((i & 0x7) == 7) { if (graph_cursor == col) data |= 0x55; display_setposition(col, GRAPH_START_Y + page); display_data(1, &data); page--; } } } static void graphics_draw_screen(void) { int i; /* initialise profile */ for (i = 0; i < GRAPH_POINTS; i++) graphics_draw_column(i); } static uint8_t __attribute__ ((noinline)) graph_value(temp_t temp) { int32_t v; if (temp == INVALID_TEMPERATURE) v = 255; else v = ((int32_t)temp - graph_temp_min) * GRAPH_HEIGHT / graph_temp_range; return v; } static void graphics_init(void) { uint8_t i; uint32_t total_time; temp_t temp_min, temp_max; uint8_t *graph_actual_p; run_status = RUN_STATUS_READY; temp_min = temp_max = get_profile_temperature(1); total_time = 0; for (i = 0; i < 6; i++) { temp_t temp = get_profile_temperature(i+2); uint32_t time = profile_time(i); if (time == 0) continue; total_time += time; if (temp > temp_max) temp_max = temp; if (temp < temp_min) temp_min = temp; } time_per_unit = (total_time + GRAPH_POINTS - 1) / GRAPH_POINTS; temp_min -= 10 * 10; temp_max += 10 * 10; graph_temp_range = temp_max - temp_min; graph_temp_min = temp_min; graph_cursor = 255; graph_actual_p = &graph_actual[0]; for (i = 0; i < 4; i++) *(graph_actual_p++) = 255; } static void graphics_poll(void) { if (menu_redraw) { graphics_init(); graphics_draw_screen(); } if (run_status == RUN_STATUS_RUNNING) { uint8_t col = control_now() / time_per_unit; if (col <= GRAPH_POINTS) { if (col != graph_cursor) { graph_actual[0] = graph_actual[1]; graph_actual[1] = graph_actual[2]; } graph_actual[2] = graph_value(therm_temp()); } else { run_status = RUN_STATUS_FINISHED; } graph_cursor = col; graphics_draw_column(col-1); graphics_draw_column(col); } } void menu_poll(void) { uint8_t button_pressed; ATOMIC_BLOCK(ATOMIC_FORCEON) { button_pressed = button; button = 0; } if (button_pressed == BUTTON_LEFT || button_pressed == BUTTON_RIGHT) menu_process_leftright(button_pressed == BUTTON_LEFT); if (button_pressed == BUTTON_UP || button_pressed == BUTTON_DOWN) menu_process_updown(button_pressed == BUTTON_UP); if (menu_redraw || (menu_current == MENU_RUN_PROFILE) || (menu_current == MENU_MAIN)) { display_menu(menu_redraw); menu_set_fields(menu_row); } if (menu_current == MENU_RUN_PROFILE) { graphics_poll(); } menu_redraw = FALSE; }