#include <LiquidCrystal_I2C.h>
#include <Wire.h>
// Config LCD-Display
#define LCD_I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 4
#define SELECT_CURSOR 62
#define EDIT_CURSOR 0
byte EDIT_CHAR[8] = { B01000,
B01100,
B01110,
B01111,
B01110,
B01100,
B01000,};
// Config Buttons
#define BUTTON_UP 5
#define BUTTON_DOWN 2
#define BUTTON_SELECT 4
#define BUTTON_RETURN 3
#define MOVE_UP true
#define MOVE_DOWN false
#define BUTTON_DELAY 100
// PWM Output Pins
#define PWM_HEAT_PIN 6
#define PWM_FAN_PIN 9
// Menu Definition
#define MAINMENU 0
#define REFLOWMENU 10
#define REFLOWPROCESS 11
#define REFLOWCONFIG 20
#define REFLOWUPDATE_1 21
#define REFLOWUPDATE_2 22
#define REFLOWEDIT_1 211
#define REFLOWEDIT_2 221
#define COOLDOWN 30
#define COOLDOWNPROCESS 31
#define MIN_MENU 1
#define MAX_MENU 3
#define DISPLAY_INTERVAL 500
// Thermistor Definition
#define TEMP_INTERVAL 500 // Interval wie oft die Temperatur abgefragt wird (milliseunden)
#define TEMP_COUNT 10 // Je mehr abfragen, desto stabiler isr das Ergebnis, dauert aber länger
#define TEMP_SENSOR_PIN A0 // Pin für den 10kO NTC Wiederstand
#define NOMINAL_RESISTANCE 100000 // Wiederstand des NTC bei Nominaltemperatur
#define NOMINAL_TEMPERATURE 25 // Temperatur bei der der NTC den angegebenen Wiederstand hat
#define B_VALUE 3950 // Beta Coefficient(B25 aus Datenblatt des NTC)
#define REFERENCE_RESISTANCE 100000 // Wert des Wiederstands der mit dem NTC in Serie geschalten ist
int average[TEMP_COUNT]; // Array Variable für das Mitteln der Temperatur
float averageTemp = 0; // Variable für das Mitteln der Temperatur
float currentTemp; // Variable für die Berechnung der temperatur nach Steinhart
char currentTempStr[5];
unsigned long lastTempTime = 0; // Speichert die letzte Zeit (millis) der Temperaturabfrage
unsigned long currentTempTime = 0; // Erfasst die aktuelle Zeit für den Abfrageinterval
// Config Main Menu
char *MAIN_MENU[4] { "--Reflow Main Menu--",
" Reflow Start ",
" Reflow Config ",
" Reflow Cooldown "};
// Config Reflow List
char *REFLOW_LIST[] { "---Reflow Program---",
" Reflow 138\xDF" "C ",
" Reflow 183\xDF" "C ",
" Reflow Usersetting "};
// Cooldown Menu
char *COOLDOWN_MENU[] { "---Cool Down Menu---",
" Temperatur: \xDF" "C",
" ",
" Start "};
// Config Reflow List
char *REFLOW_CONFIG_1[] { "--Reflow Config xxx-",
" Preheat R: x.x \xDF" "C/s",
" Preheat T: xxx \xDF" "C ",
" Preheat D: xxx s "};
// Config Reflow List
char *REFLOW_CONFIG_2[] { "--Reflow Config xxx-",
" Reflow R: x.x \xDF" "C/s ",
" Reflow T: xxx \xDF" "C ",
" Reflow D: xxx s "};
// Reflow Config Settings [ 138° | 183° | USR ]
char *solderTemp[3] = {"138", "183", "USR"};
float preheatRamp[3] = {1.0, 1.5, 2.0};
char preheatRampStr[3];
byte preheatTemp[3] = {80, 100, 120};
char preheatTempStr[3];
byte preheatDuration[3] = {60, 80, 100};
char preheatDurationStr[3];
float reflowRamp[3] = {0.5, 1.0, 1.5};
char reflowRampStr[3];
byte reflowTemp[3] = {140, 180, 200};
char reflowTempStr[3];
byte reflowDuration[3] = {60, 80, 100};
char reflowDurationStr[3];
// Current Display Settings
char *currentDisplayMenu[4];
char menuLocator[] = {0, 0, 0};
byte currentCursor = 0;
int currentMenu = 0;
bool coolDownFan = false;
bool relowProcess = false;
bool updateMenuDisplay = true;
bool updateCursorPosition = true;
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COLUMNS, LCD_LINES);
//################################################################
// SETUP
//################################################################
void setup() {
// Init LCD
lcd.init();
lcd.backlight();
lcd.createChar(0, EDIT_CHAR);
// Define IO-Ports
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUTTON_SELECT, INPUT_PULLUP);
pinMode(BUTTON_RETURN, INPUT_PULLUP);
pinMode(PWM_HEAT_PIN, OUTPUT);
pinMode(PWM_FAN_PIN, OUTPUT);
pinMode(TEMP_SENSOR_PIN, INPUT); // Setzt den Pin des NTC Wiederstands als Eingang
// Print StartUP
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("---Reflow Control---");
lcd.setCursor(0, 2);
lcd.print(" O. Wagener ");
delay(1000);
// Initialize Main Menu
enterMenu();
}
//################################################################
// LOOP
//################################################################
void loop() {
switch (currentMenu) {
case COOLDOWN:
case COOLDOWNPROCESS:
// Update Display Menu
if (updateMenuDisplay){ updateMenu(); }
// Update Temperatur
// Erfasst die aktuelle Zeit für den Abfrageinterval
currentTempTime = millis();
// Löst bei erreichen der Intervalzeit die Temperaturberechnung aus
if(currentTempTime - lastTempTime >= TEMP_INTERVAL)
{
// speichert die Zeit der letzten Abfrage
lastTempTime = currentTempTime;
// Startet die Temperaturerfassungsroutine
tempCalculation();
lcd.setCursor(13, 1);
lcd.print(currentTempStr);
}
// Start Fan Cooldown
if (!digitalRead(BUTTON_SELECT)){ enterMenu(); }
// Return from Sub Menu
if (!digitalRead(BUTTON_RETURN)){ returnMenu(); }
break;
case MAINMENU:
case REFLOWMENU:
case REFLOWCONFIG:
case REFLOWUPDATE_1:
case REFLOWUPDATE_2:
// Update Display Menu
if (updateMenuDisplay){ updateMenu(); }
// Move Cursor Down
if (!digitalRead(BUTTON_DOWN)){ moveCursor(MOVE_DOWN); }
// Move Cursor Up
if (!digitalRead(BUTTON_UP)){ moveCursor(MOVE_UP); }
// Enter to Sub Menu
if (!digitalRead(BUTTON_SELECT)){ enterMenu(); }
// Return from Sub Menu
if (!digitalRead(BUTTON_RETURN)){ returnMenu(); }
break;
case REFLOWEDIT_1:
case REFLOWEDIT_2:
// Move Cursor Down
if (!digitalRead(BUTTON_DOWN)){ moveCursor(MOVE_DOWN); }
// Move Cursor Up
if (!digitalRead(BUTTON_UP)){ moveCursor(MOVE_UP); }
// Return from Sub Menu
if (!digitalRead(BUTTON_RETURN)){ returnMenu(); }
break;
}
// Proceed PID
}
//################################################################
// UPDATE MENU
//################################################################
void updateMenu() {
for(int i = 0; i < 4; i++){
lcd.setCursor(0, i);
lcd.print(currentDisplayMenu[i]);
}
switch (currentMenu) {
case REFLOWUPDATE_1:
lcd.setCursor(16, 0);
lcd.print(solderTemp[menuLocator[1]-1]);
dtostrf(preheatRamp[menuLocator[1]-1], 3, 1, preheatRampStr);
lcd.setCursor(12, 1);
lcd.print(preheatRampStr);
dtostrf(preheatTemp[menuLocator[1]-1], 3, 0, preheatTempStr);
lcd.setCursor(12, 2);
lcd.print(preheatTempStr);
dtostrf(preheatDuration[menuLocator[1]-1], 3, 0, preheatDurationStr);
lcd.setCursor(12, 3);
lcd.print(preheatDurationStr);
break;
case REFLOWUPDATE_2:
lcd.setCursor(16, 0);
lcd.print(solderTemp[menuLocator[1]-1]);
dtostrf(reflowRamp[menuLocator[1]-1], 3, 1, reflowRampStr);
lcd.setCursor(12, 1);
lcd.print(reflowRampStr);
dtostrf(reflowTemp[menuLocator[1]-1], 3, 0, reflowTempStr);
lcd.setCursor(12, 2);
lcd.print(reflowTempStr);
dtostrf(reflowDuration[menuLocator[1]-1], 3, 0, reflowDurationStr);
lcd.setCursor(12, 3);
lcd.print(reflowDurationStr);
break;
}
updateCursor(currentCursor, SELECT_CURSOR);
updateMenuDisplay = false;
}
//################################################################
// UPDATE CURSOR
//################################################################
void updateCursor(byte pos, char symbol) {
for(int i = 1; i < 4; i++){
lcd.setCursor(0, i);
lcd.print(" ");
}
lcd.setCursor(0, pos);
lcd.print(symbol);
}
//################################################################
// MOVE CURSOR
//################################################################
void moveCursor(bool direction) {
if(direction == MOVE_DOWN) {
if (currentMenu == REFLOWEDIT_1) {
switch (currentCursor) {
case 1:
if (preheatRamp[menuLocator[2]-1] > 0.5) {
preheatRamp[menuLocator[2]-1] -= 0.5;
dtostrf(preheatRamp[menuLocator[2]-1], 3, 1, preheatRampStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatRampStr);
}
break;
case 2:
if (preheatTemp[menuLocator[2]-1] > 30) {
preheatTemp[menuLocator[2]-1] -= 5;
dtostrf(preheatTemp[menuLocator[2]-1], 3, 0, preheatTempStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatTempStr);
}
break;
case 3:
if (preheatDuration[menuLocator[2]-1] > 10) {
preheatDuration[menuLocator[2]-1] -= 5;
dtostrf(preheatDuration[menuLocator[2]-1], 3, 0, preheatDurationStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatDurationStr);
}
break;
}
}
else {
currentCursor++;
if(currentMenu == REFLOWUPDATE_1 && currentCursor > MAX_MENU) {
currentMenu = REFLOWUPDATE_2;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_CONFIG_2[i]};}
updateMenuDisplay = true;
currentCursor = 1;
}
else if(currentCursor > MAX_MENU){currentCursor = MAX_MENU;}
updateCursor(currentCursor, SELECT_CURSOR);
}
delay(BUTTON_DELAY);
while (!digitalRead(BUTTON_DOWN));
delay(BUTTON_DELAY);
}
else if(direction == MOVE_UP) {
if (currentMenu == REFLOWEDIT_1) {
switch (currentCursor) {
case 1:
if (preheatRamp[menuLocator[2]-1] < 3.0) {
preheatRamp[menuLocator[2]-1] += 0.5;
dtostrf(preheatRamp[menuLocator[2]-1], 3, 1, preheatRampStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatRampStr);
}
break;
case 2:
if (preheatTemp[menuLocator[2]-1] < 150) {
preheatTemp[menuLocator[2]-1] += 5;
dtostrf(preheatTemp[menuLocator[2]-1], 3, 0, preheatTempStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatTempStr);
}
break;
case 3:
if (preheatDuration[menuLocator[2]-1] < 120) {
preheatDuration[menuLocator[2]-1] += 5;
dtostrf(preheatDuration[menuLocator[2]-1], 3, 0, preheatDurationStr);
lcd.setCursor(12, currentCursor);
lcd.print(preheatDurationStr);
}
break;
}
}
else {
currentCursor--;
if(currentMenu == REFLOWUPDATE_2 && currentCursor < MIN_MENU) {
currentMenu = REFLOWUPDATE_1;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_CONFIG_1[i]};}
updateMenuDisplay = true;
currentCursor = 3;
}
else if(currentCursor < MIN_MENU){currentCursor = MIN_MENU;}
updateCursor(currentCursor, SELECT_CURSOR);
}
delay(BUTTON_DELAY);
while(!digitalRead(BUTTON_UP));
delay(BUTTON_DELAY);
}
}
//################################################################
// ENTER MENU
//################################################################
void enterMenu() {
switch (currentMenu) {
// Currently in Main Menu
case MAINMENU:
switch (currentCursor) {
// Main Menu initialize
case 0:
menuLocator[0] = currentCursor;
currentCursor = 1;
currentMenu = MAINMENU;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {MAIN_MENU[i]};}
break;
// Go to Reflow Menu
case 1:
menuLocator[0] = currentCursor;
currentCursor = 1;
currentMenu = REFLOWMENU;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_LIST[i]};}
updateMenuDisplay = true;
break;
// Go to Reflow Config
case 2:
menuLocator[0] = currentCursor;
currentCursor = 1;
currentMenu = REFLOWCONFIG;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_LIST[i]};}
updateMenuDisplay = true;
break;
// Go to Cooldown Menu
case 3:
menuLocator[0] = currentCursor;
currentCursor = 3;
currentMenu = COOLDOWN;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {COOLDOWN_MENU[i]};}
updateMenuDisplay = true;
break;
}
break;
// Currently in Cooldown Menu
case COOLDOWN:
menuLocator[1] = currentCursor;
currentCursor = 3;
currentMenu = COOLDOWNPROCESS;
lcd.setCursor(0, currentCursor);
lcd.print(" Cooldown ");
digitalWrite(PWM_FAN_PIN, HIGH);
updateCursor(currentCursor, EDIT_CURSOR);
break;
// Currently in Reflow Config Menu
case REFLOWCONFIG:
menuLocator[1] = currentCursor;
currentCursor = 1;
currentMenu = REFLOWUPDATE_1;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_CONFIG_1[i]};}
updateMenuDisplay = true;
break;
case REFLOWUPDATE_1:
menuLocator[2] = currentCursor;
currentMenu = REFLOWEDIT_1;
updateCursor(currentCursor, EDIT_CURSOR);
break;
case REFLOWUPDATE_2:
menuLocator[2] = currentCursor;
currentMenu = REFLOWEDIT_2;
updateCursor(currentCursor, EDIT_CURSOR);
break;
// Reflow Start Menu
case REFLOWMENU:
break;
}
delay(BUTTON_DELAY);
while(!digitalRead(BUTTON_SELECT));
}
//################################################################
// RETURN CURSOR
//################################################################
void returnMenu() {
switch (currentMenu) {
case REFLOWMENU:
case REFLOWCONFIG:
case COOLDOWN:
currentCursor = menuLocator[0];
menuLocator[0] = 0;
currentMenu = MAINMENU;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {MAIN_MENU[i]};}
updateMenuDisplay = true;
break;
case COOLDOWNPROCESS:
currentCursor = menuLocator[1];
menuLocator[1] = 0;
currentMenu = COOLDOWN;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {COOLDOWN_MENU[i]};}
digitalWrite(PWM_FAN_PIN, LOW);
updateMenuDisplay = true;
break;
case REFLOWUPDATE_1:
case REFLOWUPDATE_2:
currentCursor = menuLocator[1];
menuLocator[1] = 0;
currentMenu = REFLOWCONFIG;
for(int i = 0; i < 4; i++) {currentDisplayMenu[i] = {REFLOW_LIST[i]};}
updateMenuDisplay = true;
break;
case REFLOWEDIT_1:
currentCursor = menuLocator[2];
menuLocator[2] = 0;
currentMenu = REFLOWUPDATE_1;
updateCursor(currentCursor, SELECT_CURSOR);
break;
case REFLOWEDIT_2:
currentCursor = menuLocator[2];
menuLocator[2] = 0;
currentMenu = REFLOWUPDATE_2;
updateCursor(currentCursor, SELECT_CURSOR);
break;
}
delay(BUTTON_DELAY);
while(!digitalRead(BUTTON_RETURN));
}
//################################################################
// TEMP CALCULATION
//################################################################
void tempCalculation()
{
// Nimmt N Abfragen in einer Reihe, mit einem kurzen delay
for (int i=0; i < TEMP_COUNT; i++)
{
average[i] = analogRead(TEMP_SENSOR_PIN);
delay(5);
}
// Mittelt alle Abfragen
averageTemp = 0;
for (int i=0; i < TEMP_COUNT; i++)
{
averageTemp += average[i];
}
averageTemp /= TEMP_COUNT;
// Umwandlung des Wertes in Wiederstand
averageTemp = 1023 / averageTemp - 1;
averageTemp = REFERENCE_RESISTANCE / averageTemp;
// Umrechnung aller Ergebnisse in die Temperatur mittels einer Steinhard Berechnung
currentTemp = averageTemp / NOMINAL_RESISTANCE; // (R/Ro)
currentTemp = log(currentTemp); // ln(R/Ro)
currentTemp /= B_VALUE; // 1/B * ln(R/Ro)
currentTemp += 1.0 / (NOMINAL_TEMPERATURE + 273.15); // + (1/To)
currentTemp = 1.0 / currentTemp; // Invertieren
currentTemp -= 273.15; // Umwandeln in °C
dtostrf(currentTemp, 5, 1, currentTempStr);
}