#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <PID_v1.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define i2c_Address 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin Definitions
#define DS1820PIN 6
#define POWER_ON_PIN 3
#define TEMP_UP_PIN 5
#define TEMP_DOWN_PIN 4
#define BREW_SW_PIN 7
#define MOTOR_PIN 8
#define WATER_TANK_PIN A2
#define SSR_PIN A3
// Constants
double setTemp = 200; // Set temperature
// Initialize OneWire and DallasTemperature objects
OneWire oneWire(DS1820PIN);
DallasTemperature sensors(&oneWire);
// Initialize PID variables
double Input, Output;
double Kp = 22, Ki = 1.5, Kd = 20;
PID myPID(&Input, &Output, &setTemp, Kp, Ki, Kd, DIRECT);
// Define state enumeration
enum State {
STATE_CHECK_WATER,
STATE_HEATING,
STATE_ADJUST_TEMP_UP,
STATE_ADJUST_TEMP_DOWN,
STATE_WATER_TANK_FILLED,
STATE_BREWING,
};
// Function prototypes
void handleCheckWaterState();
void handleHeatingState();
void handleAdjustTempUpState();
void handleAdjustTempDownState();
void handleWaterTankFilledState();
void handleBrewingState();
// Global variable for current state
State currentState = STATE_CHECK_WATER;
// define the logo
static const unsigned char PROGMEM perc[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xc7, 0xff, 0xff,
0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xc7, 0xff, 0xff,
0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xc7, 0xff, 0xff,
0xf8, 0x00, 0x38, 0x0f, 0x81, 0xe0, 0x18, 0x03, 0xe0, 0x0e, 0x00, 0xff, 0x01, 0xc7, 0xc0, 0x7f,
0xf8, 0xfe, 0x30, 0x07, 0x01, 0x80, 0x18, 0x01, 0xc0, 0x0c, 0x00, 0xc6, 0x00, 0xc7, 0x80, 0x3f,
0xf8, 0xfe, 0x20, 0x02, 0x01, 0x80, 0x18, 0x00, 0x80, 0x08, 0x00, 0xc4, 0x00, 0x47, 0x00, 0x1f,
0xf8, 0xfe, 0x23, 0xe2, 0x3f, 0x8f, 0xff, 0xf8, 0x8f, 0xf8, 0xff, 0xc4, 0x7c, 0x47, 0x1f, 0x1f,
0xf8, 0x00, 0x23, 0xe2, 0x3f, 0x8f, 0xff, 0xf8, 0x8f, 0xf8, 0xff, 0xc4, 0x7c, 0x47, 0x1f, 0x1f,
0xf8, 0x00, 0x20, 0x02, 0x3f, 0x8f, 0xf8, 0x00, 0x8f, 0xf8, 0xff, 0xc4, 0x7c, 0x47, 0x1f, 0x1f,
0xf8, 0x00, 0xe0, 0x02, 0x3f, 0x8f, 0xf8, 0x00, 0x8f, 0xf8, 0xff, 0xc4, 0x7c, 0x47, 0x1f, 0x1f,
0xf8, 0xff, 0xe3, 0xfe, 0x3f, 0x8f, 0xf8, 0xf8, 0x8f, 0xf8, 0xff, 0xc4, 0x7c, 0x47, 0x1f, 0x1f,
0xf8, 0xff, 0xe0, 0x02, 0x3f, 0x80, 0x18, 0x00, 0x80, 0x08, 0x00, 0xc4, 0x00, 0x41, 0x00, 0x1f,
0xf8, 0xff, 0xf0, 0x02, 0x3f, 0x80, 0x1c, 0x00, 0xc0, 0x0c, 0x00, 0xc6, 0x00, 0xc1, 0x80, 0x3f,
0xf8, 0xff, 0xf0, 0x02, 0x3f, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 0x00, 0xc6, 0x00, 0xe1, 0x80, 0x3f,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
// define water image
static const unsigned char PROGMEM drip[] = {
0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x03, 0xc0, 0x07, 0xe0, 0x07, 0xe0, 0x0f, 0xf0, 0x0f, 0xf0,
0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x0f, 0xf0, 0x03, 0xc0
};
void setup() {
Serial.begin(9600);
delay(250); // wait for the OLED to power up
display.begin(i2c_Address, true);
display.display();
display.clearDisplay();
display.drawBitmap(0, 0, perc, 128, 64, SH110X_WHITE);
// display.setTextSize(2); // Normal 1:1 pixel scale
// display.setTextColor(SH110X_WHITE); // Draw white text
// display.setCursor(25, 20);
// display.print(F("Welcome"));
display.display();
delay(1000);
display.invertDisplay(true);
delay(1000);
display.invertDisplay(false);
delay(1000);
display.invertDisplay(true);
delay(5000);
display.clearDisplay();
display.display();
display.invertDisplay(false);
pinMode(POWER_ON_PIN, INPUT);
pinMode(TEMP_UP_PIN, INPUT);
pinMode(TEMP_DOWN_PIN, INPUT);
pinMode(BREW_SW_PIN, INPUT);
pinMode(MOTOR_PIN, OUTPUT);
pinMode(WATER_TANK_PIN, INPUT_PULLUP);
pinMode(SSR_PIN, OUTPUT);
digitalWrite(MOTOR_PIN, LOW);
digitalWrite(SSR_PIN, LOW);
myPID.SetMode(AUTOMATIC); // Set PID to automatic mode
sensors.begin();
}
void loop() {
switch (currentState) {
case STATE_CHECK_WATER:
CheckWaterState();
break;
case STATE_HEATING:
HeatingState();
break;
case STATE_ADJUST_TEMP_UP:
TempUpState();
break;
case STATE_ADJUST_TEMP_DOWN:
TempDownState();
break;
case STATE_WATER_TANK_FILLED:
WaterTankFilledState();
break;
case STATE_BREWING:
BrewingState();
break;
}
}
////////////////////////// functions
void CheckWaterState() {
// Logic for checking water state
// if the water level in the boiler is too low go to the water fill state.
Serial.print(F("State: water check "));
if (digitalRead(WATER_TANK_PIN) == HIGH) {
currentState = STATE_WATER_TANK_FILLED;
} else {
currentState = STATE_HEATING;
}
}
void HeatingState() {
// Logic for heating state
Serial.print(F("State: heating "));
digitalWrite(MOTOR_PIN, LOW);
// check if the brew button has been pressed
if (digitalRead(TEMP_UP_PIN) == HIGH) { // if the temp up sw is pressed increase the temp
currentState = STATE_ADJUST_TEMP_UP;
} else if (digitalRead(TEMP_DOWN_PIN) == HIGH) { // vise versa for the temp down switch.
currentState = STATE_ADJUST_TEMP_DOWN;
} else if (digitalRead(BREW_SW_PIN) == HIGH) { // check if the brew sw is pressed
currentState = STATE_BREWING;
} else if (digitalRead(WATER_TANK_PIN) == HIGH) { // check if theres water in the tank
currentState = STATE_WATER_TANK_FILLED;
} else {
sensors.requestTemperatures();
Input = sensors.getTempFByIndex(0); // get the temp of the water and send the temp to the pid
Serial.print(F("Temperature: "));
Serial.println(Input);
Serial.println(setTemp);
// becuase of the delay in the temp readning, if the temp is not within 10 deg of the set temp turn on the SSR
if (Input + 10 < setTemp) {
digitalWrite(SSR_PIN, HIGH);
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SH110X_WHITE); // Draw white text
display.setCursor(1, 10);
display.print("HEATING...");
display.display();
} else if (Input + 10 > setTemp) { // if the temp is within 10 deg use PID
digitalWrite(SSR_PIN, LOW);
myPID.Compute(); // calculate the PID
sendPWM(Output); // send the PWM to the
sendMess(setTemp); // send the set temp to the screen
}
}
}
void TempUpState() {
// logic for increasing temperature
// dont let the temp go above 209 deg
if (setTemp < 209) {
setTemp++;
sendMess(setTemp); // send the new set temp to the screen
delay(500);
currentState = STATE_HEATING;
}
currentState = STATE_HEATING;
}
void TempDownState() {
// Add logic for decreasing temperature
// dont let the set temp go below 195 deg
if (setTemp > 195) {
setTemp--;
sendMess(setTemp);
Serial.println(setTemp);
delay(500);
currentState = STATE_HEATING;
}
currentState = STATE_HEATING;
}
void WaterTankFilledState() {
// logic for handling water tank being filled
while (digitalRead(WATER_TANK_PIN) == HIGH) { // Check if WATER_TANK_PIN is HIGH
digitalWrite(MOTOR_PIN, HIGH); // Turn on the motor until tank is full
digitalWrite(SSR_PIN, LOW); // dont let the SSR turn on while the motot is on
}
digitalWrite(MOTOR_PIN, LOW); // Turn off the motor
currentState = STATE_HEATING; // Transition to heating state
}
void BrewingState() {
// Logic for brewing state
int i;
if (digitalRead(WATER_TANK_PIN) == LOW && digitalRead(BREW_SW_PIN) == HIGH) { // the brew switch needs to be high and the water tank needs to be filled.
digitalWrite(MOTOR_PIN, HIGH); // Turn on the motor until brew switch is low.
digitalWrite(SSR_PIN, LOW);
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SH110X_WHITE); // Draw white text
display.setCursor(1, 10);
display.print("BREWING...");
display.display(); // dont let the SSR turn on while the motot is on
// draw dots
display.drawBitmap(0, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(16, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(32, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(48, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(64, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(80, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(96, 30, drip, 16, 16, 1);
// display.display();
display.drawBitmap(112, 30, drip, 16, 16, 1);
display.display();
delay(1000);
}
digitalWrite(MOTOR_PIN, LOW); // Turn off the motor
currentState = STATE_HEATING;
}
///////////////////// other functions
/* send the pwm out to the SSR
the PID sends an output of 0-255 however the SSR needs to have PWM with a period of around
250-300 to work, so to do this the Duty cycle of the PWM is defined by the PID
*/
void sendPWM(int duty) {
// becuase the PWM logic makes the SSR turn on even for a mS this code is added so if the Duty cycle is 0 the SSR is off.
if (duty > 0) {
Serial.print(F("send pwm"));
Serial.println(duty);
analogWrite(SSR_PIN, 255); // turn on the SSR
delay(duty); // wait for the duty cycle
analogWrite(SSR_PIN, 0); // tunr off the SSR
delay(255 - duty); // wait for the rest of the duty cycle.
} else if (duty == 0) {
analogWrite(SSR_PIN, 0);
}
}
void sendMess(double temp) {
// send the set temp on the screen.
display.display();
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SH110X_WHITE); // Draw white text
display.setCursor(10, 25);
display.println(temp);
display.display();
}