#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <DHT11.h>
#include <Versatile_RotaryEncoder.h>
/* Grinder Project
© 2023 George Crawford and Kevin Davy
ElectroKev.com
V31
Production Release
Acceleration of Rotary Encoder Knob
Acceleration for both Time and Grams per second to 0.2 when acclerated. 0.01 when not accelerated.
Improved stepped settings for grams per second to improve weight accuracy when time calibrated.
Saved GPS screen now reflects new 10th of a gram accuracy.
Increased Milk Temperqture Accuracy
Display Bug Fixes */
DHT11 dht11(7);
const int versionNumber = 31;
const int clearEEPROMButtonPin = 2; // New button pin for clearing EEPROM
const int clearHoldDuration = 3000; // Hold duration for clearing EEPROM in milliseconds
//const int potGrindTimePin = A2;
//const int potGramsPerSecPin = A3;
//------------
// Define the pin for the thermistor
const int thermistorPin = A3;
// Define the value of the resistor
const float resistorValue =15000.0; // Resistance of the resistor (10kΩ)
// Define the parameters for the Steinhart-Hart equation
//NEVER EVER EVER EVER EVER CHANGE THE VALUES BELOW
const float A = 0.001129148;
const float B = 0.000195125;//000234125
const float C = 0.0000000996741; //0000000876741
//higher the co e the lower the temp
//lower the co e the highewr the temp
//-------------
float adjustStopMilkC;
const int buttonPin = 3;
const int settingsButton = 9;
const int instantGrindButtonPin = 6;
const int ledPin = 4;
const int relayPin = 5;
const int powerLightPin = 10;
const uint8_t i2cAddr = 0x27;
const int pauseHoldDuration = 1; // 0.03 seconds in milliseconds
bool offBackFlash = false;
bool countdownStarted = false;
bool isPaused = false;
bool showTemperature = false; // State variable to track temperature display
unsigned long pauseButtonStartTime = 0;
unsigned long clearButtonStartTime = 0;
float totalGramsStored;
float grinding;
float totalGrams;
float gramsPerSecond;
int heatMilkC= 35;
int stopMilkC =63;
float grindSpeed;
float grindSpeedExceeded = 20;
float gpsSpeedExceeded = 35;
float tempCelsius;
#define LCD_COLS 20
#define LCD_ROWS 4
#define EEPROM_ADDRESS 0
#define grindTimeMemory 10
#define gpsMemory 20
#define finalDrinkTempSave 30
LiquidCrystal_I2C lcd(i2cAddr, LCD_COLS, LCD_ROWS);
#define clk A0 // (A3)
#define dt A1 // (A2)
#define sw 11 // (A4)
Versatile_RotaryEncoder *versatile_encoder;
void setup() {
Wire.begin();
Wire.setClock(400000L);
versatile_encoder = new Versatile_RotaryEncoder(clk, dt, sw);
versatile_encoder->setHandleRotate(handleRotate);
versatile_encoder->setHandlePress(handlePress);
versatile_encoder->setHandlePressRelease(handlePressRelease);
versatile_encoder->setHandleLongPress(handleLongPress);
versatile_encoder->setHandlePressRotate(handlePressRotate);
versatile_encoder->setHandlePressRotateRelease(handlePressRotateRelease);
lcd.init();
lcd.begin(16, 2);
lcd.backlight();
lcd.setBacklight(255);
pinMode(buttonPin, INPUT_PULLUP);
pinMode(sw, INPUT_PULLUP);
pinMode(instantGrindButtonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
pinMode(relayPin, OUTPUT);
pinMode(powerLightPin, OUTPUT);
pinMode(clearEEPROMButtonPin, INPUT_PULLUP);
EEPROM.get(EEPROM_ADDRESS, totalGramsStored); //read flash ram for previous stored grams dispensed
EEPROM.get(grindTimeMemory, grinding); //read flash ram for previous stored grams dispensed
EEPROM.get(gpsMemory, gramsPerSecond); //read flash ram for previous stored grams dispensed
EEPROM.get(finalDrinkTempSave, stopMilkC);
analogWrite(powerLightPin, 10);
pinMode(thermistorPin, INPUT_PULLUP);
// WELCOME SCREENS START HERE
// WELCOME SCREEN 1
int humidity = dht11.readHumidity();
delay(500);
int temperature = dht11.readTemperature();
//delay(500);
lcd.init();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("*");
lcd.setCursor(19,0);
lcd.print("*");
lcd.setCursor(0, 1);
lcd.print(" ElectroKev.com");
lcd.setCursor(0, 2);
lcd.print(" Coffee Time!");
lcd.setCursor(0,3);
lcd.print(temperature);
lcd.print("c");
lcd.setCursor(17,3);
lcd.print(humidity);
lcd.print("%");
delay(2000);
// WELCOME SCREEN 2
lcd.clear();
lcd.setCursor(3, 1);
//lcd.print("All Time Grinds");
lcd.print("All Time Total:");
lcd.setCursor(8,2);
lcd.print(totalGramsStored,0);
lcd.print(" g");
delay(1000);
// OPEN MAIN SETTINGS SCREEN
updateScreen();
}
void loop() {
if (versatile_encoder->ReadEncoder()) {
updateScreen();}
if (digitalRead(buttonPin)==LOW){
greenButtonPressed();}
if (digitalRead(clearEEPROMButtonPin)==LOW){
masterReset();}
if (digitalRead(instantGrindButtonPin) == LOW) {
oneSecondGrind();}
if (digitalRead(settingsButton)==LOW){
aboutScreen();
}
}
void updateScreen(){
lcd.clear();
//lcd.setCursor(0, 0);
//lcd.print(" Settings: ");
lcd.setCursor(0, 0);
lcd.print("Time: ");
lcd.setCursor(8,0);
lcd.print(grinding,1);
lcd.print(" Seconds");
totalGrams = gramsPerSecond * grinding;
lcd.setCursor(0, 1);
lcd.print("Grams: ");
lcd.setCursor(8,1);
lcd.print(totalGrams, 1);
lcd.print(" g ");
lcd.setCursor(0,2);
lcd.print("g/s: ");
lcd.setCursor(8,2);
lcd.print(gramsPerSecond*10 /10,2);
lcd.setCursor(0, 3);
lcd.print(" <Hold to Start>");
grindSpeed=millis();
}
void handleRotate(int8_t rotation) {
//Serial.print("#1 Rotated: ");
if (rotation > 0){
if (grinding > 0.1) {
grinding = grinding - 0.1;
//Alleceration of RE
if (grindSpeedExceeded>millis()-grindSpeed){
grinding = grinding - 1.5;
}
}
}
else
if (grinding < 50) {
grinding=grinding+0.1;
if (grindSpeedExceeded>millis()-grindSpeed){
grinding = grinding + 1.5;
}
}
}
void handleLongPress() {
startGrinding();
}
void handlePressRotate(int8_t rotation) {
if (rotation > 0){
if (gramsPerSecond >= 0.1) {
gramsPerSecond=gramsPerSecond-0.01;
//Alleceration of RE
if (gpsSpeedExceeded>millis()-grindSpeed){
gramsPerSecond=gramsPerSecond-0.2;
}
}
}
else {
gramsPerSecond=gramsPerSecond+0.01;
if (gpsSpeedExceeded>millis()-grindSpeed){
gramsPerSecond=gramsPerSecond+0.2;
}
}
}
void handlePressRelease(){
}
void handleLongPressRelease(){
}
void handlePress(){
}
void handlePressRotateRelease(){
EEPROM.put(gpsMemory, gramsPerSecond);
calibrateScreen();
}
void saveSettings(){
EEPROM.put(grindTimeMemory, grinding);
}
void greenButtonPressed(){
lcd.clear();
showTemperature = !showTemperature; // Toggle temperature display state
// If showing temperature, continuously display it
while (showTemperature) {
if (digitalRead(settingsButton)==LOW){
drinkTemperature();
lcd.clear();
}
int rawADC = analogRead(thermistorPin); // Read the analog value from the thermistor
float resistance = resistorValue / ((1023.0 / rawADC) - 1); // Calculate resistance of the thermistor
// Calculate temperature using Steinhart-Hart equation
float tempKelvin = 1 / (A + B * log(resistance) + C * pow(log(resistance), 3));
float tempCelsiusStretch = tempKelvin - 273.15; // Convert temperature to Celsius
float inputValue = tempCelsiusStretch; // Replace this with your number
//NEVER EVER EVER EVER EVER CHANGE THE VALUES BELOW
if (inputValue<51){
float inputMin = 39.3;
float inputMax = 51.0;
float outputMin = 4.0;
float outputMax = 35.0;
float tempCelsius2d = (inputValue - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;
tempCelsius=(tempCelsius2d);
}
if (inputValue>=51){
float inputMin = 51.1;
float inputMax = 61.0;
float outputMin = 35.1;
float outputMax = 54.0;
float tempCelsius2d = (inputValue - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;
tempCelsius=(tempCelsius2d);
}
if (tempCelsius >= 0 && tempCelsius <= 200) {
if(tempCelsius >=0 && tempCelsius <=heatMilkC){
offBackFlash = false;
lcd.setCursor(0,0);
lcd.print(" ** Aerate Milk ** ");
}
if(tempCelsius >=heatMilkC && tempCelsius <=stopMilkC){
offBackFlash = false;
lcd.setCursor(0,0);
lcd.print(" ** Heat Milk ** ");
}
if(tempCelsius >=stopMilkC){
lcd.setCursor(0,0);
lcd.print(" ** STOP - DONE ** ");
if (!offBackFlash){
for (int backFlash = 0; backFlash < 6; backFlash++)
{
lcd.setBacklight(0);
delay(100);
lcd.setBacklight(255);
delay(100);
offBackFlash=true;
}
}
}
//lcd.setCursor(0,0);
//lcd.print(" ");
lcd.setCursor(5,1);
lcd.print("Milk Temp: ");
lcd.setCursor(3,2);
//lcd.print(rawADC);
lcd.print(tempCelsius*10 /10,1);
lcd.print(" C -> ");
lcd.setCursor(14,2);
lcd.print(stopMilkC);
lcd.print(" C");
lcd.setCursor(1,3);
lcd.print("<Press Menu Knob>");
delay(100);
// Check if the button is pressed again to exit temperature display mode
} else {
lcd.setCursor(0,0);
lcd.print("<Connect Milk Probe>");
lcd.setCursor(0,2);
lcd.print(" ? ");
}
if (digitalRead(sw) == LOW) {
showTemperature = false;
break;
}
}
updateScreen();
}
void calibrateScreen(){
lcd.clear();
lcd.setCursor(7,1);
lcd.print("Saved:");
lcd.setCursor(7,2);
lcd.print(gramsPerSecond*10/10,2);
lcd.print(" g/s");
//lcd.print(totalGrams);
delay(1000);
}
void masterReset(){
// Check if the clear EEPROM button is held for the defined duration
if (digitalRead(clearEEPROMButtonPin) == LOW) {
if (clearButtonStartTime == 0) {
// Record the start time when the button is first pressed
clearButtonStartTime = millis();
}
if (millis() - clearButtonStartTime >= clearHoldDuration) {
// Clear EEPROM and reset total grinds
totalGramsStored = 0.0;
//EEPROM.put(EEPROM_ADDRESS, totalGramsStored);
for (int i = 0; i < EEPROM.length(); ++i) {
EEPROM.write(i, 1); // Write 1 to each EEPROM address
}
gramsPerSecond=0;
grinding=0;
stopMilkC=55;
EEPROM.put(finalDrinkTempSave, stopMilkC);
lcd.clear();
lcd.setCursor(3, 1);
lcd.print("Memory Cleared");
delay(2000);
updateScreen();
}
} else {
// Reset the start time if the button is released
clearButtonStartTime = 0;
}
}
void oneSecondGrind(){
// Check if the pause button is being held
if (millis() - pauseButtonStartTime >= pauseHoldDuration) {
digitalWrite(ledPin, HIGH);
digitalWrite(relayPin, HIGH);
lcd.clear();
lcd.setCursor(2, 1);
lcd.print("Immediate Grind");
lcd.setCursor(5, 2);
lcd.print("1/2 Second");
delay(500);
digitalWrite(ledPin, LOW);
digitalWrite(relayPin, LOW);
} else {
// Reset the start time if the button is not pressed
pauseButtonStartTime = millis();
}
updateScreen();
}
void startGrinding(){
if (!countdownStarted) {
countdownStarted = true;
digitalWrite(ledPin, HIGH);
digitalWrite(relayPin, HIGH);
saveSettings(); //save current time to EEPROM
lcd.clear();
//MAIN GRINDING LOOP ***************************
for (float i = grinding; i >= 0; i -= 0.1) {
delay(62);
lcd.setCursor(6, 1);
lcd.print("Grinding ");
lcd.setCursor(3, 2);
lcd.print(i, 1); // Display one decimal place for countdown during grind
lcd.print(" sec ");
float gramsElapsed = gramsPerSecond * (grinding - i);
//lcd.setCursor(11, 2);
lcd.print(gramsElapsed, 1);
lcd.print(" g");
//CHECK FOR PAUSE FUNCTION
if (digitalRead(instantGrindButtonPin) == LOW) {
if (millis() - pauseButtonStartTime >= pauseHoldDuration) {
isPaused = !isPaused;
while (digitalRead(instantGrindButtonPin) == LOW) {
}
}
} else {
pauseButtonStartTime = millis();
}
if (isPaused) {
lcd.clear();
lcd.setCursor(7, 1);
lcd.print("PAUSED");
// Disable LED and relay when paused
digitalWrite(ledPin, LOW);
digitalWrite(relayPin, LOW);
while (isPaused) {
// Wait until unpaused
if (digitalRead(instantGrindButtonPin) == LOW) {
// Check if the pause button is being held
if (millis() - pauseButtonStartTime >= pauseHoldDuration) {
isPaused = !isPaused;
while (digitalRead(instantGrindButtonPin) == LOW) {
// Wait for the button to be released
}
}
} else {
// Reset the start time if the button is not pressed
pauseButtonStartTime = millis();
}
}
lcd.clear();
} else {
// Re-enable LED and relay when unpaused
digitalWrite(ledPin, HIGH);
digitalWrite(relayPin, HIGH);
}
//delay(55); // Adjust the delay for better synchronization
}
// END OF MAIN GRINDING LOOP *********************
digitalWrite(ledPin, LOW);
digitalWrite(relayPin, LOW);
totalGramsStored += totalGrams;
EEPROM.put(0, totalGramsStored);
lcd.clear();
lcd.setCursor(3, 1);
lcd.print("Grams Dispensed");
lcd.setCursor(7, 2);
lcd.print(totalGrams, 1);
lcd.print(" g");
countdownStarted = false;
isPaused = false;
}
while (digitalRead(sw) == HIGH) {
// Wait for the rotary encoder button to be pressed again before restarting
}
}
void drinkTemperature() {
lcd.clear();
while (digitalRead(settingsButton)==LOW){
delay(50);
lcd.setCursor(2,0);
lcd.print("Milk Temperature");
lcd.setCursor(8,2);
lcd.print(stopMilkC);
lcd.print(" C");
if (digitalRead(buttonPin)==LOW){
lcd.setCursor(8,2);
stopMilkC=stopMilkC -1;
lcd.print(stopMilkC);
lcd.print(" C");
}
if (digitalRead(instantGrindButtonPin)==LOW){
lcd.setCursor(8,2);
stopMilkC=stopMilkC +1;
lcd.print(stopMilkC);
lcd.print(" C");
}
}
EEPROM.put(finalDrinkTempSave, stopMilkC);
//updateScreen();
//delay(5000);
}
void aboutScreen(){
lcd.clear();
while (digitalRead(settingsButton)==LOW){
lcd.setCursor(0,0);
lcd.print("Grinder Project V");
lcd.print(versionNumber);
lcd.setCursor(0,1);
lcd.print("George Crawford &");
lcd.setCursor(0,2);
lcd.print("Kevin Davy");
lcd.setCursor(0,3);
lcd.print("ElectroKev.com 2023");
}
updateScreen();
}