diff --git a/.gitignore b/.gitignore index 86edaa5..8163d65 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Medias/*.c Medias/*.json Debug/ +Release/ # Created by https://www.toptal.com/developers/gitignore/api/eclipse # Edit at https://www.toptal.com/developers/gitignore?templates=eclipse diff --git a/Core/Inc/PSE_unit.h b/Core/Inc/PSE_unit.h index 9b90cad..f099824 100644 --- a/Core/Inc/PSE_unit.h +++ b/Core/Inc/PSE_unit.h @@ -12,6 +12,8 @@ #include "stm32f1xx_hal.h" +#include "lvgl.h" + typedef struct{ char name[16]; // brand / name uint16_t diameter; // diameter (in µm) @@ -37,6 +39,12 @@ typedef struct{ TIM_HandleTypeDef* tim; } pse_stepper_conf; +typedef struct{ + lv_obj_t* flow; + lv_obj_t* volume; + lv_obj_t* enabled; +} pse_home_display; + typedef struct{ uint8_t enabled; pse_ports port; // physical port on which this unit is connected @@ -45,9 +53,12 @@ typedef struct{ uint32_t set_volume; // volume to stop at (0 for no max) pse_syringe* syringe; // associated syringe pse_stepper_conf* stepper_conf; // hardware configuration for the associated stepper + pse_home_display* home_display; // Widgets for updating the home screen } pse_unit; -void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, uint8_t unit_num); -void save_units(pse_unit* units, uint8_t unit_num); + +void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, pse_home_display* home_displays, uint8_t unit_num, uint16_t ws_ind); +void load_units_short(pse_unit* units, uint8_t unit_num, uint16_t ws_ind); +void save_units(pse_unit* units, uint8_t unit_num, uint16_t ws_ind); #endif diff --git a/Core/Inc/PSE_unit_edit_screen.h b/Core/Inc/PSE_unit_edit_screen.h index 3687aa2..c0b3081 100644 --- a/Core/Inc/PSE_unit_edit_screen.h +++ b/Core/Inc/PSE_unit_edit_screen.h @@ -10,6 +10,6 @@ #include "PSE_unit.h" -void PSE_unit_edit_screen_Gen(pse_unit* units, pse_unit* unit, uint8_t units_num); +void PSE_unit_edit_screen_Gen(pse_unit* units, pse_unit* unit, uint8_t units_num, uint16_t ws_ind); #endif /* INC_PSE_EDIT_SCREEN_H_ */ diff --git a/Core/Src/PSE_unit.c b/Core/Src/PSE_unit.c index 8fd4c54..dd5b124 100644 --- a/Core/Src/PSE_unit.c +++ b/Core/Src/PSE_unit.c @@ -5,12 +5,17 @@ * Author: leo */ +#define _GNU_SOURCE + #include "PSE_unit.h" #include "fatfs.h" #include "lvgl.h" -static void generate_default_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, uint8_t unit_num){ +#include +#include + +static void generate_default_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, pse_home_display* home_displays, uint8_t unit_num){ for(int i = 0; i < unit_num; i++){ units[i] = (pse_unit){ .enabled = i%2, @@ -20,6 +25,7 @@ static void generate_default_units(pse_unit* units, pse_syringe* syringes, pse_s .set_volume = 0, .syringe = &syringes[i], .stepper_conf = &stepper_confs[i], + .home_display = &home_displays[i], }; syringes[i] = (pse_syringe){ .name = "Test", @@ -32,16 +38,27 @@ static void generate_default_units(pse_unit* units, pse_syringe* syringes, pse_s // Load units from file // Units configuration are saved as binary, in the order [pse_unit, pse_syringe, pse_stepper_conf] with each unit back to back // In case of any error fallback to a default configuration -void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, uint8_t unit_num){ +void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* stepper_confs, pse_home_display* home_displays, uint8_t unit_num, uint16_t ws_ind){ // Open save file FIL saveFile; - if(f_open(&saveFile, "PSE_save.txt", FA_READ) != FR_OK){ - lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "Could not load configuration to SD card", NULL, true); + + char* filename; + if(asprintf(&filename, "PSEsave%d", ws_ind) == -1){ + lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "Could not load configuration from SD card (filename gen error)", NULL, true); lv_obj_center(mbox1); - generate_default_units(units, syringes, stepper_confs, unit_num); + free(filename); return; } + if(f_open(&saveFile, filename, FA_READ) != FR_OK){ + lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "Could not load configuration from SD card", NULL, true); + lv_obj_center(mbox1); + generate_default_units(units, syringes, stepper_confs, home_displays, unit_num); + free(filename); + return; + } + free(filename); + // read configuration sequentially for(int i = 0; i < unit_num; i++){ FRESULT res; @@ -49,12 +66,13 @@ void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* steppe res = f_read(&saveFile, &units[i], sizeof(units[i]), &bytesRead); units[i].syringe = &syringes[i]; units[i].stepper_conf = &stepper_confs[i]; + units[i].home_display = &home_displays[i]; res += f_read(&saveFile, units[i].syringe, sizeof(pse_syringe), &bytesRead); res += f_read(&saveFile, units[i].stepper_conf, sizeof(pse_stepper_conf), &bytesRead); if(res != FR_OK){ - generate_default_units(units, syringes, stepper_confs, unit_num); + generate_default_units(units, syringes, stepper_confs, home_displays, unit_num); return; } } @@ -62,12 +80,29 @@ void load_units(pse_unit* units, pse_syringe* syringes, pse_stepper_conf* steppe f_close(&saveFile); } -void save_units(pse_unit* units, uint8_t unit_num){ +void load_units_short(pse_unit* units, uint8_t unit_num, uint16_t ws_ind){ + load_units(units, units->syringe, units->stepper_conf, units->home_display, unit_num, ws_ind); +} + +void save_units(pse_unit* units, uint8_t unit_num, uint16_t ws_ind){ // Open save file FIL saveFile; - if(f_open(&saveFile, "PSE_save.txt", FA_WRITE | FA_OPEN_ALWAYS) != FR_OK){ - lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "Could not save configuration to SD card", NULL, true); + + char* filename; + if(asprintf(&filename, "PSEsave%d", ws_ind) == -1){ + lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "Could not save configuration to SD card (filename gen error)", NULL, true); lv_obj_center(mbox1); + free(filename); + return; + } + + FRESULT res; + if((res = f_open(&saveFile, filename, FA_WRITE | FA_OPEN_ALWAYS)) != FR_OK){ + lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Error", "aaa", NULL, true); + lv_obj_t* text = lv_msgbox_get_text(mbox1); + lv_label_set_text_fmt(text, "Could not save configuration to SD card (file open failed %d <%s>)", res, filename); + lv_obj_center(mbox1); + free(filename); return; } @@ -84,4 +119,5 @@ void save_units(pse_unit* units, uint8_t unit_num){ } f_close(&saveFile); + free(filename); } diff --git a/Core/Src/PSE_unit_edit_screen.c b/Core/Src/PSE_unit_edit_screen.c index e4c594d..79516fe 100644 --- a/Core/Src/PSE_unit_edit_screen.c +++ b/Core/Src/PSE_unit_edit_screen.c @@ -22,12 +22,13 @@ static lv_obj_t* this_screen; static pse_unit* c_pse_units; static pse_unit* c_pse_unit; static uint8_t c_pse_units_num; +static uint16_t ws_index; static void back_button_handler(lv_event_t * e){ lv_event_code_t code = lv_event_get_code(e); if(code == LV_EVENT_CLICKED) { - save_units(c_pse_units, c_pse_units_num); + save_units(c_pse_units, c_pse_units_num, ws_index); // update stepper pwm generation pse_stepper_planer_compute_sps(c_pse_unit); @@ -159,7 +160,7 @@ static void jog_forward_button_handler(lv_event_t* e){ if(code == LV_EVENT_PRESSED) { pse_sp_jog_speed(unit->stepper_conf, 1); - pse_sp_set_dir(unit->stepper_conf, 0); + pse_sp_set_dir(unit->stepper_conf, 1); pse_sp_start_axis(unit->stepper_conf); } else if(code == LV_EVENT_RELEASED){ @@ -174,7 +175,7 @@ static void jog_backward_button_handler(lv_event_t* e){ if(code == LV_EVENT_PRESSED) { pse_sp_jog_speed(unit->stepper_conf, 1); - pse_sp_set_dir(unit->stepper_conf, 1); + pse_sp_set_dir(unit->stepper_conf, 0); pse_sp_start_axis(unit->stepper_conf); } else if(code == LV_EVENT_RELEASED){ @@ -226,11 +227,13 @@ static lv_obj_t* controls_widget(lv_obj_t* parent, pse_unit* unit){ return cont; } -void PSE_unit_edit_screen_Gen(pse_unit* units, pse_unit* c_unit, uint8_t units_num){ +void PSE_unit_edit_screen_Gen(pse_unit* units, pse_unit* c_unit, uint8_t units_num, uint16_t ws_ind){ // Create a new screen lv_obj_t* scr = lv_obj_create(NULL); this_screen = scr; + ws_index = ws_ind; + c_pse_units = units; c_pse_unit = c_unit; c_pse_units_num = units_num; diff --git a/Core/Src/home_screen.c b/Core/Src/home_screen.c index 5a287aa..d3cb64a 100644 --- a/Core/Src/home_screen.c +++ b/Core/Src/home_screen.c @@ -17,6 +17,9 @@ static pse_unit* units; static int units_num; +static uint16_t workspace_index = 0; +static lv_obj_t* ws_label; + static void run_handler(lv_event_t * e){ lv_event_code_t code = lv_event_get_code(e); @@ -44,7 +47,7 @@ static lv_obj_t* screen; static void unit_widget_enabled_handler(lv_event_t * e){ lv_event_code_t code = lv_event_get_code(e); - if(code == LV_EVENT_VALUE_CHANGED) { + if(code == LV_EVENT_VALUE_CHANGED) { lv_obj_t* button = lv_event_get_current_target(e); lv_obj_t* label = lv_obj_get_child(button, 0); lv_state_t state = lv_obj_get_state(button); @@ -52,15 +55,15 @@ static void unit_widget_enabled_handler(lv_event_t * e){ pse_unit* unit = lv_event_get_user_data(e); if(state & LV_STATE_CHECKED){ - lv_label_set_text(label, LV_SYMBOL_OK); + lv_label_set_text(label, LV_SYMBOL_CLOSE); unit->enabled = 0; } else{ - lv_label_set_text(label, LV_SYMBOL_CLOSE); + lv_label_set_text(label, LV_SYMBOL_OK); unit->enabled = 1; } - save_units(units, units_num); - } + save_units(units, units_num, workspace_index); + } } static void unit_home_handler(lv_event_t * e){ @@ -82,10 +85,53 @@ static void unit_widget_clicked_handler(lv_event_t* e){ if(code == LV_EVENT_CLICKED) { pse_unit* unit = lv_event_get_user_data(e); - PSE_unit_edit_screen_Gen(units, unit, units_num); + PSE_unit_edit_screen_Gen(units, unit, units_num, workspace_index); } } +static void update_readouts_widgets(){ + for(int i = 0; i < units_num; i++){ + pse_unit* pse_unit = &units[i]; + lv_label_set_text_fmt(pse_unit->home_display->flow, "%02lu.%03lu\nmL/mn", pse_unit->flow / 1000, pse_unit->flow % 1000); + lv_label_set_text_fmt(pse_unit->home_display->volume, "%lu.%lu\nmL", pse_unit->volume / 1000, pse_unit->volume % 1000); + + lv_obj_t* enabled = pse_unit->home_display->enabled; + lv_obj_t* enabled_label = lv_obj_get_child(enabled, 0); + if(pse_unit->enabled){ + lv_obj_clear_state(enabled, LV_STATE_CHECKED); + lv_label_set_text(enabled_label, LV_SYMBOL_OK); + } + else{ + lv_obj_add_state(enabled, LV_STATE_CHECKED); + lv_label_set_text(enabled_label, LV_SYMBOL_CLOSE); + } + } +} + +static void ws_left_handler(lv_event_t * e){ + lv_event_code_t code = lv_event_get_code(e); + + if(code != LV_EVENT_CLICKED) return; + + if(workspace_index == 0) return; + workspace_index--; + + lv_label_set_text_fmt(ws_label, "%d", workspace_index); + load_units_short(units, units_num, workspace_index); + update_readouts_widgets(); +} +static void ws_right_handler(lv_event_t * e){ + lv_event_code_t code = lv_event_get_code(e); + + if(code != LV_EVENT_CLICKED) return; + + workspace_index++; + + lv_label_set_text_fmt(ws_label, "%d", workspace_index); + load_units_short(units, units_num, workspace_index); + update_readouts_widgets(); +} + static lv_obj_t* PSE_unit_widget(lv_obj_t* parent, pse_unit* pse_unit){ // The main container lv_obj_t* cont = lv_obj_create(parent); @@ -98,12 +144,14 @@ static lv_obj_t* PSE_unit_widget(lv_obj_t* parent, pse_unit* pse_unit){ lv_obj_set_width(flow, lv_pct(100)); lv_obj_set_flex_grow(flow, 1); lv_label_set_text_fmt(flow, "%02lu.%03lu\nmL/mn", pse_unit->flow / 1000, pse_unit->flow % 1000); + pse_unit->home_display->flow = flow; // volume delivered readout lv_obj_t* vol = lv_label_create(cont); lv_obj_set_width(vol, lv_pct(100)); lv_obj_set_flex_grow(vol, 1); lv_label_set_text_fmt(vol, "%lu.%lu\nmL", pse_unit->volume / 1000, pse_unit->volume % 1000); + pse_unit->home_display->volume = vol; // home button lv_obj_t* home = lv_btn_create(cont); @@ -122,11 +170,12 @@ static lv_obj_t* PSE_unit_widget(lv_obj_t* parent, pse_unit* pse_unit){ lv_obj_add_event_cb(enabled, unit_widget_enabled_handler, LV_EVENT_ALL, pse_unit); lv_obj_set_flex_grow(enabled, 1); lv_obj_center(enabled_label); - lv_label_set_text(enabled_label, LV_SYMBOL_CLOSE); + lv_label_set_text(enabled_label, LV_SYMBOL_OK); if(!pse_unit->enabled){ lv_obj_add_state(enabled, LV_STATE_CHECKED); - lv_event_send(enabled, LV_EVENT_VALUE_CHANGED, NULL); + lv_label_set_text(enabled_label, LV_SYMBOL_CLOSE); } + pse_unit->home_display->enabled = enabled; return cont; } @@ -144,10 +193,11 @@ void Home_Screen_Gen(pse_unit* pse_units, uint8_t pse_unit_num){ lv_obj_set_align(top_menu, LV_ALIGN_TOP_LEFT); lv_obj_set_size(top_menu, lv_pct(100), lv_pct(20)); lv_obj_set_style_pad_all(top_menu, 5, 0); + lv_obj_set_flex_flow(top_menu, LV_FLEX_FLOW_ROW); // add a run button lv_obj_t* run_button = lv_btn_create(top_menu); - lv_obj_set_size(run_button, lv_pct(10), lv_pct(100)); + lv_obj_set_flex_grow(run_button, 3); lv_obj_set_align(run_button, LV_ALIGN_TOP_LEFT); lv_obj_add_flag(run_button, LV_OBJ_FLAG_CHECKABLE); lv_obj_add_event_cb(run_button, run_handler, LV_EVENT_ALL, NULL); @@ -157,6 +207,34 @@ void Home_Screen_Gen(pse_unit* pse_units, uint8_t pse_unit_num){ lv_label_set_text(run_label, LV_SYMBOL_PLAY); lv_obj_center(run_label); + // add workspace switcher + lv_obj_t* ws_left = lv_btn_create(top_menu); + lv_obj_set_flex_grow(ws_left, 1); + lv_obj_add_event_cb(ws_left, ws_left_handler, LV_EVENT_CLICKED, NULL); + + lv_obj_t* ws_left_label = lv_label_create(ws_left); + lv_label_set_text(ws_left_label, LV_SYMBOL_LEFT); + lv_obj_center(ws_left_label); + + lv_obj_t* ws_label_cont = lv_obj_create(top_menu); + lv_obj_set_flex_grow(ws_label_cont, 1); + lv_obj_set_height(ws_label_cont, lv_pct(100)); + lv_obj_set_style_pad_all(ws_label_cont, 0, 0); + lv_obj_center(ws_label_cont); + + ws_label = lv_label_create(ws_label_cont); + lv_label_set_text_fmt(ws_label, "%d", workspace_index); + lv_obj_set_style_text_align(ws_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(ws_label); + + lv_obj_t* ws_right = lv_btn_create(top_menu); + lv_obj_set_flex_grow(ws_right, 1); + lv_obj_add_event_cb(ws_right, ws_right_handler, LV_EVENT_CLICKED, NULL); + + lv_obj_t* ws_right_label = lv_label_create(ws_right); + lv_label_set_text(ws_right_label, LV_SYMBOL_RIGHT); + lv_obj_center(ws_right_label); + // grid layout for the 4 differents units lv_obj_t* units_grid = lv_obj_create(scr); lv_obj_align_to(units_grid, top_menu, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0); @@ -172,5 +250,5 @@ void Home_Screen_Gen(pse_unit* pse_units, uint8_t pse_unit_num){ } // fade in the new screen - lv_scr_load_anim(scr, LV_SCR_LOAD_ANIM_FADE_ON, 0, 3000, false); + lv_scr_load_anim(scr, LV_SCR_LOAD_ANIM_FADE_ON, 0, 0, false); } diff --git a/Core/Src/main.c b/Core/Src/main.c index 30edde9..982df64 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -91,6 +91,7 @@ ADS7843_Def Touch_Def = { pse_unit pse_units[PSE_UNITS_NUM]; pse_syringe pse_syringes[PSE_UNITS_NUM]; +pse_home_display pse_home_displays[PSE_UNITS_NUM]; enum steppers_axis{PSE_X_STEPPER, PSE_Y_STEPPER, PSE_Z_STEPPER, PSE_E_STEPPER, PSE_STEPPER_NUM}; pse_stepper_conf pse_stepper_confs[PSE_STEPPER_NUM] = { @@ -289,7 +290,7 @@ int main(void) f_mount(&FatFs, SD_disk_path, 0); // Load saved units - load_units(pse_units, pse_syringes, pse_stepper_confs, PSE_UNITS_NUM); + load_units(pse_units, pse_syringes, pse_stepper_confs, pse_home_displays, PSE_UNITS_NUM, 0); // Load the home screen Home_Screen_Gen(pse_units, PSE_UNITS_NUM); diff --git a/Core/Src/pse_stepper_planer.c b/Core/Src/pse_stepper_planer.c index 0c283b9..2b87a5f 100644 --- a/Core/Src/pse_stepper_planer.c +++ b/Core/Src/pse_stepper_planer.c @@ -87,10 +87,6 @@ void pse_stepper_planer_compute_sps(pse_unit* unit){ __HAL_TIM_SET_AUTORELOAD(c->tim, c->tim_period); __HAL_TIM_SET_PRESCALER(c->tim, c->tim_presc); } - lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Info", "aaa", NULL, true); - lv_obj_t * mboxtxt = lv_msgbox_get_text(mbox1); - lv_label_set_text_fmt(mboxtxt, "res : %llu\npresc : %d\nperiod : %d", res, c->tim_presc, c->tim_period); - lv_obj_center(mbox1); } void pse_sp_set_dir_all(pse_unit* units, int units_num, int dir){