diff --git a/src/display.c b/src/display.c new file mode 100644 --- /dev/null +++ b/src/display.c @@ -0,0 +1,748 @@ +// +// Display: render menus on OLED display +// + +#include "display.h" +#include "gpio.h" +#include "ssd1306/ssd1306.h" +#include "system/stringhelpers.h" +#include "flash.h" + +// Private function prototypes +static void draw_setpoint(therm_status_t* status); + + +// Button transition variables +static uint8_t sw_btn_last = 0; +static uint8_t sw_up_last = 0; +static uint8_t sw_down_last = 0; +static uint8_t sw_left_last = 0; +static uint8_t sw_right_last = 0; + + +// Buttonpress macros +#define SW_BTN_PRESSED (sw_btn_last == 0 && sw_btn == 1) // rising edge on buttonpress +#define SW_UP_PRESSED (sw_up_last == 0 && sw_up == 1) +#define SW_DOWN_PRESSED (sw_down_last == 0 && sw_down == 1) +#define SW_LEFT_PRESSED (sw_left_last == 0 && sw_left == 1) +#define SW_RIGHT_PRESSED (sw_right_last == 0 && sw_right == 1) + + +// States +static uint8_t trigger_drawsetpoint = 1; +static int16_t last_temp = 21245; +static int16_t last_temp_frac = 21245; +static int16_t last_state = 123; +static uint8_t goto_mode = MODE_HEAT; +static uint8_t reset_mode = RESET_REBOOT; + +static char* sensor_lookup[] = {"NTC", "K ", "E ", "N ", "R ", "S ", "T "}; + +static uint8_t toggle = 0; + +void display_1hz(void) +{ + toggle = !toggle; +} + +static char updown(void) +{ + if(toggle) + return '\x8f'; + else + return '\x90'; +} + +// Display state machine +void display_process(void) +{ + therm_status_t* status = runtime_status(); + therm_settings_t* set = flash_getsettings(); + + + uint8_t state_changed = status->state != last_state; + last_state = status->state; + + uint8_t temp_changed = status->temp != last_temp; + last_temp = status->temp; + + uint8_t sw_btn = !HAL_GPIO_ReadPin(SW_BTN); + uint8_t sw_up = !HAL_GPIO_ReadPin(SW_UP); + uint8_t sw_down = !HAL_GPIO_ReadPin(SW_DOWN); + uint8_t sw_left = !HAL_GPIO_ReadPin(SW_LEFT); + uint8_t sw_right = !HAL_GPIO_ReadPin(SW_RIGHT); + + switch(status->state) + { + // Idle state + case STATE_IDLE: + { + // Write text to OLED + // [ therm :: idle ] + ssd1306_drawstring("therm \x87 idle ", 0, 40); + status->pid_enabled = 0; + + if(temp_changed || state_changed) { + char tempstr[16]; + snprintf(tempstr, 16, "Temp: %g", status->temp); + ssd1306_drawstring(" ", 3, 40); + + ssd1306_drawstring(tempstr, 3, 40); + } + + if (state_changed) { + ssd1306_drawlogo(); + } + + switch(goto_mode) { + + case MODE_HEAT: + { + if(set->val.plant_type == PLANT_HEATER) + ssd1306_drawstring("\x83 heat ", 1, 40); + else + ssd1306_drawstring("\x83 cool ", 1, 40); + + } break; + + case MODE_SETUP: + { + ssd1306_drawstring("\x83 setup ", 1, 40); + } break; + + case MODE_RESET: + { + ssd1306_drawstring("\x83 reset ", 1, 40); + } break; + + #ifdef BOOTLOADER_SHORTCUT + case MODE_BOOTLOADER: + { + ssd1306_drawstring("\x83 dfu ", 1, 40); + } + #endif + } + + // Button handler + if(SW_BTN_PRESSED) { + switch(goto_mode) { + case MODE_HEAT: + status->state = STATE_PREHEAT; + break; + case MODE_SETUP: + status->state = STATE_SETSENSORTYPE; + break; + case MODE_RESET: + status->state = STATE_RESET; + reset_mode = RESET_REBOOT; + break; + #ifdef BOOTLOADER_SHORTCUT + case MODE_BOOTLOADER: + ssd1306_clearscreen(); + ssd1306_drawstring("Bootloader Entered", 0, 0); + ssd1306_drawstring("Device won't boot", 2, 0); + ssd1306_drawstring("until reflashed!", 3, 0); +// bootloader_enter(); // Resets into bootloader + status->state = STATE_RESET; // Just in case + break; + #endif + default: + status->state = STATE_PREHEAT; + } + } + else if(SW_DOWN_PRESSED && goto_mode < (MODE_SIZE - 1)) { + goto_mode++; + } + else if(SW_UP_PRESSED && goto_mode > 0) { + goto_mode--; + } + + + // Event Handler + // N/A + + } break; + + + + case STATE_SETSENSORTYPE: + { + // Write text to OLED + // [ therm :: set mode ] + // [ m = ] + ssd1306_drawstring("Sensor Type", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + ssd1306_drawstring(sensor_lookup[set->val.sensor_type], 1, 60); + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETMODE; + } + else + { + user_input((uint16_t*)&set->val.sensor_type); + if(set->val.sensor_type > 6) + set->val.sensor_type = 6; + + } + // Event Handler + // N/A + + } break; + + case STATE_SETMODE: + { + // Write text to OLED + // [ therm :: set mode ] + // [ m = ] + ssd1306_drawstring("Control Mode", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + + if(set->val.control_mode == MODE_PID) + ssd1306_drawstring("PID ", 1, 60); + else + ssd1306_drawstring("Thermostat", 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETPLANTTYPE; + } + else if (!HAL_GPIO_ReadPin(SW_UP)) { + set->val.control_mode = MODE_PID; + } + else if(!HAL_GPIO_ReadPin(SW_DOWN)) { + set->val.control_mode = MODE_THERMOSTAT; + } + // Event Handler + // N/A + + } break; + + + case STATE_SETPLANTTYPE: + { + // Write text to OLED + // [ therm :: set mode ] + // [ m = ] + ssd1306_drawstring("Plant Type", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52 ); + + if(set->val.plant_type == PLANT_HEATER) + ssd1306_drawstring("Heater", 1, 60); + else + ssd1306_drawstring("Cooler", 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + if(set->val.control_mode == MODE_PID) + status->state = STATE_SETP; + else + status->state = STATE_SETHYSTERESIS; + } + else if (!HAL_GPIO_ReadPin(SW_UP)) { + set->val.plant_type = PLANT_COOLER; + } + else if(!HAL_GPIO_ReadPin(SW_DOWN)) { + set->val.plant_type = PLANT_HEATER; + } + // Event Handler + // N/A + + } break; + + + + + case STATE_SETP: + { + // Write text to OLED + // [ therm :: set p ] + // [ p = 12 ] + ssd1306_drawstring("Proportional", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "P=%d", set->val.k_p); + ssd1306_drawstring(tempstr, 1, 60); + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETI; + } + else { + user_input((uint16_t*)&set->val.k_p); + } + + // Event Handler + // N/A + + } break; + + case STATE_SETI: + { + // Write text to OLED + // [ therm :: set i ] + // [ i = 12 ] + ssd1306_drawstring("Integral", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "I=%d", set->val.k_i); + ssd1306_drawstring(tempstr, 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETD; + } + else { + user_input((uint16_t*)&set->val.k_i); + } + + // Event Handler + // N/A + + } break; + + case STATE_SETD: + { + // Write text to OLED + // [ therm :: set d ] + // [ d = 12 ] + ssd1306_drawstring("Derivative", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "D=%d", set->val.k_d); + ssd1306_drawstring(tempstr, 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETWINDUP; + } + else { + user_input((uint16_t*)&set->val.k_d); + } + + // Event Handler + // N/A + + } break; + + + + case STATE_SETHYSTERESIS: + { + // Write text to OLED + ssd1306_drawstring("Hysteresis", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "H=%d", set->val.hysteresis); + ssd1306_drawstring(tempstr, 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETBOOTTOBREW; + } + else { + user_input((uint16_t*)&set->val.hysteresis); + } + + // Event Handler + // N/A + + } break; + + + case STATE_SETWINDUP: + { + // Write text to OLED + // [ therm :: set windup ] + // [ g = 12 ] + ssd1306_drawstring("Windup Guard", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "G=%d", set->val.windup_guard); + ssd1306_drawstring(tempstr, 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETBOOTTOBREW; + } + else { + user_input((uint16_t*)&set->val.windup_guard); + } + + // Event Handler + // N/A + + } break; + + case STATE_SETBOOTTOBREW: + { + // Write text to OLED + // [ therm :: set windup ] + // [ g = 12 ] + ssd1306_drawstring("Start on Boot", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawstring("sob=", 1, 50); + + ssd1306_drawchar(updown(), 1, 43); + + if(set->val.boottobrew) + ssd1306_drawstring("Enable ", 1, 75); + else + ssd1306_drawstring("Disable", 1, 75); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETUNITS; + } + else if(!HAL_GPIO_ReadPin(SW_UP)) { + set->val.boottobrew = 1; + } + else if(!HAL_GPIO_ReadPin(SW_DOWN)) { + set->val.boottobrew = 0; + } + + // Event Handler + // N/A + + } break; + + case STATE_SETUNITS: + { + // Write text to OLED + // [ therm :: set windup ] + // [ g = 12 ] + ssd1306_drawstring("Units: ", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + if(set->val.temp_units == TEMP_UNITS_FAHRENHEIT) + ssd1306_drawstring("Fahrenheit", 1, 60); + else + ssd1306_drawstring("Celsius ", 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_SETTEMPOFFSET; + } + else if(!HAL_GPIO_ReadPin(SW_UP)) { + set->val.temp_units = TEMP_UNITS_FAHRENHEIT; + } + else if(!HAL_GPIO_ReadPin(SW_DOWN)) { + set->val.temp_units = TEMP_UNITS_CELSIUS; + } + + // Event Handler + // N/A + + } break; + + + case STATE_SETTEMPOFFSET: + { + // Write text to OLED + // [ therm :: set temp offset ] + // [ g = 12 ] + ssd1306_drawstring("Temp Cal Offset", 0, 40); + ssd1306_drawlogo(); + + ssd1306_drawchar(updown(), 1, 52); + + char tempstr[12]; + snprintf(tempstr, 12, "O=%d", set->val.temp_offset); + ssd1306_drawstring(tempstr, 1, 60); + + ssd1306_drawstring("Press to accept", 3, 40); + + // Button handler + if(SW_BTN_PRESSED) { +// flash_save(set); + status->state = STATE_IDLE; + } + else { + user_input_signed(&set->val.temp_offset); + } + + // Event Handler + // N/A + + } break; + + + case STATE_PREHEAT: + { + // Write text to OLED + // [ therm : preheating brew ] + // [ 30 => 120 C ] + if(set->val.plant_type == PLANT_HEATER) + ssd1306_drawstring("Preheating...", 0, 0); + else + ssd1306_drawstring("Precooling...", 0, 0); + + //ssd1306_drawlogo(); + draw_setpoint(status); + + status->pid_enabled = 1; + status->setpoint = set->val.setpoint_brew; + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_IDLE; + } + else { + user_input_signed(&set->val.setpoint_brew); + } + + // Event Handler + if(status->temp >= status->setpoint) { + status->state = STATE_MAINTAIN; + } + + } break; + + case STATE_MAINTAIN: + { + // Write text to OLED + // [ therm : ready to brew ] + // [ 30 => 120 C ] + + if(set->val.plant_type == PLANT_HEATER) + ssd1306_drawstring("Preheated!", 0, 0); + else + ssd1306_drawstring("Precooled!", 0, 0); + + draw_setpoint(status); + status->pid_enabled = 1; + status->setpoint = set->val.setpoint_brew; + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_IDLE; + } + else { + user_input_signed(&set->val.setpoint_brew); + } + + // Event Handler + // N/A + + } break; + + // Thermocouple error + case STATE_TC_ERROR: + { + // Write text to OLED + // [ therm : ready to steam ] + // [ 30 => 120 C ] + ssd1306_drawstring("Error: ", 0, 0); + + char tempstr[6]; + itoa(status->error_code, tempstr, 10); + ssd1306_drawstring(tempstr, 0, 57); + + //TODO: add RTD error codes + + if(status->error_code == 1) + ssd1306_drawstring(" TC Open Circuit", 1, 0); + else if(status->error_code == 4) + ssd1306_drawstring(" TC Short to GND", 1, 0); + else if(status->error_code == 8) + ssd1306_drawstring(" TC Short to VCC", 1, 0); + else + ssd1306_drawstring("#?, Unknown Error", 1, 0); + ssd1306_drawstring(" ", 2, 0); + + ssd1306_drawstring("-> to ignore all or", 2, 0); + ssd1306_drawstring("press to continue", 3, 0); + + // Button handler + if(SW_BTN_PRESSED) { + status->state = STATE_IDLE; + #ifdef MAX31865_RTD_SENSOR + max31865_clear_errors(spi_get()); + #endif + } + else if(SW_RIGHT_PRESSED) { + set->val.ignore_error = 1; + status->state = STATE_IDLE; + } + // Event Handler + // Maybe handle if TC is plugged in + // N/A + + } break; + + + // Reset state + case STATE_RESET: + { + // Write text to OLED + // [ therm :: reset ] + ssd1306_drawstring("therm :: reset ", 0, 40); + status->pid_enabled = 0; + + ssd1306_drawlogo(); + + switch(reset_mode) { + case RESET_DEFAULTS: + { + ssd1306_drawstring("-> defaults ", 1, 40); + } break; + case RESET_BOOTLOADER: + { + ssd1306_drawstring("-> bootloader ", 1, 40); + } break; + case RESET_REBOOT: + { + ssd1306_drawstring("-> reboot ", 1, 40); + } break; + case RESET_EXIT: + { + ssd1306_drawstring("-> exit ", 1, 40); + } break; + } + + // Button handler + if(SW_BTN_PRESSED) { + switch(reset_mode) { + case RESET_BOOTLOADER: + { + ssd1306_clearscreen(); + ssd1306_drawstring("Bootloader Entered", 0, 0); + ssd1306_drawstring("Device won't boot", 2, 0); + ssd1306_drawstring("until reflashed!", 3, 0); + HAL_Delay(1000); +// bootloader_enter(); // Resets into bootloader + status->state = STATE_RESET; // Just in case + } break; + case RESET_DEFAULTS: + { + status->state = STATE_RESET; +// flash_load_defaults(set); +// flash_save(set); + NVIC_SystemReset(); + } break; + case RESET_REBOOT: + { + status->state = STATE_RESET; + NVIC_SystemReset(); + } break; + case RESET_EXIT: + { + status->state = STATE_IDLE; + } break; + } + } + else if(SW_DOWN_PRESSED && reset_mode < (RESET_SIZE-1)) { + reset_mode++; + } + else if(SW_UP_PRESSED && reset_mode > 0) { + reset_mode--; + } + + + // Event Handler + // N/A + + } break; + + // Something is terribly wrong + default: + { + status->state = STATE_IDLE; + status->pid_enabled = 0; + + } break; + + } + + if(last_state != status->state) { + // Clear screen on state change + goto_mode = MODE_HEAT; + trigger_drawsetpoint = 1; + ssd1306_clearscreen(); + } + + // Last buttonpress + sw_btn_last = sw_btn; + sw_up_last = sw_up; + sw_down_last = sw_down; + sw_left_last = sw_left; + sw_right_last = sw_right; +} + + +static float temp_last = 43002.0; +static float setpoint_last = 10023.0; + +// Draw current setpoint on display +static void draw_setpoint(therm_status_t* status) { + // FIXME: need to do this when switching modes too + if(status->temp != temp_last || trigger_drawsetpoint) { + char tempstr[8]; + snprintf(tempstr, 8, "%g ", status->temp); + ssd1306_drawstringbig(tempstr, 3, 0); + } + + if(trigger_drawsetpoint) + ssd1306_drawstringbig(">", 3, 74); + + if(status->setpoint != setpoint_last || trigger_drawsetpoint) { + char tempstr[4]; + snprintf(tempstr, 4, "%g ", status->setpoint); + ssd1306_drawstringbig(tempstr, 3, 90); + } + + trigger_drawsetpoint = 0; + setpoint_last = status->setpoint; + temp_last = status->temp; +} + +void display_startup_screen() { + ssd1306_clearscreen(); + ssd1306_drawstring("therm v0.4", 1, 40); + ssd1306_drawstring("protofusion.org/therm", 3, 0); +} + +// vim:softtabstop=4 shiftwidth=4 expandtab