# STM32F0xx Makefile
# #####################################
#
# Part of the uCtools project
# uctools.github.com
#######################################
# user configuration:
# SOURCES: list of sources in the user application
SOURCES = main.c usbd_conf.c usbd_cdc_if.c usb_device.c usbd_desc.c stm32f0xx_hal_msp.c stm32f0xx_it.c system_stm32f0xx.c gpio.c spi.c ssd1306.c stringhelpers.c display.c bootlib.c
SOURCES = main.c usbd_conf.c usbd_cdc_if.c usb_device.c usbd_desc.c stm32f0xx_hal_msp.c stm32f0xx_it.c system_stm32f0xx.c gpio.c spi.c ssd1306.c stringhelpers.c display.c bootlib.c storage.c
# TARGET: name of the user application
TARGET = main
# BUILD_DIR: directory to place output files in
BUILD_DIR = build
# LD_SCRIPT: location of the linker script
LD_SCRIPT = stm32f042c6_flash.ld
# USER_DEFS user defined macros
USER_DEFS = -D HSI48_VALUE=48000000 -D HSE_VALUE=16000000
# USER_INCLUDES: user defined includes
USER_INCLUDES =
# USB_INCLUDES: includes for the usb library
USB_INCLUDES = -Imiddlewares/ST/STM32_USB_Device_Library/Core/Inc
USB_INCLUDES += -Imiddlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc
# USER_CFLAGS: user C flags (enable warnings, enable debug info)
USER_CFLAGS = -Wall -g -ffunction-sections -fdata-sections -Os
# USER_LDFLAGS: user LD flags
USER_LDFLAGS = -fno-exceptions -ffunction-sections -fdata-sections -Wl,--gc-sections
# TARGET_DEVICE: device to compile for
TARGET_DEVICE = STM32F042x6
# end of user configuration
# binaries
CC = arm-none-eabi-gcc
AR = arm-none-eabi-ar
RANLIB = arm-none-eabi-ranlib
SIZE = arm-none-eabi-size
OBJCOPY = arm-none-eabi-objcopy
MKDIR = mkdir -p
# core and CPU type for Cortex M0
# ARM core type (CORE_M0, CORE_M3)
CORE = CORE_M0
# ARM CPU type (cortex-m0, cortex-m3)
CPU = cortex-m0
# where to build STM32Cube
CUBELIB_BUILD_DIR = $(BUILD_DIR)/STM32Cube
# various paths within the STmicro library
CMSIS_PATH = drivers/CMSIS
CMSIS_DEVICE_PATH = $(CMSIS_PATH)/Device/ST/STM32F0xx
DRIVER_PATH = drivers/STM32F0xx_HAL_Driver
# includes for gcc
INCLUDES = -I$(CMSIS_PATH)/Include
INCLUDES += -I$(CMSIS_DEVICE_PATH)/Include
INCLUDES += -I$(DRIVER_PATH)/Inc
INCLUDES += -I$(CURDIR)
INCLUDES += -I$(CURDIR)/usb
INCLUDES += $(USB_INCLUDES)
INCLUDES += $(USER_INCLUDES)
# macros for gcc
DEFS = -D$(CORE) $(USER_DEFS) -D$(TARGET_DEVICE)
# compile gcc flags
CFLAGS = $(DEFS) $(INCLUDES)
CFLAGS += -mcpu=$(CPU) -mthumb
CFLAGS += $(USER_CFLAGS)
# default action: build the user application
all: $(BUILD_DIR)/$(TARGET).hex
# build the st micro peripherial library
# (drivers and CMSIS)
CUBELIB = $(CUBELIB_BUILD_DIR)/libstm32cube.a
# List of stm32 driver objects
CUBELIB_DRIVER_OBJS = $(addprefix $(CUBELIB_BUILD_DIR)/, $(patsubst %.c, %.o, $(notdir $(wildcard $(DRIVER_PATH)/Src/*.c))))
# shortcut for building core library (make cubelib)
cubelib: $(CUBELIB)
$(CUBELIB): $(CUBELIB_DRIVER_OBJS)
$(AR) rv $@ $(CUBELIB_DRIVER_OBJS)
$(RANLIB) $@
$(CUBELIB_BUILD_DIR)/%.o: $(DRIVER_PATH)/Src/%.c | $(CUBELIB_BUILD_DIR)
$(CC) -c $(CFLAGS) -o $@ $^
@@ -281,297 +281,297 @@ void display_process(therm_settings_t* s
// Button handler
if(SW_BTN_PRESSED) {
status->state = STATE_SETBOOTTOBREW;
}
else {
user_input(&set->windup_guard);
// Event Handler
// N/A
} break;
case STATE_SETBOOTTOBREW:
{
// Write text to OLED
// [ therm :: set windup ]
// [ g = 12 ]
ssd1306_DrawString("Start on Boot", 0, 40);
ssd1306_drawlogo();
ssd1306_DrawString("sob=", 1, 45);
if(set->boottobrew)
ssd1306_DrawString("Enabled ", 1, 70);
else
ssd1306_DrawString("Disabled", 1, 70);
ssd1306_DrawString("Press to accept", 3, 40);
status->state = STATE_SETUNITS;
else if(!HAL_GPIO_ReadPin(SW_UP)) {
set->boottobrew = 1;
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
set->boottobrew = 0;
case STATE_SETUNITS:
ssd1306_DrawString("Units: ", 0, 40);
if(set->temp_units == TEMP_UNITS_FAHRENHEIT)
ssd1306_DrawString("Fahrenheit", 1, 60);
ssd1306_DrawString("Celsius ", 1, 60);
status->state = STATE_SETTEMPOFFSET;
set->temp_units = TEMP_UNITS_FAHRENHEIT;
set->temp_units = TEMP_UNITS_CELSIUS;
case STATE_SETTEMPOFFSET:
// [ therm :: set temp offset ]
ssd1306_DrawString("Thermocouple Offset", 0, 40);
char tempstr[6];
itoa(set->temp_offset, tempstr, 10);
ssd1306_DrawString("O=", 1, 45);
ssd1306_DrawString(" ", 1, 57);
ssd1306_DrawString(tempstr, 1, 57);
save_settings();
save_settings(&set);
status->state = STATE_IDLE;
user_input_signed(&set->temp_offset);
case STATE_PREHEAT_BREW:
// [ therm : preheating brew ]
// [ 30 => 120 C ]
ssd1306_DrawString("Preheating...", 0, 0);
//ssd1306_drawlogo();
draw_setpoint(status);
status->pid_enabled = 1;
status->setpoint = set->setpoint_brew;
save_setpoints(); // TODO: Check for mod
save_setpoints(&set); // TODO: Check for mod
user_input(&set->setpoint_brew);
if(status->temp >= status->setpoint) {
status->state = STATE_MAINTAIN_BREW;
case STATE_MAINTAIN_BREW:
// [ therm : ready to brew ]
ssd1306_DrawString("Preheated!", 0, 0);
case STATE_PREHEAT_STEAM:
// [ therm : preheating steam ]
status->setpoint = set->setpoint_steam;
user_input(&set->setpoint_steam);
status->state = STATE_MAINTAIN_STEAM;
case STATE_MAINTAIN_STEAM:
// [ therm : ready to steam ]
ssd1306_DrawString("Ready to Steam!", 0, 0);
case STATE_TC_ERROR:
ssd1306_DrawString("Error:", 0, 0);
if(status->tc_errno == 1)
ssd1306_DrawString("#1, Check Sensor", 1, 0);
else if(status->tc_errno == 4)
ssd1306_DrawString("#4, Check Sensor", 1, 0);
ssd1306_DrawString("#?, Unknown Error", 1, 0);
ssd1306_DrawString("Press -> to ignore", 3, 0);
else if(SW_RIGHT_PRESSED) {
set->ignore_tc_error = 1;
// Maybe handle if TC is plugged in
// Something is terribly wrong
default:
status->pid_enabled = 0;
if(last_state != status->state) {
// Clear screen on state change
goto_mode = 2;
trigger_drawsetpoint = 1;
ssd1306_clearscreen();
// 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;
int32_t temp_last = 43002;
int32_t setpoint_last = 10023;
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];
itoa_fp(status->temp, status->temp_frac, tempstr);
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;
// vim:softtabstop=4 shiftwidth=4 expandtab
#include "stm32f0xx_hal.h"
#include "config.h"
#include "states.h"
#include "ssd1306.h"
#include "gpio.h"
#include "spi.h"
#include "stringhelpers.h"
#include "display.h"
#include "storage.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
// Prototypes
// Move to header file
void process();
void restore_settings();
void save_settings();
void save_setpoints();
void SystemClock_Config(void);
therm_settings_t set;
therm_status_t status;
// Globalish setting vars
SPI_HandleTypeDef hspi1;
static __IO uint32_t TimingDelay;
void deinit(void)
HAL_DeInit();
volatile int i=0;
int main(void)
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Unset bootloader option bytes (if set) */
void bootloader_unset(void);
/* Initialize all configured peripherals */
init_gpio();
MX_USB_DEVICE_Init();
// USB startup delay
HAL_Delay(1000);
HAL_GPIO_WritePin(LED_POWER, 1);
// TODO: Awesome pwm of power LED
// Configure 1ms SysTick (change if more temporal resolution needed)
//RCC_ClocksTypeDef RCC_Clocks;
//RCC_GetClocksFreq(&RCC_Clocks);
//SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
// Init SPI busses
init_spi();
// Init OLED over SPI
ssd1306_Init();
// Default settings
set.boottobrew = 0;
set.temp_units = TEMP_UNITS_CELSIUS;
set.windup_guard = 1;
set.k_p = 1;
set.k_i = 1;
set.k_d = 1;
set.ignore_tc_error = 0;
set.setpoint_brew = 0;
set.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();
restore_settings(&set);
// Go to brew instead of idle if configured thusly
if(set.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);
// Main loop
while(1)
// Process sensor inputs
process();
// Run state machine
display_process(&set, &status);
// Clock configuration
void SystemClock_Config(void)
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInit;
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI48;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB;
PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
__SYSCFG_CLK_ENABLE();
// Grab temperature reading from MAX31855
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) {
HAL_Delay(100); // FIXME: remove?
status.tc_errno = 4;
status.state = STATE_TC_ERROR;
else if(temp_pre & 0b0000000000000001 && !set.ignore_tc_error) {
status.tc_errno = 1;
status.state_resume = status.state;
if(status.state == STATE_TC_ERROR)
status.state = status.state_resume;
uint8_t sign = status.temp >> 15;// top bit is sign
@@ -248,161 +246,97 @@ int16_t update_pid(uint16_t k_p, uint16_
// 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;
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()
update_temp(); // Read MAX31855
uint32_t ticks = HAL_GetTick();
if(ticks - last_led > 400)
HAL_GPIO_TogglePin(LED_POWER);
last_led = ticks;
// Every 200ms, set the SSR on unless output is 0
if((ticks - last_ssr_on > SSR_PERIOD))
if(status.pid_enabled)
// Get ssr output for next time
int16_t power_percent = update_pid(set.k_p, set.k_i, set.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;
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);
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';
CDC_Transmit_FS(tempstr, numlen+2);
// while(CDC_Transmit_FS("\r\n", 2) == USBD_BUSY);
last_vcp_tx = ticks;
void save_settings()
// TODO: Rework with FLASH read/write
/*
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);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_K_I, k_i);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_K_D, k_d);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_UNITS, temp_units);
Minimal_EEPROM_Lock();
*/
void save_setpoints()
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_BREWTEMP, setpoint_brew);
Minimal_EEPROM_ProgramWord(EEPROM_BASE_ADDR + EEPROM_ADDR_STEAMTEMP, setpoint_steam);
// TODO: Make a struct that has all settings in it. Pass by ref to this func in a library.
void restore_settings()
/* Minimal_EEPROM_Unlock();
while(Minimal_FLASH_GetStatus()==FLASH_BUSY);
boottobrew = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_BOOTTOBREW));
windup_guard = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_WINDUP_GUARD));
k_p = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_K_P));
k_i = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_K_I));
k_d = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_K_D));
setpoint_brew = (*(__IO uint32_t*)(EEPROM_BASE_ADDR + EEPROM_ADDR_BREWTEMP));
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(); */
new file 100644
void save_settings(therm_settings_t *tosave)
void save_setpoints(therm_settings_t *tosave)
void restore_settings(therm_settings_t *tosave)
#ifndef STORAGE_H
#define STORAGE_H
void save_settings(therm_settings_t *tosave);
void save_setpoints(therm_settings_t *tosave);
void restore_settings(therm_settings_t *tosave);
#endif
Status change: