@@ -296,112 +296,142 @@ void display_process(therm_settings_t* s
// Write text to OLED
// [ therm :: set windup ]
// [ g = 12 ]
ssd1306_DrawString("Start on Boot", 0, 40);
ssd1306_drawlogo();
ssd1306_DrawString("sob=", 1, 45);
if(set->boottobrew)
ssd1306_DrawString("Enabled ", 1, 70);
else
ssd1306_DrawString("Disabled", 1, 70);
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->boottobrew = 1;
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
set->boottobrew = 0;
// Event Handler
// N/A
} break;
case STATE_SETUNITS:
{
ssd1306_DrawString("Units: ", 0, 40);
if(set->temp_units == TEMP_UNITS_FAHRENHEIT)
ssd1306_DrawString("Fahrenheit", 1, 60);
ssd1306_DrawString("Celsius ", 1, 60);
save_settings();
status->state = STATE_IDLE;
status->state = STATE_SETTEMPOFFSET;
set->temp_units = TEMP_UNITS_FAHRENHEIT;
set->temp_units = TEMP_UNITS_CELSIUS;
case STATE_SETTEMPOFFSET:
// [ therm :: set temp offset ]
ssd1306_DrawString("Thermocouple Offset", 0, 40);
char tempstr[6];
itoa(set->temp_offset, tempstr, 10);
ssd1306_DrawString("O=", 1, 45);
ssd1306_DrawString(" ", 1, 57);
ssd1306_DrawString(tempstr, 1, 57);
else {
user_input_signed(&set->temp_offset);
case STATE_PREHEAT_BREW:
// [ therm : preheating brew ]
// [ 30 => 120 C ]
ssd1306_DrawString("Preheating...", 0, 0);
//ssd1306_drawlogo();
draw_setpoint(status);
status->pid_enabled = 1;
status->setpoint = set->setpoint_brew;
save_setpoints(); // TODO: Check for mod
user_input(&set->setpoint_brew);
if(status->temp >= status->setpoint) {
status->state = STATE_MAINTAIN_BREW;
case STATE_MAINTAIN_BREW:
// [ therm : ready to brew ]
ssd1306_DrawString("Preheated!", 0, 0);
#include "gpio.h"
#include "config.h"
#include "stm32f0xx_hal_conf.h"
#include <inttypes.h>
// Increase on each press, and increase at a fast rate after duration elapsed of continuously holding down... somehow...
uint32_t change_time_reset = 0;
void user_input(uint16_t* to_modify)
if(CHANGE_ELAPSED) {
if(!HAL_GPIO_ReadPin(SW_UP) ) {
CHANGE_RESET;
(*to_modify)++;
else if(!HAL_GPIO_ReadPin(SW_DOWN) && (*to_modify) > 0) {
(*to_modify)--;
void user_input_signed(int16_t* to_modify)
// TODO: Bounds check on int16_t
void init_gpio(void) {
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__GPIOF_CLK_ENABLE();
__GPIOA_CLK_ENABLE();
__GPIOB_CLK_ENABLE();
__SPI1_CLK_ENABLE();
//////////////////
// PORT F //
// PORTF OUTPUT
// Configure GPIO pin : PF0 [Power LED]
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
// PORTF UNUSED
// Configure GPIO pin : PF1
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
// PORT A //
// PORT A OUTPUT
// Configure GPIO pins : (SSR+ CS_OLED RES D/C CS_MAX)
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_15;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PORTA INPUT
// Configure GPIO pin : PA15
// GPIO_InitStruct.Pin = GPIO_PIN_15;
// GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
#ifndef GPIO_H
#define GPIO_H
#define CHANGE_PERIOD_MS 100
#define CHANGE_ELAPSED (HAL_GetTick() - change_time_reset) > CHANGE_PERIOD_MS
#define CHANGE_RESET change_time_reset = HAL_GetTick()
void user_input(uint16_t* to_modify);
void user_input_signed(int16_t* to_modify);
void init_gpio(void);
#endif
// vim:softtabstop=4 shiftwidth=4 expandtab
@@ -161,102 +161,104 @@ void update_temp() {
uint16_t temp_pre = rxdatal[0] | (rxdatah[0]<<8);
if(temp_pre & 0b0000000000000010) {
ssd1306_clearscreen();
//ssd1306_DrawString("Fatal Error", 3, 35);
HAL_Delay(100);
status.state = STATE_TC_ERROR;
status.temp = 0;
status.temp_frac = 0;
else if(temp_pre & 0b0000000000000001 && !set.ignore_tc_error) {
status.state_resume = status.state;
if(status.state == STATE_TC_ERROR)
status.state = status.state_resume;
uint8_t sign = status.temp >> 15;// top bit is sign
temp_pre = temp_pre >> 2; // Drop 2 lowest bits
status.temp_frac = temp_pre & 0b11; // get fractional part
status.temp_frac *= 25; // each bit is .25 a degree, up to fixed point
temp_pre = temp_pre >> 2; // Drop 2 fractional bits
int8_t signint;
if(sign) {
signint = -1;
signint = 1;
// Convert to Fahrenheit
if(set.temp_units == TEMP_UNITS_FAHRENHEIT)
status.temp = signint * ((temp_pre*100) + status.temp_frac);
status.temp = status.temp * 1.8;
status.temp += 3200;
status.temp_frac = status.temp % 100;
status.temp /= 100;
status.temp += set.temp_offset;
// Use Celsius values
status.temp = temp_pre * signint;
// PID implementation
// TODO: Make struct that has the last_temp and i_state in it, pass by ref. Make struct that has other input values maybe.
int16_t last_pid_temp = 0;
uint8_t last_pid_temp_frac = 0;
int16_t i_state = 0;
int16_t update_pid(uint16_t k_p, uint16_t k_i, uint16_t k_d, int16_t temp, uint8_t temp_frac, int16_t setpoint)
// Calculate instantaneous error
int16_t error = (int16_t)setpoint - (int16_t)temp; // TODO: Use fixed point fraction
// Proportional component
int16_t p_term = k_p * error;
// Error accumulator (integrator)
i_state += error;
// to prevent the iTerm getting huge despite lots of
// error, we use a "windup guard"
// (this happens when the machine is first turned on and
// it cant help be cold despite its best efforts)
// not necessary, but this makes windup guard values
// relative to the current iGain
int16_t windup_guard_res = set.windup_guard / k_i;
// Calculate integral term with windup guard
if (i_state > windup_guard_res)
i_state = windup_guard_res;
else if (i_state < -windup_guard_res)
i_state = -windup_guard_res;
int16_t i_term = k_i * i_state;
// Calculate differential term (slope since last iteration)
int16_t d_term = (k_d * (status.temp - last_pid_temp));
// Save temperature for next iteration
last_pid_temp = status.temp;
last_pid_temp_frac = status.temp_frac;
int16_t result = p_term + i_term - d_term;
// Put out tenths of percent, 0-1000.
if(result > 1000)
#ifndef STATES_H
#define STATES_H
typedef struct {
int32_t temp;
uint8_t temp_frac;
uint8_t state_resume;
uint8_t state;
int32_t setpoint;
uint8_t pid_enabled;
} therm_status_t;
uint8_t boottobrew;
uint8_t temp_units;
uint16_t windup_guard;
uint16_t k_p;
uint16_t k_i;
uint16_t k_d;
int16_t temp_offset;
uint8_t ignore_tc_error;
int16_t setpoint_brew;
int16_t setpoint_steam;
} therm_settings_t;
enum tempunits {
TEMP_UNITS_CELSIUS = 0,
TEMP_UNITS_FAHRENHEIT,
};
enum state {
STATE_IDLE = 0,
STATE_SETP,
STATE_SETI,
STATE_SETD,
STATE_SETSTEPS,
STATE_SETWINDUP,
STATE_SETBOOTTOBREW,
STATE_SETUNITS,
STATE_SETTEMPOFFSET,
STATE_PREHEAT_BREW,
STATE_MAINTAIN_BREW,
STATE_PREHEAT_STEAM,
STATE_MAINTAIN_STEAM,
STATE_TC_ERROR
Status change: