#include "stm32f0xx_hal.h" #include "config.h" #include "syslib.h" #include "states.h" #include "ssd1306.h" #include "max31855.h" #include "gpio.h" #include "spi.h" #include "flash.h" #include "stringhelpers.h" #include "display.h" #include "storage.h" #include "usb_device.h" #include "usbd_cdc_if.h" // Prototypes void process(); therm_settings_t set; therm_status_t status; // Globalish setting vars static __IO uint32_t TimingDelay; int main(void) { // Initialize HAL hal_init(); // Configure the system clock systemclock_init(); // Unset bootloader option bytes (if set) void bootloader_unset(void); // 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(1000); 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 OLED over SPI ssd1306_Init(); ssd1306_clearscreen(); // Default settings set.val.boottobrew = 0; set.val.temp_units = TEMP_UNITS_CELSIUS; set.val.windup_guard = 1; set.val.k_p = 1; set.val.k_i = 1; set.val.k_d = 1; set.val.ignore_tc_error = 0; set.val.setpoint_brew = 0; set.val.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(&set); // Go to brew instead of idle if configured thusly if(set.val.boottobrew) status.state = STATE_PREHEAT_BREW; // Startup screen ssd1306_DrawString("therm v0.2", 1, 40); ssd1306_DrawString("protofusion.org/therm", 3, 0); HAL_Delay(1500); flash_restore(&set); HAL_Delay(1500); ssd1306_clearscreen(); // Main loop while(1) { // Process sensor inputs process(); // Run state machine display_process(&set, &status); } } // 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; int32_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 = setpoint - temp; // TODO: Use fixed point fraction // Proportional component int32_t p_term = k_p * error; // Error accumulator (integrator) 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; // 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; int32_t i_term = k_i * i_state; // Calculate differential term (slope since last iteration) int32_t d_term = (k_d * (status.temp - last_pid_temp)); // Save temperature for next iteration last_pid_temp = status.temp; 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; } 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 // Turn SSR output on/off according to set duty cycle. // TODO: Eventually maybe replace with a very slow timer or something. Double-check this code... void process() { uint32_t ticks = HAL_GetTick(); if(ticks - last_led > 400) { last_led = ticks; } if((ticks - last_pid > PID_PERIOD)) { #ifdef MAX31855_TC_SENSOR max31855_readtemp(spi_get(), &set, &status); // Read MAX31855 #endif #ifdef MAX31865_RTD_SENSOR max31865_readtemp(&set, &status); #endif HAL_GPIO_TogglePin(LED_POWER); if(status.pid_enabled) { // Get ssr output for next time int16_t power_percent = update_pid(set.val.k_p, set.val.k_i, set.val.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; } else { ssr_output = 0; } last_pid = ticks; } // Every 200ms, set the SSR on unless output is 0 if((ticks - last_ssr_on > SSR_PERIOD)) { // Only support heating (ssr_output > 0) right now if(ssr_output > 0) { char tempstr[6]; 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); } if(ticks - 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 = ticks; } } // vim:softtabstop=4 shiftwidth=4 expandtab