# 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 storage.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 flash.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
#include "stm32f0xx_hal.h"
#include "ssd1306.h"
#include "stringhelpers.h"
#include "display.h"
#include "config.h"
#include "states.h"
#include "bootlib.h"
#include "flash.h"
#include "gpio.h"
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)
///////////////////////////////////////////////////////////////////////////////////////
/// 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;
int16_t last_temp = 21245;
void display_process(therm_settings_t* set, therm_status_t* status)
{
uint8_t 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) {
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);
}
ssd1306_drawlogo();
switch(goto_mode) {
case 2:
ssd1306_DrawString("-> heat ", 1, 40);
} break;
case 1:
ssd1306_DrawString("-> setup ", 1, 40);
case 0:
ssd1306_DrawString("-> loader ", 1, 40);
case 3:
ssd1306_DrawString("-> reset ", 1, 40);
// Button handler
if(SW_BTN_PRESSED) {
status->state = STATE_IDLE;
flash_erase(set);
status->state = STATE_PREHEAT_BREW;
break;
status->state = STATE_SETP;
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_IDLE; // Just in case
default:
else if(SW_UP_PRESSED && goto_mode < 2) {
else if(SW_UP_PRESSED && goto_mode < 3) {
goto_mode++;
else if(SW_DOWN_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->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(&set->k_p);
case STATE_SETI:
// [ therm :: set i ]
// [ i = 12 ]
ssd1306_DrawString("Integral", 0, 40);
@@ -318,97 +327,97 @@ void display_process(therm_settings_t* s
// [ therm :: set windup ]
// [ g = 12 ]
ssd1306_DrawString("Units: ", 0, 40);
if(set->temp_units == TEMP_UNITS_FAHRENHEIT)
ssd1306_DrawString("Fahrenheit", 1, 60);
else
ssd1306_DrawString("Celsius ", 1, 60);
status->state = STATE_SETTEMPOFFSET;
else if(!HAL_GPIO_ReadPin(SW_UP)) {
set->temp_units = TEMP_UNITS_FAHRENHEIT;
else if(!HAL_GPIO_ReadPin(SW_DOWN)) {
set->temp_units = TEMP_UNITS_CELSIUS;
case STATE_SETTEMPOFFSET:
// [ therm :: set temp offset ]
ssd1306_DrawString("Temp Cal Offset", 0, 40);
itoa(set->temp_offset, tempstr, 10);
ssd1306_DrawString("O=", 1, 45);
save_settings(&set);
flash_save(&set);
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(&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);
new file 100644
#include "stm32f0xx_hal_flash.h"
void flash_init(therm_settings_t* tosave)
uint16_t size = sizeof(therm_settings_t)-1;
uint32_t flash_adr = END_ADDR - size;
flash_adr -= 2;
uint8_t* flash_ptr = (uint8_t *)flash_adr;
// Check if flash is blank
uint16_t i = 0;
uint16_t count = 0;
char tempstr[10];
itoa(flash_adr, tempstr, 10);
ssd1306_DrawString(tempstr, 1, 0);
uint16_t test;
for(i=0;i<size;i++)
test = *flash_ptr;
if(test==0xFF) count++;
ssd1306_DrawString("END LOOP ", 0, 0);
// If blank, do nothing and just use values from RAM
// If not blank, check the checksums
if(count != size)
ssd1306_DrawString("FLASH NOT BLANK", 1, 0);
// Calculate Checksums
uint8_t cksum0=0,cksum1=0;
uint8_t rdSum0=0,rdSum1=0;
flash_adr = END_ADDR - size;
flash_ptr = (uint8_t *)flash_adr;
for(i=1; i < size; i++)
cksum0 += *flash_ptr++;
cksum1 += cksum0;
// Read flash checksums
rdSum0 = *flash_ptr++;
rdSum1 = *flash_ptr;
// Compare Checksums values
if((rdSum1==cksum1)&&(rdSum0==cksum0)) {
ssd1306_DrawString("CHECKSUM OK", 2, 0);
flash_read(tosave);
ssd1306_DrawString("CHECKSUM BAD", 2, 0);
return; // If the checksum is bad, just use vals from RAM
ssd1306_DrawString("FLASH BLANK", 1, 0);
void flash_save(therm_settings_t* tosave)
ssd1306_DrawString("BEGIN SAVE", 2, 0);
HAL_Delay(1500);
flash_erase(tosave);
flash_write(tosave);
flash_checksum(tosave);
ssd1306_DrawString("END SAVE", 2, 0);
void flash_read(therm_settings_t *tosave)
ssd1306_DrawString("READING SAVE", 1, 0);
itoa(sizeof(therm_settings_t), tempstr, 10);
ssd1306_DrawString(tempstr, 2, 0);
uint16_t size = sizeof(therm_settings_t)-1; // in Bytes
uint8_t *flash_ptr = (uint8_t *)flash_adr;
uint8_t *struct_ptr = (uint8_t*)tosave;
uint16_t i;
*struct_ptr++ = *flash_ptr++;
ssd1306_DrawString("READ COMPLETE", 3, 0);
void flash_write(therm_settings_t* tosave)
HAL_FLASH_Unlock();
uint32_t start_address = END_ADDR-size; // write to end of page
uint32_t struct_ptr = (uint32_t*) tosave;
uint16_t length;
if(size%2==0)
length = size/2;
length = size/2+1;
for(i=0;i<length;i++)
HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, start_address, struct_ptr);
struct_ptr++;
start_address +=2;
HAL_FLASH_Lock();
void flash_checksum(therm_settings_t* tosave)
uint16_t i,size,checksum;
uint32_t flash_adr;
uint8_t *flash_ptr;
size = sizeof(*tosave)-1; // in Bytes
flash_adr = END_ADDR-size;
checksum = (cksum1<<8) | cksum0;
HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, flash_adr, checksum);
void flash_erase(therm_settings_t* tosave)
uint8_t FLASHStatus = 1; // FLASH_COMPLETE=1
uint32_t end_addr = END_ADDR;
uint32_t NbrOfPage = abs( (sizeof(*tosave)-1)/0x400 )+1; // Number of pages to be erased, most definitely 1 but hey, we might as well try to calculate it.
uint32_t StartAddr = (end_addr+1) - (0x400*NbrOfPage); // Starting address to be erased
// Clear All pending flags
//FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
// Erase the FLASH pages
FLASH_EraseInitTypeDef erase;
erase.TypeErase = TYPEERASE_PAGES;
erase.PageAddress = StartAddr;
erase.NbPages = NbrOfPage;
uint32_t SectorError = 0;
FLASHStatus = HAL_FLASHEx_Erase(&erase, &SectorError);
// vim:softtabstop=4 shiftwidth=4 expandtab
#ifndef FLASH_H
#define FLASH_H
#define PAGE_SIZE ((uint16_t)0x400)
#define END_ADDR 0x08007FFF
void flash_init(therm_settings_t* tosave);
void flash_save(therm_settings_t* tosave);
void flash_read(therm_settings_t *tosave);
void flash_write(therm_settings_t* tosave);
void flash_checksum(therm_settings_t* tosave);
void flash_erase(therm_settings_t* tosave);
#endif
#!/bin/bash
#cd build
#st-flash write main.bin 0x8000000
#cd ..
# USB DFU:
dfu-util -a 0 -d 0483:df11 -s 0x08000000:leave -D build/main.bin
sleep 1
sleep 2
dfu-util -a 1 -s 0x1FFFF800:8 -D optbytes.dat
#include "spi.h"
#include "storage.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
// Prototypes
// Move to header file
void process();
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);
if(!HAL_GPIO_ReadPin(SW_UP))
// 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(&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);
flash_init(&set);
// 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);
Status change: