@@ -158,51 +158,53 @@ void SystemClock_Config(void)
int32_t temp = 0;
uint8_t temp_frac = 0;
uint8_t state_resume = 0;
void update_temp() {
// Assert CS
HAL_GPIO_WritePin(MAX_CS, 0);
uint8_t rxdatah[1] = {0x00};
uint8_t rxdatal[1] = {0x00};
HAL_SPI_Receive(&hspi1, rxdatah, 1, 100);
HAL_SPI_Receive(&hspi1, rxdatal, 1, 100);
// Release CS
HAL_GPIO_WritePin(MAX_CS, 1);
// Assemble data array into one var
uint16_t temp_pre = rxdatal[0] | (rxdatah[0]<<8);
if(temp_pre & 0b0000000000000010) {
ssd1306_clearscreen();
ssd1306_DrawString("Fatal Error", 3, 35);
//ssd1306_DrawString("Fatal Error", 3, 35);
HAL_Delay(100);
state = STATE_TC_ERROR;
temp = 0;
temp_frac = 0;
}
else if(temp_pre & 0b0000000000000001 && !ignore_tc_error) {
state_resume = state;
else
{
if(state == STATE_TC_ERROR)
state = state_resume;
uint8_t sign = 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
temp_pre = temp_pre >> 2; // Drop 2 fractional bits
int8_t signint;
@@ -210,59 +212,60 @@ void update_temp() {
signint = -1;
else {
signint = 1;
// Convert to Fahrenheit
if(temp_units == TEMP_UNITS_FAHRENHEIT)
temp = signint * ((temp_pre*100) + temp_frac);
temp = temp * 1.8;
temp += 3200;
temp_frac = temp % 100;
temp /= 100;
// Use Celsius values
temp = temp_pre * signint;
// Print temp to cdc
/*
CDC_Transmit_FS("Temp: ", 6);
char tempstr[6];
zitoa(temp, tempstr);
CDC_Transmit_FS(tempstr, sizeof(tempstr));
CDC_Transmit_FS("\r\n", 2);
*/
// 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)
@@ -330,58 +333,48 @@ void process()
ssr_output = 0;
// Only support heating (ssr_output > 0) right now
if(ssr_output > 0) {
itoa(ssr_output, tempstr, 10);
ssd1306_DrawString(tempstr, 0, 90);
HAL_GPIO_WritePin(SSR_PIN, 1);
last_ssr_on = ticks;
// Kill SSR after elapsed period less than SSR_PERIOD
if(ticks - last_ssr_on > ssr_output || ssr_output == 0)
HAL_GPIO_WritePin(SSR_PIN, 0);
void draw_setpoint() {
char tempstr[3];
itoa_fp(temp, temp_frac, tempstr);
ssd1306_DrawStringBig(" ", 3, 0);
ssd1306_DrawStringBig(tempstr, 3, 0);
ssd1306_DrawStringBig(">", 3, 74);
itoa(setpoint, tempstr, 10);
ssd1306_DrawStringBig(" ", 3, 90);
ssd1306_DrawStringBig(tempstr, 3, 90);
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)
void save_settings()
Minimal_EEPROM_Unlock();
// Try programming a word at an address divisible by 4
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_BOOTTOBREW, boottobrew);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_WINDUP_GUARD, windup_guard);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_K_P, k_p);
@@ -429,48 +422,77 @@ void restore_settings()
while(Minimal_FLASH_GetStatus()==FLASH_BUSY);
setpoint_steam = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_STEAMTEMP));
temp_units = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_UNITS));
Minimal_EEPROM_Lock(); */
int16_t last_temp = 21245;
///////////////////////////////////////////////////////////////////////////////////////
/// 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;
int32_t temp_last = 43002;
int32_t setpoint_last = 10023;
// FIXME: need to do this when switching modes too
if(temp != temp_last || trigger_drawsetpoint) {
if(trigger_drawsetpoint)
if(setpoint != setpoint_last || trigger_drawsetpoint) {
trigger_drawsetpoint = 0;
setpoint_last = setpoint;
temp_last = temp;
void machine()
uint8_t last_state = state;
uint8_t temp_changed = temp != last_temp;
last_temp = 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(state)
// Idle state
case STATE_IDLE:
// Write text to OLED
// [ therm :: idle ]
ssd1306_DrawString("therm :: idle ", 0, 40);
pid_enabled = 0;
if(temp_changed) {
@@ -746,48 +768,49 @@ void machine()
save_settings();
state = STATE_IDLE;
else if(!HAL_GPIO_ReadPin(SW_UP)) {
temp_units = TEMP_UNITS_FAHRENHEIT;
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
temp_units = TEMP_UNITS_CELSIUS;
// Event Handler
// N/A
} break;
case STATE_PREHEAT_BREW:
// [ therm : preheating brew ]
// [ 30 => 120 C ]
ssd1306_DrawString("Preheating...", 0, 0);
//ssd1306_drawlogo();
draw_setpoint();
pid_enabled = 1;
setpoint = setpoint_brew;
// Button handler
if(SW_BTN_PRESSED) {
save_setpoints(); // TODO: Check for mod
user_input(&setpoint_brew);
if(temp >= setpoint) {
state = STATE_MAINTAIN_BREW;
case STATE_MAINTAIN_BREW:
// [ therm : ready to brew ]
@@ -878,38 +901,39 @@ void machine()
else if(SW_RIGHT_PRESSED) {
ignore_tc_error = 1;
// Maybe handle if TC is plugged in
// Something is terribly wrong
default:
if(last_state != 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;
// vim:softtabstop=4 shiftwidth=4 expandtab
Status change: