Initial commit.

master
emeryth 2018-03-18 18:10:32 +01:00
commit de400a11fb
1 changed files with 725 additions and 0 deletions

725
ReflowOven2.ino Normal file
View File

@ -0,0 +1,725 @@
/*******************************************************************************
* ControLeo Reflow Oven Controller (v2)
* Author: Keith Rome
* Website: www.wintellect.com/blogs/krome
*
* Based on the original ControLeo Reflow Oven Controller sample by Peter Easton @ whizoo.com
*
* This version is a near-complete rewrite that uses a phase-based profile system with
* minimum/maximum times per phase. Also supports additional runtime data on the display
* (time elapsed in current phase), triggering the buzzer alarm at various phase transitions,
* and sends data to the Serial console to help with fine-tuning the reflow profile for
* your individual oven hardware. Also uses EEPROM nonvolatile memory on ControLeo to
* remember the last profile that was selected when the oven is powered back up again.
*
* Thanks to Rocketscream for the original code using the PID library. The code has
* been heavily modified (removing PID) to give finer control over individual heating
* elements.
*
* This is an example of a reflow oven controller. The reflow curve below is for a
* lead-free profile, but this code supports both leaded and lead-free profiles.
*
* Temperature (Degree Celcius) Magic Happens Here!
* 245-| x x
* | x x
* | x x
* | x x
* 200-| x x
* | x | | x
* | x | | x
* | x | |
* 150-| x | |
* | x | | |
* | x | | |
* | x | | |
* | x | | |
* | x | | |
* | x | | |
* 30 -| x | | |
* |< 60 - 90 s >|< 90 - 120 s >|< 90 - 120 s >|
* | Preheat Stage | Soaking Stage | Reflow Stage | Cool
* 0 |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
* Time (Seconds)
*
* Here is an example serial log generated by running a complete cycle using
* the default lead-bearing solder profile:
*
* ControLeo Reflow Oven v2 firmware startup
* Last selected profile loading from eeprom: 1
* Advancing to next profile
* Advancing to next profile
* Button Pressed: CONTROLEO_BUTTON_BOTTOM
* Starting Profile: Leaded solder
* Leaving Phase: Idle (0), Elapsed: 8s
* Entering Phase: Pre-heat (1), Exit Temp:145C, Min Time: 0s, Max: 0s, Ideal: 90s
* **Exit Temperature Reached, Time in phase: 173s, Temperature: 145.00C
* Leaving Phase: Pre-heat (1), Elapsed: 173s
* Entering Phase: Soak (2), Exit Temp:180C, Min Time: 30s, Max: 120s, Ideal: 30s
* **Duration/Temperature Reached, Time in phase: 55s, Temperature: 180.00C
* Leaving Phase: Soak (2), Elapsed: 55s
* Entering Phase: Liquidus (3), Exit Temp:210C, Min Time: 30s, Max: 90s, Ideal: 60s, alarm sounds on exit
* **Duration/Temperature Reached, Time in phase: 51s, Temperature: 210.00C
* Leaving Phase: Liquidus (3), Elapsed: 51s
* Entering Phase: Reflow (4), Exit Temp:180C (falling), Min Time: 30s, Max: 90s, Ideal: 60s
* *** Alarm ***
* **Duration/Temperature Reached, Time in phase: 69s, Temperature: 180.00C
* Leaving Phase: Reflow (4), Elapsed: 69s
* Entering Phase: Cooling (5), Exit Temp:50C (falling), Min Time: 0s, Max: 0s, Ideal: 0s
* **Exit Temperature Reached, Time in phase: 679s, Temperature: 50.00C
* Profile Finished, Total Elapsed Time: 1028s, Peak Temperature Observed: 212.25C
* Leaving Phase: Cooling (5), Elapsed: 679s
* Entering Phase: Idle (0), Exit Temp:0C, Min Time: 0s, Max: 0s, Ideal: 0s
*
*
*
* This firmware builds on the work of other talented individuals:
* ==========================================
* Rocketscream (www.rocketscream.com)
* Produced the Arduino reflow oven shield ands code that inspired this project.
*
* ==========================================
* Limor Fried of Adafruit (www.adafruit.com)
* Author of Arduino MAX6675 library. Adafruit has been the source of tonnes of
* tutorials, examples, and libraries for everyone to learn.
*
* Disclaimer
* ==========
* Dealing with high voltage is a very dangerous act! Please make sure you know
* what you are dealing with and have proper knowledge before hand. Your use of
* any information or materials on this reflow oven controller is entirely at
* your own risk, for which we shall not be liable.
*
* Released under WTFPL license.
*
* Revision Description
* ======== ===========
* 1.00 Initial public release.
* 2.00 Public release.
*******************************************************************************/
// ***** INCLUDES *****
#include <Wire.h>
//#include <ControLeo2.h>
#include "ControLeo2_MAX31855.h"
#include <EEPROM.h>
#include "max6675.h"
#include <LiquidCrystal.h>
// Defines for the 2 buttons
#define CONTROLEO_BUTTON_TOP_PIN 3 // Top button is on D11
#define CONTROLEO_BUTTON_BOTTOM_PIN 2 // Bottom button is on D2
#define CONTROLEO_BUTTON_NONE 0
#define CONTROLEO_BUTTON_TOP 1 // S1
#define CONTROLEO_BUTTON_BOTTOM 2 // S2
// The Buzzer is on D13
#define CONTROLEO_BUZZER_PIN 4
// ***** CONSTANTS *****
#define CLOCK_INTERVAL 100 // how frequently the state machine checks for advancements (ms)
#define SAMPLE_INTERVAL 500 // how frequently temperature measurements are updated (ms)
#define CYCLE_INTERVAL 1000 // how frequently the oven cycles through the current phase's heating pattern (ms per bit)
#define MAX_START_TEMP 50 // maximum temperature where a new reflow session will be allowed to start
#define NUM_PHASES 4 // number of phases in a profile (always assume a final "cooling" phase)
#define NUM_HEATERS 3 // number of heaters installed
#define DEFAULT_PROFILE 0 // default temperature profile at startup (overridden by EEPROM)
#define BUZZER_DURATION 250 // how long to play buzzer sounds
#define ADDR_CURR_PROFILE 0 // address in EEPROM for storing the current profile ID
#define OFF 0
#define ON 1
// ***** PRODUCT IDENTIFICATION *****
const char* BRAND_ID = "ControLeo";
const char* PRODUCT_ID = "Reflow Oven v2";
int thermoDO = 8;
int thermoCS = 9;
int thermoCLK = 10;
const int rs = A3, en = A2, d4 = A1, d5 = A0, d6 = 15, d7 = 14;
// ***** HARDWARE INTERFACES *****
struct Hardware {
unsigned long DisableBuzzerAt;
int HeaterPins[NUM_HEATERS]; // Pin assignments
//ControLeo2_LiquidCrystal LCD; // Specify LCD interface
LiquidCrystal LCD;
//ControLeo2_MAX31855 Thermocouple; // Specify MAX31855 thermocouple interface
MAX6675 Thermocouple;
};
Hardware hardware = { 0, {
7, // upper heater pin
5, // lower heater pin
6 // booster heater pin
},
//ControLeo2_LiquidCrystal(),
LiquidCrystal(rs, en, d4, d5, d6, d7),
MAX6675(thermoCLK, thermoCS, thermoDO)};
// ***** PROFILES *****
// Each element of this array is a 8-second window for an element. For example, if the value is 0b11001111 then
// the element will be on for 2 seconds, off for 2 seconds then on for 4 seconds. This pattern will keep
// repeating itself until the temperature rises through the temperate transition point given in tempPoints. This
// gives fine control over each element and has the following benefits:
// 1. Prevents individual elements from getting too hot, perhaps burning insulation.
// 2. Ensures heat comes from the right part of the oven at the right time
// 3. Helps overall current draw by being able to turn off some elements while turning others on
// ==================== YOU SHOULD TUNE THESE VALUES TO YOUR REFLOW OVEN!!! ====================
enum CrossingDirection {
RISE,
FALL
};
struct ReflowPhase {
char* Name;
int ExitTemperatureC;
CrossingDirection RisingOrFalling;
int MinDurationS;
int MaxDurationS;
int TargetDurationS;
int HeaterPattern[NUM_HEATERS];
boolean AlarmOnExit;
};
ReflowPhase idlePhase = { "Idle", 0, RISE, 0, 0, 0, { 0b00000000, 0b00000000, 0b00000000 }, false };
ReflowPhase coolingPhase = { "Cooling", MAX_START_TEMP, FALL, 0, 0, 0, { 0b00000000, 0b00000000, 0b00000000 }, false };
struct ReflowProfile {
char* Name;
ReflowPhase Phases[NUM_PHASES];
};
ReflowProfile profiles[] = {
{
"Lead-free solder",
{ // Zone Exit(C) Direction Min(S) Max(S) Tgt(S) Upper Lower Boost Alarm
{ "Pre-heat", 150, RISE, 0, 0, 90, { 0b11001101, 0b10111110, 0b01010011 }, false },
{ "Soak", 205, RISE, 30, 120, 30, { 0b01000100, 0b10101011, 0b00010000 }, false },
{ "Liquidus", 235, RISE, 30, 90, 60, { 0b11011110, 0b10111111, 0b01101101 }, false },
{ "Reflow", 225, FALL, 30, 90, 60, { 0b00010001, 0b01000100, 0b00000000 }, true },
}
},
{
"Leaded solder",
{ // Zone Exit(C) Direction Min(S) Max(S) Tgt(S) Upper Lower Boost Alarm
{ "Pre-heat", 145, RISE, 0, 0, 90, { 0b11001101, 0b01110110, 0b01010011 }, false },
{ "Soak", 180, RISE, 30, 120, 30, { 0b01000100, 0b10101011, 0b00010001 }, false },
{ "Liquidus", 210, RISE, 30, 90, 60, { 0b10111110, 0b11110111, 0b00101000 }, true },
{ "Reflow", 180, FALL, 30, 90, 60, { 0b01000000, 0b00011000, 0b00000100 }, false },
}
},
};
#define NUM_PROFILES (sizeof(profiles)/sizeof(ReflowProfile)) //array size is computed from initialized data
// ***** STATE TRACKING *****
struct OvenState {
int SelectedProfile;
boolean IsFaulted;
boolean IsActive;
double TemperatureC;
double PeakTemperatureC;
unsigned long LastClocked;
unsigned long NextClock;
unsigned long NextSample;
unsigned long NextCycle;
int ActiveHeatCycle;
int ActivePhase;
unsigned long EnteredCurrentPhase;
int SecInPhase;
unsigned long ActiveSince;
ReflowPhase PhaseSchedule[NUM_PHASES + 2];
};
OvenState currentState = {
-1, false, false, 0, 0,
0, CLOCK_INTERVAL, SAMPLE_INTERVAL, CYCLE_INTERVAL,
0, 0, 0, 0, 0,
{ idlePhase, idlePhase, idlePhase, idlePhase, idlePhase, coolingPhase} };
void setup()
{
// *********** Start of ControLeo2 initialization ***********
// Set up the buzzer and buttons
pinMode(CONTROLEO_BUZZER_PIN, OUTPUT);
pinMode(CONTROLEO_BUTTON_TOP_PIN, INPUT_PULLUP);
pinMode(CONTROLEO_BUTTON_BOTTOM_PIN, INPUT_PULLUP);
// Set the relays as outputs and turn them off
// The relay outputs are on D4 to D7 (4 outputs)
for (int i=4; i<8; i++) {
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
// Set up the LCD's number of rows and columns
// lcd.begin(16, 2);
// Create the degree symbol for the LCD
// unsigned char degree[8] = {12,18,18,12,0,0,0,0};
// lcd.createChar(0, degree);
// *********** End of ControLeo2 initialization ***********
InitializeDisplay();
Serial.begin(9600);
Serial.print(BRAND_ID); Serial.print(" "); Serial.print(PRODUCT_ID); Serial.println(" firmware startup");
// configure heater GPIO
for (int i = 0; i < NUM_HEATERS; i++) {
pinMode(hardware.HeaterPins[i], OUTPUT);
digitalWrite(hardware.HeaterPins[i], LOW);
}
int lastProfile = EEPROM.read(ADDR_CURR_PROFILE);
if (lastProfile == 255) {
lastProfile = DEFAULT_PROFILE;
Serial.print("Default profile loading: "); Serial.println(DEFAULT_PROFILE);
} else {
Serial.print("Last selected profile loading from eeprom: "); Serial.println(lastProfile);
}
while (currentState.SelectedProfile != lastProfile) {
AdvanceProfile(true);
}
DisplaySplashScreen();
ResetState();
}
void loop()
{
unsigned long now = millis();
// check for clock overflow (should only happen if you leave the oven on for 50+ days)
if (currentState.LastClocked > now) {
currentState.LastClocked = 0;
currentState.NextClock = CLOCK_INTERVAL;
currentState.NextCycle = CYCLE_INTERVAL;
currentState.NextSample = SAMPLE_INTERVAL;
currentState.EnteredCurrentPhase = 0;
}
// check to see if we should capture a new temperature sample
if (currentState.NextSample <= now) {
CaptureTemperatureSample();
currentState.NextSample = now + SAMPLE_INTERVAL;
}
// should we check for phase transition this cycle?
if (currentState.NextClock <= now) {
CheckForPhaseTransition();
currentState.LastClocked = now;
currentState.NextClock = now + CLOCK_INTERVAL;
}
// advance the heater pattern
if (currentState.NextCycle <= now) {
AdvanceHeatingCycle();
currentState.NextCycle = now + CYCLE_INTERVAL;
}
// always check for button press so there is no unnecessary lag
// also, in the unlikely event that a phase transition and button press happen in the same
// cycle, the button press will always get the last s
CheckForButtonPress();
// turn off the buzzer if we need to
if (hardware.DisableBuzzerAt != 0 && hardware.DisableBuzzerAt <= now) {
EnableBuzzer(OFF);
}
delay(10);
}
void InitializeDisplay()
{
// Degree symbol for LCD
unsigned char degree[8] = {140,146,146,140,128,128,128,128};
hardware.LCD.begin(16, 2);
hardware.LCD.createChar(0, degree);
hardware.LCD.clear();
delay(3000);
}
void DisplaySplashScreen()
{
hardware.LCD.print(BRAND_ID);
hardware.LCD.setCursor(0, 1);
hardware.LCD.print(PRODUCT_ID);
delay(3000);
hardware.LCD.clear();
DisplayProfile();
UpdateDisplayedTemperature();
EnableBuzzer(ON);
}
void PrintAt(int line, int column, int maxWidth, boolean rightAlign, char*msg)
{
int msgLen = strlen(msg);
if (msgLen > maxWidth)
msgLen = maxWidth;
int startCol = column;
if (rightAlign && (msgLen < maxWidth))
startCol += (maxWidth - msgLen);
hardware.LCD.setCursor(column, line);
for (int i = 0; i < (startCol - column); i++)
hardware.LCD.print(" ");
hardware.LCD.print(msg);
for (int i = startCol + msgLen; i < maxWidth; i++)
hardware.LCD.print(" ");
}
void WriteAt(int line, int column, int glyph)
{
hardware.LCD.setCursor(column, line);
hardware.LCD.write(glyph);
}
void DisplayProfile()
{
PrintAt(0, 0, 16, false, profiles[currentState.SelectedProfile].Name);
}
void DisplayPhase()
{
PrintAt(0, 0, 11, false, currentState.PhaseSchedule[currentState.ActivePhase].Name);
}
void DisplayElapsedInPhase(boolean forceUpdate)
{
int secInPhase = (millis() - currentState.EnteredCurrentPhase) / 1000;
if (forceUpdate || (secInPhase != currentState.SecInPhase)) {
currentState.SecInPhase = secInPhase;
char msg[6];
sprintf(msg, " %ds", secInPhase);
PrintAt(0, 11, 5, true, msg);
}
}
void UpdateDisplayedTemperature()
{
// if the thermocouple is in a faulted state, then display error
if (currentState.IsFaulted) {
PrintAt(1, 0, 16, false, "fault detected");
return;
}
// Print current temperature
char buffer[12];
dtostrf(currentState.TemperatureC, 3, 1, buffer);
int sz = strlen(buffer);
char msg[12];
sprintf(msg, "%s %s", buffer, "C");
PrintAt(1, 0, 12, false, msg);
Serial.println(currentState.TemperatureC);
// draw the special glyph for degree symbol
WriteAt(1, sz, 0);
}
void EnableBuzzer(uint8_t onOrOff)
{
//analogWrite(CONTROLEO_BUZZER_PIN, onOrOff? 15:0);
digitalWrite(CONTROLEO_BUZZER_PIN, onOrOff? 1:0);
if (onOrOff == ON) {
Serial.println("*** Alarm ***");
hardware.DisableBuzzerAt = millis() + BUZZER_DURATION;
} else {
hardware.DisableBuzzerAt = 0;
}
}
void CaptureTemperatureSample()
{
// Read current temperature
//double currentTemperature = hardware.Thermocouple.readThermocouple(CELSIUS);
double currentTemperature = hardware.Thermocouple.readCelsius();
// don't bother doing anything unless it actually changes
if (currentState.TemperatureC == currentTemperature) return;
// Check for thermocouple problem
if (currentTemperature == FAULT_OPEN || currentTemperature == FAULT_SHORT_GND || currentTemperature == FAULT_SHORT_VCC) {
// There is a problem with the thermocouple
Serial.print("Thermocouple Fault: ");
if (currentTemperature == FAULT_OPEN) Serial.println("FAULT_OPEN");
if (currentTemperature == FAULT_SHORT_GND) Serial.println("FAULT_SHORT_GND");
if (currentTemperature == FAULT_SHORT_VCC) Serial.println("FAULT_SHORT_VCC");
currentState.IsFaulted = true;
End(false);
} else {
currentState.IsFaulted = false;
}
// track peaks
if (currentTemperature > currentState.PeakTemperatureC) {
currentState.PeakTemperatureC = currentTemperature;
}
// update display
currentState.TemperatureC = currentTemperature;
UpdateDisplayedTemperature();
}
void CheckForButtonPress()
{
int button = GetRisingEdgeOfButtonPress();
if (button == CONTROLEO_BUTTON_NONE) return;
if (button == CONTROLEO_BUTTON_TOP) {
Serial.println("Button Pressed: CONTROLEO_BUTTON_TOP");
} else {
Serial.println("Button Pressed: CONTROLEO_BUTTON_BOTTOM");
}
if (button == CONTROLEO_BUTTON_TOP) {
// top button was pressed
// if the oven is idle, the top button will cycle through the known profiles
// but is ignored while the oven is operating
if (!currentState.IsActive) {
AdvanceProfile(false);
}
}
if (button == CONTROLEO_BUTTON_BOTTOM) {
// bottom button was pressed
// bottom button starts/stops the oven
if (currentState.IsActive) {
End(true);
} else {
Start();
}
}
}
#define DEBOUNCE_DETECT 50 // minimum time button needs be pressed
#define DEBOUNCE_RESET 500 // minimum time between button signals
int GetRisingEdgeOfButtonPress()
{
static int heldButton = CONTROLEO_BUTTON_NONE;
static int lastEvent = CONTROLEO_BUTTON_NONE;
static unsigned long lastReset = 0;
static unsigned long heldSince = 0;
unsigned long now = millis();
int signal = CONTROLEO_BUTTON_NONE;
int buttonEvent = CONTROLEO_BUTTON_NONE;
// Read the current button status
signal = CONTROLEO_BUTTON_NONE;
if (digitalRead(CONTROLEO_BUTTON_TOP_PIN) == LOW)
signal = CONTROLEO_BUTTON_TOP;
else if (digitalRead(CONTROLEO_BUTTON_BOTTOM_PIN) == LOW)
signal = CONTROLEO_BUTTON_BOTTOM;
if (heldButton == signal && heldButton != CONTROLEO_BUTTON_NONE) {
// a button is currently pressed, but is it pressed for long enough?
if ((lastEvent == CONTROLEO_BUTTON_NONE) && (now - heldSince >= DEBOUNCE_DETECT)) {
// trigger it only once
buttonEvent = signal;
lastEvent = signal;
}
}
if (heldButton != CONTROLEO_BUTTON_NONE && signal == CONTROLEO_BUTTON_NONE) {
// button was released
heldButton = CONTROLEO_BUTTON_NONE;
lastReset = now;
heldSince = 0;
lastEvent = CONTROLEO_BUTTON_NONE;
}
if (heldButton == CONTROLEO_BUTTON_NONE && signal != CONTROLEO_BUTTON_NONE && (now - lastReset >= DEBOUNCE_RESET)) {
// new button is being pressed
heldButton = signal;
heldSince = now;
}
return buttonEvent;
}
void AdvanceHeatingCycle()
{
int mask = 0b10000000 >> currentState.ActiveHeatCycle;
if (currentState.IsActive) {
currentState.ActiveHeatCycle++;
if (currentState.ActiveHeatCycle > 7) {
currentState.ActiveHeatCycle = 0;
}
} else {
mask = 0b00000000; // disable all heaters
}
// toggle heater GPIO pins
ReflowPhase currentPhase = currentState.PhaseSchedule[currentState.ActivePhase];
for (int heaterIx = 0; heaterIx < NUM_HEATERS; heaterIx++) {
if (currentPhase.HeaterPattern[heaterIx] & mask) {
digitalWrite(hardware.HeaterPins[heaterIx], HIGH);
} else {
digitalWrite(hardware.HeaterPins[heaterIx], LOW);
}
}
}
void CheckForPhaseTransition()
{
if (!currentState.IsActive) return;
DisplayElapsedInPhase(false);
// can't leave idle without explicit start
if (currentState.ActivePhase == 0) return;
ReflowPhase currentPhase = currentState.PhaseSchedule[currentState.ActivePhase];
int timeInPhase = (millis() - currentState.EnteredCurrentPhase) / 1000;
CrossingDirection dir = currentPhase.RisingOrFalling;
// check for phase change due to temperature rise
if (dir == RISE) {
if (currentPhase.ExitTemperatureC <= currentState.TemperatureC) {
if (currentPhase.MinDurationS == 0) {
MoveToNextPhase("Exit Temperature Reached", timeInPhase);
} else {
// but only allow phase change if any minimum time spent in phase has been met
if (timeInPhase > currentPhase.MinDurationS) {
MoveToNextPhase("Duration/Temperature Reached", timeInPhase);
}
}
}
}
// check for phase change due to temperature fall
if (dir == FALL) {
if (currentPhase.ExitTemperatureC >= currentState.TemperatureC) {
if (currentPhase.MinDurationS == 0) {
MoveToNextPhase("Exit Temperature Reached", timeInPhase);
} else {
// but only allow phase change if any minimum time spent in phase has been met
if (timeInPhase > currentPhase.MinDurationS) {
MoveToNextPhase("Duration/Temperature Reached", timeInPhase);
}
}
}
}
// check for phase change due to timer overrun
if (currentPhase.MaxDurationS > 0) {
if (timeInPhase >= currentPhase.MaxDurationS) {
MoveToNextPhase("Max Duration Exceeded", timeInPhase);
}
}
}
void MoveToNextPhase(char* reason, int timeInPhase)
{
Serial.print("**");
Serial.print(reason);
Serial.print(", Time in phase: ");
Serial.print(timeInPhase);
Serial.print("s, Temperature: ");
Serial.print(currentState.TemperatureC);
Serial.println("C");
TransitionToPhase(currentState.ActivePhase + 1);
}
void Start()
{
Serial.print("Starting Profile: "); Serial.println(profiles[currentState.SelectedProfile].Name);
currentState.PeakTemperatureC = 0; // reset
currentState.ActiveSince = millis(); // reset
currentState.IsActive = true;
TransitionToPhase(1);
}
void End(boolean isUserInitiated)
{
if (isUserInitiated) {
Serial.print("Profile Aborted by User");
} else {
Serial.print("Profile Finished");
}
Serial.print(", Total Elapsed Time: "); Serial.print((millis() - currentState.ActiveSince) / 1000); Serial.print("s");
Serial.print(", Peak Temperature Observed: "); Serial.print(currentState.PeakTemperatureC); Serial.println("C");
currentState.PeakTemperatureC = 0; // reset
currentState.ActiveSince = millis(); // reset
ResetState();
}
void ResetState()
{
CaptureTemperatureSample();
if (currentState.TemperatureC > MAX_START_TEMP) {
currentState.IsActive = true;
TransitionToPhase(NUM_PHASES + 1);
} else {
TransitionToPhase(0);
currentState.IsActive = false;
// Return to profile display
DisplayProfile();
}
}
void TransitionToPhase(int phase)
{
if (phase == currentState.ActivePhase) return;
if (phase > NUM_PHASES + 1) {
// transition beyond cooling phase
End(false);
return;
}
int timeInLastPhase = (millis() - currentState.EnteredCurrentPhase) / 1000;
ReflowPhase oldPhase = currentState.PhaseSchedule[currentState.ActivePhase];
ReflowPhase newPhase = currentState.PhaseSchedule[phase];
Serial.print("Leaving Phase: "); Serial.print(oldPhase.Name);
Serial.print(" ("); Serial.print(currentState.ActivePhase);
Serial.print("), Elapsed: "); Serial.print(timeInLastPhase); Serial.println("s");
Serial.print("Entering Phase: "); Serial.print(newPhase.Name);
Serial.print(" ("); Serial.print(phase);
Serial.print("), Exit Temp:"); Serial.print(newPhase.ExitTemperatureC); Serial.print("C");
if (newPhase.RisingOrFalling == FALL) Serial.print(" (falling)");
Serial.print(", Min Time: "); Serial.print(newPhase.MinDurationS);
Serial.print("s, Max: "); Serial.print(newPhase.MaxDurationS);
Serial.print("s, Ideal: "); Serial.print(newPhase.TargetDurationS); Serial.print("s");
if (newPhase.AlarmOnExit) Serial.print(", alarm sounds on exit");
Serial.println();
currentState.ActivePhase = phase;
currentState.EnteredCurrentPhase = millis();
DisplayPhase();
DisplayElapsedInPhase(true);
if (oldPhase.AlarmOnExit) {
EnableBuzzer(ON);
}
}
void AdvanceProfile(boolean silently)
{
Serial.println("Advancing to next profile");
// select the next profile
currentState.SelectedProfile++;
if (currentState.SelectedProfile >= NUM_PROFILES) {
currentState.SelectedProfile = 0;
}
// copy the phases to phase schedule
ReflowProfile newProfile = profiles[currentState.SelectedProfile];
for (int phaseIx = 0; phaseIx < NUM_PHASES; phaseIx++) {
// first phase in schedule is always the "idle" phase, and last is always "cooling"
currentState.PhaseSchedule[phaseIx +1] = newProfile.Phases[phaseIx];
}
if (!silently) {
// update the UI
DisplayProfile();
// remember which profile was last selected
EEPROM.write(ADDR_CURR_PROFILE, currentState.SelectedProfile);
}
}