#include "display.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_state = STATE_IDLE;
static uint8_t goto_mode = MODE_HEAT;
static uint8_t reset_mode = RESET_REBOOT;
// Display state machine
void display_process(therm_settings_t* set, therm_status_t* status)
{
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 :: idle ", 0, 40);
status->pid_enabled = 0;
if(temp_changed || state_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);
}
if (state_changed) {
ssd1306_drawlogo();
switch(goto_mode) {
case MODE_HEAT:
ssd1306_drawstring("-> heat ", 1, 40);
} break;
case MODE_SETUP:
ssd1306_drawstring("-> setup ", 1, 40);
case MODE_RESET:
ssd1306_drawstring("-> reset ", 1, 40);
#ifdef BOOTLOADER_SHORTCUT
case MODE_BOOTLOADER:
ssd1306_drawstring("-> dfu ", 1, 40);
#endif
// Button handler
if(SW_BTN_PRESSED) {
status->state = STATE_PREHEAT;
break;
status->state = STATE_SETP;
status->state = STATE_RESET;
reset_mode = RESET_REBOOT;
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
default:
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
case STATE_SETP:
// [ therm :: set p ]
// [ p = 12 ]
ssd1306_drawstring("Proportional", 0, 40);
itoa(set->val.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((uint16_t*)&set->val.k_p);
case STATE_SETI:
// [ therm :: set i ]
// [ i = 12 ]
ssd1306_drawstring("Integral", 0, 40);
itoa(set->val.k_i, tempstr, 10);
ssd1306_drawstring("I=", 1, 45);
status->state = STATE_SETD;
user_input((uint16_t*)&set->val.k_i);
case STATE_SETD:
// [ therm :: set d ]
// [ d = 12 ]
ssd1306_drawstring("Derivative", 0, 40);
itoa(set->val.k_d, tempstr, 10);
ssd1306_drawstring("D=", 1, 45);
status->state = STATE_SETWINDUP;
user_input((uint16_t*)&set->val.k_d);
case STATE_SETWINDUP:
// [ therm :: set windup ]
// [ g = 12 ]
ssd1306_drawstring("Windup Guard", 0, 40);
itoa(set->val.windup_guard, tempstr, 10);
ssd1306_drawstring("G=", 1, 45);
status->state = STATE_SETBOOTTOBREW;
user_input((uint16_t*)&set->val.windup_guard);
case STATE_SETBOOTTOBREW:
ssd1306_drawstring("Start on Boot", 0, 40);
ssd1306_drawstring("sob=", 1, 45);
if(set->val.boottobrew)
ssd1306_drawstring("Enabled ", 1, 70);
else
ssd1306_drawstring("Disabled", 1, 70);
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;
case STATE_SETUNITS:
ssd1306_drawstring("Units: ", 0, 40);
if(set->val.temp_units == TEMP_UNITS_FAHRENHEIT)
ssd1306_drawstring("Fahrenheit", 1, 60);
ssd1306_drawstring("Celsius ", 1, 60);
status->state = STATE_SETTEMPOFFSET;
set->val.temp_units = TEMP_UNITS_FAHRENHEIT;
set->val.temp_units = TEMP_UNITS_CELSIUS;
case STATE_SETTEMPOFFSET:
// [ therm :: set temp offset ]
ssd1306_drawstring("Temp Cal Offset", 0, 40);
itoa(set->val.temp_offset, tempstr, 10);
ssd1306_drawstring("O=", 1, 45);
flash_save(set);
status->state = STATE_IDLE;
user_input_signed((int16_t*)(&set->val.temp_offset));
user_input_signed(&set->val.temp_offset);
case STATE_PREHEAT:
// [ therm : preheating brew ]
// [ 30 => 120 C ]
ssd1306_drawstring("Preheating...", 0, 0);
//ssd1306_drawlogo();
draw_setpoint(status);
status->pid_enabled = 1;
status->setpoint = set->val.setpoint_brew;
user_input((uint16_t*)&set->val.setpoint_brew);
if(status->temp >= status->setpoint) {
status->state = STATE_MAINTAIN;
case STATE_MAINTAIN:
// [ therm : ready to brew ]
ssd1306_drawstring("Preheated!", 0, 0);
// Thermocouple error
case STATE_TC_ERROR:
// [ therm : ready to steam ]
ssd1306_drawstring("Error: ", 0, 0);
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);
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);
#ifdef MAX31865_RTD_SENSOR
max31865_clear_errors(spi_get());
else if(SW_RIGHT_PRESSED) {
set->val.ignore_error = 1;
// Maybe handle if TC is plugged in
// Reset state
case STATE_RESET:
// [ therm :: reset ]
ssd1306_drawstring("therm :: reset ", 0, 40);
switch(reset_mode) {
case RESET_DEFAULTS:
ssd1306_drawstring("-> defaults ", 1, 40);
case RESET_BOOTLOADER:
ssd1306_drawstring("-> bootloader ", 1, 40);
case RESET_REBOOT:
ssd1306_drawstring("-> reboot ", 1, 40);
case RESET_EXIT:
ssd1306_drawstring("-> exit ", 1, 40);
HAL_Delay(1000);
flash_load_defaults(set);
NVIC_SystemReset();
else if(SW_DOWN_PRESSED && reset_mode < (RESET_SIZE-1)) {
reset_mode++;
else if(SW_UP_PRESSED && reset_mode > 0) {
reset_mode--;
// Something is terribly wrong
if(last_state != status->state) {
// Clear screen on state change
goto_mode = MODE_HEAT;
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;
static int32_t temp_last = 43002;
static int32_t setpoint_last = 10023;
// 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[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;
void display_startup_screen() {
ssd1306_drawstring("therm v0.2", 1, 40);
ssd1306_drawstring("protofusion.org/therm", 3, 0);
// vim:softtabstop=4 shiftwidth=4 expandtab
#ifndef DISPLAY_H
#define DISPLAY_H
#include <stdlib.h>
#include "stm32f0xx_hal.h"
#include "states.h"
#include "ssd1306.h"
#include "stringhelpers.h"
#include "config.h"
#include "syslib.h"
#include "flash.h"
#include "gpio.h"
#include "max31865.h"
void display_startup_screen();
void display_process(therm_settings_t* set, therm_status_t* status);
// Increase on each press, and increase at a fast rate after duration elapsed of continuously holding down... somehow...
static uint32_t change_time_reset = 0;
// Increment/decrement unsigned variable with up/down buttons
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)--;
// 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 (*to_modify < 32768)
if (*to_modify >= -32768)
// Initialize GPIO
void gpio_init(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;
// GPIO_InitStruct.Pull = GPIO_PULLUP;
// HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PORTA UNUSED
// Configure GPIO pins : PA0 PA8
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_8;
// USART1 [PORTA]
// Configure GPIO pins : PA9 PA10
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Alternate = GPIO_AF1_USART1;
// SPI1 [PORTA]
// Configure GPIO pin : PA, MOSI, SCK
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_5;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
// Configure GPIO pin: PA, MISO
GPIO_InitStruct.Pin = GPIO_PIN_6;
// USB [PORTA]
/** USB GPIO Configuration
PA11 ------> USB_DM
PA12 ------> USB_DP
*/
// Configure GPIO pin : PA, D+, D-
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Alternate = GPIO_AF2_USB; // Can also be AF5
// PORT B //
// PORT B UNUSED
// Configure GPIO pins : PB0 PB1 PB8
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_8;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// PORT B INPUT
// Configure GPIO pins : PB3 PB4 PB5 PB6 PB7
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
// Enable DMA clocks (Is AHB even the right thing???)
//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // EMZ TODO get the right ones
#ifndef GPIO_H
#define GPIO_H
#include <inttypes.h>
#include "stm32f0xx_hal_conf.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 user_input_signed(int32_t* to_modify);
void gpio_init(void);
#include "pid.h"
#ifdef MAX31855_TC_SENSOR
#include "max31855.h"
#else
#include "spi.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();
// Configure the system clock
systemclock_init();
// Unset bootloader option bytes (if set)
// FIXME this was never getting called. Try again sometime.
//bootloader_unset();
// Init GPIO
gpio_init();
// Init USB (TODO: Handle plugged/unplugged with external power)
MX_USB_DEVICE_Init();
// set.val.usb_plugged =
// USB startup delay
HAL_Delay(500);
HAL_GPIO_WritePin(LED_POWER, 1);
// Enter into bootloader if up button pressed on boot
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;
// 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;
uint32_t last_pid = 0;
int16_t ssr_output = 0; // Duty cycle of ssr, 0 to SSR_PERIOD
// Main loop
while(1)
// Process sensor inputs
if(HAL_GetTick() - last_led > 400)
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
itoa(ssr_output, tempstr, 10);
ssd1306_drawstring(tempstr, 0, 90);
ssr_output = 0;
last_pid = HAL_GetTick();
// Kill SSR once the desired on-time has elapsed
if(HAL_GetTick() - last_ssr_on > ssr_output || ssr_output <= 0)
HAL_GPIO_WritePin(SSR_PIN, 0);
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);
last_ssr_on = HAL_GetTick();
// Make sure everything is off
// 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);
tempstr[numlen] = '\r';
tempstr[numlen+1] = '\n';
// if(set.val.usb_plugged)
// CDC_Transmit_FS(tempstr, numlen+2);
// while(CDC_Transmit_FS("\r\n", 2) == USBD_BUSY);
last_vcp_tx = HAL_GetTick();
// Run state machine
display_process(&set, &status);
// Registers
#define MAX31865_REG_CONFIG 0x00
#define MAX31865_REG_RTD_MSB 0x01
#define MAX31865_REG_RTD_LSB 0x02
#define MAX31865_REG_HFAULT_THRESH_MSB 0x03
#define MAX31865_REG_HFAULT_THRESH_LSB 0x04
#define MAX31865_REG_LFAULT_THRESH_MSB 0x05
#define MAX31865_REG_LFAULT_THRESH_LSB 0x06
#define MAX31865_REG_FAULTSTATUS 0x07
#define MAX31865_REG_WRITE_MODIFIER 0x80
// Fields
#define MAX31865_CONF_VBIAS (1<<7)
#define MAX31865_CONF_AUTO_CONVERT (1<<6)
#define MAX31865_CONF_1SHOT (1<<5)
#define MAX31865_CONF_3WIRE (1<<4)
#define MAX31865_CONF_FAULT_NOACTION 0b0000
#define MAX31865_CONF_FAULT_AUTODELAY 0b0100
#define MAX31865_CONF_FAULT_MANUALELAY1 0b1000
#define MAX31865_CONF_FAULT_MANUALELAY2 0b1100
#define MAX31865_CONF_FAULT_CLEAR (1<<1)
#define MAX31865_CONF_50_60HZ_FILTER (1<<0)
void max31865_config(SPI_HandleTypeDef* hspi1)
uint8_t config = 0x00;
config |= MAX31865_CONF_VBIAS;
config |= MAX31865_CONF_AUTO_CONVERT;
config |= MAX31865_CONF_FAULT_CLEAR;
uint8_t reg = (MAX31865_REG_WRITE_MODIFIER | MAX31865_REG_CONFIG);
// Assert CS
HAL_GPIO_WritePin(MAX_CS, 0);
HAL_SPI_Transmit(hspi1, ®, 1, 100);
HAL_SPI_Transmit(hspi1, &config, 1, 100);
// Release CS
HAL_GPIO_WritePin(MAX_CS, 1);
void max31865_clear_errors(SPI_HandleTypeDef* hspi1) {
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
uint8_t regh = MAX31865_REG_RTD_MSB;
uint8_t rxdatah[1] = {0x00};
uint8_t rxdatal[1] = {0x00};
HAL_SPI_Transmit(hspi1, ®h, 1, 100);
HAL_SPI_Receive(hspi1, rxdatah, 1, 100);
HAL_SPI_Receive(hspi1, rxdatal, 1, 100);
// Assemble data array into one var
uint16_t adc_count = ((rxdatah[0] & 0x7F) << 8) | rxdatal[0];
if((rxdatah[0] & 0x80) && !set->val.ignore_error) {
uint8_t reg = MAX31865_REG_FAULTSTATUS;
uint8_t data[1] = {0x00};
HAL_SPI_Receive(hspi1, data, 1, 100);
status->error_code = data[0];
HAL_Delay(400); // FIXME: remove?
status->state_resume = status->state;
status->state = STATE_TC_ERROR;
status->temp = 0;
status->temp_frac = 0;
// Convert to Fahrenheit
//use all fixed point math!
//convert adc to resistance
//Rrtd = adc / range * Rref
status->temp = adc_count * 9830 * 10;
status->temp /= 32768;
//resistance to temp
//(x - in1) * (cal2 - cal1) / (in2 - in1) + cal1
status->temp = ((status->temp * 100) - 100000) * (39200 - 3200) / (175860 - 100000) + 3200;
//grab the fraction
status->temp_frac = (status->temp / 10) % 10;
//scale back to degrees
status->temp = status->temp / 100;
//add in the offset
status->temp += set->val.temp_offset;
// Convert to Celsius
status->temp = ((status->temp * 10) - 10000) * (20000 - 0) / (17586 - 10000) + 0;
// 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;
else if(result < -1000)
result = -1000;
// Return feedback
return result;
#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: