@@ -332,13 +332,13 @@ void display_process(therm_settings_t* s
// Button handler
if(SW_BTN_PRESSED) {
flash_save(set);
status->state = STATE_IDLE;
}
else {
user_input_signed((int16_t*)(&set->val.temp_offset));
user_input_signed(&set->val.temp_offset);
// Event Handler
// N/A
} break;
@@ -351,13 +351,13 @@ void display_process(therm_settings_t* s
// [ 30 => 120 C ]
ssd1306_drawstring("Preheating...", 0, 0);
//ssd1306_drawlogo();
draw_setpoint(status);
status->pid_enabled = 1;
status->setpoint = set->val.setpoint_brew;
@@ -377,13 +377,13 @@ void display_process(therm_settings_t* s
// [ therm : ready to brew ]
ssd1306_drawstring("Preheated!", 0, 0);
@@ -564,7 +564,13 @@ static void draw_setpoint(therm_status_t
trigger_drawsetpoint = 0;
setpoint_last = status->setpoint;
temp_last = status->temp;
void display_startup_screen() {
ssd1306_clearscreen();
ssd1306_drawstring("therm v0.2", 1, 40);
ssd1306_drawstring("protofusion.org/therm", 3, 0);
// vim:softtabstop=4 shiftwidth=4 expandtab
@@ -12,9 +12,10 @@
#include "flash.h"
#include "gpio.h"
#ifdef MAX31865_RTD_SENSOR
#include "max31865.h"
#endif
void display_startup_screen();
void display_process(therm_settings_t* set, therm_status_t* status);
@@ -18,23 +18,25 @@ void user_input(uint16_t* to_modify)
// Increment/decrement signed variable with up/down buttons
void user_input_signed(int16_t* to_modify)
void user_input_signed(int32_t* to_modify)
{
// TODO: Bounds check on int16_t
//fixme: need to cast to 16/32 bits correctly
if(CHANGE_ELAPSED) {
if(!HAL_GPIO_ReadPin(SW_UP) ) {
CHANGE_RESET;
(*to_modify)++;
if (*to_modify < 32768)
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
(*to_modify)--;
if (*to_modify >= -32768)
// Initialize GPIO
@@ -9,12 +9,12 @@
#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 user_input_signed(int32_t* to_modify);
void gpio_init(void);
@@ -2,29 +2,29 @@
#include "config.h"
#include "syslib.h"
#include "pid.h"
#include "states.h"
#include "ssd1306.h"
#ifdef MAX31855_TC_SENSOR
#include "max31855.h"
#else
#include "spi.h"
#include "stringhelpers.h"
#include "display.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
therm_settings_t set;
therm_status_t status;
pid_state_t pid_state;
int main(void)
// Initialize HAL
hal_init();
@@ -50,42 +50,41 @@ int main(void)
if(!HAL_GPIO_ReadPin(SW_UP))
bootloader_enter();
// Init SPI busses
spi_init();
// Init RTD chip
max31865_config(spi_get());
// Init OLED over SPI
ssd1306_init();
// Startup screen
display_startup_screen();
// Default status
status.temp = 0;
status.temp_frac = 0;
status.state_resume = 0;
status.state = STATE_IDLE;
status.setpoint = 70;
status.pid_enabled = 0;
pid_init(&pid_state);
// Go to brew instead of idle if configured thusly
if(set.val.boottobrew)
status.state = STATE_PREHEAT;
HAL_Delay(1000);
// Restore settings from flash memory
flash_restore(&set);
HAL_Delay(2000);
// Soft timers
uint32_t last_ssr_on = 0;
uint32_t last_vcp_tx = 0;
uint32_t last_led = 0;
@@ -101,27 +100,33 @@ int main(void)
last_led = HAL_GetTick();
if((HAL_GetTick() - last_pid > PID_PERIOD))
max31855_readtemp(spi_get(), &set, &status); // Read MAX31855
max31865_readtemp(spi_get(), &set, &status);
if(status.pid_enabled)
// Get ssr output for next time
int16_t power_percent = pid_update(set.val.k_p, set.val.k_i, set.val.k_d, status.temp, status.temp_frac, status.setpoint, &set, &status);
int16_t power_percent = pid_update(&set, &status, &pid_state);
//power-percent is 0-1000?
ssr_output = power_percent; //(((uint32_t)SSR_PERIOD * (uint32_t)10 * (uint32_t)100) * power_percent) / (uint32_t)1000000;
// put ssr output on display
ssd1306_drawstring(" ", 0, 90); //fixme: this is bad, but I can't get the old digits to clear otherwise
char tempstr[6];
itoa(ssr_output, tempstr, 10);
ssd1306_drawstring(tempstr, 0, 90);
else
ssr_output = 0;
@@ -135,18 +140,16 @@ int main(void)
HAL_GPIO_WritePin(LED_POWER, 0);
// Every 200ms, set the SSR on unless output is 0
if(HAL_GetTick() - last_ssr_on > SSR_PERIOD)
// Only support heating (ssr_output > 0) right now
if(ssr_output > 0)
HAL_GPIO_WritePin(SSR_PIN, 1);
HAL_GPIO_WritePin(LED_POWER, 1);
last_ssr_on = HAL_GetTick();
@@ -154,13 +157,13 @@ int main(void)
HAL_GPIO_WritePin(SSR_PIN, 0);
// Transmit temperature over USB-CDC on a regulat basis
// Transmit temperature over USB-CDC on a regular basis
if(HAL_GetTick() - last_vcp_tx > VCP_TX_FREQ)
// Print temp to cdc
char tempstr[16];
itoa_fp(status.temp, status.temp_frac, tempstr);
uint8_t numlen = strlen(tempstr);
@@ -50,20 +50,14 @@ void max31865_clear_errors(SPI_HandleTyp
max31865_config(hspi1);
// Grab temperature reading from MAX31865
void max31865_readtemp(SPI_HandleTypeDef* hspi1, therm_settings_t* set, therm_status_t* status)
///////////////////////////////////
// This is duplicated from MAX31855, update for MAX31865 registers/etc
// TODO: Set configuration register based on params in config.h (2-wire, 4-wire, etc RTD). This is register 0x00.
// 2-wire RTC or 2-wire (duh) NTC thermistor will be the only options
// Need option for resistance of RTD
// These options should be stored in the set structure and should be menu-selectable
// TODO: Set RTD ref resistance in set struct
// Assert CS
HAL_GPIO_WritePin(MAX_CS, 0);
uint8_t regh = MAX31865_REG_RTD_MSB;
#include "stm32f0xx_hal.h"
// 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.
static int16_t last_pid_temp = 0;
static uint8_t last_pid_temp_frac = 0;
static int32_t i_state = 0;
int16_t pid_update(uint16_t k_p, uint16_t k_i, uint16_t k_d, int16_t temp, uint8_t temp_frac, int16_t setpoint, therm_settings_t* set, therm_status_t* status)
void pid_init(pid_state_t* state)
state->i_state = 0;
state->last_pid_temp = 0;
state->last_pid_temp_frac = 0;
int16_t pid_update(therm_settings_t* set, therm_status_t* status, pid_state_t *state)
// Calculate instantaneous error
int16_t error = setpoint - temp; // TODO: Use fixed point fraction
int16_t error = status->setpoint - status->temp; // TODO: Use fixed point fraction
// Proportional component
int32_t p_term = k_p * error;
int32_t p_term = set->val.k_p * error;
// Error accumulator (integrator)
i_state += error;
state->i_state += error;
// to prevent the iTerm getting huge from 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
int32_t windup_guard_res = set->val.windup_guard / k_i;
int32_t windup_guard_res = set->val.windup_guard / set->val.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;
if (state->i_state > windup_guard_res)
state->i_state = windup_guard_res;
else if (state->i_state < -windup_guard_res)
state->i_state = -windup_guard_res;
int32_t i_term = k_i * i_state;
int32_t i_term = set->val.k_i * state->i_state;
// Calculate differential term (slope since last iteration)
int32_t d_term = (k_d * (status->temp - last_pid_temp));
int32_t d_term = (set->val.k_d * (status->temp - state->last_pid_temp));
// Save temperature for next iteration
last_pid_temp = status->temp;
last_pid_temp_frac = status->temp_frac;
state->last_pid_temp = status->temp;
state->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)
result = 1000;
#ifndef PIDS_H
#define PIDS_H
int16_t pid_update(uint16_t k_p, uint16_t k_i, uint16_t k_d, int16_t temp, uint8_t temp_frac, int16_t setpoint, therm_settings_t* set, therm_status_t* status);
typedef struct {
int16_t last_pid_temp;
uint8_t last_pid_temp_frac;
int32_t i_state;
} pid_state_t;
void pid_init(pid_state_t* state);
int16_t pid_update(therm_settings_t* set, therm_status_t* status, pid_state_t* state);
Status change: