updated control loop + draw target graph +temps

This commit is contained in:
2021-08-21 22:15:43 +02:00
parent eefe46cae8
commit d0fbc713a5
13 changed files with 1280 additions and 95 deletions

View File

@@ -88,10 +88,8 @@
#define SMOOTH_FONT
// Nucleo-F767ZI has a ~216MHZ CPU clock, this is divided by 4, 8, 16 etc
//#define SPI_FREQUENCY 27000000 // 27MHz SPI clock
#define SPI_FREQUENCY 55000000 // 55MHz is over-clocking ILI9341 but seems to work reliably!
#define SPI_FREQUENCY 27000000 // 27MHz SPI clock
//#define SPI_FREQUENCY 55000000 // 55MHz is over-clocking ILI9341 but seems to work reliably!
#define SPI_READ_FREQUENCY 15000000 // Reads need a slower SPI clock, probably ends up at 13.75MHz (CPU clock/16)

View File

@@ -18,11 +18,11 @@ monitor_speed = 115200
lib_deps =
yuriisalimov/MAX6675_Thermocouple@^2.0.2
bodmer/TFT_eSPI@^2.3.70
br3ttb/PID@^1.2.1
;siruli/MAX6675@^2.1.0
lib_ldf_mode = deep+
build_flags =
build_flags =
-D USER_SETUP_LOADED=1
-include include/ILI9341_STM32.h
-include include/ILI9341_STM32.h
-D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
-D USBCON
-D PIO_FRAMEWORK_ARDUINO_USB_FULLSPEED_FULLMODE

View File

@@ -11,4 +11,12 @@
#define THERM_SO PB4
#define THERM_CL PB6
#define HEAT_OUT PA2
#define HEAT_OUT PA2
#define BUZZER A4
#define BUTTON_SELECT 0
#define BUTTON_AXIS_Y 0
#define BUTTON_AXIS_X 0
#define BUTTON_MENU 0
#define BUTTON_BACK 0

View File

@@ -1,10 +1,281 @@
#include "button.h"
#include "controlloop.h"
#define DEBOUNCE_MS 100
Button start_btn(0, false, DEBOUNCE_MS, true);
Button stop_btn(0, false, DEBOUNCE_MS, true);
Button set_btn(0, false, DEBOUNCE_MS, true);
Button *buttons[] = {&start_btn, &stop_btn, &set_btn};
void setButton(uint8_t index, bool state)
{
buttons[index]->setButton(state);
}
void readAllButtons(void)
{
for (auto &&button : buttons)
{
button->read();
}
}
void initButton(void)
{
}
void handleButton(void)
{
readAllButtons();
if (start_btn.isPressed())
{
setReflowStatus(true);
}
if (stop_btn.isPressed())
{
setReflowStatus(false);
}
if (set_btn.isPressed())
{
//enter menu
}
}
/*----------------------------------------------------------------------*
* Arduino Button Library v1.0 *
* Jack Christensen May 2011, published Mar 2012 *
* *
* Library for reading momentary contact switches like tactile button *
* switches. Intended for use in state machine constructs. *
* Use the read() function to read all buttons in the main loop, *
* which should execute as fast as possible. *
* *
* This work is licensed under the Creative Commons Attribution- *
* ShareAlike 3.0 Unported License. To view a copy of this license, *
* visit http://creativecommons.org/licenses/by-sa/3.0/ or send a *
* letter to Creative Commons, 171 Second Street, Suite 300, *
* San Francisco, California, 94105, USA. *
*----------------------------------------------------------------------*/
#include "Button.h"
/*----------------------------------------------------------------------*
* Button(pin, puEnable, invert, dbTime) instantiates a button object. *
* pin Is the Arduino pin the button is connected to. *
* puEnable Enables the AVR internal pullup resistor if != 0 (can also *
* use true or false). *
* invert If invert == 0, interprets a high state as pressed, low as *
* released. If invert != 0, interprets a high state as *
* released, low as pressed (can also use true or false). *
* dbTime Is the debounce time in milliseconds. *
* *
* (Note that invert cannot be implied from puEnable since an external *
* pullup could be used.) *
*----------------------------------------------------------------------*/
Button::Button(uint8_t pin, uint8_t invert, uint32_t dbTime, bool isTouch) : _isTouchButton(isTouch)
{
if (!_isTouchButton)
{
_pin = pin;
pinMode(_pin, INPUT_PULLUP);
_state = digitalRead(_pin);
}
_invert = invert;
_dbTime = dbTime;
if (_invert != 0)
_state = !_state;
_time = millis();
_lastState = _state;
_changed = 0;
_lastTime = _time;
_lastChange = _time;
}
/*----------------------------------------------------------------------*
* read() returns the state of the button, 1==pressed, 0==released, *
* does debouncing, captures and maintains times, previous states, etc. *
*----------------------------------------------------------------------*/
uint8_t Button::read(void)
{
static uint32_t ms;
static uint8_t pinVal;
ms = millis();
if (_isTouchButton)
{
pinVal = _touchState;
}
else
{
pinVal = digitalRead(_pin);
}
if (_invert != 0)
pinVal = !pinVal;
if (ms - _lastChange < _dbTime)
{
_lastTime = _time;
_time = ms;
_changed = 0;
return _state;
Serial.println("State is: " + _state);
}
else
{
_lastTime = _time;
_lastState = _state;
_state = pinVal;
_time = ms;
if (_state != _lastState)
{
_lastChange = ms;
_changed = 1;
}
else
{
_changed = 0;
}
return _state;
#ifdef DEBUG
Serial.println("State is: " + _state);
#endif
}
}
void Button::setButton(bool state)
{
_touchState = state;
}
uint8_t Button::readAxis()
{
static uint32_t ms;
static uint8_t pinVal;
static uint16_t val;
ms = millis();
val = analogRead(_pin);
if (val > 3900)
{
pinVal = 1;
_axis = DPAD_V_FULL;
#ifdef DEBUG
Serial.println("Value is: " + val);
#endif
}
else if (val > 1500 && val < 2000)
{
pinVal = 1;
_axis = DPAD_V_HALF;
#ifdef DEBUG
Serial.println("Value is: " + val);
#endif
}
else
{
pinVal = 0;
_axis = DPAD_V_NONE;
}
if (_invert == 0)
pinVal = !pinVal;
if (ms - _lastChange < _dbTime)
{
_lastTime = _time;
_time = ms;
_changed = 0;
return _state;
}
else
{
_lastTime = _time;
_lastState = _state;
_state = pinVal;
_time = ms;
if (_state != _lastState)
{
_lastChange = ms;
_changed = 1;
}
else
{
_changed = 0;
}
return _state;
}
return _state && _changed;
}
/*----------------------------------------------------------------------*
* isPressed() and isReleased() check the button state when it was last *
* read, and return false (0) or true (!=0) accordingly. *
* These functions do not cause the button to be read. *
*----------------------------------------------------------------------*/
uint8_t Button::isPressed(void)
{
return _state == 0 ? 0 : 1;
}
uint8_t Button::isAxisPressed(void)
{
if (_state)
return _axis;
else
return 0;
}
uint8_t Button::isReleased(void)
{
return _state == 0 ? 1 : 0;
}
/*----------------------------------------------------------------------*
* wasPressed() and wasReleased() check the button state to see if it *
* changed between the last two reads and return false (0) or *
* true (!=0) accordingly. *
* These functions do not cause the button to be read. *
*----------------------------------------------------------------------*/
uint8_t Button::wasPressed(void)
{
return _state && _changed;
}
uint8_t Button::wasAxisPressed(void)
{
if (_state && _changed)
return _axis;
else
return 0;
}
uint8_t Button::wasReleased(void)
{
return !_state && _changed;
}
/*----------------------------------------------------------------------*
* pressedFor(ms) and releasedFor(ms) check to see if the button is *
* pressed (or released), and has been in that state for the specified *
* time in milliseconds. Returns false (0) or true (1) accordingly. *
* These functions do not cause the button to be read. *
*----------------------------------------------------------------------*/
uint8_t Button::pressedFor(uint32_t ms)
{
return (_state == 1 && _time - _lastChange >= ms) ? 1 : 0;
}
uint8_t Button::releasedFor(uint32_t ms)
{
return (_state == 0 && _time - _lastChange >= ms) ? 1 : 0;
}
/*----------------------------------------------------------------------*
* lastChange() returns the time the button last changed state, *
* in milliseconds. *
*----------------------------------------------------------------------*/
uint32_t Button::lastChange(void)
{
return _lastChange;
}

View File

@@ -4,4 +4,64 @@
#include "board.h"
void initButton(void);
void handleButton(void);
void handleButton(void);
void setButton(uint8_t index, bool state);
/*----------------------------------------------------------------------*
* Arduino Button Library v1.0 *
* Jack Christensen Mar 2012 *
* *
* This work is licensed under the Creative Commons Attribution- *
* ShareAlike 3.0 Unported License. To view a copy of this license, *
* visit http://creativecommons.org/licenses/by-sa/3.0/ or send a *
* letter to Creative Commons, 171 Second Street, Suite 300, *
* San Francisco, California, 94105, USA. *
*----------------------------------------------------------------------*/
#ifndef Button_h
#define Button_h
// #if ARDUINO >= 100
#include <Arduino.h>
// #else
// #include <WProgram.h>
// #endif
#define DPAD_V_FULL 2
#define DPAD_V_HALF 1
#define DPAD_V_NONE 0
class Button
{
public:
Button(uint8_t pin, uint8_t invert, uint32_t dbTime, bool isTouch);
uint8_t read();
uint8_t readAxis();
uint8_t isPressed();
uint8_t isAxisPressed();
uint8_t isReleased();
uint8_t wasPressed();
uint8_t wasAxisPressed();
uint8_t wasReleased();
uint8_t pressedFor(uint32_t ms);
uint8_t releasedFor(uint32_t ms);
uint32_t lastChange();
void setButton(bool state);
private:
uint8_t _pin; //arduino pin number
uint8_t _puEnable; //internal pullup resistor enabled
uint8_t _invert; //if 0, interpret high state as pressed, else interpret low state as pressed
uint8_t _state; //current button state
uint8_t _lastState; //previous button state
uint8_t _changed; //state changed since last read
uint8_t _axis; //state changed since last read
uint32_t _time; //time of current state (all times are in ms)
uint32_t _lastTime; //time of previous state
uint32_t _lastChange; //time of last state change
uint32_t _dbTime; //debounce time
const bool _isTouchButton;
bool _touchState;
};
#endif

View File

@@ -0,0 +1,536 @@
#include "controlloop.h"
#include "thermo.h"
#include "button.h"
#include "lcd.h"
// Button AXIS_Y = Button(BUTTON_AXIS_Y, true, DEBOUNCE_MS);
// Button AXIS_X = Button(BUTTON_AXIS_X, true, DEBOUNCE_MS);
/*******************************************************************************
Title: Reflow Oven Controller
Version: 1.20
Date: 26-11-2012
Company: Rocket Scream Electronics
Author: Lim Phang Moh
Website: www.rocketscream.com
Brief
=====
This is an example firmware for our Arduino compatible reflow oven controller.
The reflow curve used in this firmware is meant for lead-free profile
(it's even easier for leaded process!). You'll need to use the MAX31855
library for Arduino if you are having a shield of v1.60 & above which can be
downloaded from our GitHub repository. Please check our wiki
(www.rocketscream.com/wiki) for more information on using this piece of code
together with the reflow oven controller shield.
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)
This firmware owed very much on the works of other talented individuals as
follows:
==========================================
Brett Beauregard (www.brettbeauregard.com)
==========================================
Author of Arduino PID library. On top of providing industry standard PID
implementation, he gave a lot of help in making this reflow oven controller
possible using his awesome library.
==========================================
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.
Licences
========
This reflow oven controller hardware and firmware are released under the
Creative Commons Share Alike v3.0 license
http://creativecommons.org/licenses/by-sa/3.0/
You are free to take this piece of code, use it and modify it.
All we ask is attribution including the supporting libraries used in this
firmware.
Required Libraries
==================
- Arduino PID Library:
>> https://github.com/br3ttb/Arduino-PID-Library
- MAX31855 Library (for board v1.60 & above):
>> https://github.com/rocketscream/MAX31855
- MAX6675 Library (for board v1.50 & below):
>> https://github.com/adafruit/MAX6675-library
Revision Description
======== ===========
1.20 Adds supports for v1.60 (and above) of Reflow Oven Controller
Shield:
- Uses MAX31855KASA+ chip and pin reassign (allowing A4 & A5 (I2C)
to be used for user application).
- Uses analog based switch (allowing D2 & D3 to be used for user
application).
Adds waiting state when temperature too hot to start reflow process.
Corrected thermocouple disconnect error interpretation (MAX6675).
1.10 Arduino IDE 1.0 compatible.
1.00 Initial public release.
*******************************************************************************/
// Comment either one the following #define to select your board revision
// Newer board version starts from v1.60 using MAX31855KASA+ chip
// ***** LCD MESSAGES *****
const char *lcdMessagesReflowStatus[] = {
"Ready",
"Preheat",
"Soaking",
"Reflow",
"Cooling",
"Done",
"Warning, HOT!",
"Error"};
// ***** DEGREE SYMBOL FOR LCD *****
unsigned char degree[8] = {
140, 146, 146, 140, 128, 128, 128, 128};
// ***** PID CONTROL VARIABLES *****
double setpoint;
double input;
double output;
double kp = PID_KP_PREHEAT;
double ki = PID_KI_PREHEAT;
double kd = PID_KD_PREHEAT;
int windowSize;
int inputInt;
unsigned long windowStartTime;
unsigned long nextCheck;
unsigned long nextRead;
unsigned long timerSoak;
unsigned long buzzerPeriod;
// Reflow oven controller state machine state variable
reflowState_t reflowState;
// Reflow oven controller status
reflowStatus_t reflowStatus;
// Switch debounce state machine state variable
debounceState_t debounceState;
// Switch debounce timer
long lastDebounceTime;
// Switch press status
switch_t switchStatus;
// Seconds timer
int timerSeconds;
//output state
bool outputState = false;
bool isFault;
String activeStatus;
int oldTemp = 0;
byte state;
bool disableMenu;
bool profileIsOn;
// Specify PID control interface & it must be here, after all declaration
PID reflowOvenPID(&input, &output, &setpoint, kp, ki, kd, DIRECT);
void initControlLoop(void)
{
// Set window size
windowSize = 2000;
// Initialize time keeping variable
nextCheck = millis();
// Initialize thermocouple reading variable
nextRead = millis();
}
String getReflowStatus_str(void)
{
switch (reflowStatus)
{
case REFLOW_STATUS_OFF:
{
return String("Reflow OFF");
}
break;
case REFLOW_STATUS_ON:
{
return String("Reflow ON");
}
break;
default:
{
return String("Unknow State");
}
break;
}
}
String getReflowState_str(void)
{
switch (reflowState)
{
case REFLOW_STATE_IDLE:
return String("Idle");
break;
case REFLOW_STATE_PREHEAT:
return String("Preheating");
break;
case REFLOW_STATE_SOAK:
return String("Soaking");
break;
case REFLOW_STATE_REFLOW:
return String("Reflowing");
break;
case REFLOW_STATE_COOL:
return String("Cooling");
break;
case REFLOW_STATE_COMPLETE:
return String("Completed");
break;
case REFLOW_STATE_TOO_HOT:
return String("OVERHEATING!");
break;
case REFLOW_STATE_ERROR:
return String("Error");
break;
default:
return String("Unknow State");
break;
}
}
void setReflowStatus(bool newStatus)
{
if (newStatus)
{
profileIsOn = true;
}
else
{
profileIsOn = false;
reflowStatus = REFLOW_STATUS_OFF;
// Reinitialize state machine
reflowState = REFLOW_STATE_IDLE;
}
}
reflowStatus_t getReflowStatus(void)
{
return reflowStatus;
}
double getReflowTargetTemp(void)
{
return setpoint;
}
bool getOutputState(void)
{
return outputState;
}
void handleReflowStatemachine(void)
{
// Reflow oven controller state machine
switch (reflowState)
{
case REFLOW_STATE_IDLE:
activeStatus = "Idle";
// If oven temperature is still above room temperature
if (input >= TEMPERATURE_ROOM)
{
reflowState = REFLOW_STATE_TOO_HOT;
Serial.println("Status: Too hot to start");
}
else
{
// If switch is pressed to start reflow process
//if (switchStatus == SWITCH_1)
if (profileIsOn != 0)
{
// Send header for CSV file
Serial.println("Time Setpoint Input Output");
// Intialize seconds timer for serial debug information
timerSeconds = 0;
// Initialize PID control window starting time
windowStartTime = millis();
// Ramp up to minimum soaking temperature
setpoint = TEMPERATURE_SOAK_MIN;
// Tell the PID to range between 0 and the full window size
reflowOvenPID.SetOutputLimits(0, windowSize);
reflowOvenPID.SetSampleTime(PID_SAMPLE_TIME);
// Turn the PID on
reflowOvenPID.SetMode(AUTOMATIC);
// Proceed to preheat stage
reflowState = REFLOW_STATE_PREHEAT;
}
}
break;
case REFLOW_STATE_PREHEAT:
activeStatus = "Preheat";
reflowStatus = REFLOW_STATUS_ON;
// If minimum soak temperature is achieve
if (input >= TEMPERATURE_SOAK_MIN)
{
// Chop soaking period into smaller sub-period
timerSoak = millis() + SOAK_MICRO_PERIOD;
// Set less agressive PID parameters for soaking ramp
reflowOvenPID.SetTunings(PID_KP_SOAK, PID_KI_SOAK, PID_KD_SOAK);
// Ramp up to first section of soaking temperature
setpoint = TEMPERATURE_SOAK_MIN + SOAK_TEMPERATURE_STEP;
// Proceed to soaking state
reflowState = REFLOW_STATE_SOAK;
}
break;
case REFLOW_STATE_SOAK:
activeStatus = "Soak";
// If micro soak temperature is achieved
if (millis() > timerSoak)
{
timerSoak = millis() + SOAK_MICRO_PERIOD;
// Increment micro setpoint
setpoint += SOAK_TEMPERATURE_STEP;
if (setpoint > TEMPERATURE_SOAK_MAX)
{
// Set agressive PID parameters for reflow ramp
reflowOvenPID.SetTunings(PID_KP_REFLOW, PID_KI_REFLOW, PID_KD_REFLOW);
// Ramp up to first section of soaking temperature
setpoint = TEMPERATURE_REFLOW_MAX;
// Proceed to reflowing state
reflowState = REFLOW_STATE_REFLOW;
}
}
break;
case REFLOW_STATE_REFLOW:
activeStatus = "Reflow";
// We need to avoid hovering at peak temperature for too long
// Crude method that works like a charm and safe for the components
if (input >= (TEMPERATURE_REFLOW_MAX - 5))
{
// Set PID parameters for cooling ramp
reflowOvenPID.SetTunings(PID_KP_REFLOW, PID_KI_REFLOW, PID_KD_REFLOW);
// Ramp down to minimum cooling temperature
setpoint = TEMPERATURE_COOL_MIN;
// Proceed to cooling state
reflowState = REFLOW_STATE_COOL;
}
break;
case REFLOW_STATE_COOL:
activeStatus = "Cool";
// If minimum cool temperature is achieve
if (input <= TEMPERATURE_COOL_MIN)
{
// Retrieve current time for buzzer usage
buzzerPeriod = millis() + 1000;
// Turn on buzzer and green LED to indicate completion
digitalWrite(BUZZER, HIGH);
// Turn off reflow process
reflowStatus = REFLOW_STATUS_OFF;
// Proceed to reflow Completion state
reflowState = REFLOW_STATE_COMPLETE;
}
break;
case REFLOW_STATE_COMPLETE:
activeStatus = "Complete";
if (millis() > buzzerPeriod)
{
// Turn off buzzer and green LED
digitalWrite(BUZZER, LOW);
// Reflow process ended
reflowState = REFLOW_STATE_IDLE;
profileIsOn = 0;
Serial.println("Profile is OFF");
}
break;
case REFLOW_STATE_TOO_HOT:
// If oven temperature drops below room temperature
if (input < TEMPERATURE_ROOM)
{
// Ready to reflow
reflowState = REFLOW_STATE_IDLE;
}
break;
case REFLOW_STATE_ERROR:
// If thermocouple problem is still present
if (input == 0) // || (input == FAULT_SHORT_GND) ||
// (input == FAULT_SHORT_VCC))
{
// Wait until thermocouple wire is connected
reflowState = REFLOW_STATE_ERROR;
}
else
{
// Clear to perform reflow process
reflowState = REFLOW_STATE_IDLE;
}
break;
}
}
void handleReflowPID(void)
{
unsigned long now;
// PID computation and SSR control
if (reflowStatus == REFLOW_STATUS_ON)
{
now = millis();
reflowOvenPID.Compute();
if ((now - windowStartTime) > windowSize)
{
// Time to shift the Relay Window
windowStartTime += windowSize;
}
if (output > (now - windowStartTime))
{
digitalWrite(HEAT_OUT, HIGH);
outputState = true;
}
else
{
digitalWrite(HEAT_OUT, LOW);
outputState = false;
}
}
// Reflow oven process is off, ensure oven is off
else
{
digitalWrite(HEAT_OUT, LOW);
outputState = false;
}
}
void handleTemperatureReadings(void)
{
unsigned long now;
// Time to read thermocouple?
if (millis() > nextRead)
{
// Read thermocouple next sampling period
nextRead += SENSOR_SAMPLING_TIME;
// Read current temperature
input = getTemperature();
// Check and print any faults
uint8_t fault = getThermoCoupleFault();
// inputInt = (int) input;
// if ((input <= inputInt) || (input >= inputInt)) {
// loopScreen();
// }
if ((((int)input) < inputInt) || (((int)input) > inputInt))
{
//loopScreen();
}
inputInt = input / 1;
if (oldTemp != inputInt)
{
if (state == 0)
{
loopScreen();
}
#ifdef Serial
if ((input > 0) && (input <= 500))
{
Serial.print("Float temp: " + String(input));
Serial.print(" ; ");
Serial.println("Integer temp: " + String(inputInt));
}
#endif
}
// If thermocouple problem detected
if (input == 0)
{
// Illegal operation
reflowState = REFLOW_STATE_ERROR;
reflowStatus = REFLOW_STATUS_OFF;
}
oldTemp = inputInt;
}
if (millis() > nextCheck)
{
// Check input in the next seconds
nextCheck += 1000;
// If reflow process is on going
if (reflowStatus == REFLOW_STATUS_ON)
{
// Increase seconds timer for reflow curve analysis
timerSeconds++;
// Send temperature and time stamp to serial
Serial.print(timerSeconds);
Serial.print(" ");
Serial.print(setpoint);
Serial.print(" ");
Serial.print(input);
Serial.print(" ");
Serial.println(output);
}
else
{
// Turn off red LED
digitalWrite(LED_BUILTIN, LOW);
}
// If currently in error state
if (reflowState == REFLOW_STATE_ERROR)
{
// No thermocouple wire connected
Serial.println("TC Error!");
}
}
}
/* ---- REFLOW MAIN LOOP ---- */
void handleControlLoop()
{
if (state == 9)
{
Serial.println("handlecontrolloop: ERROR state");
return;
}
handleTemperatureReadings();
handleReflowStatemachine();
handleReflowPID();
}

