2020-12-08 23:09:46 +01:00
/** firmware for ThermoHybaid MBS 0.2G MBLK002 thermo-cycler replacement controller board
2020-02-17 18:08:17 +01:00
* @ file
2016-08-14 21:02:38 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2020-02-17 18:08:17 +01:00
* @ date 2016 - 2020
2016-08-14 21:02:38 +02:00
*/
2016-01-17 14:54:54 +01:00
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // standard utilities
2016-08-14 21:02:38 +02:00
# include <string.h> // string utilities
2019-06-18 17:43:43 +02:00
# include <time.h> // date/time utilities
# include <ctype.h> // utilities to check chars
2020-12-07 19:14:00 +01:00
# include <math.h> // NAN definition
2016-01-17 14:54:54 +01:00
/* STM32 (including CM3) libraries */
2016-01-18 16:23:35 +01:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/cm3/scb.h> // vector table definition
2016-01-29 11:25:30 +01:00
# include <libopencm3/cm3/nvic.h> // interrupt utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/stm32/rcc.h> // real-time control clock library
2016-01-29 11:25:30 +01:00
# include <libopencm3/stm32/exti.h> // external interrupt utilities
2016-08-14 21:02:38 +02:00
# include <libopencm3/stm32/rtc.h> // real time clock utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
# include <libopencm3/stm32/dbgmcu.h> // debug utilities
2018-04-06 17:37:17 +02:00
# include <libopencm3/stm32/desig.h> // design utilities
2016-10-23 17:42:27 +02:00
# include <libopencm3/stm32/flash.h> // flash utilities
2020-12-07 19:14:00 +01:00
# include <libopencm3/stm32/adc.h> // ADC utilities
# include <libopencm3/stm32/timer.h> // timer utilities
2016-01-17 14:54:54 +01:00
/* own libraries */
2016-01-28 21:21:50 +01:00
# include "global.h" // board definitions
2017-04-03 13:32:45 +02:00
# include "print.h" // printing utilities
2016-01-18 16:23:35 +01:00
# include "usb_cdcacm.h" // USB CDC ACM utilities
2018-04-06 17:37:17 +02:00
# include "terminal.h" // handle the terminal interface
# include "menu.h" // menu utilities
2020-12-07 19:14:00 +01:00
# include "oled_text.h" // utilities to display text on OLED
# include "sensor_max1247.h" // to read the thermistor ADC values
# include "sensor_ds18b20.h" // to read temperature from a DS18B20
2016-01-17 14:54:54 +01:00
2020-02-02 21:19:03 +01:00
/** watchdog period in ms */
# define WATCHDOG_PERIOD 10000
2019-06-18 17:43:43 +02:00
/** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime
* set to 1 if VBAT can keep the RTC running when the board is unpowered , indicating the date and time
*/
2020-02-02 21:19:03 +01:00
# define RTC_DATE_TIME 0
/** number of RTC ticks per second
* @ note use integer divider of oscillator to keep second precision
*/
2020-12-07 19:14:00 +01:00
# define RTC_TICKS_SECOND 8
2019-06-18 17:43:43 +02:00
/** RTC time when device is started */
static time_t time_start = 0 ;
2016-10-23 17:42:27 +02:00
2016-08-14 21:02:38 +02:00
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @ {
*/
2020-12-07 19:14:00 +01:00
static volatile uint32_t rtc_internal_tick_flag = 0 ; /**< set with time when internal RTC ticked */
static volatile bool rtc_internal_second_flag = false ; /**< set when a second passed */
2016-08-14 21:02:38 +02:00
/** @} */
2020-12-07 19:14:00 +01:00
# define BED_PIN_393A PB3 /**< pin connected to LN393 output A */
# define BED_PIN_3393 PB4 /**< pin connected to ST339 output 3 */
# define BED_PIN_LK1 PB5 /**< pin connected to link 1 */
# define BED_PIN_LK2 PC14 /**< pin connected to link 2 */
# define BED_PIN_LK3 PC15 /**< pin connected to link 3 */
# define BED_PIN_LK4 PB1 /**< pin connected to link 4 */
2020-12-08 23:11:52 +01:00
# define LID_TEC_CHANNEL 0 /**< PA0/ADC12_CH0 is connected to the 12 kOhm thermistor in the lid heater */
2020-12-07 19:14:00 +01:00
# define LID_HEATER_PIN PA10 /**< pin to optocoupler cathode controlling triac to lid heater */
# define LID_HEATER_TIMER 1 /**< timer connected to lid heater pin */
# define LID_HEATER_CHANNEL 3 /**< timer channel connected to lid heater pin */
# define LID_HEATER_OC TIM_OC3 /**< output compare for timer channel connected to lid heater pin */
# define MBLK019_CH26_PIN PA1 /**< to control TR2/6 for the peltier elements */
# define MBLK019_CH14_PIN PA2 /**< to control TR1/4 for the peltier elements */
# define MBLK019_CH35_PIN PA4 /**< to control TR3/5 for the peltier elements */
# define MBLK019_PRESENCE_PIN PA3 /**< connected to ground when MBLK019 board is present */
# define CONTROL_PLAY_GREEN_LED_PIN PA5 /**< to control green LED of play/pause indicator (active high) */
# define led_cool_on() gpio_set(GPIO_PORT(CONTROL_PLAY_GREEN_LED_PIN), GPIO_PIN(CONTROL_PLAY_GREEN_LED_PIN)) /**< switch green play/pause LED on */
# define led_cool_off() gpio_clear(GPIO_PORT(CONTROL_PLAY_GREEN_LED_PIN), GPIO_PIN(CONTROL_PLAY_GREEN_LED_PIN)) /**< switch green play/pause LED off */
# define CONTROL_PLAY_ORANGE_LED_PIN PA6 /**< to control orange LED of play/pause indicator (active high) */
# define led_heat_on() gpio_set(GPIO_PORT(CONTROL_PLAY_ORANGE_LED_PIN), GPIO_PIN(CONTROL_PLAY_ORANGE_LED_PIN)) /**< switch orange play/pause LED on */
# define led_heat_off() gpio_clear(GPIO_PORT(CONTROL_PLAY_ORANGE_LED_PIN), GPIO_PIN(CONTROL_PLAY_ORANGE_LED_PIN)) /**< switch orange play/pause LED off */
# define CONTROL_POWER_RED_LED_PIN PA7 /**< to control red LED of power indicator (active high) */
# define led_power_on() gpio_set(GPIO_PORT(CONTROL_POWER_RED_LED_PIN), GPIO_PIN(CONTROL_POWER_RED_LED_PIN)) /**< switch power LED on */
# define led_power_off() gpio_clear(GPIO_PORT(CONTROL_POWER_RED_LED_PIN), GPIO_PIN(CONTROL_POWER_RED_LED_PIN)) /**< switch power LED off */
2020-12-08 23:07:36 +01:00
# define CONTROL_PLAY_BUTTON_LED_PIN PB0 /**< to read play/pause button (connected to ground when pressed) */
# define TEC_POWER_YELLOW PB11 /**< pin to choose which power rail to connect to yellow TEC power input (high = VCC, low = GND) */
# define tec_power_yellow_on() gpio_set(GPIO_PORT(TEC_POWER_YELLOW), GPIO_PIN(TEC_POWER_YELLOW)) // allow connecting yellow to VCC
# define tec_power_yellow_off() gpio_clear(GPIO_PORT(TEC_POWER_YELLOW), GPIO_PIN(TEC_POWER_YELLOW)) // allow connect yellow to GND
# define TEC_POWER_ORANGE PB10 /**< pin to choose which power rail to connect to orange TEC power input (high = VCC, low = GND) */
# define tec_power_orange_on() gpio_set(GPIO_PORT(TEC_POWER_ORANGE), GPIO_PIN(TEC_POWER_ORANGE)) // allow connect orange to VCC
# define tec_power_orange_off() gpio_clear(GPIO_PORT(TEC_POWER_ORANGE), GPIO_PIN(TEC_POWER_ORANGE)) // allow connect orange to GND
# define TEC_POWER_PWM PB9 /**< pin to actually let power go through TECs, where PWM can be used (active high) */
# define tec_power_pwm_on() gpio_set(GPIO_PORT(TEC_POWER_PWM), GPIO_PIN(TEC_POWER_PWM)) /**< connect yellow/orange wire as set */
# define tec_power_pwm_off() gpio_clear(GPIO_PORT(TEC_POWER_PWM), GPIO_PIN(TEC_POWER_PWM)) /**< disconnect yellow/orange wire */
2020-12-07 19:14:00 +01:00
# define TEC_POWER_TIMER 4 /**< timer connected to pin */
# define TEC_POWER_CHANNEL 4 /**< timer channel connected to pin */
# define TEC_POWER_OC TIM_OC4 /**< timer output compare connected to pin */
# define HEATSINK_FAN_PIN PA15 /**< pin to switch the nMOS to control the fan cooling the bad heatsink, low to disable, pulled up externally, must be 5V tolerant */
# define heatsink_fan_on() gpio_set(GPIO_PORT(HEATSINK_FAN_PIN), GPIO_PIN(HEATSINK_FAN_PIN)) /**< switch fan on, cooling the bed heat sink */
# define heatsink_fan_off() gpio_clear(GPIO_PORT(HEATSINK_FAN_PIN), GPIO_PIN(HEATSINK_FAN_PIN)) /**< switch fan off, when the bed is not used */
static bool led_power_blink = false ; /**< remember we are blinking the power LED */
static bool led_heat_blink = false ; /**< remember we are blinking the orange play/pause LED */
static bool led_cool_blink = false ; /**< remember we are blinking the green play/pause LED */
const uint8_t channels [ ] = { ADC_CHANNEL17 , ADC_CHANNEL ( LID_TEC_CHANNEL ) } ; /**< voltages to convert (channel 17 = internal voltage reference) */
static bool ds18b20_present = false ; /**< if DS18B20 temperature sensor is present */
/** target temperature to be reached by the lid */
2020-12-08 23:11:52 +01:00
static float lid_target = NAN ;
2020-12-07 19:14:00 +01:00
/** target temperature to be reached by the bed */
static uint16_t bed_target = 0 ;
/** the current state of the thermo-cycler */
enum state_e {
STATE_IDLE , /**< doing nothing, waiting for a command */
STATE_SAFE , /**< safe state entered, probably because of an error */
STATE_HEAT , /**< simply heat up bed */
STATE_COOL , /**< simply cool down bed */
2020-12-10 18:16:16 +01:00
STATE_FAN , /**< simply use fan to set to ambient temperature */
STATE_PREPARE , /**< heat up for initialisation/first denaturation */
STATE_INITIALISATION , /**< first (longer) denaturation phase */
STATE_TO_DENATURATION , /**< transition to denaturation phase */
STATE_DENATURATION , /**< denaturation phase */
STATE_TO_ANNEALING , /**< transition to annealing phase */
STATE_ANNEALING , /**< annealing phase */
STATE_TO_EXTENSTION , /**< transition to extension phase */
STATE_EXTENSION , /**< extension phase */
STATE_TO_HOLD , /**< transition to final hold */
STATE_HOLD , /**< final hold */
2020-12-07 19:14:00 +01:00
} state = STATE_IDLE ;
2020-12-10 18:16:16 +01:00
/** name of the states */
const char * state_names [ ] = {
[ STATE_IDLE ] = " ready " ,
[ STATE_SAFE ] = " safe " ,
[ STATE_HEAT ] = " heating " ,
[ STATE_COOL ] = " cooling " ,
[ STATE_FAN ] = " fanning " ,
[ STATE_PREPARE ] = " >initialisation " ,
[ STATE_INITIALISATION ] = " initialisation " ,
[ STATE_TO_DENATURATION ] = " >denaturation " ,
[ STATE_DENATURATION ] = " denaturation " ,
[ STATE_TO_ANNEALING ] = " >annealing " ,
[ STATE_ANNEALING ] = " annealing " ,
[ STATE_TO_EXTENSTION ] = " >extension " ,
[ STATE_EXTENSION ] = " extension " ,
// there is an optional final extension
[ STATE_TO_HOLD ] = " >final hold " ,
[ STATE_HOLD ] = " final hold " ,
} ;
2020-12-07 19:14:00 +01:00
/** set if an error or anomaly has been encountered */
static char * error = NULL ;
2017-04-03 13:32:45 +02:00
size_t putc ( char c )
2016-01-17 14:54:54 +01:00
{
2017-04-03 13:32:45 +02:00
size_t length = 0 ; // number of characters printed
2018-04-06 17:37:17 +02:00
static char last_c = 0 ; // to remember on which character we last sent
if ( ' \n ' = = c ) { // send carriage return (CR) + line feed (LF) newline for each LF
if ( ' \r ' ! = last_c ) { // CR has not already been sent
2017-04-19 16:24:07 +02:00
usb_cdcacm_putchar ( ' \r ' ) ; // send CR over USB
2018-04-06 17:37:17 +02:00
length + + ; // remember we printed 1 character
2016-01-17 14:54:54 +01:00
}
}
2018-04-06 17:37:17 +02:00
usb_cdcacm_putchar ( c ) ; // send byte over USB
length + + ; // remember we printed 1 character
last_c = c ; // remember last character
2019-03-26 19:27:40 +01:00
return length ; // return number of characters printed
2016-01-17 14:54:54 +01:00
}
2020-12-07 19:14:00 +01:00
/** enter in safe state mode
* @ note useful when an error occurred or an anomaly has been detected
*/
static void safe_state ( void )
{
// this is the safest configuration of TEC switching (in cooling mode it switches all off, in heating mode it only heats mildly the top part
gpio_set ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
gpio_set ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
gpio_set ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
2020-12-08 23:07:36 +01:00
// take control over the H-bridge and switch it off
2020-12-07 19:14:00 +01:00
rcc_periph_clock_enable ( GPIO_RCC ( TEC_POWER_PWM ) ) ; // enable clock for GPIO port peripheral
2020-12-08 23:07:36 +01:00
gpio_set_mode ( GPIO_PORT ( TEC_POWER_PWM ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( TEC_POWER_PWM ) ) ; // set pin as output
2020-12-07 19:14:00 +01:00
tec_power_pwm_off ( ) ; // switch off power
2020-12-08 23:07:36 +01:00
tec_power_yellow_off ( ) ; // connect wire to ground
2020-12-10 18:16:50 +01:00
tec_power_orange_off ( ) ; // connect wire to ground
2020-12-07 19:14:00 +01:00
// disable heater lid
rcc_periph_clock_enable ( GPIO_RCC ( LID_HEATER_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_set_mode ( GPIO_PORT ( LID_HEATER_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_OPENDRAIN , GPIO_PIN ( LID_HEATER_PIN ) ) ; // set pin back as output open-drain
gpio_set ( GPIO_PORT ( LID_HEATER_PIN ) , GPIO_PIN ( LID_HEATER_PIN ) ) ; // don't sink current, not powering the opto-coupler and triac
heatsink_fan_off ( ) ; // bed is not active, so we can stop the fan, since we want to stop drawing power and having spinning things
led_heat_blink = false ; // stop blinking LED
led_heat_off ( ) ; // switch off LED
led_cool_blink = false ; // stop blinking LED
led_cool_off ( ) ; // switch off LED
if ( error ) {
led_power_blink = false ; // start blinking red LED to indicate error
2020-12-10 18:16:16 +01:00
oled_text_line ( " error " , 2 ) ;
2020-12-07 19:14:00 +01:00
oled_text_update ( ) ;
}
}
/** get temperature of lid (in °C)
* @ return lid temperature
*/
static float lid_temperature ( void )
{
// read lid temperature using ADC
ADC_SR ( ADC1 ) = 0 ; // reset flags
uint16_t adc_values [ LENGTH ( channels ) ] ;
float voltages [ LENGTH ( channels ) ] ;
for ( uint8_t i = 0 ; i < LENGTH ( channels ) ; i + + ) {
adc_start_conversion_regular ( ADC1 ) ; // start conversion (using trigger)
while ( ! adc_eoc ( ADC1 ) ) ; // wait until conversion finished
adc_values [ i ] = adc_read_regular ( ADC1 ) ; // read voltage value (clears flag)
voltages [ i ] = adc_values [ i ] * 1.2 / adc_values [ 0 ] ; // use 1.2V internal voltage reference to get ADC voltage
}
//return voltages[1];
// convert to °C
// calibrated using a DS18B20 (accuracy = +- 0.5°C), with 12-bit precision
2020-12-08 23:14:57 +01:00
// 2.3491 V = 21.500 C, 0.1337 V = 104.9 °C
return - 37.6456 * voltages [ 1 ] + 109.933 ;
2020-12-07 19:14:00 +01:00
}
/** set the power delivered to the lid heater
* @ param [ in ] percent power ( e . g . duty cycle ) in %
* @ note we use % since we control the duty cycle of a 1 s period over a triac ( e . g . 100 Hz control )
*/
static void lid_power ( uint8_t percent )
{
if ( STATE_SAFE = = state & & 0 ! = percent ) {
puts ( " can't set lid power in safe state \n " ) ;
return ;
}
if ( 0 = = percent ) {
timer_set_oc_value ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC , 0 ) ; // duty cycle to 0%, to switch off heater
} else if ( percent > = 100 ) {
timer_set_oc_value ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC , UINT16_MAX ) ; // duty cycle to 100%, to switch completely on heater
} else {
timer_set_oc_value ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC , UINT16_MAX / 100 * percent - 1 ) ; // set duty cycle
}
}
2020-12-08 23:11:52 +01:00
/** run PID control for lid temperature */
2020-12-07 19:14:00 +01:00
static void lid_pid ( void )
{
// I tried Ziegler– Nichols method, but it overshoots and oscillates far too much (even with the no overshoot rule)
//#define LID_KU 64
//#define LID_TU 14.96
//#define LID_KP (0.2 * LID_KU)
//#define LID_KI (0.4 * LID_KU / LID_TU)
//#define LID_KD (0.066 * LID_KU * LID_TU)
# define LID_KP 24.0
# define LID_KI 0.0
# define LID_KD 0.0
if ( isnan ( lid_target ) ) { // no target has been defined
lid_power ( 0 ) ;
// reinitialise errors
return ;
}
static float error_sum = 0.0 ; // value used for the integral part
static float error_prev = 0.0 ; // value used for the derivate part
const float error_cur = lid_target - lid_temperature ( ) ; // get current error
error_sum + = error_cur ;
const float error_diff = error_cur - error_prev ;
error_prev = error_cur ;
float power = LID_KP * error_cur + LID_KI * error_sum + LID_KD * error_diff ; // calculate needed power
// enforce limits
if ( power < 0.0 ) {
power = 0.0 ;
2020-12-08 23:15:21 +01:00
} else if ( power > 50.0 ) { // limit power to 50%, else it gets too warm too quick and could damage the hardware
power = 50.0 ;
2020-12-07 19:14:00 +01:00
}
lid_power ( ( uint8_t ) power ) ; // set power
}
static void tec_power ( uint16_t duty_cycle )
{
if ( STATE_SAFE = = state & & 0 ! = duty_cycle ) { // don't allow setting in save state (except switching off)
puts ( " can't set TEC power in safe state \n " ) ;
return ;
}
timer_set_oc_value ( TIM ( TEC_POWER_TIMER ) , TEC_POWER_OC , duty_cycle ) ; // duty cycle to 0%, to switch off heater
// ensure the fan is on when there is power
if ( duty_cycle ) {
heatsink_fan_on ( ) ;
}
}
/** set TEC to heat */
static void tec_heat ( void )
{
tec_power ( 0 ) ; // ensure power is off while switching
sleep_ms ( 2 ) ; // wait for the PWM to take effect
2020-12-08 23:07:36 +01:00
tec_power_orange_off ( ) ; // connect GND from orange TEC line
tec_power_yellow_on ( ) ; // connect VCC to yellow TEC line
2020-12-07 19:14:00 +01:00
// set TEC configuration to heat the top half at max
// the following heating configurations exist (when orange is connected to minus and yellow to plus)
// 26 14 35 top bott A@3V
// -- -- -- heat off 0.6
// ++ -- -- heat heat 0.6
// -- ++ -- heat heat 0.8
// -- -- ++ off heat 0.8
// -- ++ ++ off heat 1.1
// ++ -- ++ off heat 1.1
// ++ ++ -- heat heat 0.8
// ++ ++ ++ off heat 1.5
gpio_clear ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ; // sink current, powering the opto-coupler, switching the transistor on
gpio_clear ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ; // sink current, powering the opto-coupler, switching the transistor on
gpio_clear ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ; // sink current, powering the opto-coupler, switching the transistor on
}
/** set TEC to cool */
static void tec_cool ( void )
{
tec_power ( 0 ) ; // ensure power is off while switching
sleep_ms ( 2 ) ; // wait for the PWM to take effect
2020-12-08 23:07:36 +01:00
tec_power_yellow_off ( ) ; // connect GND from yellow TEC line
tec_power_orange_on ( ) ; // connect VCC to orange TEC line
2020-12-07 19:14:00 +01:00
// set TEC configuration to cool the top half at max
// the following heating configurations exist (when orange is connected to minus and yellow to plus)
// 26 14 35 top bot A@3V
// -- -- -- off off 0.0
// ++ -- -- off heat 0.6
// -- ++ -- off cool 0.8
// -- -- ++ off off 0.0
// -- ++ ++ cool cool 1.5
// ++ -- ++ off heat 1.5
// ++ ++ -- cool off 1.4
// ++ ++ ++ cool heat 2.1
2020-12-10 18:17:49 +01:00
// cool only top at max power
2020-12-07 19:14:00 +01:00
gpio_clear ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ; // sink current, powering the opto-coupler, switching the transistor on
gpio_clear ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ; // sink current, powering the opto-coupler, switching the transistor on
gpio_set ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ; // don't sink current, not powering the opto-coupler, switching the transistor off
2020-12-10 18:17:49 +01:00
// cool top and bottom, sharing the load
//gpio_set(GPIO_PORT(MBLK019_CH26_PIN), GPIO_PIN(MBLK019_CH26_PIN));
//gpio_clear(GPIO_PORT(MBLK019_CH14_PIN), GPIO_PIN(MBLK019_CH14_PIN));
//gpio_clear(GPIO_PORT(MBLK019_CH35_PIN), GPIO_PIN(MBLK019_CH35_PIN));
2020-12-07 19:14:00 +01:00
}
/** read bed top half temperature
* @ return temperature in ° C
*/
static float bed_tophalf_temperature ( void )
{
const uint16_t measurement = sensor_max1247_read ( 0 ) ; // read measured value from corresponding thermistor
// convert to °C
// calibrated using a DS18B20 (accuracy = +- 0.5°C), with 12-bit precision
2020-12-08 23:13:37 +01:00
// 558 = 19.500 °C, 616 = 21.8 °C, 1624 = 50.625 °C, 2850 = 82.250 °C, 2926 = 83.625 °C
2020-12-07 19:14:00 +01:00
2020-12-08 23:13:37 +01:00
return measurement * 0.0270798 + 4.38946 ;
}
/** read bed bottom half temperature
* @ return temperature in ° C
*/
static float bed_bothalf_temperature ( void )
{
const uint16_t measurement = sensor_max1247_read ( 1 ) ; // read measured value from corresponding thermistor
// convert to °C
// calibrated using a DS18B20 (accuracy = +- 0.5°C), with 12-bit precision
// 549 = 19.312 °C, 12 = 63.3 °C
return measurement * 0.0270798 + 4.38946 ;
}
/** read bed tube temperature
* @ return temperature in ° C
*/
static float bed_tube_temperature ( void )
{
const uint16_t measurement = sensor_max1247_read ( 3 ) ; // read measured value from corresponding thermistor
// convert to °C
2020-12-10 18:19:36 +01:00
// calibrated using a TP101 with 0.1 °C precision (unknown accuracy but is on par with DS18B20)
// 557 = 19.5 °C, 3221 = 100.9 °C
2020-12-08 23:13:37 +01:00
2020-12-10 18:19:36 +01:00
return measurement * 0.0305556 + 2.48056 ;
2020-12-07 19:14:00 +01:00
}
/** read heat sink temperature
* @ return temperature in ° C
*/
static float bed_heatsink_temperature ( void )
{
const uint16_t measurement = sensor_max1247_read ( 2 ) ; // read measured value from corresponding thermistor
// convert to °C
// calibrated using a DS18B20 (accuracy = +- 0.5°C), with 12-bit precision
2020-12-08 23:13:37 +01:00
// 557 = 19.312 °C, 1624 = 50.625 °C
2020-12-07 19:14:00 +01:00
2020-12-08 23:13:37 +01:00
return measurement * 0.0273778 + 4.22317 ;
2020-12-07 19:14:00 +01:00
}
/** run PID control for bed temperature (using the top half) */
static void bed_pid ( void )
{
// I tried Ziegler– Nichols method, but it overshoots and oscillates far too much (even with the no overshoot rule)
//#define LID_KU 64
//#define LID_TU 14.96
//#define LID_KP (0.2 * LID_KU)
//#define LID_KI (0.4 * LID_KU / LID_TU)
//#define LID_KD (0.066 * LID_KU * LID_TU)
# define BED_KP 256
# define BED_KI 0
# define BED_KD 0
if ( 0 = = bed_target ) { // no target has been defined
tec_power ( 0 ) ;
// reinitialise errors
return ;
}
static int32_t error_sum = 0 ; // value used for the integral part
static int32_t error_prev = 0 ; // value used for the derivate part
const uint16_t temperature = sensor_max1247_read ( 0 ) ; // read top half sensor
const int32_t error_cur = adds32_safe ( bed_target , - temperature ) ;
error_sum = adds32_safe ( error_sum , error_cur ) ;
const int32_t error_diff = adds32_safe ( error_cur , - error_prev ) ;
error_prev = error_cur ;
const int32_t p = BED_KP * error_cur ; // calculate proportional part
const int32_t i = BED_KI * error_sum ; // calculate integral part
const int32_t d = BED_KD * error_diff ; // calculate derivate part
int32_t power = adds32_safe ( adds32_safe ( p , i ) , d ) ; // calculate needed power
if ( STATE_COOL = = state ) {
power = - power ;
}
// enforce limits
if ( power < 0 ) {
power = 0 ;
} else if ( power > UINT16_MAX ) {
power = UINT16_MAX ;
}
printf ( " set: %u is: %u %.02f p: %d i: %d d: %d power: %d \n " , bed_target , temperature , bed_tophalf_temperature ( ) , p , i , d , power ) ;
tec_power ( ( uint16_t ) power ) ; // set power
}
2020-12-10 18:16:16 +01:00
/** set new state
* @ param [ in ] new new state to set
*/
static void set_state ( enum state_e new )
{
if ( STATE_SAFE = = state & & STATE_SAFE ! = new ) {
puts ( " restart controller to exit safe state \n " ) ;
return ;
}
switch ( new ) {
case STATE_IDLE : // you can go to idle from any state (except safe)
tec_power ( 0 ) ; // ensure bed is not powered anymore
tec_power_yellow_off ( ) ; // disconnect all wires
tec_power_orange_off ( ) ; // disconnect all wires
lid_power ( 0 ) ; // ensure lid is not powered
break ;
case STATE_SAFE : // go to safe state
safe_state ( ) ; // put all peripherals in safe mode
break ;
case STATE_HEAT :
heatsink_fan_on ( ) ; // switch fan on
tec_power ( 0 ) ; // switch power off before changing mode
tec_heat ( ) ; // switch to heating mode
break ;
case STATE_COOL :
heatsink_fan_on ( ) ; // switch fan on
tec_power ( 0 ) ; // switch power off before changing mode
tec_cool ( ) ; // switch to heating mode
break ;
case STATE_FAN :
tec_power ( 0 ) ; // ensure bed is not powered anymore
tec_power_yellow_off ( ) ; // disconnect all wires
tec_power_orange_off ( ) ; // disconnect all wires
lid_power ( 0 ) ; // ensure lid is not powered
heatsink_fan_on ( ) ; // switch fan on
break ;
case STATE_PREPARE :
if ( STATE_IDLE = = state ) { // only allowed from idle state
bed_target = 95.0 ; // set target temperature
lid_target = bed_target ; // set temperature
heatsink_fan_on ( ) ; // switch fan on
tec_power ( 0 ) ; // switch power off
tec_heat ( ) ; // switch to heating mode
// TODO set unlimited time
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_INITIALISATION :
if ( STATE_PREPARE = = state ) { // only allowed from state
// temperature and heating is already set
// TODO set time to 5 min
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_TO_DENATURATION :
if ( STATE_EXTENSION = = state ) { // only allowed from state
lid_target = 95.0 ; // set target temperature
bed_target = 95.0 ; // set target temperature
// heating is already set
// TODO set unlimited time
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_DENATURATION :
if ( STATE_TO_DENATURATION = = state ) { // only allowed from state
// temperature and heating is already set
// TODO set time to 5 min
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_TO_ANNEALING :
if ( STATE_DENATURATION = = state ) { // only allowed from state
bed_target = 45.0 ; // set target temperature (5 °C below primer)
lid_target = bed_target ; // same temperature
tec_power ( 0 ) ; // switch power off
tec_cool ( ) ; // switch to cooling mode
// TODO set unlimited time
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_ANNEALING :
if ( STATE_TO_ANNEALING = = state ) { // only allowed from state
tec_power ( 0 ) ; // switch power off before switching mode
tec_heat ( ) ; // switch to heating mode
// temperature is already set
// TODO set time to 30-40 s
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_TO_EXTENSTION :
if ( STATE_ANNEALING = = state ) { // only allowed from state
bed_target = 72.0 ; // set target temperature
lid_target = bed_target ; // set target temperature
// heating is already set
// TODO set unlimited time
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_EXTENSION :
if ( STATE_TO_EXTENSTION = = state ) { // only allowed from state
// temperature and heating is already set
// TODO set time to ~1 min/kb of expected product; 5-10 min on last cycle.
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_TO_HOLD :
if ( STATE_EXTENSION = = state ) { // only allowed from state
bed_target = 10.0 ; // set target temperature (5 °C below primer)
lid_target = NAN ; // we don't need to heat anymore
lid_power ( 0 ) ; // switch power off
tec_power ( 0 ) ; // switch power off
tec_cool ( ) ; // switch to cooling mode
// TODO set unlimited time
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
case STATE_HOLD :
if ( STATE_TO_HOLD = = state ) { // only allowed from state
// nothing to do, we just reached the final hold up
// TODO set time to 30-40 s
} else { // transition not allowed
set_state ( STATE_SAFE ) ;
}
break ;
default : // unknown new state
error = " unknown state to set " ;
set_state ( STATE_SAFE ) ;
break ;
}
// display state
if ( new ! = state & & STATE_SAFE ! = state ) {
state = new ; // save new state
printf ( " new state: %s \n " , state_names [ state ] ) ; // show new state
oled_text_line ( state_names [ state ] , 1 ) ; // set new state
oled_text_update ( ) ; // show on display
}
}
2018-04-06 17:37:17 +02:00
/** display available commands
* @ param [ in ] argument no argument required
*/
static void command_help ( void * argument ) ;
/** show software and hardware version
* @ param [ in ] argument no argument required
*/
static void command_version ( void * argument ) ;
/** show uptime
* @ param [ in ] argument no argument required
*/
static void command_uptime ( void * argument ) ;
/** reset board
* @ param [ in ] argument no argument required
*/
static void command_reset ( void * argument ) ;
/** switch to DFU bootloader
* @ param [ in ] argument no argument required
*/
static void command_bootloader ( void * argument ) ;
2020-12-07 19:14:00 +01:00
/** switch power to TECs
* @ param [ in ] argument pointer to unsigned integer : 0 to power all of , 1 to connect yellow to 12 V , 2 to connect orange to 12 V
*/
static void command_bed_power ( void * argument )
{
if ( argument ) { // segment has been provided
const int32_t target = * ( int32_t * ) argument ; // get while segment to turn on/off
if ( target > UINT16_MAX | | target < - INT16_MAX ) {
printf ( " can't set temperature over %u \n " , UINT16_MAX ) ;
return ;
}
if ( 0 = = target ) { // switch off
bed_target = 0 ; // remember we have no target
2020-12-10 18:16:16 +01:00
set_state ( STATE_IDLE ) ; // set new state
} else if ( 1 = = target ) { // just use fan
bed_target = 0 ; // remember we have no target
set_state ( STATE_FAN ) ; // set new state
2020-12-07 19:14:00 +01:00
} else if ( target > 0 ) { // heat
bed_target = target ; // remember target
2020-12-10 18:16:16 +01:00
set_state ( STATE_HEAT ) ; // set new state
2020-12-07 19:14:00 +01:00
} else { // cool
bed_target = - target ; // remember target
2020-12-10 18:16:16 +01:00
set_state ( STATE_COOL ) ; // set new state
2020-12-07 19:14:00 +01:00
}
}
// print segment status
2020-12-08 23:07:36 +01:00
const bool yellow = gpio_get ( GPIO_PORT ( TEC_POWER_YELLOW ) , GPIO_PIN ( TEC_POWER_YELLOW ) ) ;
const bool orange = gpio_get ( GPIO_PORT ( TEC_POWER_ORANGE ) , GPIO_PIN ( TEC_POWER_ORANGE ) ) ;
if ( ! yellow & & ! orange ) {
puts ( " TEC power off \n " ) ;
} else if ( orange & & ! yellow ) {
2020-12-07 19:14:00 +01:00
puts ( " TEC set to cooling \n " ) ;
2020-12-08 23:07:36 +01:00
} else if ( yellow & & ! orange ) {
2020-12-07 19:14:00 +01:00
puts ( " TEC set to heating \n " ) ;
2020-12-08 23:07:36 +01:00
} else {
puts ( " unknown TEC power setting \n " ) ;
2020-12-07 19:14:00 +01:00
}
}
2020-12-10 18:19:51 +01:00
/** switch transistors to power to TEC segments
* @ param [ in ] argument pointer to unsigned integer : 0 to power all off
*/
static void command_bed_segment ( void * argument )
{
if ( argument ) { // segment has been provided
const int32_t segment = * ( int32_t * ) argument ; // get while segment to turn on/off
switch ( segment ) {
case 0 : // switch all off
gpio_set ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ;
gpio_set ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ;
gpio_set ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ;
break ;
case 1 :
gpio_clear ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ;
break ;
case - 1 :
gpio_set ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ;
break ;
case 2 :
gpio_clear ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ;
break ;
case - 2 :
gpio_set ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ;
break ;
case 3 :
gpio_clear ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ;
break ;
case - 3 :
gpio_set ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ;
break ;
default :
printf ( " unknown segment: %d \n " , segment ) ;
break ;
}
sleep_ms ( 1 ) ; // wait to take effect
}
// print segment status
printf ( " CH26: %s CH14: %s CH35: %s \n " , \
gpio_get ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ? " off " : " on " , \
gpio_get ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ? " off " : " on " , \
gpio_get ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ? " off " : " on " ) ;
}
2020-12-07 19:14:00 +01:00
/** switch power to lid heater
* @ param [ in ] argument pointer to unsigned integer : 0 to power off , 1 to power on
*/
static void command_lid_power ( void * argument )
{
if ( NULL = = argument ) {
puts ( " provide lid power in % \n " ) ;
} else { // segment has been provided
const uint8_t power = * ( uint32_t * ) argument ; // get while segment to turn on/off
lid_power ( power ) ;
printf ( " lip power set to %u %% \n " , power ) ;
}
}
/** set lid target temperature
* @ param [ in ] argument pointer to unsigned integer : 0 to power off , else temperature in ° C
*/
static void command_lid_temperature ( void * argument )
{
if ( argument ) {
const uint32_t target = * ( uint32_t * ) argument ;
if ( 0 = = target ) {
lid_target = NAN ;
lid_power ( 0 ) ;
} else {
lid_target = target * 1.0 ;
}
}
if ( isnan ( lid_target ) ) {
puts ( " no lid target temperature \n " ) ;
} else {
printf ( " lid target temperature: %u °C \n " , ( uint8_t ) lid_target ) ;
}
}
static void command_safe ( void * argument )
{
( void ) argument ; // we won't use the argument
2020-12-10 18:16:16 +01:00
set_state ( STATE_SAFE ) ;
2020-12-07 19:14:00 +01:00
printf ( " in safe state \n " ) ;
}
static void command_state ( void * argument )
{
( void ) argument ; // we won't use the argument
2020-12-10 18:16:16 +01:00
puts ( state_names [ state ] ) ;
2020-12-07 19:14:00 +01:00
putc ( ' \n ' ) ;
if ( error ) {
printf ( " last error: %s \n " , error ) ;
puts ( " to clear error, restart controller \n " ) ;
} else {
printf ( " no error \n " ) ;
}
}
2018-04-06 17:37:17 +02:00
/** list of all supported commands */
static const struct menu_command_t menu_commands [ ] = {
{
. shortcut = ' h ' ,
. name = " help " ,
. command_description = " display help " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_help ,
} ,
{
. shortcut = ' v ' ,
. name = " version " ,
. command_description = " show software and hardware version " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_version ,
} ,
{
. shortcut = ' u ' ,
. name = " uptime " ,
. command_description = " show uptime " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_uptime ,
} ,
{
. shortcut = ' r ' ,
. name = " reset " ,
. command_description = " reset board " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_reset ,
} ,
{
2020-12-07 19:14:00 +01:00
. shortcut = ' B ' ,
2018-04-06 17:37:17 +02:00
. name = " bootloader " ,
. command_description = " reboot into DFU bootloader " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_bootloader ,
} ,
2020-12-07 19:14:00 +01:00
{
. shortcut = ' b ' ,
. name = " bed " ,
. command_description = " provide power to bed " ,
. argument = MENU_ARGUMENT_SIGNED ,
. argument_description = " [+-temp] " ,
. command_handler = & command_bed_power ,
} ,
2020-12-10 18:19:51 +01:00
{
. shortcut = ' s ' ,
. name = " segment " ,
. command_description = " TEC segment configuration " ,
. argument = MENU_ARGUMENT_SIGNED ,
. argument_description = " [+-0,1,2,3] " ,
. command_handler = & command_bed_segment ,
} ,
2020-12-07 19:14:00 +01:00
{
. shortcut = ' L ' ,
. name = " lid_power " ,
. command_description = " set lid power " ,
. argument = MENU_ARGUMENT_UNSIGNED ,
. argument_description = " % " ,
. command_handler = & command_lid_power ,
} ,
{
. shortcut = ' l ' ,
. name = " lid " ,
. command_description = " set lid target temperature " ,
. argument = MENU_ARGUMENT_UNSIGNED ,
. argument_description = " °C " ,
. command_handler = & command_lid_temperature ,
} ,
{
. shortcut = ' s ' ,
. name = " safe " ,
. command_description = " enter safe state " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_safe ,
} ,
{
. shortcut = ' e ' ,
. name = " error " ,
. command_description = " show current state and error " ,
. argument = MENU_ARGUMENT_NONE ,
. argument_description = NULL ,
. command_handler = & command_state ,
} ,
2018-04-06 17:37:17 +02:00
} ;
static void command_help ( void * argument )
{
( void ) argument ; // we won't use the argument
printf ( " available commands: \n " ) ;
menu_print_commands ( menu_commands , LENGTH ( menu_commands ) ) ; // print global commands
}
static void command_version ( void * argument )
{
( void ) argument ; // we won't use the argument
printf ( " firmware date: %04u-%02u-%02u \n " , BUILD_YEAR , BUILD_MONTH , BUILD_DAY ) ; // show firmware build date
2020-06-24 11:54:33 +02:00
printf ( " device serial: %08x%08x%04x%04x \n " , DESIG_UNIQUE_ID2 , DESIG_UNIQUE_ID1 , DESIG_UNIQUE_ID0 & 0xffff , DESIG_UNIQUE_ID0 > > 16 ) ; // not that the half-works are reversed in the first word
2018-04-06 17:37:17 +02:00
}
2020-06-09 00:59:19 +02:00
static void command_uptime ( void * argument )
{
( void ) argument ; // we won't use the argument
const uint32_t uptime = ( rtc_get_counter_val ( ) - time_start ) / RTC_TICKS_SECOND ; // get time from internal RTC
printf ( " uptime: %u.%02u:%02u:%02u \n " , uptime / ( 24 * 60 * 60 ) , ( uptime / ( 60 * 60 ) ) % 24 , ( uptime / 60 ) % 60 , uptime % 60 ) ;
}
2018-04-06 17:37:17 +02:00
static void command_reset ( void * argument )
{
( void ) argument ; // we won't use the argument
scb_reset_system ( ) ; // reset device
while ( true ) ; // wait for the reset to happen
}
static void command_bootloader ( void * argument )
{
( void ) argument ; // we won't use the argument
2020-01-02 13:01:40 +01:00
// set DFU magic to specific RAM location
__dfu_magic [ 0 ] = ' D ' ;
__dfu_magic [ 1 ] = ' F ' ;
__dfu_magic [ 2 ] = ' U ' ;
__dfu_magic [ 3 ] = ' ! ' ;
scb_reset_system ( ) ; // reset system (core and peripherals)
2018-04-06 17:37:17 +02:00
while ( true ) ; // wait for the reset to happen
}
2016-01-17 14:54:54 +01:00
2016-08-14 21:02:38 +02:00
/** process user command
* @ param [ in ] str user command string ( \ 0 ended )
*/
static void process_command ( char * str )
2016-01-17 14:54:54 +01:00
{
2018-04-06 17:37:17 +02:00
// ensure actions are available
2019-06-18 17:44:50 +02:00
if ( NULL = = menu_commands | | 0 = = LENGTH ( menu_commands ) ) {
2018-04-06 17:37:17 +02:00
return ;
2016-08-14 21:02:38 +02:00
}
2018-04-06 17:37:17 +02:00
// don't handle empty lines
2019-06-18 17:44:50 +02:00
if ( ! str | | 0 = = strlen ( str ) ) {
2018-04-06 17:37:17 +02:00
return ;
}
bool command_handled = false ;
if ( ! command_handled ) {
command_handled = menu_handle_command ( str , menu_commands , LENGTH ( menu_commands ) ) ; // try if this is not a global command
}
if ( ! command_handled ) {
printf ( " command not recognized. enter help to list commands \n " ) ;
2016-08-14 21:02:38 +02:00
}
2016-01-17 14:54:54 +01:00
}
2016-08-14 21:02:38 +02:00
/** program entry point
* this is the firmware function started by the micro - controller
*/
2016-10-23 17:42:27 +02:00
void main ( void ) ;
void main ( void )
2018-04-06 17:37:17 +02:00
{
2016-01-29 00:24:49 +01:00
rcc_clock_setup_in_hse_8mhz_out_72mhz ( ) ; // use 8 MHz high speed external clock to generate 72 MHz internal clock
2016-08-14 21:02:38 +02:00
2016-10-23 17:42:27 +02:00
# if DEBUG
2017-01-30 09:44:51 +01:00
// enable functionalities for easier debug
2016-10-23 17:42:27 +02:00
DBGMCU_CR | = DBGMCU_CR_IWDG_STOP ; // stop independent watchdog counter when code is halted
DBGMCU_CR | = DBGMCU_CR_WWDG_STOP ; // stop window watchdog counter when code is halted
DBGMCU_CR | = DBGMCU_CR_STANDBY ; // allow debug also in standby mode (keep digital part and clock powered)
DBGMCU_CR | = DBGMCU_CR_STOP ; // allow debug also in stop mode (keep clock powered)
DBGMCU_CR | = DBGMCU_CR_SLEEP ; // allow debug also in sleep mode (keep clock powered)
2017-02-06 17:40:28 +01:00
# else
2017-01-30 09:44:51 +01:00
// setup watchdog to reset in case we get stuck (i.e. when an error occurred)
iwdg_set_period_ms ( WATCHDOG_PERIOD ) ; // set independent watchdog period
iwdg_start ( ) ; // start independent watchdog
2016-10-23 17:42:27 +02:00
# endif
2017-04-03 13:32:45 +02:00
board_setup ( ) ; // setup board
2017-04-19 16:26:40 +02:00
usb_cdcacm_setup ( ) ; // setup USB CDC ACM (for printing)
2020-12-07 19:14:00 +01:00
puts ( " \n welcome to the CuVoodoo MBLK001 thermo-cycler driver \n " ) ; // print welcome message
2016-01-29 00:24:49 +01:00
2019-12-21 19:26:27 +01:00
# if DEBUG
// show reset cause
if ( RCC_CSR & ( RCC_CSR_LPWRRSTF | RCC_CSR_WWDGRSTF | RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF | RCC_CSR_PORRSTF | RCC_CSR_PINRSTF ) ) {
puts ( " reset cause(s): " ) ;
if ( RCC_CSR & RCC_CSR_LPWRRSTF ) {
puts ( " low-power " ) ;
}
if ( RCC_CSR & RCC_CSR_WWDGRSTF ) {
puts ( " window-watchdog " ) ;
}
if ( RCC_CSR & RCC_CSR_IWDGRSTF ) {
puts ( " independent-watchdog " ) ;
}
if ( RCC_CSR & RCC_CSR_SFTRSTF ) {
puts ( " software " ) ;
}
if ( RCC_CSR & RCC_CSR_PORRSTF ) {
puts ( " POR/PDR " ) ;
}
if ( RCC_CSR & RCC_CSR_PINRSTF ) {
puts ( " pin " ) ;
}
putc ( ' \n ' ) ;
RCC_CSR | = RCC_CSR_RMVF ; // clear reset flags
}
# endif
2017-01-30 09:44:51 +01:00
# if !(DEBUG)
// show watchdog information
2019-12-21 19:26:58 +01:00
printf ( " setup watchdog: %.2fs " , WATCHDOG_PERIOD / 1000.0 ) ;
if ( FLASH_OBR & FLASH_OBR_OPTERR ) {
2020-02-19 21:16:07 +01:00
puts ( " (option bytes not set in flash: software wachtdog used, not automatically started at reset) \n " ) ;
2019-12-21 19:26:58 +01:00
} else if ( FLASH_OBR & FLASH_OBR_WDG_SW ) {
2020-02-19 21:16:07 +01:00
puts ( " (software watchdog used, not automatically started at reset) \n " ) ;
2017-01-30 09:44:51 +01:00
} else {
2020-02-19 21:16:07 +01:00
puts ( " (hardware watchdog used, automatically started at reset) \n " ) ;
2017-01-30 09:44:51 +01:00
}
# endif
2020-12-07 19:14:00 +01:00
// re-use JTAG pins as GPIO (all pins are used)
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for alternate function domain
gpio_primary_remap ( AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON , 0 ) ; // disable JTAG but keep SWD
2016-08-14 21:02:38 +02:00
// setup RTC
2020-02-19 21:16:07 +01:00
puts ( " setup internal RTC: " ) ;
2018-10-29 14:14:32 +01:00
// note: the blue pill LSE oscillator is affected when toggling the onboard LED, thus prefer the HSE
2020-02-02 21:19:03 +01:00
rtc_auto_awake ( RCC_HSE , 8000000 / 128 / RTC_TICKS_SECOND - 1 ) ; // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running)
2016-08-14 21:02:38 +02:00
rtc_interrupt_enable ( RTC_SEC ) ; // enable RTC interrupt on "seconds"
nvic_enable_irq ( NVIC_RTC_IRQ ) ; // allow the RTC to interrupt
2019-06-18 17:43:43 +02:00
time_start = rtc_get_counter_val ( ) ; // get start time from internal RTC
2020-02-19 21:16:07 +01:00
puts ( " OK \n " ) ;
2016-08-14 21:02:38 +02:00
2020-12-07 19:14:00 +01:00
puts ( " setup front panel: " ) ;
rcc_periph_clock_enable ( GPIO_RCC ( CONTROL_PLAY_GREEN_LED_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_clear ( GPIO_PORT ( CONTROL_PLAY_GREEN_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_GREEN_LED_PIN ) ) ; // switch LED off
gpio_set_mode ( GPIO_PORT ( CONTROL_PLAY_GREEN_LED_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( CONTROL_PLAY_GREEN_LED_PIN ) ) ; // set pin as output push-pull to be able to power LED
rcc_periph_clock_enable ( GPIO_RCC ( CONTROL_PLAY_ORANGE_LED_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_clear ( GPIO_PORT ( CONTROL_PLAY_ORANGE_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_ORANGE_LED_PIN ) ) ; // switch LED off
gpio_set_mode ( GPIO_PORT ( CONTROL_PLAY_ORANGE_LED_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( CONTROL_PLAY_ORANGE_LED_PIN ) ) ; // set pin as output push-pull to be able to power LED
rcc_periph_clock_enable ( GPIO_RCC ( CONTROL_POWER_RED_LED_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_clear ( GPIO_PORT ( CONTROL_POWER_RED_LED_PIN ) , GPIO_PIN ( CONTROL_POWER_RED_LED_PIN ) ) ; // switch LED off
gpio_set_mode ( GPIO_PORT ( CONTROL_POWER_RED_LED_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( CONTROL_POWER_RED_LED_PIN ) ) ; // set pin as output push-pull to be able to power LED
2020-12-08 23:15:53 +01:00
// play/pause button
rcc_periph_clock_enable ( GPIO_RCC ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // enable clock for button
gpio_set_mode ( GPIO_PORT ( CONTROL_PLAY_BUTTON_LED_PIN ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // set button pin to input
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable alternate function clock for external interrupt
exti_select_source ( GPIO_EXTI ( CONTROL_PLAY_BUTTON_LED_PIN ) , GPIO_PORT ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // mask external interrupt of this pin only for this port
gpio_set ( GPIO_PORT ( CONTROL_PLAY_BUTTON_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // pull up to be able to detect button push (go low)
exti_set_trigger ( GPIO_EXTI ( CONTROL_PLAY_BUTTON_LED_PIN ) , EXTI_TRIGGER_FALLING ) ; // trigger when button is pressed
exti_enable_request ( GPIO_EXTI ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // enable external interrupt
nvic_enable_irq ( GPIO_NVIC_EXTI_IRQ ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // enable interrupt
2020-12-07 19:14:00 +01:00
puts ( " OK \n " ) ;
puts ( " setup heating bed pins: " ) ;
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_393A ) ) ; // enable clock for GPIO port peripheral
gpio_set_mode ( GPIO_PORT ( BED_PIN_393A ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_FLOAT , GPIO_PIN ( BED_PIN_393A ) ) ; // set pin to input to read state
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_3393 ) ) ; // enable clock for GPIO port peripheral
gpio_set_mode ( GPIO_PORT ( BED_PIN_3393 ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_FLOAT , GPIO_PIN ( BED_PIN_3393 ) ) ; // set pin to input to read state
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_LK1 ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( BED_PIN_LK1 ) , GPIO_PIN ( BED_PIN_LK1 ) ) ; // pull up
gpio_set_mode ( GPIO_PORT ( BED_PIN_LK1 ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( BED_PIN_LK1 ) ) ; // set pin to input to read state
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_LK2 ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( BED_PIN_LK2 ) , GPIO_PIN ( BED_PIN_LK2 ) ) ; // pull up
gpio_set_mode ( GPIO_PORT ( BED_PIN_LK2 ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( BED_PIN_LK2 ) ) ; // set pin to input to read state
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_LK3 ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( BED_PIN_LK3 ) , GPIO_PIN ( BED_PIN_LK3 ) ) ; // pull up
gpio_set_mode ( GPIO_PORT ( BED_PIN_LK3 ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( BED_PIN_LK3 ) ) ; // set pin to input to read state
rcc_periph_clock_enable ( GPIO_RCC ( BED_PIN_LK4 ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( BED_PIN_LK4 ) , GPIO_PIN ( BED_PIN_LK4 ) ) ; // pull up
gpio_set_mode ( GPIO_PORT ( BED_PIN_LK4 ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( BED_PIN_LK4 ) ) ; // set pin to input to read state
if ( gpio_get ( GPIO_PORT ( BED_PIN_LK1 ) , GPIO_PIN ( BED_PIN_LK1 ) ) & & gpio_get ( GPIO_PORT ( BED_PIN_LK2 ) , GPIO_PIN ( BED_PIN_LK2 ) ) & & gpio_get ( GPIO_PORT ( BED_PIN_LK3 ) , GPIO_PIN ( BED_PIN_LK3 ) ) & & gpio_get ( GPIO_PORT ( BED_PIN_LK4 ) , GPIO_PIN ( BED_PIN_LK4 ) ) ) { // nothing is connected
error = " heating bed board not connected " ; // set error
puts ( " KO \n " ) ;
} else if ( gpio_get ( GPIO_PORT ( BED_PIN_LK1 ) , GPIO_PIN ( BED_PIN_LK1 ) ) & & gpio_get ( GPIO_PORT ( BED_PIN_LK2 ) , GPIO_PIN ( BED_PIN_LK2 ) ) & & ! gpio_get ( GPIO_PORT ( BED_PIN_LK3 ) , GPIO_PIN ( BED_PIN_LK3 ) ) & & ! gpio_get ( GPIO_PORT ( BED_PIN_LK4 ) , GPIO_PIN ( BED_PIN_LK4 ) ) ) { // the LK jumper setting is correct
puts ( " OK \n " ) ;
} else { // the jumper setting is unknown
error = " not heating bed board detected " ; // set error
puts ( " KO \n " ) ;
}
puts ( " setup MAX1247 to read bed thermistors: " ) ;
sensor_max1247_setup ( ) ; // setup communication with MAX1247 ADC
puts ( " OK \n " ) ;
puts ( " setup ADC to read lid thermistor: " ) ;
rcc_periph_clock_enable ( RCC_ADC1 ) ; // enable clock for ADC domain
adc_power_off ( ADC1 ) ; // switch off ADC while configuring it
adc_set_right_aligned ( ADC1 ) ; // ensure it is right aligned to get the actual value in the 16-bit register
adc_disable_scan_mode ( ADC1 ) ; // ensure scan mode is disabled
adc_enable_discontinuous_mode_regular ( ADC1 , 1 ) ; // use discontinuous mode (to go through all channels of the group, one after another)
adc_set_single_conversion_mode ( ADC1 ) ; // ensure continuous mode is not used (that's not the same as discontinuous)
adc_set_sample_time_on_all_channels ( ADC1 , ADC_SMPR_SMP_239DOT5CYC ) ; // use 239.5 cycles to sample (17.1 us are required for the internal voltage reference, (239.5 + 12.5) cycles @ 14 MHz max = 18 us)
adc_set_regular_sequence ( ADC1 , LENGTH ( channels ) , ( uint8_t * ) channels ) ; // set channel to convert
adc_enable_external_trigger_regular ( ADC1 , ADC_CR2_EXTSEL_SWSTART ) ; // use software trigger to start the conversion (of the regular group)
adc_enable_temperature_sensor ( ) ; // enable internal voltage reference
adc_power_on ( ADC1 ) ; // switch on ADC
sleep_us ( 1 ) ; // wait t_stab for the ADC to stabilize
adc_reset_calibration ( ADC1 ) ; // remove previous non-calibration
adc_calibrate ( ADC1 ) ; // calibrate ADC for less accuracy errors
rcc_periph_clock_enable ( RCC_ADC12_IN ( LID_TEC_CHANNEL ) ) ; // enable clock for GPIO domain for lid thermistor channel
gpio_set_mode ( ADC12_IN_PORT ( LID_TEC_CHANNEL ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_ANALOG , ADC12_IN_PIN ( LID_TEC_CHANNEL ) ) ; // set lid thermistor channel as analogue input for the ADC
puts ( " OK \n " ) ;
puts ( " setup lid heater: " ) ;
// verify if it is connected (the pin should be pulled up to 5V)
rcc_periph_clock_enable ( GPIO_RCC ( LID_HEATER_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_clear ( GPIO_PORT ( LID_HEATER_PIN ) , GPIO_PIN ( LID_HEATER_PIN ) ) ; // pull down
gpio_set_mode ( GPIO_PORT ( LID_HEATER_PIN ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( LID_HEATER_PIN ) ) ; // set pin as input
sleep_us ( 100 ) ; // let signal settle
if ( ! gpio_get ( GPIO_PORT ( LID_HEATER_PIN ) , GPIO_PIN ( LID_HEATER_PIN ) ) ) { // signal is not pulled up
error = " power board not connected " ;
puts ( " KO \n " ) ;
} else { // power board is connected
// set up PWM output
rcc_periph_clock_enable ( RCC_TIM_CH ( LID_HEATER_TIMER , LID_HEATER_CHANNEL ) ) ; // enable clock for GPIO peripheral
gpio_set ( TIM_CH_PORT ( LID_HEATER_TIMER , LID_HEATER_CHANNEL ) , TIM_CH_PIN ( LID_HEATER_TIMER , LID_HEATER_CHANNEL ) ) ; // don't sink current (e.g. not powering the opto-coupler/triac))
gpio_set_mode ( TIM_CH_PORT ( LID_HEATER_TIMER , LID_HEATER_CHANNEL ) , GPIO_MODE_OUTPUT_10_MHZ , GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN , TIM_CH_PIN ( LID_HEATER_TIMER , LID_HEATER_CHANNEL ) ) ; // set pin as output
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for alternate function (PWM)
rcc_periph_clock_enable ( RCC_TIM ( LID_HEATER_TIMER ) ) ; // enable clock for timer peripheral
rcc_periph_reset_pulse ( RST_TIM ( LID_HEATER_TIMER ) ) ; // reset timer state
timer_set_mode ( TIM ( LID_HEATER_TIMER ) , TIM_CR1_CKD_CK_INT , TIM_CR1_CMS_EDGE , TIM_CR1_DIR_UP ) ; // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
// since we are controlling a triac, but we don't know the zero-crossing point, we can only switch on/off on half AC waves, e.g. 100 Hz
timer_set_prescaler ( TIM ( LID_HEATER_TIMER ) , 1099 - 1 ) ; // set period to 1 Hz ((72E6/(1099)) / 2**16 = 0.9997)
timer_set_period ( TIM ( LID_HEATER_TIMER ) , UINT16_MAX ) ; // use the whole range as period, even if we can only control up to 100 Hz
timer_set_oc_value ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC , 0 ) ; // duty cycle to 0%, to switch off heater
timer_set_oc_mode ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC , TIM_OCM_PWM2 ) ; // set timer to generate PWM (heater switched of as long as CNT < CCR)
timer_enable_oc_output ( TIM ( LID_HEATER_TIMER ) , LID_HEATER_OC ) ; // enable output to generate the PWM signal
timer_enable_break_main_output ( TIM ( LID_HEATER_TIMER ) ) ; // required to enable timer, even when no dead time is used
timer_set_counter ( TIM ( LID_HEATER_TIMER ) , 0 ) ; // reset counter
timer_enable_counter ( TIM ( LID_HEATER_TIMER ) ) ; // enable timer
puts ( " OK \n " ) ;
}
puts ( " setup TEC controller: " ) ;
rcc_periph_clock_enable ( GPIO_RCC ( MBLK019_CH26_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_PIN ( MBLK019_CH26_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
gpio_set_mode ( GPIO_PORT ( MBLK019_CH26_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_OPENDRAIN , GPIO_PIN ( MBLK019_CH26_PIN ) ) ; // set pin as output open-drain
rcc_periph_clock_enable ( GPIO_RCC ( MBLK019_CH14_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_PIN ( MBLK019_CH14_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
gpio_set_mode ( GPIO_PORT ( MBLK019_CH14_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_OPENDRAIN , GPIO_PIN ( MBLK019_CH14_PIN ) ) ; // set pin as output open-drain
rcc_periph_clock_enable ( GPIO_RCC ( MBLK019_CH35_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_PIN ( MBLK019_CH35_PIN ) ) ; // don't sink current (e.g. not powering the opto-coupler/transistor)
gpio_set_mode ( GPIO_PORT ( MBLK019_CH35_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_OPENDRAIN , GPIO_PIN ( MBLK019_CH35_PIN ) ) ; // set pin as output open-drain
rcc_periph_clock_enable ( GPIO_RCC ( MBLK019_PRESENCE_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_set ( GPIO_PORT ( MBLK019_PRESENCE_PIN ) , GPIO_PIN ( MBLK019_PRESENCE_PIN ) ) ; // pull up
gpio_set_mode ( GPIO_PORT ( MBLK019_PRESENCE_PIN ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , GPIO_PIN ( MBLK019_PRESENCE_PIN ) ) ; // set pin to input to read state
if ( gpio_get ( GPIO_PORT ( MBLK019_PRESENCE_PIN ) , GPIO_PIN ( MBLK019_PRESENCE_PIN ) ) ) {
error = " MBLK019 not connected " ;
puts ( " KO \n " ) ;
} else {
puts ( " OK \n " ) ;
}
2020-12-08 23:07:36 +01:00
puts ( " setup TEC power supply: " ) ;
2020-12-07 19:14:00 +01:00
rcc_periph_clock_enable ( GPIO_RCC ( TEC_POWER_YELLOW ) ) ; // enable clock for GPIO port peripheral
2020-12-08 23:07:36 +01:00
gpio_clear ( GPIO_PORT ( TEC_POWER_YELLOW ) , GPIO_PIN ( TEC_POWER_YELLOW ) ) ; // don't connect wire to VCC
gpio_set_mode ( GPIO_PORT ( TEC_POWER_YELLOW ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( TEC_POWER_YELLOW ) ) ; // set pin as output
2020-12-07 19:14:00 +01:00
rcc_periph_clock_enable ( GPIO_RCC ( TEC_POWER_ORANGE ) ) ; // enable clock for GPIO port peripheral
2020-12-08 23:07:36 +01:00
gpio_clear ( GPIO_PORT ( TEC_POWER_ORANGE ) , GPIO_PIN ( TEC_POWER_ORANGE ) ) ; // don't connect wire to VCC
gpio_set_mode ( GPIO_PORT ( TEC_POWER_ORANGE ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_PUSHPULL , GPIO_PIN ( TEC_POWER_ORANGE ) ) ; // set pin as output
// set up PWM output
rcc_periph_clock_enable ( RCC_TIM_CH ( TEC_POWER_TIMER , TEC_POWER_CHANNEL ) ) ; // enable clock for GPIO peripheral
gpio_clear ( TIM_CH_PORT ( TEC_POWER_TIMER , TEC_POWER_CHANNEL ) , TIM_CH_PIN ( TEC_POWER_TIMER , TEC_POWER_CHANNEL ) ) ; // don't let power trough
gpio_set_mode ( TIM_CH_PORT ( TEC_POWER_TIMER , TEC_POWER_CHANNEL ) , GPIO_MODE_OUTPUT_10_MHZ , GPIO_CNF_OUTPUT_ALTFN_PUSHPULL , TIM_CH_PIN ( TEC_POWER_TIMER , TEC_POWER_CHANNEL ) ) ; // set pin as output
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for alternate function (PWM)
rcc_periph_clock_enable ( RCC_TIM ( TEC_POWER_TIMER ) ) ; // enable clock for timer peripheral
rcc_periph_reset_pulse ( RST_TIM ( TEC_POWER_TIMER ) ) ; // reset timer state
timer_set_mode ( TIM ( TEC_POWER_TIMER ) , TIM_CR1_CKD_CK_INT , TIM_CR1_CMS_EDGE , TIM_CR1_DIR_UP ) ; // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
// peltier elements can safely be PWMed at 300 Hz to 3000 Hz, we will keep it under 2 kHz to avoid the audible range
timer_set_prescaler ( TIM ( TEC_POWER_TIMER ) , rcc_ahb_frequency / 1500000 - 1 ) ; // set the clock frequency to 1.5 kHz
timer_set_period ( TIM ( TEC_POWER_TIMER ) , UINT16_MAX ) ; // use the whole range as period, even if we can only control up to 100 Hz
timer_set_oc_value ( TIM ( TEC_POWER_TIMER ) , TEC_POWER_OC , 0 ) ; // duty cycle to 0%, to switch off heater
timer_set_oc_mode ( TIM ( TEC_POWER_TIMER ) , TEC_POWER_OC , TIM_OCM_PWM1 ) ; // set timer to generate PWM (heater switched of as long as CNT < CCR)
timer_enable_oc_output ( TIM ( TEC_POWER_TIMER ) , TEC_POWER_OC ) ; // enable output to generate the PWM signal
timer_enable_break_main_output ( TIM ( TEC_POWER_TIMER ) ) ; // required to enable timer, even when no dead time is used
timer_set_counter ( TIM ( TEC_POWER_TIMER ) , 0 ) ; // reset counter
timer_enable_counter ( TIM ( TEC_POWER_TIMER ) ) ; // enable timer
puts ( " OK \n " ) ;
2020-12-07 19:14:00 +01:00
puts ( " setup heat sink fan: " ) ;
// we can't test if it is connected (we only control the MOSFET directly powering the fan)
rcc_periph_clock_enable ( GPIO_RCC ( HEATSINK_FAN_PIN ) ) ; // enable clock for GPIO port peripheral
gpio_clear ( GPIO_PORT ( HEATSINK_FAN_PIN ) , GPIO_PIN ( HEATSINK_FAN_PIN ) ) ; // switch off fan
gpio_set_mode ( GPIO_PORT ( HEATSINK_FAN_PIN ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_OPENDRAIN , GPIO_PIN ( HEATSINK_FAN_PIN ) ) ; // set pin as output open-drain, gate of nMOS it pulled up externally
puts ( " OK \n " ) ;
puts ( " setup display: " ) ;
if ( oled_text_setup ( ) ) { // setup OLED display with default slave address
oled_text_clear ( ) ; // clear buffer (else last state is displayed)
oled_text_line ( " PCR 3000 " , 0 ) ;
2020-12-10 18:16:16 +01:00
oled_text_line ( state_names [ state ] , 1 ) ; // show state
2020-12-07 19:14:00 +01:00
oled_text_update ( ) ;
puts ( " OK \n " ) ;
} else {
puts ( " KO \n " ) ;
}
puts ( " setup DS18B20 temperature sensor: " ) ;
sensor_ds18b20_setup ( ) ; // configure 1-Wire bus to read from sensor
if ( 1 = = sensor_ds18b20_number ( ) ) { // check number of devices available
sensor_ds18b20_precision ( 0 , 12 ) ; // set precision to 12 bits
ds18b20_present = true ; // remember the sensor is present (and there is only one)
sensor_ds18b20_convert ( 0 ) ; // start conversion (it takes almost 1 s)
puts ( " OK \n " ) ;
} else {
puts ( " KO \n " ) ;
}
2018-04-06 17:37:17 +02:00
// setup terminal
terminal_prefix = " " ; // set default prefix
terminal_process = & process_command ; // set central function to process commands
terminal_setup ( ) ; // start terminal
2016-10-23 17:42:27 +02:00
2020-12-07 19:14:00 +01:00
if ( error & & STATE_SAFE ! = state ) { // an error has occurred during initialisation
2020-12-10 18:16:16 +01:00
set_state ( STATE_SAFE ) ; // go to safe state
2020-12-07 19:14:00 +01:00
} else {
led_power_on ( ) ; // indicate user we are ready
}
2018-04-06 17:37:17 +02:00
// start main loop
2016-02-18 10:39:08 +01:00
bool action = false ; // if an action has been performed don't go to sleep
button_flag = false ; // reset button flag
2020-12-07 19:14:00 +01:00
led_on ( ) ; // indicate user boot completed
2016-01-29 00:24:49 +01:00
while ( true ) { // infinite loop
2016-10-23 17:42:27 +02:00
iwdg_reset ( ) ; // kick the dog
2019-06-18 17:45:53 +02:00
if ( user_input_available ) { // user input is available
2016-02-18 10:39:08 +01:00
action = true ; // action has been performed
2016-01-29 00:24:49 +01:00
led_toggle ( ) ; // toggle LED
2018-04-06 17:37:17 +02:00
char c = user_input_get ( ) ; // store receive character
terminal_send ( c ) ; // send received character to terminal
2016-08-14 21:02:38 +02:00
}
2019-06-18 17:45:53 +02:00
if ( button_flag ) { // user pressed button
2016-08-14 21:02:38 +02:00
action = true ; // action has been performed
2020-12-07 19:14:00 +01:00
sleep_ms ( 100 ) ; // wait a bit to remove noise
2020-12-08 23:15:53 +01:00
if ( ! gpio_get ( GPIO_PORT ( CONTROL_PLAY_BUTTON_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ) {
2020-12-07 19:14:00 +01:00
puts ( " button pressed \n " ) ;
gpio_toggle ( GPIO_PORT ( CONTROL_PLAY_ORANGE_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_ORANGE_LED_PIN ) ) ;
gpio_toggle ( GPIO_PORT ( CONTROL_PLAY_GREEN_LED_PIN ) , GPIO_PIN ( CONTROL_PLAY_GREEN_LED_PIN ) ) ;
}
sleep_ms ( 100 ) ; // wait a bit to remove double trigger
2016-08-14 21:02:38 +02:00
button_flag = false ; // reset flag
2016-02-18 10:39:08 +01:00
}
2019-06-18 17:45:53 +02:00
if ( rtc_internal_tick_flag ) { // the internal RTC ticked
2020-12-07 19:14:00 +01:00
const uint32_t ticks = rtc_internal_tick_flag ; // save tick time
rtc_internal_tick_flag = 0 ; // reset flag
2017-06-27 15:44:03 +02:00
action = true ; // action has been performed
2020-12-07 19:14:00 +01:00
if ( 0 = = ( ticks % ( RTC_TICKS_SECOND / 2 ) ) ) { // time to blink the LEDs
if ( 0 = = ( ticks % RTC_TICKS_SECOND ) ) { // switch on the LEDs
if ( led_power_blink ) {
led_power_on ( ) ;
}
if ( led_heat_blink ) {
led_heat_on ( ) ;
}
if ( led_cool_blink ) {
led_cool_on ( ) ;
}
} else { // switch off LEDs
if ( led_power_blink ) {
led_power_off ( ) ;
}
if ( led_heat_blink ) {
led_heat_off ( ) ;
}
if ( led_cool_blink ) {
led_cool_off ( ) ;
}
}
}
if ( STATE_SAFE ! = state ) {
if ( ! isnan ( lid_target ) ) {
lid_pid ( ) ; // run PID loop for lid heater
}
if ( bed_target ) {
bed_pid ( ) ; // run PID loop for bed heater
}
}
}
if ( rtc_internal_second_flag ) { // one second has passed
rtc_internal_second_flag = false ; // clear flag
action = true ; // remember we did something
led_toggle ( ) ; // toggle LED (good to indicate if main function is stuck)
// read temperatures
const float lid_temp = lid_temperature ( ) ;
const float heatsink_temp = bed_heatsink_temperature ( ) ;
2020-12-08 23:13:37 +01:00
const float top_temp = bed_tophalf_temperature ( ) ;
const float bot_temp = bed_bothalf_temperature ( ) ;
2020-12-10 18:19:36 +01:00
const float tube_temp = bed_tube_temperature ( ) ;
2020-12-07 19:14:00 +01:00
// read bed temperatures
2020-12-10 18:19:36 +01:00
printf ( " bed: top=%u %.2f, bottom=%u %.02f, sink=%u %.02f, tube=%u %.02f; 393-A=%u; 339-3=%u \n " , sensor_max1247_read ( 0 ) , top_temp , sensor_max1247_read ( 1 ) , bot_temp , sensor_max1247_read ( 2 ) , heatsink_temp , sensor_max1247_read ( 3 ) , tube_temp , gpio_get ( GPIO_PORT ( BED_PIN_393A ) , GPIO_PIN ( BED_PIN_393A ) ) ? 1 : 0 , gpio_get ( GPIO_PORT ( BED_PIN_3393 ) , GPIO_PIN ( BED_PIN_3393 ) ) ? 1 : 0 ) ;
2020-12-08 23:13:37 +01:00
printf ( " lid: %.04f °C \n " , lid_temp ) ;
2020-12-07 19:14:00 +01:00
if ( ds18b20_present ) {
const float temp = sensor_ds18b20_temperature ( 0 ) ; // get temperature
sensor_ds18b20_convert ( 0 ) ; // start next conversion (since it takes almost 1 s)
printf ( " DS18B20: %.03f °C \n " , temp ) ;
}
// time to check if everything is OK
if ( ! error & & STATE_SAFE ! = state ) { // only check if we are not in the safe state
// check lid temperature
2020-12-08 23:13:37 +01:00
if ( lid_temp < 5.0 ) { // voltage is at the upper limit (3.3V), meaning it is directly connected to the 3.3V pull-up resistor, and the lid thermistor does not pull it to ground
2020-12-07 19:14:00 +01:00
error = " lid thermistor is probably not connected " ;
2020-12-08 23:13:37 +01:00
} else if ( lid_temp > 100.0 ) {
2020-12-07 19:14:00 +01:00
error = " lid is getting too warm " ;
}
// check fan
if ( STATE_IDLE ! = state & & STATE_SAFE ! = state ) {
heatsink_fan_on ( ) ; // ensure the fan is on when heating/cooling bed
} else if ( STATE_IDLE = = state ) {
if ( heatsink_temp > 45.0 ) { // ensure heat sink is not above 50 °C when resting
heatsink_fan_on ( ) ;
} else { // heat sink is now cold enough to touch
heatsink_fan_off ( ) ;
}
}
if ( heatsink_temp > 80.0 ) {
error = " heat sink is getting too warm " ;
}
if ( error ) { // an error has occurred
2020-12-10 18:16:16 +01:00
set_state ( STATE_SAFE ) ; // go to safe state
2020-12-07 19:14:00 +01:00
}
2020-02-02 21:19:03 +01:00
}
2016-08-14 21:02:38 +02:00
}
if ( action ) { // go to sleep if nothing had to be done, else recheck for activity
2016-02-18 10:39:08 +01:00
action = false ;
} else {
__WFI ( ) ; // go to sleep
}
2016-10-23 17:42:27 +02:00
} // main loop
2016-01-17 14:54:54 +01:00
}
2016-01-29 11:25:30 +01:00
2016-08-14 21:02:38 +02:00
/** @brief interrupt service routine called when tick passed on RTC */
void rtc_isr ( void )
{
2020-12-07 19:14:00 +01:00
static uint32_t tick = RTC_TICKS_SECOND ; // this will let us known then a second passed
2016-08-14 21:02:38 +02:00
rtc_clear_flag ( RTC_SEC ) ; // clear flag
2020-12-07 19:14:00 +01:00
rtc_internal_tick_flag = rtc_get_counter_val ( ) ; // notify to show new time
tick - - ; // count down ticks
if ( 0 = = tick ) { // do the check here to not miss a tick
rtc_internal_second_flag = true ; // let main loop know a second passed
tick = RTC_TICKS_SECOND ; // reset count down
}
2016-08-14 21:02:38 +02:00
}
2020-12-08 23:15:53 +01:00
/** interrupt service routine called when button is pressed */
void GPIO_EXTI_ISR ( CONTROL_PLAY_BUTTON_LED_PIN ) ( void )
{
exti_reset_request ( GPIO_EXTI ( CONTROL_PLAY_BUTTON_LED_PIN ) ) ; // reset interrupt
button_flag = true ; // perform button action
}