Changeset - d87613e49192
[Not reviewed]
cortex-f0
0 5 0
Ethan Zonca - 9 years ago 2016-07-06 18:54:18
ez@ethanzonca.com
Added support for adjustable hysteresis in thermostatic mode. Also fixed missing logo rendering on initial powerup.
5 files changed with 47 insertions and 8 deletions:
0 comments (0 inline, 0 general)
config.h
Show inline comments
 
#ifndef CONFIG_H
 
#define CONFIG_H
 

	
 
// Temperature sensor type
 
#define MAX31855_TC_SENSOR
 
//#define MAX31865_RTD_SENSOR
 

	
 

	
 
// Virtual serial port transmit rate
 
#define VCP_TX_FREQ 1000
 

	
 
// Solid-state relay maximum on-time
 
#define SSR_PERIOD 200
 

	
 
// Interval of PID calculations
 
#define PID_PERIOD 120
 

	
 

	
 

	
 
// Pin settings
 
#define LED_POWER GPIOF,GPIO_PIN_0
 
#define MAX_CS GPIOA,GPIO_PIN_15
 

	
 
#define SW_BTN  GPIOB, GPIO_PIN_4
 
#define SW_UP   GPIOB, GPIO_PIN_7
 
#define SW_DOWN GPIOB, GPIO_PIN_3
 
#define SW_LEFT GPIOB, GPIO_PIN_5
 
#define SW_RIGHT GPIOB, GPIO_PIN_6
 

	
 
#define SSR_PIN GPIOA, GPIO_PIN_1
 

	
 
// Visual niceness
 
#define hal_init() HAL_Init()
 

	
 
// Add bootloader option to top of idle screen menu
 
#define BOOTLOADER_SHORTCUT
 

	
 

	
 
#define DEFAULT_BOOT_TO_BREW 0
 
#define DEFAULT_TEMP_UNITS TEMP_UNITS_FAHRENHEIT
 
#define DEFAULT_WINDUP_GUARD 10
 
#define DEFAULT_K_P 10
 
#define DEFAULT_K_I 1
 
#define DEFAULT_K_D 1
 
#define DEFAULT_TEMP_OFFSET 0
 
#define DEFAULT_IGNORE_ERROR 0
 
#define DEFAULT_SETPOINT_BREW 70
 
#define DEFAULT_SETPOINT_STEAM 70
 
#define DEFAULT_HYSTERESIS 1
 

	
 

	
 
#endif
 

	
 
// vim:softtabstop=4 shiftwidth=4 expandtab 
display.c
Show inline comments
 
#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_temp_frac = 21245;
 
static int16_t last_state = STATE_IDLE;
 
static int16_t last_state = 123;
 
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 || status->temp_frac != last_temp_frac;
 
    last_temp = status->temp;
 
    last_temp_frac = status->temp_frac;
 

	
 
    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:
 
                {
 
                    if(set->val.plant_type == PLANT_HEATER)
 
@@ -145,110 +145,139 @@ void display_process(therm_settings_t* s
 
            // [ therm :: set mode ]
 
            // [ m =          ]
 
            ssd1306_drawstring("Control Mode", 0, 40);
 
            ssd1306_drawlogo();
 

	
 
            if(set->val.control_mode == MODE_PID)
 
                ssd1306_drawstring("PID       ", 1, 60);
 
            else
 
                ssd1306_drawstring("Thermostat", 1, 60);
 

	
 
            ssd1306_drawstring("Press to accept", 3, 40);
 
            
 
            // Button handler
 
            if(SW_BTN_PRESSED) {
 
                status->state = STATE_SETPLANTTYPE;
 
            }
 
            else if (!HAL_GPIO_ReadPin(SW_UP)) {
 
                set->val.control_mode = MODE_PID;
 
            }
 
            else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
 
                set->val.control_mode = MODE_THERMOSTAT;
 
            }
 
            // Event Handler
 
            // N/A
 
 
 
        } break;
 

	
 

	
 
        case STATE_SETPLANTTYPE:
 
        {
 
            // Write text to OLED
 
            // [ therm :: set mode ]
 
            // [ m =          ]
 
            ssd1306_drawstring("Plant Type", 0, 40);
 
            ssd1306_drawlogo();
 

	
 
            if(set->val.plant_type == PLANT_HEATER)
 
                ssd1306_drawstring("Heater", 1, 60);
 
            else
 
                ssd1306_drawstring("Cooler", 1, 60);
 

	
 
            ssd1306_drawstring("Press to accept", 3, 40);
 
            
 
            // Button handler
 
            if(SW_BTN_PRESSED) {
 
                if(set->val.control_mode == MODE_PID)
 
                    status->state = STATE_SETP;
 
                else
 
                    status->state = STATE_SETBOOTTOBREW;
 
                    status->state = STATE_SETHYSTERESIS;
 
            }
 
            else if (!HAL_GPIO_ReadPin(SW_UP)) {
 
                set->val.plant_type = PLANT_COOLER;
 
            }
 
            else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
 
                set->val.plant_type = PLANT_HEATER;
 
            }
 
            // Event Handler
 
            // N/A
 
 
 
        } break;
 

	
 

	
 

	
 
        case STATE_SETHYSTERESIS:
 
        {
 
            // Write text to OLED
 
            ssd1306_drawstring("Hysteresis", 0, 40);
 
            ssd1306_drawlogo();
 

	
 
            char tempstr[6];
 
            itoa(set->val.hysteresis, tempstr, 10);
 
            ssd1306_drawstring("H=", 1, 45);
 
            ssd1306_drawstring("    ", 1, 57);
 
            ssd1306_drawstring(tempstr, 1, 57);
 

	
 
            ssd1306_drawstring("Press to accept", 3, 40);
 
            
 
            // Button handler
 
            if(SW_BTN_PRESSED) {
 
                status->state = STATE_SETBOOTTOBREW;
 
            }
 
            else {
 
                user_input((uint16_t*)&set->val.hysteresis);
 
            }
 

	
 
            // Event Handler
 
            // N/A
 
 
 
        } break;
 

	
 

	
 
        case STATE_SETP:
 
        {
 
            // Write text to OLED
 
            // [ therm :: set p ]
 
            // [ p = 12         ]
 
            ssd1306_drawstring("Proportional", 0, 40);
 
            ssd1306_drawlogo();
 

	
 
            char tempstr[6];
 
            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);
 
            
 
            // Button handler
 
            if(SW_BTN_PRESSED) {
 
                status->state = STATE_SETI;
 
            }
 
            else {
 
                user_input((uint16_t*)&set->val.k_p);
 
            }
 

	
 
            // Event Handler
 
            // N/A
 
 
 
        } break;
 

	
 
        case STATE_SETI:
 
        {
 
            // Write text to OLED
 
            // [ therm :: set i ]
 
            // [ i = 12         ]
 
            ssd1306_drawstring("Integral", 0, 40);
 
            ssd1306_drawlogo();
 

	
 
            char tempstr[6];
 
            itoa(set->val.k_i, tempstr, 10);
 
            ssd1306_drawstring("I=", 1, 45);
 
            ssd1306_drawstring("    ", 1, 57);
 
            ssd1306_drawstring(tempstr, 1, 57);
 

	
 
            ssd1306_drawstring("Press to accept", 3, 40);
 
            
 
            // Button handler
 
            if(SW_BTN_PRESSED) {
 
                status->state = STATE_SETD;
flash.c
Show inline comments
 
#include "flash.h"
 

	
 
__attribute__((__section__(".eeprom"))) uint16_t eeprom[512];
 

	
 
#define MAGIC_NUMBER 0x0BAE
 

	
 
static void __flash_write(therm_settings_t* tosave);
 

	
 
void flash_save(therm_settings_t* tosave)
 
{
 
    __flash_write(tosave);
 
}
 

	
 
void flash_restore(therm_settings_t* torestore)
 
{
 
	//read flash and calculate checksum
 
	uint16_t checksum = MAGIC_NUMBER;
 
	uint16_t i;
 
    for(i = 0; i < (sizeof(therm_settings_t)/2); i++)
 
	{
 
		torestore->data[i] = eeprom[i];
 
		checksum ^= torestore->data[i];
 
	}
 

	
 
    //if checksum doesn't match, load default settings
 
    if((checksum ^ eeprom[i+1]) != 0) {
 
    	flash_load_defaults(torestore);
 
    }
 
}
 

	
 
void flash_load_defaults(therm_settings_t* torestore) {
 

	
 
	torestore->val.boottobrew = DEFAULT_BOOT_TO_BREW;
 
	torestore->val.temp_units = DEFAULT_TEMP_UNITS;
 
	torestore->val.windup_guard = DEFAULT_WINDUP_GUARD;
 
	torestore->val.k_p = DEFAULT_K_P;
 
	torestore->val.k_i = DEFAULT_K_I;
 
	torestore->val.k_d = DEFAULT_K_D;
 
	torestore->val.temp_offset = DEFAULT_TEMP_OFFSET;
 
	torestore->val.ignore_error = DEFAULT_IGNORE_ERROR;
 
	torestore->val.setpoint_brew = DEFAULT_SETPOINT_BREW;
 
	torestore->val.setpoint_steam = DEFAULT_SETPOINT_STEAM;
 
    torestore->val.hysteresis = DEFAULT_HYSTERESIS;
 
}
 

	
 
static void __flash_write(therm_settings_t* tosave)
 
{
 
    // Erase mem
 
    HAL_FLASH_Unlock();
 

	
 
    // Erase the FLASH pages
 
    FLASH_EraseInitTypeDef erase;
 
    erase.TypeErase = TYPEERASE_PAGES;
 
    erase.PageAddress = (uint32_t) eeprom;
 
    erase.NbPages = 1;
 
    uint32_t SectorError = 0;
 
    HAL_FLASHEx_Erase(&erase, &SectorError);
 
    CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
 

	
 
    // write to flash and calculate the checksum
 
    uint16_t checksum = MAGIC_NUMBER;
 
    uint16_t i;
 
    for(i = 0; i < (sizeof(therm_settings_t)/2); i++)
 
	{
 
		HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, (uint32_t)&eeprom[i], tosave->data[i]);
 
    	checksum ^= tosave->data[i];
 
	}
 

	
 
    // write the checksum
 
    HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, (uint32_t)&eeprom[i+1], checksum);
 

	
 
    HAL_FLASH_Lock();
 
}
 

	
 

	
 