View File

@@ -0,0 +1,79 @@
#pragma once
#include <PID_v1.h>
#include "Arduino.h"
// ***** CONSTANTS *****
#define TEMPERATURE_ROOM 50
#define TEMPERATURE_SOAK_MIN 150
#define TEMPERATURE_SOAK_MAX 185
#define TEMPERATURE_REFLOW_MAX 220
#define TEMPERATURE_COOL_MIN 100
#define SENSOR_SAMPLING_TIME 1000
#define SOAK_TEMPERATURE_STEP 5
#define SOAK_MICRO_PERIOD 9000
#define DEBOUNCE_PERIOD_MIN 50
#define PREHEAT_PERIOD 12000
#define REFLOW_PERIOD 12000
#define COOLDOWN_PERIOD 30000
// ***** PID PARAMETERS *****
// ***** PRE-HEAT STAGE *****
#define PID_KP_PREHEAT 100
#define PID_KI_PREHEAT 0.025
#define PID_KD_PREHEAT 20
// ***** SOAKING STAGE *****
#define PID_KP_SOAK 300
#define PID_KI_SOAK 0.05
#define PID_KD_SOAK 250
// ***** REFLOW STAGE *****
#define PID_KP_REFLOW 300
#define PID_KI_REFLOW 0.05
#define PID_KD_REFLOW 350
#define PID_SAMPLE_TIME 1000
// This is for testing on different board
// #define LCD_PIN 14
// #define ODROID
// ***** TYPE DEFINITIONS *****
typedef enum REFLOW_STATE
{
REFLOW_STATE_IDLE,
REFLOW_STATE_PREHEAT,
REFLOW_STATE_SOAK,
REFLOW_STATE_REFLOW,
REFLOW_STATE_COOL,
REFLOW_STATE_COMPLETE,
REFLOW_STATE_TOO_HOT,
REFLOW_STATE_ERROR
} reflowState_t;
typedef enum REFLOW_STATUS
{
REFLOW_STATUS_OFF,
REFLOW_STATUS_ON
} reflowStatus_t;
typedef enum SWITCH
{
SWITCH_NONE,
SWITCH_1,
SWITCH_2
} switch_t;
typedef enum DEBOUNCE_STATE
{
DEBOUNCE_STATE_IDLE,
DEBOUNCE_STATE_CHECK,
DEBOUNCE_STATE_RELEASE
} debounceState_t;
void initControlLoop(void);
void handleControlLoop(void);
String getReflowStatus_str(void);
String getReflowState_str(void);
reflowStatus_t getReflowStatus(void);
void setReflowStatus(bool newStatus);
double getReflowTargetTemp(void);
bool getOutputState(void);

View File

@@ -1,9 +1,17 @@
#include "heater.h"
uint16_t heatingProfile[] =
{
30,
50,
70,
90
};
void initHeater(void)
{
pinMode(HEAT_OUT,OUTPUT);
digitalWrite(HEAT_OUT, LOW);
}
void handleHeater(void)

View File

@@ -1,6 +1,8 @@
#include "lcd.h"
#include "thermo.h"
#include "controlloop.h"
#include "button.h"
#include <TFT_eSPI.h> // Include the graphics library
@@ -8,56 +10,190 @@ TFT_eSPI tft = TFT_eSPI(); // Create object "tft"
TFT_eSprite looptime_spr = TFT_eSprite(&tft);
//chart data
TFT_eSprite chartbg_spr = TFT_eSprite(&tft);
TFT_eSprite chartActual_spr = TFT_eSprite(&tft);
TFT_eSprite chartTarget_spr = TFT_eSprite(&tft);
TFT_eSprite chartXaxis_spr = TFT_eSprite(&tft);
TFT_eSprite chartYaxis_spr = TFT_eSprite(&tft);
TFT_eSprite chartArea_spr = TFT_eSprite(&tft);
TFT_eSprite reflowstate_spr = TFT_eSprite(&tft);
TFT_eSprite output_spr = TFT_eSprite(&tft);
//buttons
String buttonNames[] = {"START", "STOP", "SET"};
char buttonlabels[BUTTONS_N][10] = {"", "", ""};
TFT_eSPI_Button key[BUTTONS_N];
uint16_t calData[5] = {549, 3080, 343, 3159, 4};
void prep_chart(void)
uint32_t lastButtonTime = 0;
void updateTemperature(void)
{
chartbg_spr.createSprite(CHART_W, CHART_H);
//draw actualTemperature
chartArea_spr.fillRoundRect(TEMP_X, 0, 60-TEMP_R, TEMP_H, TEMP_R, TEMP_BG_COLOR);
chartArea_spr.drawString("A:", TEMP_X + TEMP_LABEL_X, TEMP_H / 4);
chartArea_spr.drawString("T:", TEMP_X + TEMP_LABEL_X, TEMP_H / 4 * 3);
chartArea_spr.drawNumber(getTemperature(), TEMP_X + TEMP_VALUE_X, TEMP_H / 4);
if (getReflowStatus() == REFLOW_STATUS_OFF)
{
chartArea_spr.drawString("--", TEMP_X + TEMP_VALUE_X, TEMP_H / 4 * 3);
}
else
{
chartArea_spr.drawNumber(getReflowTargetTemp(), TEMP_X + TEMP_VALUE_X, TEMP_H / 4 * 3);
}
}
uint32_t calcTemp(double temp)
{
//map(long x, long in_min, long in_max, long out_min, long out_max)
return CHART_H - map(temp, CHART_TEMP_MIN, CHART_TEMP_MAX, CHART_Y_AXIS_OFFSET, CHART_H) + CHART_X_AXIS_OFFSET;
}
uint32_t calcTime(uint32_t timeMs)
{
return map((timeMs / 1000), 0, CHART_TIME_MAX, 0, CHART_W - CHART_Y_AXIS_OFFSET);
}
void updateTargetChart(void)
{
chartArea_spr.fillSprite(CHART_BG_COLOR);
//draw preheat
uint32_t stepTimeStart = 0;
uint32_t stepTimeStop = calcTime(PREHEAT_PERIOD);
chartArea_spr.drawLine(stepTimeStart,
CHART_H - CHART_X_AXIS_OFFSET,
stepTimeStop,
calcTemp(TEMPERATURE_SOAK_MIN),
CHART_TARGET_COLOR);
//draw soak
stepTimeStart = stepTimeStop;
stepTimeStop += calcTime(SOAK_MICRO_PERIOD * (((TEMPERATURE_SOAK_MAX - TEMPERATURE_SOAK_MIN) / SOAK_TEMPERATURE_STEP) - 1));
chartArea_spr.drawLine(stepTimeStart,
calcTemp(TEMPERATURE_SOAK_MIN),
stepTimeStop,
calcTemp(TEMPERATURE_SOAK_MAX),
CHART_TARGET_COLOR);
//draw reflow
stepTimeStart = stepTimeStop;
stepTimeStop += calcTime(REFLOW_PERIOD);
chartArea_spr.drawLine(stepTimeStart,
calcTemp(TEMPERATURE_SOAK_MAX),
stepTimeStop,
calcTemp(TEMPERATURE_REFLOW_MAX),
CHART_TARGET_COLOR);
//draw cool
stepTimeStart = stepTimeStop;
stepTimeStop += calcTime(COOLDOWN_PERIOD);
chartArea_spr.drawLine(stepTimeStart,
calcTemp(TEMPERATURE_REFLOW_MAX),
stepTimeStop,
calcTemp(TEMPERATURE_ROOM),
CHART_TARGET_COLOR);
updateTemperature();
chartArea_spr.pushSprite(CHART_X + CHART_Y_AXIS_OFFSET, CHART_Y);
}
void prepTargetChart(void)
{
chartArea_spr.createSprite(CHART_W - CHART_Y_AXIS_OFFSET, CHART_H - CHART_X_AXIS_OFFSET);
chartArea_spr.setTextFont(TEMP_FONT);
chartArea_spr.setTextColor(TEMP_F_COLOR);
chartArea_spr.setTextDatum(CL_DATUM);
updateTargetChart();
}
void updateReflowState(void)
{
reflowstate_spr.fillRoundRect(STATE_OFFSET, 0, (STATE_W / 2) - (2 * STATE_OFFSET), STATE_H, STATE_R, STATE_BG_COLOR);
reflowstate_spr.fillRoundRect(STATE_W / 2 + STATE_OFFSET, 0, (STATE_W / 2) - (2 * STATE_OFFSET), STATE_H, STATE_R, STATE_BG_COLOR);
reflowstate_spr.drawString(getReflowState_str(), STATE_W / 4, STATE_H / 2);
reflowstate_spr.drawString(getReflowStatus_str(), STATE_W / 4 * 3, STATE_H / 2);
reflowstate_spr.pushSprite(STATE_X, STATE_Y);
}
void prepReflowstate(void)
{
reflowstate_spr.createSprite(STATE_W, STATE_H);
reflowstate_spr.setTextFont(STATE_FONT);
reflowstate_spr.setTextColor(STATE_F_COLOR);
reflowstate_spr.setTextDatum(MC_DATUM);
updateReflowState();
}
void updateOutput(void)
{
output_spr.fillSprite(OUTPUT_BG);
if (getOutputState())
{
output_spr.fillCircle(OUTPUT_W / 2, OUTPUT_H / 2, OUTPUT_R, OUTPUT_ON_COLOR);
}
else
{
output_spr.drawCircle(OUTPUT_W / 2, OUTPUT_H / 2, OUTPUT_R, OUTPUT_OFF_COLOR);
}
output_spr.pushSprite(OUTPUT_X, OUTPUT_Y);
}
void prepOutput(void)
{
output_spr.createSprite(OUTPUT_W, OUTPUT_H);
updateOutput();
}
void prepChart(void)
{
chartXaxis_spr.createSprite(CHART_W, CHART_X_AXIS_OFFSET);
chartYaxis_spr.createSprite(CHART_Y_AXIS_OFFSET, CHART_H);
//draw X-axis
chartbg_spr.drawLine(0, CHART_H - CHART_X_AXIS_OFFSET, CHART_W, CHART_H - CHART_X_AXIS_OFFSET, CHART_LINE_COLOR);
chartXaxis_spr.drawLine(0, 0, CHART_W, 0, CHART_LINE_COLOR);
//draw Y-axis
chartbg_spr.drawLine(CHART_Y_AXIS_OFFSET, 0, CHART_Y_AXIS_OFFSET, CHART_H, CHART_LINE_COLOR);
chartYaxis_spr.drawLine(CHART_Y_AXIS_OFFSET - 1, 0, CHART_Y_AXIS_OFFSET - 1, CHART_H, CHART_LINE_COLOR);
//draw Y axis labels
uint16_t tickIndex = (CHART_H - CHART_X_AXIS_OFFSET) / CHART_Y_TICKS;
chartbg_spr.setTextColor(CHART_TEXT_COLOR);
for (int i = 0; i < CHART_Y_TICKS + 1; i++)
chartXaxis_spr.setTextColor(CHART_TEXT_COLOR);
chartYaxis_spr.setTextColor(CHART_TEXT_COLOR);
for (int i = 0; i < CHART_Y_TICKS; i++)
{
//tick value
uint16_t y_tick_step = CHART_TEMP_MAX - ((CHART_TEMP_MAX - CHART_TEMP_MIN) / CHART_Y_TICKS * i) - CHART_TEMP_MIN;
chartbg_spr.drawLine(CHART_Y_AXIS_OFFSET - 2, tickIndex * (i + 1), CHART_Y_AXIS_OFFSET + 2, tickIndex * (i + 1), CHART_LINE_COLOR);
chartbg_spr.setTextDatum(BR_DATUM);
chartbg_spr.drawString(String(y_tick_step), CHART_Y_AXIS_OFFSET - 3, tickIndex * (i + 1), CHART_FONT);
uint16_t y_tick_step = CHART_TEMP_MAX - ((CHART_TEMP_MAX - CHART_TEMP_MIN) / CHART_Y_TICKS * i + 1) + CHART_TEMP_MIN;
chartYaxis_spr.drawLine(CHART_Y_AXIS_OFFSET - 8, tickIndex * (i + 1), CHART_Y_AXIS_OFFSET, tickIndex * (i + 1), CHART_LINE_COLOR);
chartYaxis_spr.setTextDatum(BR_DATUM);
chartYaxis_spr.drawString(String(y_tick_step), CHART_Y_AXIS_OFFSET - 3, tickIndex * (i + 1), CHART_FONT);
}
//draw X axis labels
tickIndex = (CHART_W - CHART_Y_AXIS_OFFSET) / CHART_X_TICKS;
for (int i = 0; i < CHART_X_TICKS + 1; i++)
{
uint16_t x_tick_step = (CHART_TIME_MAX / CHART_Y_TICKS * i);
chartbg_spr.drawLine(tickIndex * i + CHART_Y_AXIS_OFFSET, CHART_H - CHART_X_AXIS_OFFSET + 2, tickIndex * i + CHART_Y_AXIS_OFFSET, CHART_H - CHART_X_AXIS_OFFSET - 2, CHART_LINE_COLOR);
chartbg_spr.setTextDatum(TR_DATUM);
chartbg_spr.drawString(String(x_tick_step),tickIndex * i + CHART_Y_AXIS_OFFSET,CHART_H - CHART_X_AXIS_OFFSET + 3,CHART_FONT);
uint16_t x_tick_step = (CHART_TIME_MAX / CHART_X_TICKS * (i));
chartXaxis_spr.drawLine(tickIndex * i + CHART_Y_AXIS_OFFSET - 1, 0, tickIndex * i + CHART_Y_AXIS_OFFSET - 1, 8, CHART_LINE_COLOR);
chartXaxis_spr.setTextDatum(TR_DATUM);
chartXaxis_spr.drawString(String(x_tick_step), tickIndex * i + CHART_Y_AXIS_OFFSET - 1, 3, CHART_FONT);
}
chartbg_spr.pushSprite(CHART_X, CHART_Y);
chartXaxis_spr.pushSprite(CHART_X, CHART_Y + CHART_H - CHART_X_AXIS_OFFSET);
chartYaxis_spr.pushSprite(CHART_X, CHART_Y);
}
void prep_sprite(void)
void prepStatus(void)
{
looptime_spr.createSprite(20, tft.fontHeight(1) + 1);
}
void prep_buttons(void)
void updateStatus(void)
{
looptime_spr.fillSprite(TFT_BLACK);
looptime_spr.setTextDatum(MC_DATUM);
looptime_spr.drawNumber(getLooptime(), looptime_spr.width() / 2, looptime_spr.height() / 2, 1);
looptime_spr.pushSprite(1, 1);
}
void prepButtons(void)
{
const uint32_t button_width = tft.width() / BUTTONS_N;
@@ -114,35 +250,44 @@ void touch_calibrate()
void updateGUIButtons(void)
{
uint16_t t_x = 0, t_y = 0; // To store the touch coordinates
bool pressed = tft.getTouch(&t_x, &t_y);
for (uint8_t b = 0; b < BUTTONS_N; b++)
uint32_t timeNow = millis();
if (timeNow - lastButtonTime > BUTTON_INTERVAL)
{
if (pressed && key[b].contains(t_x, t_y))
{
key[b].press(true); // tell the button it is pressed
}
else
{
key[b].press(false); // tell the button it is NOT pressed
}
}
uint16_t t_x = 0, t_y = 0; // To store the touch coordinates
bool pressed = tft.getTouch(&t_x, &t_y);
// Check if any key has changed state
for (uint8_t b = 0; b < BUTTONS_N; b++)
{
// If button was just pressed, redraw inverted button
if (key[b].justPressed())
for (uint8_t b = 0; b < BUTTONS_N; b++)
{
key[b].drawButton(true, buttonNames[b]);
if (pressed && key[b].contains(t_x, t_y))
{
//Serial.println("key pressed");
key[b].press(true); // tell the button it is pressed
setButton(b, true);
}
else
{
//Serial.println("key released");
key[b].press(false); // tell the button it is NOT pressed
setButton(b, false);
}
}
// If button was just released, redraw normal color button
if (key[b].justReleased())
// Check if any key has changed state
for (uint8_t b = 0; b < BUTTONS_N; b++)
{
key[b].drawButton(false, buttonNames[b]);
// If button was just pressed, redraw inverted button
if (key[b].justPressed())
{
key[b].drawButton(true, buttonNames[b]);
}
// If button was just released, redraw normal color button
if (key[b].justReleased())
{
key[b].drawButton(false, buttonNames[b]);
}
}
lastButtonTime = timeNow;
}
}
@@ -158,20 +303,36 @@ void initLCD(void)
tft.invertDisplay(false);
//touch_calibrate();
tft.setTouch(calData);
prep_sprite();
prep_buttons();
prep_chart();
prepStatus();
prepButtons();
prepChart();
prepReflowstate();
prepTargetChart();
prepOutput();
}
// -------------------------------------------------------------------------
// Main loop
// -------------------------------------------------------------------------
uint32_t lastLCDupdate = 0;
void handleLCD()
{
looptime_spr.fillSprite(TFT_BLACK);
looptime_spr.setTextDatum(MC_DATUM);
looptime_spr.drawNumber(getLooptime(), looptime_spr.width() / 2, looptime_spr.height() / 2, 1);
looptime_spr.pushSprite(1, 1);
updateGUIButtons();
//tft.drawString(showValue("Temp", getTemperature(), "grC"), 10, 10);
updateStatus();
uint32_t timeNow = millis();
if (timeNow - lastLCDupdate > LCD_INTERVAL)
{
updateGUIButtons();
updateReflowState();
updateTargetChart();
updateOutput();
lastLCDupdate = timeNow;
}
}
void loopScreen(void)
{
}

