@@ -10,7 +10,7 @@
# SOURCES: list of sources in the user application
SOURCES = main.c usbd_conf.c usbd_cdc_if.c usb_device.c usbd_desc.c stm32f0xx_hal_msp.c stm32f0xx_it.c system_stm32f0xx.c gpio.c spi.c ssd1306.c stringhelpers.c eeprom_min.c
SOURCES = main.c usbd_conf.c usbd_cdc_if.c usb_device.c usbd_desc.c stm32f0xx_hal_msp.c stm32f0xx_it.c system_stm32f0xx.c gpio.c spi.c ssd1306.c stringhelpers.c eeprom_min.c display.c
# TARGET: name of the user application
TARGET = main
new file 100644
#include "stm32f0xx_hal.h"
#include "ssd1306.h"
#include "stringhelpers.h"
#include "display.h"
#include "config.h"
#include "states.h"
#include "gpio.h"
uint8_t goto_mode = 2;
// State machine
uint8_t sw_btn_last = 0;
uint8_t sw_up_last = 0;
uint8_t sw_down_last = 0;
uint8_t sw_left_last = 0;
uint8_t sw_right_last = 0;
#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)
///////////////////////////////////////////////////////////////////////////////////////
/// freaking multiple setpoint support ///
uint8_t step_duration[10] = {0,0,0,0,0,0,0,0,0,0};
int16_t step_setpoint[10] = {0,0,0,0,0,0,0,0,0,0};
uint8_t final_setpoint = 0;
// Multiple screens to set setpoint and duration on each screen
// press center to go to the next one, and press left or right or something to confirm
// When executing, complete on time AND(?) temperature. Maybe allow switching to OR via settings
////////////////////////////////////////////////////////////////////////////////////////////////
uint8_t trigger_drawsetpoint = 1;
int16_t last_temp = 21245;
void display_process(therm_settings_t* set, therm_status_t* status)
{
uint8_t 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 :: idle ", 0, 40);
status->pid_enabled = 0;
if(temp_changed) {
char tempstr[6];
itoa_fp(status->temp, status->temp_frac, tempstr);
ssd1306_DrawString("Temp: ", 3, 40);
ssd1306_DrawString(" ", 3, 72);
ssd1306_DrawString(tempstr, 3, 72);
}
ssd1306_drawlogo();
switch(goto_mode) {
case 2:
ssd1306_DrawString("-> heat ", 1, 40);
} break;
case 1:
ssd1306_DrawString("-> setup ", 1, 40);
case 0:
ssd1306_DrawString("-> reset ", 1, 40);
// Button handler
if(SW_BTN_PRESSED) {
status->state = STATE_PREHEAT_BREW;
break;
status->state = STATE_SETP;
ssd1306_clearscreen();
ssd1306_DrawString("Entering Bootloader", 1, 0);
ssd1306_DrawString("(hopefully)", 2, 0);
//HAL_Delay(1000);
HAL_RCC_DeInit();
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
__set_PRIMASK(1);
__set_MSP(0x200010000);
*((unsigned long *)0x200017F0) = 0xDEADBEEF; // 6KB STM32F042
NVIC_SystemReset();
status->state = STATE_IDLE;
default:
else if(SW_UP_PRESSED && goto_mode < 2) {
goto_mode++;
else if(SW_DOWN_PRESSED && goto_mode > 0) {
goto_mode--;
// Event Handler
// N/A
case STATE_SETP:
// [ therm :: set p ]
// [ p = 12 ]
ssd1306_DrawString("Proportional", 0, 40);
itoa(set->k_p, tempstr, 10);
ssd1306_DrawString("P=", 1, 45);
ssd1306_DrawString(" ", 1, 57);
ssd1306_DrawString(tempstr, 1, 57);
ssd1306_DrawString("Press to accept", 3, 40);
status->state = STATE_SETI;
else {
user_input(&set->k_p);
case STATE_SETI:
// [ therm :: set i ]
// [ i = 12 ]
ssd1306_DrawString("Integral", 0, 40);
itoa(set->k_i, tempstr, 10);
ssd1306_DrawString("I=", 1, 45);
status->state = STATE_SETD;
user_input(&set->k_i);
case STATE_SETD:
// [ therm :: set d ]
// [ d = 12 ]
ssd1306_DrawString("Derivative", 0, 40);
itoa(set->k_d, tempstr, 10);
ssd1306_DrawString("D=", 1, 45);
status->state = STATE_SETWINDUP;
user_input(&set->k_d);
case STATE_SETSTEPS:
// [ step #1:: Duration: ### ]
// [ Setpoint: ### ]
itoa(final_setpoint, tempstr, 10);
ssd1306_DrawString("Step #", 0, 0);
ssd1306_DrawString(tempstr, 0, 40);
ssd1306_DrawString("Duration: ", 0, 5);
itoa(step_duration[final_setpoint], tempstr, 10);
ssd1306_DrawString(tempstr, 0, 70);
ssd1306_DrawString("Setpoint: ", 0, 0);
itoa(step_setpoint[final_setpoint], tempstr, 10);
// Button handler - TODO: increment max_step if pressed
// return and go to next state otherwise
status->state = STATE_SETSTEPS;
final_setpoint++;
// else if(SW_LEFT_PRESSED) {
// state++; // go to next state or something
// }
case STATE_SETWINDUP:
// [ therm :: set windup ]
// [ g = 12 ]
ssd1306_DrawString("Windup Guard", 0, 40);
itoa(set->windup_guard, tempstr, 10);
ssd1306_DrawString("G=", 1, 45);
status->state = STATE_SETBOOTTOBREW;
user_input(&set->windup_guard);
case STATE_SETBOOTTOBREW:
ssd1306_DrawString("Start on Boot", 0, 40);
ssd1306_DrawString("sob=", 1, 45);
if(set->boottobrew)
ssd1306_DrawString("Enabled ", 1, 70);
else
ssd1306_DrawString("Disabled", 1, 70);
status->state = STATE_SETUNITS;
else if(!HAL_GPIO_ReadPin(SW_UP)) {
set->boottobrew = 1;
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
set->boottobrew = 0;
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();
set->temp_units = TEMP_UNITS_FAHRENHEIT;
set->temp_units = TEMP_UNITS_CELSIUS;
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);
case STATE_PREHEAT_STEAM:
// [ therm : preheating steam ]
status->setpoint = set->setpoint_steam;
user_input(&set->setpoint_steam);
status->state = STATE_MAINTAIN_STEAM;
case STATE_MAINTAIN_STEAM:
// [ therm : ready to steam ]
ssd1306_DrawString("Ready to Steam!", 0, 0);
case STATE_TC_ERROR:
ssd1306_DrawString("Error:", 0, 0);
ssd1306_DrawString("Connect thermocouple", 1, 0);
ssd1306_DrawString("Press -> to ignore", 3, 0);
else if(SW_RIGHT_PRESSED) {
set->ignore_tc_error = 1;
// Maybe handle if TC is plugged in
// Something is terribly wrong
if(last_state != status->state) {
// Clear screen on state change
goto_mode = 2;
trigger_drawsetpoint = 1;
// 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;
int32_t temp_last = 43002;
int32_t setpoint_last = 10023;
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[3];
ssd1306_DrawStringBig(" ", 3, 0);
ssd1306_DrawStringBig(tempstr, 3, 0);
if(trigger_drawsetpoint)
ssd1306_DrawStringBig(">", 3, 74);
if(status->setpoint != setpoint_last || trigger_drawsetpoint) {
itoa(status->setpoint, tempstr, 10);
ssd1306_DrawStringBig(" ", 3, 90);
ssd1306_DrawStringBig(tempstr, 3, 90);
trigger_drawsetpoint = 0;
setpoint_last = status->setpoint;
temp_last = status->temp;
// vim:softtabstop=4 shiftwidth=4 expandtab
#ifndef DISPLAY_H
#define DISPLAY_H
void display_process(therm_settings_t* set, therm_status_t* status);
void draw_setpoint(therm_status_t* status);
#endif
#include "eeprom_min.h"
#include "spi.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
@@ -14,57 +16,19 @@
// Prototypes
// Move to header file
void process();
void machine();
void restore_settings();
void save_settings();
void save_setpoints();
void SystemClock_Config(void);
///////////////////////
enum tempunits {
TEMP_UNITS_CELSIUS = 0,
TEMP_UNITS_FAHRENHEIT,
};
// State definition
enum state {
STATE_IDLE = 0,
STATE_SETP,
STATE_SETI,
STATE_SETD,
STATE_SETSTEPS,
STATE_SETWINDUP,
STATE_SETBOOTTOBREW,
STATE_SETUNITS,
STATE_PREHEAT_BREW,
STATE_MAINTAIN_BREW,
STATE_PREHEAT_STEAM,
STATE_MAINTAIN_STEAM,
STATE_TC_ERROR
uint8_t state = STATE_IDLE;
therm_settings_t set;
therm_status_t status;
// Globalish setting vars
uint8_t boottobrew = 0;
uint8_t temp_units = TEMP_UNITS_CELSIUS;
uint16_t windup_guard = 1;
uint16_t k_p = 1;
uint16_t k_i = 1;
uint16_t k_d = 1;
uint8_t ignore_tc_error = 0;
int16_t setpoint_brew = 0;
int16_t setpoint_steam = 0;
SPI_HandleTypeDef hspi1;
static __IO uint32_t TimingDelay;
void deinit(void)
HAL_DeInit();
@@ -101,6 +65,31 @@ int main(void)
// Init OLED over SPI
ssd1306_Init();
// Default settings
set.boottobrew = 0;
set.temp_units = TEMP_UNITS_CELSIUS;
set.windup_guard = 1;
set.k_p = 1;
set.k_i = 1;
set.k_d = 1;
set.ignore_tc_error = 0;
set.setpoint_brew = 0;
set.setpoint_steam = 0;
// Default status
status.temp = 0;
status.temp_frac = 0;
status.state_resume = 0;
status.state = STATE_IDLE;
status.setpoint = 0;
status.pid_enabled = 0;
// Load settings (if any) from EEPROM
restore_settings();
if(set.boottobrew)
status.state = STATE_PREHEAT_BREW; // Go to brew instead of idle if configured thusly
// Startup screen
ssd1306_DrawString("therm v0.1", 1, 40);
@@ -108,11 +97,7 @@ int main(void)
HAL_Delay(1500);
if(boottobrew)
state = STATE_PREHEAT_BREW; // Go to brew instead of idle if configured thusly
// Main loop
while(1)
@@ -120,7 +105,7 @@ int main(void)
process();
// Run state machine
machine();
display_process(&set, &status);
@@ -154,11 +139,6 @@ void SystemClock_Config(void)
// Read temperature and update global temp vars
int32_t temp = 0;
uint8_t temp_frac = 0;
uint8_t state_resume = 0;
void update_temp() {
@@ -181,29 +161,29 @@ void update_temp() {
//ssd1306_DrawString("Fatal Error", 3, 35);
HAL_Delay(100);
state = STATE_TC_ERROR;
temp = 0;
temp_frac = 0;
status.state = STATE_TC_ERROR;
else if(temp_pre & 0b0000000000000001 && !ignore_tc_error) {
state_resume = state;
else if(temp_pre & 0b0000000000000001 && !set.ignore_tc_error) {
status.state_resume = status.state;
if(state == STATE_TC_ERROR)
if(status.state == STATE_TC_ERROR)
state = state_resume;
status.state = status.state_resume;
uint8_t sign = temp >> 15;// top bit is sign
uint8_t sign = status.temp >> 15;// top bit is sign
temp_pre = temp_pre >> 2; // Drop 2 lowest bits
temp_frac = temp_pre & 0b11; // get fractional part
temp_frac *= 25; // each bit is .25 a degree, up to fixed point
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;
@@ -216,19 +196,19 @@ void update_temp() {
// Convert to Fahrenheit
if(temp_units == TEMP_UNITS_FAHRENHEIT)
if(set.temp_units == TEMP_UNITS_FAHRENHEIT)
temp = signint * ((temp_pre*100) + temp_frac);
temp = temp * 1.8;
temp += 3200;
temp_frac = temp % 100;
temp /= 100;
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;
// Use Celsius values
temp = temp_pre * signint;
status.temp = temp_pre * signint;
@@ -257,7 +237,7 @@ int16_t update_pid(uint16_t k_p, uint16_
// 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 = windup_guard / k_i;
int16_t windup_guard_res = set.windup_guard / k_i;
// Calculate integral term with windup guard
if (i_state > windup_guard_res)
@@ -267,11 +247,11 @@ int16_t update_pid(uint16_t k_p, uint16_
int16_t i_term = k_i * i_state;
// Calculate differential term (slope since last iteration)
int16_t d_term = (k_d * (temp - last_pid_temp));
int16_t d_term = (k_d * (status.temp - last_pid_temp));
// Save temperature for next iteration
last_pid_temp = temp;
last_pid_temp_frac = temp_frac;
last_pid_temp = status.temp;
last_pid_temp_frac = status.temp_frac;
int16_t result = p_term + i_term - d_term;
@@ -289,9 +269,7 @@ int16_t update_pid(uint16_t k_p, uint16_
uint32_t last_ssr_on = 0;
uint32_t last_vcp_tx = 0;
uint32_t last_led = 0;
int32_t setpoint = 0;
int16_t ssr_output = 0; // Duty cycle of ssr, 0 to SSR_PERIOD
uint8_t pid_enabled = 0;
// Process things
void process()
@@ -310,10 +288,10 @@ void process()
// Every 200ms, set the SSR on unless output is 0
if((ticks - last_ssr_on > SSR_PERIOD))
if(pid_enabled)
if(status.pid_enabled)
// Get ssr output for next time
int16_t power_percent = update_pid(k_p, k_i, k_d, temp, temp_frac, setpoint);
int16_t power_percent = update_pid(set.k_p, set.k_i, set.k_d, status.temp, status.temp_frac, status.setpoint);
//power-percent is 0-1000
ssr_output = power_percent; //(((uint32_t)SSR_PERIOD * (uint32_t)10 * (uint32_t)100) * power_percent) / (uint32_t)1000000;
@@ -343,32 +321,19 @@ void process()
if(ticks - last_vcp_tx > VCP_TX_FREQ)
// Print temp to cdc
itoa_fp(temp, temp_frac, tempstr);
char tempstr[16];
itoa_fp(status.temp, status.temp_frac, tempstr);
uint8_t numlen = strlen(tempstr);
tempstr[numlen] = '\r';
tempstr[numlen+1] = '\n';
while(CDC_Transmit_FS(tempstr, sizeof(tempstr)) == USBD_BUSY);
while(CDC_Transmit_FS("\r\n", 2) == USBD_BUSY);
while(CDC_Transmit_FS(tempstr, numlen+2) == USBD_BUSY);
// while(CDC_Transmit_FS("\r\n", 2) == USBD_BUSY);
last_vcp_tx = ticks;
void save_settings()
/*
@@ -426,519 +391,8 @@ void restore_settings()
Minimal_EEPROM_Lock(); */
void draw_setpoint() {
if(temp != temp_last || trigger_drawsetpoint) {
if(setpoint != setpoint_last || trigger_drawsetpoint) {
itoa(setpoint, tempstr, 10);
setpoint_last = setpoint;
temp_last = temp;
void machine()
uint8_t last_state = state;
uint8_t temp_changed = temp != last_temp;
last_temp = temp;
switch(state)
pid_enabled = 0;
state = STATE_PREHEAT_BREW;
state = STATE_SETP;
state = STATE_IDLE;
itoa(k_p, tempstr, 10);
state = STATE_SETI;
user_input(&k_p);
itoa(k_i, tempstr, 10);
state = STATE_SETD;
user_input(&k_i);
itoa(k_d, tempstr, 10);
state = STATE_SETWINDUP;
user_input(&k_d);
state = STATE_SETSTEPS;
itoa(windup_guard, tempstr, 10);
state = STATE_SETBOOTTOBREW;
user_input(&windup_guard);
state = STATE_SETUNITS;
boottobrew = 1;
boottobrew = 0;
temp_units = TEMP_UNITS_FAHRENHEIT;
temp_units = TEMP_UNITS_CELSIUS;
draw_setpoint();
pid_enabled = 1;
setpoint = setpoint_brew;
user_input(&setpoint_brew);
if(temp >= setpoint) {
state = STATE_MAINTAIN_BREW;
setpoint = setpoint_steam;
user_input(&setpoint_steam);
state = STATE_MAINTAIN_STEAM;
ignore_tc_error = 1;
if(last_state != state) {
#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;
uint8_t ignore_tc_error;
int16_t setpoint_brew;
int16_t setpoint_steam;
} therm_settings_t;
Status change: