mirror of
https://github.com/DerSpatz/PCB-reflow-solder-heat-plate.git
synced 2025-02-17 18:49:15 +01:00
Merge pull request #3 from oltdaniel/feature/platformioFirmware
WIP initial platformio setup
This commit is contained in:
commit
40fb6e9614
5
Firmware/pcb_reflow_fw_PlatformIO/.gitignore
vendored
Normal file
5
Firmware/pcb_reflow_fw_PlatformIO/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
10
Firmware/pcb_reflow_fw_PlatformIO/.vscode/extensions.json
vendored
Normal file
10
Firmware/pcb_reflow_fw_PlatformIO/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
10
Firmware/pcb_reflow_fw_PlatformIO/README.md
Normal file
10
Firmware/pcb_reflow_fw_PlatformIO/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Firmware
|
||||
|
||||
This firmware project structure is based on [PlatformIO](https://platformio.org/).
|
||||
The flashing has been configured to use an Arduino as a programmer using the
|
||||
following project: https://github.com/ElTangas/jtag2updi.
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Split main file into multiple ones _(makes it easier to work on)_
|
||||
- [ ] Better Instructions for flashing.
|
16284
Firmware/pcb_reflow_fw_PlatformIO/avrdude.conf
Normal file
16284
Firmware/pcb_reflow_fw_PlatformIO/avrdude.conf
Normal file
File diff suppressed because it is too large
Load Diff
32
Firmware/pcb_reflow_fw_PlatformIO/platformio.ini
Normal file
32
Firmware/pcb_reflow_fw_PlatformIO/platformio.ini
Normal file
@ -0,0 +1,32 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:ATmega4809]
|
||||
platform = atmelmegaavr
|
||||
board = ATmega4809
|
||||
framework = arduino
|
||||
upload_protocol = custom
|
||||
upload_flags =
|
||||
-C
|
||||
${platformio.workspace_dir}/../avrdude.conf
|
||||
-p
|
||||
m4809
|
||||
-P
|
||||
$UPLOAD_PORT
|
||||
-b
|
||||
115200
|
||||
-c
|
||||
jtag2updi
|
||||
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
|
||||
lib_deps =
|
||||
adafruit/Adafruit GFX Library@^1.11.2
|
||||
adafruit/Adafruit SSD1306@^2.5.4
|
||||
paulstoffregen/OneWire@^2.3.7
|
||||
milesburton/DallasTemperature@^3.10.0
|
943
Firmware/pcb_reflow_fw_PlatformIO/src/main.cpp
Normal file
943
Firmware/pcb_reflow_fw_PlatformIO/src/main.cpp
Normal file
@ -0,0 +1,943 @@
|
||||
/* Solder Reflow Plate Sketch
|
||||
* H/W - Ver Spatz-1.0
|
||||
* S/W - Ver 0.35
|
||||
* by Chris Halsall and Nathan Heidt
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODOS:
|
||||
* - add digital sensor setup routine if they are detected, but not setup
|
||||
* - figure out a method for how to use all the temperature sensors
|
||||
* - implement an observer/predictor for the temperature sensors. Kalman filter
|
||||
* time?!?
|
||||
*/
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include <DallasTemperature.h>
|
||||
#include <EEPROM.h>
|
||||
#include <OneWire.h>
|
||||
#include <SPI.h>
|
||||
|
||||
// Version Definitions
|
||||
static const PROGMEM float hw = 0.9;
|
||||
static const PROGMEM float sw = 0.15;
|
||||
|
||||
// Screen Definitions
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 32
|
||||
#define SCREEN_ADDRESS 0x3C // I2C Address
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire,
|
||||
-1); // Create Display
|
||||
|
||||
// Pin Definitions
|
||||
#define MOSFET_PIN PIN_PC3
|
||||
#define UPSW_PIN PIN_PF3
|
||||
#define DNSW_PIN PIN_PD4
|
||||
#define TEMP_PIN PIN_PF2 // A2
|
||||
#define VCC_PIN PIN_PF4 // A0
|
||||
#define LED_GREEN_PIN PIN_PC5
|
||||
#define LED_RED_PIN PIN_PC4
|
||||
#define ONE_WIRE_BUS PIN_PD5
|
||||
|
||||
#define MOSFET_PIN_OFF 255
|
||||
|
||||
enum menu_state_t {
|
||||
MENU_IDLE,
|
||||
MENU_SELECT_PROFILE,
|
||||
MENU_HEAT,
|
||||
MENU_INC_TEMP,
|
||||
MENU_DEC_TEMP
|
||||
};
|
||||
enum buttons_state_t {
|
||||
BUTTONS_NO_PRESS,
|
||||
BUTTONS_BOTH_PRESS,
|
||||
BUTTONS_UP_PRESS,
|
||||
BUTTONS_DN_PRESS
|
||||
};
|
||||
enum single_button_state_t {
|
||||
BUTTON_PRESSED,
|
||||
BUTTON_RELEASED,
|
||||
BUTTON_NO_ACTION
|
||||
};
|
||||
|
||||
// Button interrupt state
|
||||
volatile single_button_state_t up_button_state = BUTTON_NO_ACTION;
|
||||
volatile single_button_state_t dn_button_state = BUTTON_NO_ACTION;
|
||||
volatile unsigned long up_state_change_time = 0;
|
||||
volatile unsigned long down_state_change_time = 0;
|
||||
|
||||
// Temperature Info
|
||||
byte max_temp_array[] = {140, 150, 160, 170, 180};
|
||||
byte max_temp_index = 0;
|
||||
#define MAX_RESISTANCE 10.0
|
||||
float bed_resistance = 1.88;
|
||||
#define MAX_AMPERAGE 5.0
|
||||
#define PWM_VOLTAGE_SCALAR 2.0
|
||||
|
||||
// These values were derived using a regression from real world data.
|
||||
// See the jupyter notebooks for more detail
|
||||
#define ANALOG_APPROXIMATION_SCALAR 1.612
|
||||
#define ANALOG_APPROXIMATION_OFFSET -20.517
|
||||
|
||||
// EEPROM storage locations
|
||||
#define CRC_ADDR 0
|
||||
#define FIRSTTIME_BOOT_ADDR 4
|
||||
#define TEMP_INDEX_ADDR 5
|
||||
#define RESISTANCE_INDEX_ADDR 6
|
||||
#define DIGITAL_TEMP_ID_ADDR 10
|
||||
|
||||
// Voltage Measurement Info
|
||||
#define VOLTAGE_REFERENCE 1.5
|
||||
|
||||
// Solder Reflow Plate Logo
|
||||
static const uint8_t PROGMEM logo[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x80, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1f, 0xe0, 0x03, 0x01, 0x80, 0x00, 0x00, 0x30, 0x70, 0x00, 0x21, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x03, 0x00, 0xc7, 0x80, 0x00, 0x20,
|
||||
0x18, 0xf0, 0x61, 0x80, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x3e,
|
||||
0xcc, 0xc0, 0xc0, 0x04, 0x19, 0x98, 0x61, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x01, 0xf3, 0x77, 0xd8, 0xc7, 0xe0, 0x06, 0x33, 0x18, 0x61, 0x8f,
|
||||
0x88, 0x00, 0x00, 0x00, 0x06, 0x03, 0x3b, 0x61, 0xd0, 0xc6, 0x00, 0x07,
|
||||
0xe2, 0x18, 0x61, 0x98, 0xd8, 0x04, 0x00, 0x00, 0x01, 0xc6, 0x0b, 0x60,
|
||||
0xd9, 0x86, 0x00, 0x06, 0x03, 0x30, 0xff, 0xb0, 0x78, 0x66, 0x00, 0x00,
|
||||
0x40, 0xe4, 0x0f, 0x60, 0xdf, 0x06, 0x00, 0x07, 0x03, 0xe0, 0x31, 0xe0,
|
||||
0x78, 0x62, 0x00, 0x00, 0x40, 0x3c, 0x0f, 0x61, 0xd8, 0x06, 0x00, 0x07,
|
||||
0x83, 0x00, 0x31, 0xe0, 0x78, 0x63, 0x00, 0x00, 0x60, 0x36, 0x1b, 0x63,
|
||||
0xc8, 0x02, 0x00, 0x02, 0xc1, 0x00, 0x18, 0xb0, 0xcc, 0xe2, 0x00, 0x00,
|
||||
0x30, 0x33, 0x3b, 0x36, 0x4e, 0x03, 0x00, 0x02, 0x61, 0xc0, 0x0c, 0x99,
|
||||
0xcd, 0xfe, 0x00, 0x00, 0x0f, 0xe1, 0xe1, 0x3c, 0x03, 0xf3, 0x00, 0x02,
|
||||
0x38, 0x7e, 0x0c, 0x8f, 0x07, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7f, 0x84, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xe4, 0x00, 0x18,
|
||||
0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x04, 0x3c, 0x3c, 0x18, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x1e, 0x06, 0x7f, 0xc6, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3e, 0x03, 0x18,
|
||||
0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x04, 0x36, 0x7f, 0x19, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0xe6, 0xc7, 0x19, 0xf8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x07, 0x83, 0x18,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x06, 0x07, 0x81, 0x18, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0xc3, 0x98, 0x70, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x7e, 0x08,
|
||||
0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static const uint8_t logo_width = 128;
|
||||
static const uint8_t logo_height = 27;
|
||||
|
||||
// Heating Animation
|
||||
static const uint8_t PROGMEM heat_animate[] = {
|
||||
0b00000001, 0b00000000, 0b00000001, 0b10000000, 0b00000001, 0b10000000,
|
||||
0b00000001, 0b01000000, 0b00000010, 0b01000000, 0b00100010, 0b01000100,
|
||||
0b00100100, 0b00100100, 0b01010101, 0b00100110, 0b01001001, 0b10010110,
|
||||
0b10000010, 0b10001001, 0b10100100, 0b01000001, 0b10011000, 0b01010010,
|
||||
0b01000100, 0b01100010, 0b00100011, 0b10000100, 0b00011000, 0b00011000,
|
||||
0b00000111, 0b11100000};
|
||||
static const uint8_t heat_animate_width = 16;
|
||||
static const uint8_t heat_animate_height = 16;
|
||||
|
||||
// Tick
|
||||
static const uint8_t PROGMEM tick[] = {
|
||||
0b00000000, 0b00000100, 0b00000000, 0b00001010, 0b00000000, 0b00010101,
|
||||
0b00000000, 0b00101010, 0b00000000, 0b01010100, 0b00000000, 0b10101000,
|
||||
0b00000001, 0b01010000, 0b00100010, 0b10100000, 0b01010101, 0b01000000,
|
||||
0b10101010, 0b10000000, 0b01010101, 0b00000000, 0b00101010, 0b00000000,
|
||||
0b00010100, 0b00000000, 0b00001000, 0b00000000, 0b01111111, 0b11100000};
|
||||
static const uint8_t tick_width = 16;
|
||||
static const uint8_t tick_height = 15;
|
||||
|
||||
// This needs to be specified or the compiler will fail as you can't initialize
|
||||
// a flexible array member in a nested context
|
||||
#define MAX_PROFILE_LENGTH 8
|
||||
|
||||
// TODO(HEIDT) may need to switch away from floats for speed/sizeA
|
||||
struct solder_profile_t {
|
||||
uint8_t points;
|
||||
float seconds[MAX_PROFILE_LENGTH];
|
||||
float fraction[MAX_PROFILE_LENGTH];
|
||||
};
|
||||
|
||||
// TODO(HEIDT) how to adjust for environments where the board starts hot or
|
||||
// cold? profiles pulled from here:
|
||||
// https://www.compuphase.com/electronics/reflowsolderprofiles.htm#_
|
||||
#define NUM_PROFILES 2
|
||||
const static solder_profile_t profiles[NUM_PROFILES] = {
|
||||
{.points = 4,
|
||||
.seconds = {90, 180, 240, 260},
|
||||
.fraction = {.65, .78, 1.00, 1.00}},
|
||||
{.points = 2, .seconds = {162.0, 202.0}, .fraction = {.95, 1.00}}};
|
||||
|
||||
// temperature must be within this range to move on to next step
|
||||
#define TARGET_TEMP_THRESHOLD 2.5
|
||||
|
||||
// PID values
|
||||
float kI = 0.2;
|
||||
float kD = 0.25;
|
||||
float kP = 8.0;
|
||||
float I_clip = 220;
|
||||
float error_I = 0;
|
||||
|
||||
// Optional temperature sensor
|
||||
OneWire oneWire(ONE_WIRE_BUS);
|
||||
DallasTemperature sensors(&oneWire);
|
||||
int sensor_count = 0;
|
||||
DeviceAddress temp_addresses[3];
|
||||
|
||||
#define DEBUG
|
||||
|
||||
#ifdef DEBUG
|
||||
#define debugprint(x) Serial.print(x);
|
||||
#define debugprintln(x) Serial.println(x);
|
||||
#else
|
||||
#define debugprint(x)
|
||||
#define debugprintln(x)
|
||||
#endif
|
||||
|
||||
// -------------------- Function prototypes -----------------------------------
|
||||
void inline heatAnimate(int &x, int &y, float v, float t, float target_temp);
|
||||
|
||||
// -------------------- General functions ----------------------------------
|
||||
|
||||
void dnsw_change_isr() {
|
||||
dn_button_state = BUTTON_PRESSED;
|
||||
down_state_change_time = millis();
|
||||
}
|
||||
|
||||
void upsw_change_isr() {
|
||||
up_button_state = BUTTON_PRESSED;
|
||||
up_state_change_time = millis();
|
||||
}
|
||||
|
||||
void setCRC(uint32_t new_crc) { EEPROM.put(CRC_ADDR, new_crc); }
|
||||
|
||||
uint32_t eepromCRC(void) {
|
||||
static const uint32_t crc_table[16] = {
|
||||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4,
|
||||
0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
|
||||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c};
|
||||
uint32_t crc = ~0L;
|
||||
// Skip first 4 bytes of EEPROM as thats where we store the CRC
|
||||
for (int index = 4; index < EEPROM.length(); ++index) {
|
||||
crc = crc_table[(crc ^ EEPROM[index]) & 0x0f] ^ (crc >> 4);
|
||||
crc = crc_table[(crc ^ (EEPROM[index] >> 4)) & 0x0f] ^ (crc >> 4);
|
||||
crc = ~crc;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
void updateCRC() {
|
||||
uint32_t new_crc = eepromCRC();
|
||||
setCRC(new_crc);
|
||||
}
|
||||
|
||||
bool validateCRC() {
|
||||
uint32_t stored_crc;
|
||||
EEPROM.get(CRC_ADDR, stored_crc);
|
||||
uint32_t calculated_crc = eepromCRC();
|
||||
debugprint("got CRCs, stored: ");
|
||||
debugprint(stored_crc);
|
||||
debugprint(", calculated: ");
|
||||
debugprintln(calculated_crc);
|
||||
return stored_crc == calculated_crc;
|
||||
}
|
||||
|
||||
inline void setupSensors() {
|
||||
sensors.begin();
|
||||
sensor_count = sensors.getDeviceCount();
|
||||
debugprint("Looking for sensors, found: ");
|
||||
debugprintln(sensor_count);
|
||||
for (int i = 0; i < min(sensor_count, sizeof(temp_addresses)); i++) {
|
||||
sensors.getAddress(temp_addresses[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void setFastPwm() { analogWriteFrequency(64); }
|
||||
|
||||
inline void setVREF() { analogReference(INTERNAL1V5); }
|
||||
|
||||
inline bool isFirstBoot() {
|
||||
uint8_t first_boot = EEPROM.read(FIRSTTIME_BOOT_ADDR);
|
||||
debugprint("Got first boot flag: ");
|
||||
debugprintln(first_boot);
|
||||
return first_boot != 1;
|
||||
}
|
||||
|
||||
inline void setFirstBoot() {
|
||||
EEPROM.write(FIRSTTIME_BOOT_ADDR, 1);
|
||||
updateCRC();
|
||||
}
|
||||
|
||||
inline float getResistance() {
|
||||
float f;
|
||||
return EEPROM.get(RESISTANCE_INDEX_ADDR, f);
|
||||
return f;
|
||||
}
|
||||
|
||||
inline void setResistance(float resistance) {
|
||||
EEPROM.put(RESISTANCE_INDEX_ADDR, resistance);
|
||||
updateCRC();
|
||||
}
|
||||
|
||||
inline void setMaxTempIndex(int index) {
|
||||
EEPROM.update(TEMP_INDEX_ADDR, index);
|
||||
updateCRC();
|
||||
}
|
||||
|
||||
inline int getMaxTempIndex(void) {
|
||||
return EEPROM.read(TEMP_INDEX_ADDR) % sizeof(max_temp_array);
|
||||
}
|
||||
|
||||
float getTemp() {
|
||||
debugprint("Temps: ");
|
||||
float t = 0;
|
||||
for (byte i = 0; i < 100; i++) { // Poll TEMP_PIN reading 100 times
|
||||
t = t + analogRead(TEMP_PIN);
|
||||
}
|
||||
t /= 100.0; // average
|
||||
t *= VOLTAGE_REFERENCE / 1024.0; // voltage
|
||||
// conversion to temp, consult datasheet:
|
||||
// https://www.ti.com/document-viewer/LMT85/datasheet/detailed-description#snis1681040
|
||||
// this is optimized for 25C to 150C
|
||||
// TODO(HEIDT) this is linearized and innacurate, could probably use the
|
||||
// nonlinear functions without much overhead.
|
||||
t = (t - 1.365) / ((.301 - 1.365) / (150.0 - 25.0)) + 25.0;
|
||||
|
||||
// The analog sensor is too far from the bed for an accurate reading
|
||||
// this simple function estimates the true bed temperature based off the
|
||||
// thermal gradient
|
||||
float estimated_temp =
|
||||
t * ANALOG_APPROXIMATION_SCALAR + ANALOG_APPROXIMATION_OFFSET;
|
||||
debugprint(estimated_temp);
|
||||
debugprint(" ");
|
||||
|
||||
sensors.requestTemperatures();
|
||||
for (int i = 0; i < sensor_count; i++) {
|
||||
float temp_in = sensors.getTempC(temp_addresses[i]);
|
||||
debugprint(temp_in);
|
||||
debugprint(" ");
|
||||
}
|
||||
debugprintln();
|
||||
|
||||
return max(t, estimated_temp);
|
||||
}
|
||||
|
||||
float getVolts() {
|
||||
float v = 0;
|
||||
for (byte i = 0; i < 20; i++) { // Poll Voltage reading 20 times
|
||||
v = v + analogRead(VCC_PIN);
|
||||
}
|
||||
v /= 20;
|
||||
|
||||
float vin = (v / 1023.0) * 1.5;
|
||||
debugprint("voltage at term: ");
|
||||
debugprintln(vin);
|
||||
vin = (vin / 0.090981) + 0.3;
|
||||
return vin;
|
||||
}
|
||||
|
||||
#define BUTTON_PRESS_TIME 50
|
||||
buttons_state_t getButtonsState() {
|
||||
single_button_state_t button_dn;
|
||||
single_button_state_t button_up;
|
||||
unsigned long button_dn_time;
|
||||
unsigned long button_up_time;
|
||||
|
||||
noInterrupts();
|
||||
button_dn = dn_button_state;
|
||||
button_up = up_button_state;
|
||||
button_dn_time = down_state_change_time;
|
||||
button_up_time = up_state_change_time;
|
||||
interrupts();
|
||||
|
||||
unsigned long cur_time = millis();
|
||||
buttons_state_t state = BUTTONS_NO_PRESS;
|
||||
|
||||
if (button_dn == BUTTON_PRESSED && button_up == BUTTON_PRESSED &&
|
||||
abs(button_dn_time - button_up_time) < BUTTON_PRESS_TIME) {
|
||||
if (cur_time - button_dn_time > BUTTON_PRESS_TIME &&
|
||||
cur_time - button_up_time > BUTTON_PRESS_TIME) {
|
||||
state = BUTTONS_BOTH_PRESS;
|
||||
noInterrupts();
|
||||
dn_button_state = BUTTON_NO_ACTION;
|
||||
up_button_state = BUTTON_NO_ACTION;
|
||||
interrupts();
|
||||
}
|
||||
} else if (button_up == BUTTON_PRESSED &&
|
||||
cur_time - button_up_time > BUTTON_PRESS_TIME) {
|
||||
state = BUTTONS_UP_PRESS;
|
||||
noInterrupts();
|
||||
up_button_state = BUTTON_NO_ACTION;
|
||||
interrupts();
|
||||
} else if (button_dn == BUTTON_PRESSED &&
|
||||
cur_time - button_dn_time > BUTTON_PRESS_TIME) {
|
||||
state = BUTTONS_DN_PRESS;
|
||||
noInterrupts();
|
||||
dn_button_state = BUTTON_NO_ACTION;
|
||||
interrupts();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// -------------------- UI Parts -----------------------------------
|
||||
|
||||
inline void clearMainMenu() {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(1);
|
||||
display.drawRoundRect(0, 0, 83, 32, 2, SSD1306_WHITE);
|
||||
}
|
||||
|
||||
inline void getResistanceFromUser() {
|
||||
float resistance = 1.88;
|
||||
while (1) {
|
||||
clearMainMenu();
|
||||
display.setCursor(3, 4);
|
||||
display.print(F("Resistance"));
|
||||
display.drawLine(3, 12, 79, 12, SSD1306_WHITE);
|
||||
display.setCursor(3, 14);
|
||||
display.print(F("UP/DN: change"));
|
||||
display.setCursor(3, 22);
|
||||
display.print(F("BOTH: choose"));
|
||||
buttons_state_t button = getButtonsState();
|
||||
if (button == BUTTONS_UP_PRESS) {
|
||||
resistance += 0.01;
|
||||
} else if (button == BUTTONS_DN_PRESS) {
|
||||
resistance -= 0.01;
|
||||
} else if (button == BUTTONS_BOTH_PRESS) {
|
||||
setResistance(resistance);
|
||||
return;
|
||||
}
|
||||
resistance = constrain(resistance, 0, MAX_RESISTANCE);
|
||||
|
||||
display.setCursor(90, 12);
|
||||
display.print(resistance);
|
||||
display.display();
|
||||
}
|
||||
}
|
||||
|
||||
inline void doSetup() {
|
||||
debugprintln("Performing setup");
|
||||
// TODO(HEIDT) show an info screen if we're doing firstime setup or if memory
|
||||
// is corrupted
|
||||
|
||||
getResistanceFromUser();
|
||||
// TODO(HEIDT) do a temperature module setup here
|
||||
|
||||
setFirstBoot();
|
||||
}
|
||||
|
||||
void showLogo() {
|
||||
unsigned long start_time = millis();
|
||||
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
|
||||
while (start_time + 2000 > millis()) {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(1);
|
||||
display.setTextColor(SSD1306_WHITE);
|
||||
display.setCursor(0, 0);
|
||||
display.drawBitmap(0, 0, logo, logo_width, logo_height, SSD1306_WHITE);
|
||||
display.setCursor(80, 16);
|
||||
display.print(F("S/W V"));
|
||||
display.print(sw, 1);
|
||||
display.setCursor(80, 24);
|
||||
display.print(F("H/W V"));
|
||||
display.print(hw, 1);
|
||||
display.display();
|
||||
buttons_state_t cur_button = getButtonsState();
|
||||
// If we press both buttons during boot, we'll enter the setup process
|
||||
if (cur_button == BUTTONS_BOTH_PRESS) {
|
||||
doSetup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void displayProfileRight(int8_t cur_profile) {
|
||||
int cur_x = 90;
|
||||
int cur_y = 30;
|
||||
// start at x=90, go to SCREEN_WIDTH-8, save 6 pixels for cooldown
|
||||
float x_dist = SCREEN_WIDTH - 90 - 8;
|
||||
display.setCursor(cur_x, cur_y);
|
||||
float total_seconds =
|
||||
(int)profiles[cur_profile].seconds[profiles[cur_profile].points - 1];
|
||||
|
||||
for (int i = 0; i < profiles[cur_profile].points; i++) {
|
||||
int x_next =
|
||||
(int)((profiles[cur_profile].seconds[i] / total_seconds) * x_dist) + 90;
|
||||
int y_next = 30 - (int)(profiles[cur_profile].fraction[i] * 28.0);
|
||||
display.drawLine(cur_x, cur_y, x_next, y_next, SSD1306_WHITE);
|
||||
cur_x = x_next;
|
||||
cur_y = y_next;
|
||||
}
|
||||
// draw down to finish TEMP_PIN
|
||||
display.drawLine(cur_x, cur_y, SCREEN_WIDTH - 2, 30, SSD1306_WHITE);
|
||||
}
|
||||
|
||||
inline uint8_t getProfile() {
|
||||
uint8_t cur_profile = 0;
|
||||
while (1) {
|
||||
clearMainMenu();
|
||||
display.setCursor(3, 4);
|
||||
display.print(F("Pick profile"));
|
||||
display.drawLine(3, 12, 79, 12, SSD1306_WHITE);
|
||||
display.setCursor(3, 14);
|
||||
display.print(F(" UP/DN: cycle"));
|
||||
display.setCursor(3, 22);
|
||||
display.print(F(" BOTH: choose"));
|
||||
buttons_state_t cur_button = getButtonsState();
|
||||
if (cur_button == BUTTONS_BOTH_PRESS) {
|
||||
clearMainMenu();
|
||||
return cur_profile;
|
||||
} else if (cur_button == BUTTONS_DN_PRESS) {
|
||||
cur_profile--;
|
||||
} else if (cur_button == BUTTONS_UP_PRESS) {
|
||||
cur_profile++;
|
||||
}
|
||||
cur_profile %= NUM_PROFILES;
|
||||
displayProfileRight(cur_profile);
|
||||
display.display();
|
||||
}
|
||||
}
|
||||
|
||||
void cancelledTimer() { // Cancelled via 5 minute Time Limit
|
||||
// Initiate Swap Display
|
||||
int x = 0; // Display change counter
|
||||
int y = 150; // Display change max (modulused below)
|
||||
|
||||
// Wait to return on any button press
|
||||
while (getButtonsState() == BUTTONS_NO_PRESS) {
|
||||
// Update Display
|
||||
display.clearDisplay();
|
||||
display.drawRoundRect(22, 0, 84, 32, 2, SSD1306_WHITE);
|
||||
display.setCursor(25, 4);
|
||||
display.print(F(" TIMED OUT"));
|
||||
display.drawLine(25, 12, 103, 12, SSD1306_WHITE);
|
||||
|
||||
// Swap Main Text
|
||||
if (x < (y * 0.3)) {
|
||||
display.setCursor(25, 14);
|
||||
display.println(" Took longer");
|
||||
display.setCursor(25, 22);
|
||||
display.println(" than 5 mins");
|
||||
} else if (x < (y * 0.6)) {
|
||||
display.setCursor(28, 14);
|
||||
display.println("Try a higher");
|
||||
display.setCursor(25, 22);
|
||||
display.println(" current PSU");
|
||||
} else {
|
||||
display.setCursor(25, 14);
|
||||
display.println(" Push button");
|
||||
display.setCursor(25, 22);
|
||||
display.println(" to return");
|
||||
}
|
||||
x = (x + 1) % y; // Display change increment and modulus
|
||||
|
||||
display.setTextSize(3);
|
||||
display.setCursor(5, 4);
|
||||
display.print(F("!"));
|
||||
display.setTextSize(3);
|
||||
display.setCursor(108, 4);
|
||||
display.print(F("!"));
|
||||
display.setTextSize(1);
|
||||
display.display();
|
||||
delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
inline void showHeatMenu(byte max_temp) {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(2);
|
||||
display.setCursor(22, 4);
|
||||
display.print(F("HEATING"));
|
||||
display.setTextSize(1);
|
||||
display.setCursor(52, 24);
|
||||
display.print(max_temp);
|
||||
display.print(F("C"));
|
||||
display.display();
|
||||
}
|
||||
|
||||
void stepPID(float target_temp, float current_temp, float last_temp, float dt,
|
||||
int min_pwm) {
|
||||
float error = target_temp - current_temp;
|
||||
float D = (current_temp - last_temp) / dt;
|
||||
|
||||
error_I += error * dt * kI;
|
||||
error_I = constrain(error_I, 0, I_clip);
|
||||
|
||||
// PWM is inverted so 0 duty is 100% power
|
||||
float PWM = 255.0 - (error * kP + D * kD + error_I);
|
||||
PWM = constrain(PWM, min_pwm, 255);
|
||||
|
||||
debugprintln("PID");
|
||||
debugprintln(dt);
|
||||
debugprintln(error);
|
||||
debugprintln(error_I);
|
||||
debugprint("PWM: ");
|
||||
debugprintln(PWM);
|
||||
analogWrite(MOSFET_PIN, (int)PWM);
|
||||
}
|
||||
|
||||
bool heat(byte max_temp, int profile_index) {
|
||||
// Heating Display
|
||||
showHeatMenu(max_temp);
|
||||
delay(3000);
|
||||
|
||||
float t; // Used to store current temperature
|
||||
float v; // Used to store current voltage
|
||||
|
||||
unsigned long profile_max_time = millis() / 1000 + (8 * 60);
|
||||
unsigned long step_start_time = (millis() / 1000);
|
||||
int current_step = 0;
|
||||
|
||||
// Other control variables
|
||||
int x = 0; // Heat Animate Counter
|
||||
int y = 80; // Heat Animate max (modulused below)
|
||||
|
||||
float start_temp = getTemp();
|
||||
float goal_temp = profiles[profile_index].fraction[0] * max_temp;
|
||||
float step_runtime = profiles[profile_index].seconds[0];
|
||||
float last_time = 0;
|
||||
float last_temp = getTemp();
|
||||
error_I = 0;
|
||||
|
||||
while (1) {
|
||||
// Cancel heat, don't even wait for uppress so we don't risk missing it
|
||||
// during the loop
|
||||
if (getButtonsState() != BUTTONS_NO_PRESS) {
|
||||
analogWrite(MOSFET_PIN, MOSFET_PIN_OFF);
|
||||
debugprintln("cancelled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check Heating not taken more than 8 minutes
|
||||
if (millis() / 1000 > profile_max_time) {
|
||||
analogWrite(MOSFET_PIN, MOSFET_PIN_OFF);
|
||||
debugprintln("exceeded time");
|
||||
cancelledTimer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Measure Values
|
||||
// TODO(HEIDT) getting the temperature from the digital sensors is by far
|
||||
// the slowest part of this loop. figure out an approach that allows control
|
||||
// faster than sensing
|
||||
t = getTemp();
|
||||
v = getVolts();
|
||||
float max_possible_amperage = v / bed_resistance;
|
||||
// TODO(HEIDT) approximate true resistance based on cold resistance and
|
||||
// temperature
|
||||
float vmax = (MAX_AMPERAGE * bed_resistance) * PWM_VOLTAGE_SCALAR;
|
||||
int min_PWM = 255 - ((vmax * 255.0) / v);
|
||||
min_PWM = constrain(min_PWM, 0, 255);
|
||||
debugprint("Min PWM: ");
|
||||
debugprintln(min_PWM);
|
||||
debugprintln(bed_resistance);
|
||||
|
||||
// Determine what target temp is and PID to it
|
||||
float time_into_step = ((float)millis() / 1000.0) - (float)step_start_time;
|
||||
float target_temp =
|
||||
min(((goal_temp - start_temp) * (time_into_step / step_runtime)) +
|
||||
start_temp,
|
||||
goal_temp);
|
||||
|
||||
// TODO(HEIDT) PID for a ramp will always lag, other options may be better
|
||||
stepPID(target_temp, t, last_temp, time_into_step - last_time, min_PWM);
|
||||
last_time = time_into_step;
|
||||
|
||||
// if we finish the step timewise
|
||||
if (time_into_step >= step_runtime) {
|
||||
// and if we're within the goal temperature of the step
|
||||
if (abs(t - goal_temp) < TARGET_TEMP_THRESHOLD) {
|
||||
// move onto the next step in the profile
|
||||
current_step++;
|
||||
// if that was the last step, we're done!
|
||||
if (current_step == profiles[profile_index].points) {
|
||||
analogWrite(MOSFET_PIN, MOSFET_PIN_OFF);
|
||||
return 1;
|
||||
}
|
||||
// otherwise, get the next goal temperature and runtime, and do the
|
||||
// process again
|
||||
last_time = 0.0;
|
||||
start_temp = t;
|
||||
goal_temp = profiles[profile_index].fraction[current_step] * max_temp;
|
||||
step_runtime = profiles[profile_index].seconds[current_step] -
|
||||
profiles[profile_index].seconds[current_step - 1];
|
||||
step_start_time = millis() / 1000.0;
|
||||
}
|
||||
}
|
||||
|
||||
heatAnimate(x, y, v, t, target_temp);
|
||||
}
|
||||
}
|
||||
|
||||
void evaluate_heat() {
|
||||
debugprintln("Starting thermal evaluation");
|
||||
uint8_t duties[] = {255, 225, 200, 150, 100, 50, 0};
|
||||
unsigned long runtime = 60 * 5; // run each for 5 minutes
|
||||
|
||||
for (int i = 0; i < sizeof(duties); i++) {
|
||||
debugprint("Running to duty of: ");
|
||||
debugprintln(duties[i]);
|
||||
unsigned long start_time = millis();
|
||||
analogWrite(MOSFET_PIN, duties[i]);
|
||||
float elapsed_time = (millis() - start_time) / 1000.0;
|
||||
while (elapsed_time < runtime) {
|
||||
debugprint("elapsed time: ");
|
||||
debugprintln(elapsed_time);
|
||||
debugprint("runtime: ");
|
||||
debugprintln(runtime);
|
||||
elapsed_time = (millis() - start_time) / 1000.0;
|
||||
float v = getVolts();
|
||||
float t = getTemp();
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
analogWrite(MOSFET_PIN, MOSFET_PIN_OFF);
|
||||
}
|
||||
|
||||
void inline heatAnimate(int &x, int &y, float v, float t, float target) {
|
||||
// Heat Animate Control
|
||||
display.clearDisplay();
|
||||
display.drawBitmap(0, 3, heat_animate, heat_animate_width,
|
||||
heat_animate_height, SSD1306_WHITE);
|
||||
display.drawBitmap(112, 3, heat_animate, heat_animate_width,
|
||||
heat_animate_height, SSD1306_WHITE);
|
||||
display.fillRect(0, 3, heat_animate_width, heat_animate_height * (y - x) / y,
|
||||
SSD1306_BLACK);
|
||||
display.fillRect(112, 3, heat_animate_width,
|
||||
heat_animate_height * (y - x) / y, SSD1306_BLACK);
|
||||
x = (x + 1) % y; // Heat animate increment and modulus
|
||||
|
||||
// Update display
|
||||
display.setTextSize(2);
|
||||
display.setCursor(22, 4);
|
||||
display.print(F("HEATING"));
|
||||
display.setTextSize(1);
|
||||
display.setCursor(20, 24);
|
||||
display.print(F("~"));
|
||||
display.print(v, 1);
|
||||
display.print(F("V"));
|
||||
if (t >= 100) {
|
||||
display.setCursor(63, 24);
|
||||
} else if (t >= 10) {
|
||||
display.setCursor(66, 24);
|
||||
} else {
|
||||
display.setCursor(69, 24);
|
||||
}
|
||||
display.print(F("~"));
|
||||
display.print(t, 0);
|
||||
display.print(F("C"));
|
||||
display.print(F("/"));
|
||||
display.print(target, 0);
|
||||
display.print(F("C"));
|
||||
display.display();
|
||||
}
|
||||
|
||||
void cancelledPB() { // Cancelled via push button
|
||||
// Update Display
|
||||
display.clearDisplay();
|
||||
display.drawRoundRect(22, 0, 84, 32, 2, SSD1306_WHITE);
|
||||
display.setCursor(25, 4);
|
||||
display.print(F(" CANCELLED"));
|
||||
display.display();
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
void coolDown() {
|
||||
float t = getTemp(); // Used to store current temperature
|
||||
|
||||
// Wait to return on any button press, or TEMP_PIN below threshold
|
||||
while (getButtonsState() == BUTTONS_NO_PRESS && t > 45.00) {
|
||||
display.clearDisplay();
|
||||
display.drawRoundRect(22, 0, 84, 32, 2, SSD1306_WHITE);
|
||||
display.setCursor(25, 4);
|
||||
display.print(F(" COOL DOWN"));
|
||||
display.drawLine(25, 12, 103, 12, SSD1306_WHITE);
|
||||
display.setCursor(25, 14);
|
||||
display.println(" Still Hot");
|
||||
t = getTemp();
|
||||
if (t >= 100) {
|
||||
display.setCursor(49, 22);
|
||||
} else {
|
||||
display.setCursor(52, 22);
|
||||
}
|
||||
display.print(F("~"));
|
||||
display.print(t, 0);
|
||||
display.print(F("C"));
|
||||
display.setTextSize(3);
|
||||
display.setCursor(5, 4);
|
||||
display.print(F("!"));
|
||||
display.setTextSize(3);
|
||||
display.setCursor(108, 4);
|
||||
display.print(F("!"));
|
||||
display.setTextSize(1);
|
||||
display.display();
|
||||
}
|
||||
}
|
||||
|
||||
void completed() {
|
||||
// Update Display
|
||||
display.clearDisplay();
|
||||
display.drawRoundRect(22, 0, 84, 32, 2, SSD1306_WHITE);
|
||||
display.setCursor(25, 4);
|
||||
display.print(F(" COMPLETED "));
|
||||
display.drawLine(25, 12, 103, 12, SSD1306_WHITE);
|
||||
display.setCursor(25, 14);
|
||||
display.println(" Push button");
|
||||
display.setCursor(25, 22);
|
||||
display.println(" to return");
|
||||
display.drawBitmap(0, 9, tick, tick_width, tick_height, SSD1306_WHITE);
|
||||
display.drawBitmap(112, 9, tick, tick_width, tick_height, SSD1306_WHITE);
|
||||
display.display();
|
||||
|
||||
// Wait to return on any button press
|
||||
while (getButtonsState() == BUTTONS_NO_PRESS) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void showMainMenuLeft(int &x, int &y) {
|
||||
if (x < (y * 0.5)) {
|
||||
display.setCursor(3, 4);
|
||||
display.print(F("PRESS BUTTONS"));
|
||||
display.drawLine(3, 12, 79, 12, SSD1306_WHITE);
|
||||
display.setCursor(3, 14);
|
||||
display.print(F(" Change MAX"));
|
||||
display.setCursor(3, 22);
|
||||
display.print(F(" Temperature"));
|
||||
} else {
|
||||
display.setCursor(3, 4);
|
||||
display.print(F("HOLD BUTTONS"));
|
||||
display.drawLine(3, 12, 79, 12, SSD1306_WHITE);
|
||||
display.setCursor(3, 18);
|
||||
display.print(F("Begin Heating"));
|
||||
}
|
||||
x = (x + 1) % y; // Display change increment and modulus
|
||||
}
|
||||
|
||||
inline void showMainMenuRight() {
|
||||
display.setCursor(95, 6);
|
||||
display.print(F("TEMP"));
|
||||
display.setCursor(95, 18);
|
||||
display.print(max_temp_array[max_temp_index]);
|
||||
display.print(F("C"));
|
||||
display.display();
|
||||
}
|
||||
|
||||
inline void mainMenu() {
|
||||
// Debounce
|
||||
menu_state_t cur_state = MENU_IDLE;
|
||||
|
||||
int x = 0; // Display change counter
|
||||
int y = 200; // Display change max (modulused below)
|
||||
uint8_t profile_index = 0;
|
||||
|
||||
while (1) {
|
||||
switch (cur_state) {
|
||||
case MENU_IDLE: {
|
||||
clearMainMenu();
|
||||
buttons_state_t cur_button = getButtonsState();
|
||||
|
||||
if (cur_button == BUTTONS_BOTH_PRESS) {
|
||||
cur_state = MENU_SELECT_PROFILE;
|
||||
} else if (cur_button == BUTTONS_UP_PRESS) {
|
||||
cur_state = MENU_INC_TEMP;
|
||||
} else if (cur_button == BUTTONS_DN_PRESS) {
|
||||
cur_state = MENU_DEC_TEMP;
|
||||
}
|
||||
} break;
|
||||
case MENU_SELECT_PROFILE: {
|
||||
debugprintln("getting thermal profile");
|
||||
profile_index = getProfile();
|
||||
cur_state = MENU_HEAT;
|
||||
} break;
|
||||
case MENU_HEAT: {
|
||||
if (!heat(max_temp_array[max_temp_index], profile_index)) {
|
||||
cancelledPB();
|
||||
coolDown();
|
||||
} else {
|
||||
coolDown();
|
||||
completed();
|
||||
}
|
||||
cur_state = MENU_IDLE;
|
||||
} break;
|
||||
case MENU_INC_TEMP: {
|
||||
if (max_temp_index < sizeof(max_temp_array) - 1) {
|
||||
max_temp_index++;
|
||||
debugprintln("incrementing max temp");
|
||||
setMaxTempIndex(max_temp_index);
|
||||
}
|
||||
cur_state = MENU_IDLE;
|
||||
} break;
|
||||
case MENU_DEC_TEMP: {
|
||||
if (max_temp_index > 0) {
|
||||
max_temp_index--;
|
||||
debugprintln("decrementing max temp");
|
||||
setMaxTempIndex(max_temp_index);
|
||||
}
|
||||
cur_state = MENU_IDLE;
|
||||
} break;
|
||||
}
|
||||
|
||||
// Change Display (left-side)
|
||||
showMainMenuLeft(x, y);
|
||||
|
||||
// Update Display (right-side)
|
||||
showMainMenuRight();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Main Logic -----------------------------------
|
||||
|
||||
void setup() {
|
||||
// Pin Direction control
|
||||
pinMode(MOSFET_PIN, OUTPUT);
|
||||
pinMode(UPSW_PIN, INPUT);
|
||||
pinMode(DNSW_PIN, INPUT);
|
||||
pinMode(TEMP_PIN, INPUT);
|
||||
pinMode(VCC_PIN, INPUT);
|
||||
pinMode(LED_GREEN_PIN, OUTPUT);
|
||||
|
||||
digitalWrite(LED_GREEN_PIN, HIGH);
|
||||
analogWrite(MOSFET_PIN, 255); // VERY IMPORTANT, DONT CHANGE!
|
||||
|
||||
attachInterrupt(DNSW_PIN, dnsw_change_isr, FALLING);
|
||||
attachInterrupt(UPSW_PIN, upsw_change_isr, FALLING);
|
||||
|
||||
Serial.begin(9600);
|
||||
|
||||
// Enable Fast PWM with no prescaler
|
||||
setFastPwm();
|
||||
setVREF();
|
||||
|
||||
// Start-up Diplay
|
||||
debugprintln("Showing startup");
|
||||
showLogo();
|
||||
|
||||
debugprintln("Checking sensors");
|
||||
// check onewire TEMP_PIN sensors
|
||||
setupSensors();
|
||||
|
||||
debugprintln("Checking first boot");
|
||||
if (isFirstBoot() || !validateCRC()) {
|
||||
doSetup();
|
||||
}
|
||||
|
||||
// Pull saved values from EEPROM
|
||||
max_temp_index = getMaxTempIndex();
|
||||
bed_resistance = getResistance();
|
||||
|
||||
debugprintln("Entering main menu");
|
||||
// Go to main menu
|
||||
mainMenu();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Not used
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user