View File

@@ -3,32 +3,77 @@
#include "status.h"
// #define WIDTH 240
// #define HEIGHT 60
#define BUTTON_H 60
#define BUTTON_W 240
#define BUTTONS_N 3
#define BUTTON_PADDING 2
#define BUTTON_RADIUS 8
#define BUTTON_COLOR TFT_BLUE
#define KEY_TEXTSIZE 1 // Font size multiplier
#define TFT_WIDTH 240
#define TFT_HEIGT 320
#define TFT_DEFAULT_R 4
#define LCD_INTERVAL 100
#define BUTTON_H 60
#define BUTTON_W TFT_WIDTH
#define BUTTONS_N 3
#define BUTTON_PADDING 2
#define BUTTON_RADIUS 8
#define BUTTON_COLOR TFT_BLUE
#define BUTTON_INTERVAL 20
#define KEY_TEXTSIZE 1 // Font size multiplier
#define CHART_X 0
#define CHART_Y 30
#define CHART_W 240
#define CHART_H 200
#define CHART_FONT 1
#define CHART_Y 40
#define CHART_Y_AXIS_OFFSET 24
#define CHART_X_AXIS_OFFSET 10
#define CHART_TIME_MAX 360 //time scale in seconds
#define CHART_W TFT_WIDTH - 5
#define CHART_H 200
#define CHART_FONT 1
#define CHART_TIME_MAX 140 //time scale in seconds
#define CHART_TEMP_MIN 20 //offset in degrees
#define CHART_TEMP_MAX 360 //degrees
#define CHART_Y_TICKS 10
#define CHART_X_TICKS 10
#define CHART_LINE_COLOR TFT_WHITE
#define CHART_TEXT_COLOR TFT_RED
#define CHART_TEMP_MAX 240 //degrees
#define CHART_Y_TICKS 11
#define CHART_X_TICKS 7
#define CHART_LINE_COLOR TFT_WHITE
#define CHART_TEXT_COLOR TFT_RED
#define CHART_TARGET_COLOR TFT_WHITE
#define CHART_ACTUAL_COLOR TFT_RED
#define CHART_BG_COLOR TFT_BLACK
#define STATE_X 0
#define STATE_Y 13
#define STATE_W TFT_WIDTH
#define STATE_H 24
#define STATE_FONT 2
#define STATE_OFFSET 3
#define STATE_F_COLOR TFT_BLACK
#define STATE_R TFT_DEFAULT_R
#define STATE_BG_COLOR TFT_GREEN
#define TEMP_W 60
#define TEMP_H 40
#define TEMP_X CHART_W - TEMP_W - 20//allign right
#define TEMP_Y 40
#define TEMP_FONT 2
#define TEMP_F_COLOR TFT_WHITE
#define TEMP_BG_COLOR TFT_BLUE
#define TEMP_R TFT_DEFAULT_R
#define TEMP_LABEL_X 2
#define TEMP_VALUE_X 30
#define OUTPUT_W 12
#define OUTPUT_H 12
#define OUTPUT_R 5
#define OUTPUT_Y 0
#define OUTPUT_X TFT_WIDTH - OUTPUT_W - 2
#define OUTPUT_ON_COLOR TFT_RED
#define OUTPUT_OFF_COLOR TFT_WHITE
#define OUTPUT_BG TFT_BLACK
#define DEBOUNCE_MS 100
void initLCD(void);
void handleLCD(void);
void loopScreen(void);