// vim:softtabstop=4 shiftwidth=4 expandtab 
main.c
Show inline comments
 
@@ -47,191 +47,197 @@ int main(void)
 
    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
 
    #ifdef MAX31865_RTD_SENSOR
 
    max31865_config(spi_get());
 
    #endif
 
 
    // 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);
 
    ssd1306_clearscreen();
 
 
    // Soft timers
 
    uint32_t last_ssr_on = 0;
 
    uint32_t last_vcp_tx = 0;
 
    uint32_t last_led = 0;
 
    uint32_t last_pid = 0;
 
    uint32_t last_thermostat = 0;
 
    int16_t ssr_output = 0; // Duty cycle of ssr, 0 to SSR_PERIOD 
 
 
    uint8_t thermostat_plant_on = 0;
 
 
    // Main loop
 
    while(1)
 
    {
 
        // Process sensor inputs
 
 
        if(HAL_GetTick() - last_led > 400) 
 
        {
 
            last_led = HAL_GetTick();
 
        }
 
 
        if(set.val.control_mode == MODE_PID && (HAL_GetTick() - last_pid > PID_PERIOD))
 
        {
 
 
            #ifdef MAX31865_RTD_SENSOR
 
            max31865_readtemp(spi_get(), &set, &status);
 
			#else
 
			max31855_readtemp(spi_get(), &set, &status); // Read MAX31855
 
			#endif
 
 
 
            if(status.pid_enabled) 
 
            {
 
                // Get ssr output for next time
 
                int16_t power_percent = pid_update(&set, &status, &pid_state);
 
 
                if(set.val.plant_type == PLANT_HEATER)
 
                    power_percent *= -1;
 
 
                //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;
 
            }
 
 
            last_pid = HAL_GetTick();
 
        }
 
 
        // Kill SSR once the desired on-time has elapsed
 
        if(set.val.control_mode == MODE_PID && (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(set.val.control_mode == MODE_PID && HAL_GetTick() - last_ssr_on > SSR_PERIOD)
 
        {
 
            // Heat or cool, if we need to 
 
            if(ssr_output > 0) 
 
            {
 
                HAL_GPIO_WritePin(SSR_PIN, 1);
 
                HAL_GPIO_WritePin(LED_POWER, 1);
 
                last_ssr_on = HAL_GetTick();
 
            }
 
            else {
 
                // Make sure everything is off
 
                HAL_GPIO_WritePin(SSR_PIN, 0);
 
                HAL_GPIO_WritePin(LED_POWER, 0);
 
            }
 
            
 
        }
 
        
 
 
        // Thermostatic control
 
        if(set.val.control_mode == MODE_THERMOSTAT && HAL_GetTick() - last_thermostat > SSR_PERIOD)
 
        {
 
 
            #ifdef MAX31865_RTD_SENSOR
 
            max31865_readtemp(spi_get(), &set, &status);
 
			#else
 
			max31855_readtemp(spi_get(), &set, &status); // Read MAX31855
 
			#endif
 
 
            // TODO: Migrate this FxP conversion to the readtemp code or similar
 
            int8_t temp_frac = status.temp_frac > 9 ? status.temp_frac / 10 : status.temp_frac;
 
            temp_frac = status.temp > 0 ? temp_frac : temp_frac * -1;
 
            int32_t temp = (status.temp * 10) + temp_frac;
 
 
            uint8_t plant_on = 0;
 
 
            // EMZ FIXME: This could be way simpler
 
            if(set.val.plant_type == PLANT_HEATER && status.setpoint * 10 < temp)
 
                plant_on = 1;
 
            else if(set.val.plant_type == PLANT_COOLER && status.setpoint * 10 > temp)
 
                plant_on = 1;
 
            if(set.val.plant_type == PLANT_HEATER && status.setpoint * 10 < temp - set.val.hysteresis * 10)
 
                thermostat_plant_on = 1;
 
            else if(set.val.plant_type == PLANT_HEATER && status.setpoint * 10 > temp + set.val.hysteresis * 10)
 
                thermostat_plant_on = 0;
 
 
            if(set.val.plant_type == PLANT_COOLER && status.setpoint * 10 > temp + set.val.hysteresis * 10)
 
                thermostat_plant_on = 1;
 
            else if(set.val.plant_type == PLANT_COOLER && status.setpoint * 10 < temp - set.val.hysteresis * 10)
 
                thermostat_plant_on = 0;
 
 
            // EMZ: TODO: Refactor to output_enabled or something
 
            if(status.pid_enabled && plant_on)
 
            if(status.pid_enabled && thermostat_plant_on)
 
            {
 
                // EMZ TODO: functionalize this
 
            	// 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);
 
 
 
 
                HAL_GPIO_WritePin(SSR_PIN, 1);
 
                HAL_GPIO_WritePin(LED_POWER, 1);
 
            }
 
            else
 
            {
 
                HAL_GPIO_WritePin(SSR_PIN, 0);
 
                HAL_GPIO_WritePin(LED_POWER, 0);
 
            }
 
            
 
            last_thermostat = HAL_GetTick();
 
        }
 
 
 
        // 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); 
 
    }
 
}
 
// vim:softtabstop=4 shiftwidth=4 expandtab 
states.h
Show inline comments
 
#ifndef STATES_H
 
#define STATES_H
 

	
 
#include "stm32f0xx_hal.h"
 
#include "config.h"
 

	
 
typedef struct {
 
    int32_t temp;
 
    uint8_t temp_frac;
 
    uint8_t state_resume;
 
    uint8_t state;
 
    int32_t setpoint;
 
    uint8_t pid_enabled;
 
    uint8_t error_code;
 
} therm_status_t;
 

	
 
typedef union
 
{
 
     struct {
 
        uint32_t boottobrew;
 
        uint32_t temp_units;
 
        uint32_t windup_guard;
 
        uint32_t k_p;
 
        uint32_t k_i;
 
        uint32_t k_d;
 
        int32_t temp_offset;
 
        uint32_t ignore_error;
 
        int32_t setpoint_brew;
 
        int32_t setpoint_steam;
 
        uint32_t control_mode;
 
        uint32_t plant_type;
 
        uint32_t hysteresis;
 
    } val;
 

	
 
    uint16_t data[128];
 
} therm_settings_t;
 

	
 
enum tempunits {
 
    TEMP_UNITS_CELSIUS = 0,
 
    TEMP_UNITS_FAHRENHEIT,
 
};
 

	
 
enum state {
 
    STATE_IDLE = 0,
 
    
 
    STATE_SETMODE,
 
    STATE_SETPLANTTYPE,
 
    STATE_SETHYSTERESIS,
 
    STATE_SETP,
 
    STATE_SETI,
 
    STATE_SETD,
 
    STATE_SETSTEPS,
 
    STATE_SETWINDUP,
 
    STATE_SETBOOTTOBREW,
 
    STATE_SETUNITS,
 
    STATE_SETTEMPOFFSET,
 

	
 
    STATE_PREHEAT,
 
    STATE_MAINTAIN,
 

	
 
    STATE_TC_ERROR,
 
	STATE_RESET,
 
};
 

	
 
enum control_mode {
 
    MODE_PID = 0,
 
    MODE_THERMOSTAT,
 
};
 

	
 
enum plant_type {
 
    PLANT_HEATER = 0,
 
    PLANT_COOLER,
 
};
 

	
 
enum GOTO_MODE {
 
	#ifdef BOOTLOADER_SHORTCUT
 
	MODE_BOOTLOADER,
 
	#endif
 
	MODE_HEAT,
 
	MODE_SETUP,
 
	MODE_RESET,
 
	MODE_SIZE,
 
};
 

	
 
enum RESET_MODE {
 
	RESET_REBOOT = 0,
 
	RESET_DEFAULTS,
 
	RESET_BOOTLOADER,
 
	RESET_EXIT,
 
	RESET_SIZE,
 
};
 

	
 
#endif
0 comments (0 inline, 0 general)