// // 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: %4.1f", 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, "%4.1f", 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