View File

@@ -6,26 +6,26 @@
#include "button.h"
#include "heater.h"
#include "status.h"
#include "controlloop.h"
void setup()
{
// put your setup code here, to run once:
initStatus();
initLCD();
initThermo();
initButton();
initHeater();
initControlLoop();
}
void loop()
{
// put your main code here, to run repeatedly:
handleLCD();
handleThermo();
handleButton();
handleHeater();
handleStatus();
handleControlLoop();
}

View File

@@ -6,14 +6,12 @@ uint32_t thermo_lastTime = 0;
double lastTemperature = 0;
// the setup function runs once when you press reset or power the board
void initThermo()
{
Thermocouple *originThermocouple = new MAX6675_Thermocouple(THERM_CL, THERM_CS, THERM_SO);
thermocouple = new SmoothThermocouple(originThermocouple, SMOOTHING_FACTOR);
}
// the loop function runs over and over again forever
void handleThermo(void)
{
// Reads temperature
@@ -21,13 +19,32 @@ void handleThermo(void)
if (timeNow - thermo_lastTime > THERMO_INTERVAL)
{
lastTemperature = thermocouple->readCelsius();
Serial.printf("Thermocouple = %d\n",lastTemperature);
thermo_lastTime = timeNow;
}
//delay(100); // optionally, only to delay the output of information in the example.
}
double getTemperature(void)
{
return lastTemperature;
return 23;//lastTemperature;
}
bool getThermoCoupleFault(void)
{
#ifdef MAX31856
uint8_t fault = thermocouple.readFault();
if (fault)
{
if (fault & MAX6675_FAULT_CJRANGE) //Serial.println("Cold Junction Range Fault");
if (fault & MAX31856_FAULT_TCRANGE) //Serial.println("Thermocouple Range Fault");
if (fault & MAX31856_FAULT_CJHIGH) //Serial.println("Cold Junction High Fault");
if (fault & MAX31856_FAULT_CJLOW) //Serial.println("Cold Junction Low Fault");
if (fault & MAX31856_FAULT_TCHIGH) //Serial.println("Thermocouple High Fault");
if (fault & MAX31856_FAULT_TCLOW) //Serial.println("Thermocouple Low Fault");
if (fault & MAX31856_FAULT_OVUV) //Serial.println("Over/Under Voltage Fault");
if (fault & MAX31856_FAULT_OPEN) //Serial.println("Thermocouple Open Fault");
return true;
}
#else
return false;
#endif
}

View File

@@ -7,8 +7,10 @@
#include <SmoothThermocouple.h>
#define THERMO_INTERVAL 200
#define SMOOTHING_FACTOR 5
#define SMOOTHING_FACTOR 2
void initThermo(void);
void handleThermo(void);
double getTemperature(void);
bool getThermoCoupleFault(void);