Not just a Smart Coffee Maker with Smartphone control, it's a multipurpose ThermoGraph also!
Things used in this project
Hardware components
Software apps and online services
Serial Bluetooth Terminal created by Kai Morich
Arduino IDE
Story
Introduction
Smart automatic Coffee Maker with Graph can be controlled via Bluetooth... or just load fresh water, and the system will start automatically!
Every part is handmade and all of the functions are my own ideas.
Arduino Controlled Intelligent Coffee Maker
I coded "Test Mode" also, so the controller can be used as a multipurpose temperature meter with external temperature sensor.
Developed on Arduino Nano / Uno, uses ST7920 display, HC-05 Bluetooth adapter, Dallas DS18B20 temperature sensor, relay to control the heater coil, button with leds, buzzer and a CD4017BE decade counter drives the LED bar.
How to start
Autostart: in standby mode the system cyclically measures the temperature in the background. Loading fresh water causes temperature drop, and the system will start.
This function can be enabled / disabled by sending "auto" from Smartphone, and the setting will be stored in the EEPROM memory.
Press button: to start / interrupt coffee making, or exit Test Mode.
Send "start" message from your smartphone, or "stop".
Video
Shows all working ways.
Photo Gallery
How it was made, how it looks like... just click/tap below!
One Time Analysis during Coffee Making
After 60 seconds from start the MCU compares the stored initial and the current temperature values in order to draw conclusions based only on temperature and elapsed time, which can be:
"You forgot the water." - (temperature went too high)
"Coffee for Two." - (less water - more tempr elevation)
"Coffee for Six." - (more water - less tempr elevation)
"Heating Coil error!" - (no significant tempr elevation)
In addition the Sensor Error detection works continuously as long as the coffee machine is switched on.
As can be seen, six dose of water required more time to reach the same temperature which the small dose reaches much sooner.
Starting without water results very high temperature, however, the flat line indicates unchanged temperature thus a heater coil error.
Changing Measurement Unit
The measurement unit can be changed by sending "c" or "f" from the Smartphone, even during a coffee making procedure, and it leaves no mark on the graph.
Dynamic Screen Elements
If the screen is full, the graph starts to roll to the left.
Depends on the measured value some screen element can be in its way. To keep avoid collision, their places are dynamic.
Well, it was a pleasant pastime:
Test Mode
By the time I created all of these, the idea came up, why not use the graph for other purposes?
So I coded the Test Mode; just attach an external temperature sensor and the system can be used as a multipurpose temperature meter with graph.
In this working way the extreme values of the graph are -20°C (-4°F) and +128°C (+262°F).
Sending numbers from the Smartphone between 1 and 999999 will be accepted as measurement interval in seconds, so the width of the graph is between 128 seconds and 11.57 days.
Bluetooth Communication
Accepts commands and sends reports, detects if a smartphone connects or disconnects.
Sent messages are blue and the response / report messages are green sent by the coffee maker.
Led Control
A CD4027BE decade counter drives the LED bar, receives CLK from the MCU at every temperature messurement, and increases the position. If a new coffee making procedure or the Test Mode starts, the Atmega 328P sends an RST signal to set the default position.
The button has a bicolor LED with only two pins, so my simple and funny solution can be seen in the code, how to control it.
Solid Green: standby or the coffee is ready, the coil is off
Solid Red: coffee making in progress, the heater coil is on
Red / Green in turn: Test Mode
Flashing Red: error occurred, that can be
-Sensor error,
-Heater Coil error, or
-You forgot the water, so the coffee making procedure was interrupted by the system
Schematics
// Program Code - Smart Coffee Maker. Developed on Arduino Nano, uses ST7920 display, HC-05 Bluetooth adapter,
// Dallas DS18B20 temp sensor, relay, button, leds, buzzer and a CD4017BE decade counter.
//
// Can be controlled via Bluetooth by sending commands and working parameters or using the Button... or just load fresh water!
//
// Functions:
// -Graph: dynamic temperature graph on the screen
// -Autostart: loading fresh water causes temperature drop, and the system will start
// This function can be enabled / disabled by sending "Auto"
// -Analysis during coffee making: 60 seconds after starting the system compares the current and initial temperature values
// and draws conclusions which can be:
// 1. You forgot the water (temperature is too high)
// 2. Coffee for Two (less water - more temp elevation)
// 3. Coffee for Six (more water - less temp elevation)
// 4. Heating Coil error (no significant temp elevation from start)
// -Sensor error detection (getting abnormal values)
// -Celsius or Fahrenheit working: can be switched by sending C of F from the Smartphone, even during a coffee making procedure.
// Initial and previous temperature values will be converted used for statistics and analysis.
// This setting is stored in the EEPROM memory.
// -Test Mode: attaching an external temperature sensor the system can be used as a multipurpose temperature meter.
// In this mode the graph interval is between -20C / -4F and +128C / +262F.
// Sending numeric values between 1 and 999999 will be accepted as measurement interval in seconds, so
// the graph width can be between 128 second and 11.57 days.
// -Bluetooth communication: accepts commands and sends reports, detects if a smartphone connects or disconnects
// -LED bar control: the MCU controls a CD4027BE decade counter, and the LED bar will step at all temp measurement cycle
// --------------------------------------------------------------------------------------------------------------------------------
//
// designed, built and programmed
// by Gyula Osi
//
// All rights reserved.
// ---- Display
#include "U8glib.h"
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // OLED display I2C Bus, SDA(TX) A4, SCL(RX) A5
U8GLIB_ST7920_128X64 u8g(11, 10, 9, U8G_PIN_NONE); // display constructor PIN6-D11, PIN5-D10, PIN4-D9
byte frame = 0; // start logo pointer
// ---- Ports & Controlled Peripherals
// red led on A0
// grn led on A1
const int bright[2] = {0, 500};
const byte buzzer = 3;
const byte cd4017beCLK = 4; // control CD4017BE decade counter
const byte cd4017beRST = 5;
const byte btn = 13;
const byte relay = 12;
// ---- System Strings
#define strSize 3
String str[strSize]; // system events and reports printout
#define reportSize 5
const String reportStr[reportSize] = {"Starting...", "Going standby soon...", "Stopped.", " *** TEST MODE ***", "Normal mode."};
//#define standbySize 5
//const String standbyStr[standbySize] = {"Press key", ">", "or", "send", "start"};
const String autostartStr[2] = {"Autostart off.", "Autostart on."};
// ---- Structure of System State Binary Flag Array
#define flagElements 6
bool binFlags[flagElements] = {0, 1, 1, 0, 0, 0};
// ---- elements [i] purpose init/standby mode val
// 0 checked 0
// 1 finished 1
// 2 standby 1
// 3 standby msg sent 0
// 4 coil/sensor error 0
// 5 test mode 0
// ---- Temperature Measurement and Related Features
#include "OneWire.h"
#include "DallasTemperature.h"
#define DS18B20 2 // setup the OneWire bus on D2
OneWire temprWire(DS18B20); // setup DS18B20 to work on the OneWire bus
DallasTemperature sensors(&temprWire);
float tempr; // measured value
float temprInit; // copy of measured value for one time comparison
float temprPrev; // copy of measured value for cyclic comparison
#define mUnit 2
float temprBottomLim[mUnit] = { 0, 26}; // autostart under tempr of AND one-time analysis under tempr of
const char unit[mUnit] = {'F', 'C'};
const String unitStr[mUnit] = {"Fahrenheit mode.", "Celsius mode."};
float trendSens[mUnit] = { 0, 0.1}; // +/- range (F/C) which will evaulated as constant temperature
// *** Fahrenheit reference values will be calculated at startup!
bool trend[2] = {1, 0}; // describes temperature trends as below
// ---- elements [i] purpose
// 0 changing
// 1 trend
#define checkSize 7
const String checkStr[checkSize] = {"Water detected!", "Heating coil error!", "Coffee for six", "Coffee for two", "You forgot the water!", "Sensor error!", "Your Coffee is ready."};
float temprCheck[mUnit][checkSize] = {{ }, {-0.15, 5, 17, 28, 60, -127, 110}};
// |<-- REL -->|<--CONST-->|
// [j] F / C
// ---------------------------------------------
// ---- elements [i] purpose used as
// 0 autostart rel value
// 1 coil error detection rel value
// 2 water for six rel value
// 3 water for two rel value
// 4 no water detection rel value
// 5 no signal on D2 const reference
// 6 boiling point const reference
// ---- Graph Declarations and Variables
#define posYsize 128
byte posY[posYsize];
byte pointer = 0;
#define refrElements 42
float temprRefr[refrElements];
#define rangeElements 2 // min / max
#define mode 2 // normal / test mode
float graphRange[mUnit][mode][rangeElements] = {{{ }, { }}, {{24, 127.938889 + 2}, {-20, graphRange[1][0][1]}}};
// [k] 0 1 0 1
// [j] | 0 | 1
// ---- elements [i] | | | |
// 0 minFnorm maxFnorm minFtest maxFtest
// 1 minCnorm maxCnorm minCtest maxCtest
float graphDiff[mUnit][mode]; // vertical steps by temperature
// ---- System Timers, Control & Analytics
#include <elapsedMillis.h>
elapsedMillis timer0; // 8-bit, PWM timer, used by function elapsedMillis()
unsigned long tmrPrev = 0; // the elapsed will be the previous when the interval is up
bool cyclic = 0;
const long tmrInt[5] = {500, 2000, 60000, 640000, 800000};
// ---- elements [i] purpose
// 0 meas interval (test mode), led ctrl
// 1 meas interval (normal mode)
// 2 one time analytics (normal mode, during coffee making) / cyclic analytics (standby mode)
// 3 sending standby msg (normal mode, after coffee making)
// 4 system standby (normal mode, after coffee making)
long copyof_tmrInt0 = tmrInt[0];
const String tmrInt0Str = "Interval changed.";
// ---- Configuration of Serial Communication
const byte btState = 6;
bool conn = 0;
const String connStr[2] = {"Connection lost.", "Smartphone connected."};
#include <SoftwareSerial.h>
const int RX1 = 7;
const int TX1 = 8;
SoftwareSerial sUART(RX1,TX1);
#define exRXSize 6
char exRX[exRXSize]; // variable to receive data from the serial port
// ---- Memory Management
#include <EEPROM.h>
#define occBytes 2
const byte addr[occBytes] = { 0, 1};
bool sysSettings[occBytes];
// ---- Memory Map addr[i] | data | descr | stored val
// -----------------------------------------------------
// 0 | bool | autostart | 0: no autostart, 1: autostart
// 1 | bool | meas unit | 0: Fahrenheit, 1: Celsius
// ---- Terminators & The Cleanup Crew
const char termCharSpc = ' ';
const String termStr;
const byte termByte = 0;
const bool termBool = 0;
const uint8_t frame0[] U8G_PROGMEM = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0xFE, 0xFF,
0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0xFC, 0xFF, 0x7F, 0xF0,
0x3F, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
0xFC, 0xFF, 0x7F, 0xF0, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF,
0xFF, 0xFF, 0x1F, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF,
0x8F, 0xFF, 0x1F, 0x7F, 0x8C, 0x3F, 0x1E, 0xFF, 0x00, 0xFE, 0x1F, 0xFF,
0xF1, 0x00, 0x18, 0xC0, 0x8F, 0xFF, 0x1F, 0x7F, 0x8C, 0x3F, 0x1E, 0xFF,
0x00, 0xFE, 0x1F, 0xFF, 0xF1, 0x00, 0x18, 0xC0, 0x8F, 0xFF, 0x1F, 0x1F,
0x0C, 0x3E, 0x1E, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0x31, 0xFE, 0x7F, 0xFC,
0x8F, 0xFF, 0x1F, 0x1F, 0x0C, 0x3E, 0x1E, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF,
0x31, 0xFE, 0x7F, 0xFC, 0x0F, 0x0E, 0x18, 0x1F, 0x0C, 0x3E, 0x1E, 0xFC,
0x00, 0xF8, 0x1F, 0x7C, 0x30, 0xFE, 0x7F, 0xF0, 0x0F, 0x0E, 0x18, 0x1F,
0x0C, 0x3E, 0x1E, 0xFC, 0x00, 0xF8, 0x1F, 0x7C, 0x30, 0xFE, 0x7F, 0xF0,
0x0F, 0xFE, 0x18, 0x1F, 0x0C, 0x3E, 0x1E, 0x3C, 0x3E, 0xF8, 0x1F, 0x7C,
0xF0, 0x00, 0x7E, 0xF0, 0x0F, 0xFE, 0x18, 0x1F, 0x0C, 0x3E, 0x1E, 0x3C,
0x3E, 0xF8, 0x1F, 0x7C, 0xF0, 0x00, 0x7E, 0xF0, 0x0F, 0xFE, 0x18, 0x1F,
0x0C, 0x3E, 0x1E, 0x3C, 0x3E, 0xF8, 0x1F, 0x7C, 0xF0, 0x3F, 0x78, 0xF0,
0x0F, 0xFE, 0x18, 0x1F, 0x0C, 0x3E, 0x1E, 0x3C, 0x3E, 0xF8, 0x1F, 0x7C,
0xF0, 0x3F, 0x78, 0xF0, 0x0F, 0xFE, 0x18, 0x1F, 0x0C, 0x3E, 0x1E, 0x3C,
0x3E, 0xF8, 0x1F, 0x7C, 0xF0, 0x3F, 0x78, 0xF0, 0x0F, 0xFE, 0x18, 0x1F,
0x0C, 0x3E, 0x1E, 0x3C, 0x3E, 0xF8, 0x1F, 0x7C, 0xF0, 0x3F, 0x78, 0xF0,
0x3F, 0x00, 0x7E, 0x00, 0x3C, 0x80, 0x07, 0xF0, 0x00, 0xF8, 0x7F, 0x00,
0x3C, 0x00, 0x1E, 0xC0, 0x3F, 0x00, 0x7E, 0x00, 0x3C, 0x80, 0x07, 0xF0,
0x00, 0xF8, 0x7F, 0x00, 0x3C, 0x00, 0x1E, 0xC0, 0xFF, 0xFF, 0xFF, 0x1F,
0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7E, 0x06, 0xE6, 0x3F, 0x06, 0xC6, 0x7F, 0xFE, 0xE7, 0x3F, 0x7E,
0xFE, 0xC7, 0x7F, 0x00, 0x00, 0x30, 0x06, 0x66, 0x60, 0x06, 0x66, 0x00,
0x60, 0x60, 0x60, 0x30, 0x06, 0x60, 0x00, 0x00, 0x00, 0x30, 0x06, 0x66,
0x60, 0x06, 0x66, 0x00, 0x60, 0x60, 0x60, 0x30, 0x06, 0x60, 0x00, 0x00,
0x00, 0x30, 0x1E, 0x66, 0x60, 0x06, 0x66, 0x00, 0x60, 0x60, 0x60, 0x30,
0x06, 0x60, 0x00, 0x00, 0x00, 0x30, 0x3E, 0x66, 0x60, 0x06, 0x66, 0x00,
0x60, 0x60, 0x60, 0x30, 0x06, 0x60, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0xE6,
0x61, 0x1E, 0xC7, 0x3F, 0x70, 0xE0, 0x3F, 0x3C, 0xFE, 0xC3, 0x3F, 0x00,
0x00, 0x3C, 0x7E, 0xE6, 0x61, 0x1E, 0xC7, 0x3F, 0x70, 0xE0, 0x3F, 0x3C,
0xFE, 0xC3, 0x3F, 0x00, 0x00, 0x3C, 0xDE, 0xE7, 0x61, 0x1E, 0x07, 0x70,
0x70, 0xE0, 0x1D, 0x3C, 0x1E, 0x00, 0x70, 0x00, 0x00, 0x3C, 0x1E, 0xE7,
0x61, 0x1E, 0x07, 0x70, 0x70, 0xE0, 0x31, 0x3C, 0x1E, 0x00, 0x70, 0x00,
0x00, 0x3C, 0x1E, 0xE6, 0x61, 0x1E, 0x07, 0x70, 0x70, 0xE0, 0x61, 0x3C,
0x1E, 0x00, 0x70, 0x00, 0x00, 0x3C, 0x1E, 0xE6, 0x61, 0x1E, 0x07, 0x70,
0x70, 0xE0, 0x61, 0x3C, 0x1E, 0x00, 0x70, 0x00, 0x00, 0x7F, 0x1E, 0xE6,
0x3F, 0xFC, 0xE3, 0x3F, 0x70, 0xE0, 0x61, 0x7E, 0xFE, 0xE7, 0x3F, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
const uint8_t frame1[] PROGMEM = {
0xF8, // 11111000 00
0x21, // 00100001 01
0x22, // 00100010 02
0x24, // 00100100 03
0x08, // 00001000 04
0x10, // 00010000 05
0x24, // 00100100 06
0x44, // 01000100 07
0x0F, // 00001111 08
0x04, // 00000100 09
0x04, // 00000100 10
0x03, // 00000011 11
};
const uint8_t frame2[] PROGMEM = {
0x08, // 00001000 00
0x1C, // 00011100 01
0x3E, // 00111110 02
0x7F, // 01111111 03
};
const uint8_t frame3[] PROGMEM = {
0x7F, // 01111111 00
0x3E, // 00111110 01
0x1C, // 00011100 02
0x08, // 00001000 03
};
void setup() {
u8g.begin();
fillArrays();
pinMode(cd4017beCLK, OUTPUT);
pinMode(cd4017beRST, OUTPUT);
CD4017BE(0); // send RESET to Decade Counter IC
pinMode(btState, INPUT);
pinMode(btn, INPUT);
pinMode(relay, OUTPUT);
sUART.begin(9600);
memo(2); // load system settings from EEPROM
sensors.begin(); // start DS18B20
temprMeas();
temprInit = tempr;
graph(2); // init graph
ledHandler(0);
}
void loop() {
sysControl();
checkBtn();
checkConn();
RX();
u8g.firstPage();
do {
draw();
}
while(u8g.nextPage());
if (frame == 0) {
delay(3000);
frame = 1;
scrClr();
}
}
// ---- Memory Management
// call param
// 2: read at startup
// 0, 1: write to the proper address
//
void memo(byte op) {
switch(op) {
case 2:
for (byte i = 0; i < occBytes; i++) {
sysSettings[i] = EEPROM.read(addr[i]);
}
break;
default:
EEPROM.write(addr[op], sysSettings[op]);
break;
}
}
// ---- Reset by Watchdog Timer
void wdRst() {
asm volatile ("jmp 0");
//wdt_disable(); // the WDT method works only on boards with new bootloader
//wdt_enable(presc); // start wd with the prescaller
//while(1) { // the prescaller time has to expire
//}
}
// ---- Start of System Control & Measurement Functions Sequence
void sysControl() {
unsigned long tmrCurr = millis();
// ---- Cyclic System Timer
// controls temperature measrement, LEDs,
// the CD4017BE decade counter and display elements
//
if ( ((!binFlags[5]) && (tmrInt[1] <= tmrCurr - tmrPrev)) || ( ((binFlags[2]) || (binFlags[4]) || (binFlags[5])) && (copyof_tmrInt0 <= tmrCurr - tmrPrev)) ) {
tmrPrev = tmrCurr;
cyclic = !cyclic;
if (binFlags[5]) {
ledHandler(2);
}
if (binFlags[4]) {
ledHandler(3);
}
if ((!binFlags[2]) || (binFlags[5])) { // call tempr measurement and related functions
temprMeas();
CD4017BE(1);
graph(0);
pointer++;
}
if ((pointer == 1) && (!binFlags[2]) && (!binFlags[5])) { // sending "starting" report
strSelector(1, 0);
}
}
// ---- Standy Mode Cyclic Analysis
// Waiting for fresh water causes temperature
// drop and automatically starts Coffee Making,
// if the Autostart procedure is activated.
if ((timer0 > tmrInt[2]) && (!binFlags[5])) {
if (binFlags[2]) {
temprMeas();
if ((tempr - temprInit <= temprCheck[sysSettings[1]][0]) && (tempr < temprBottomLim[sysSettings[1]])) {
coil(1);
strSelector(0, 0);
return;
}
else {
timer0 = 0;
}
temprInit = tempr;
}
// ---- One Time Analysis During Coffee Making
// Based on a comparison procedure of the current and reference
// temperature values. Conclusions will be correct under the
// declared temperature limit due to the non-linear characteristics of heated mass.
// By greather initial temperature only the error check analysis will work.
//
if ((!binFlags[0]) && (!binFlags[1]) && (!binFlags[2]) ) {
temprMeas();
for (byte i = 1; i < (checkSize - 2); i++) {
if ((tempr - temprInit <= temprCheck[sysSettings[1]][i]) && ((temprInit <= temprBottomLim[sysSettings[1]]) || (i == 1) || (i == 4))) {
if ((i == 1) || (i == 4)) {
binFlags[4] = 1;
coil(0);
}
strSelector(0, i);
binFlags[0] = 1;
return;
}
}
}
}
// ---- Elapsed Time-based Analytics below
if ((tempr == temprCheck[sysSettings[1]][5]) && (!binFlags[4])) { // check if there is sensor error
binFlags[4] = 1;
if (!binFlags[5]) {
coil(0);
}
strSelector(0, 5);
}
if (binFlags[5]) {
return;
}
if ((tempr >= temprCheck[sysSettings[1]][6]) && (!binFlags[1])) { // check boiling point
coil(0);
strSelector(0, 6);
}
if ((timer0 > tmrInt[3]) && (!binFlags[2]) && (!binFlags[3])) { // going standby msg AND a second security check
strSelector(1, 1);
binFlags[3] = 1;
if (!binFlags[1]) { // for case if the coil went broken after the One Time Analysis
coil(0); // and therefore there was no boiling point
strSelector(0, 1);
}
}
if (timer0 > tmrInt[4]) { // the system goes standby
binFlags[2] = 1;
strSelector(6, 0);
}
}
void temprMeas() {
temprPrev = tempr;
sensors.requestTemperatures(); // update sensor readings
if (sysSettings[1]) {
tempr = sensors.getTempCByIndex(0); // read remperature
}
else {
tempr = sensors.getTempFByIndex(0);
}
if (tempr >= temprPrev + trendSens[sysSettings[1]]) {
trend[0] = 0;
trend[1] = 1;
}
if (tempr <= temprPrev + trendSens[sysSettings[1]]) {
trend[0] = 0;
trend[1] = 0;
}
if ((tempr < temprPrev + trendSens[sysSettings[1]]) && (tempr > temprPrev - trendSens[sysSettings[1]])) {
trend[0] = 1;
}
}
// ---- Coil Control
// 0: power off
// 1: power on
void coil(byte op) {
switch (op) {
case 0:
digitalWrite(relay, 0);
binFlags[1] = 1;
strSelector(1, 2);
ledHandler(0);
break;
case 1:
digitalWrite(relay, 1);
strSelector(6, 0);
CD4017BE(0);
graph(1);
for (byte i = 0; i < flagElements; i++) {
binFlags[i] = termBool;
}
pointer = 0;
timer0 = 0;
temprMeas();
temprInit = tempr;
ledHandler(1);
break;
}
}
void exitTest() {
temprPrev = tempr;
binFlags[2] = 1;
binFlags[5] = 0;
graph(2);
ledHandler(0);
strSelector(1, 4);
}
// ---- Button Handler Routine
// * start coffee making OR
// * interrupt coffee making OR
// * exit Test Mode
//
void checkBtn() {
if (digitalRead(btn)) {
delay(40);
if (binFlags[5]) {
exitTest();
return;
}
coil(!digitalRead(relay));
}
}
// ---- LED Control
// call param
// 0: grn
// 1: red
// 2: grn/red
// 3: blinking red
void ledHandler(byte op) {
switch (op) {
case 0:
analogWrite(A0, bright[0]);
analogWrite(A1, bright[1]);
break;
case 1:
analogWrite(A1, bright[0]);
analogWrite(A0, bright[1]);
break;
case 2:
ledHandler(cyclic);
break;
case 3:
analogWrite(A1, bright[0]);
analogWrite(A0, bright[cyclic]);
break;
}
}
// ---- CD4017BE Decade Counter
// call param
// 0: RST (PIN15HIGH)
// 1: CLK (PIN14HIGH)
//
void CD4017BE(byte op) {
switch (op) {
case 0:
digitalWrite(cd4017beRST, 1);
delay(1);
digitalWrite(cd4017beRST, 0);
break;
case 1:
digitalWrite(cd4017beCLK, 1);
delay(1);
digitalWrite(cd4017beCLK, 0);
break;
}
}
// ---- Buzzer Handler
// call with frequency and delay parameters
void buzz (int b, int d) {
for (byte i = 0; i < b * 10; i++) {
analogWrite(buzzer, b * 1000);
delay(d * 10);
analogWrite(buzzer, 0);
}
}
// ---- String Handler Function
// call param with index or parameter
// 0: temperature-based analytics [i]
// 1: system event reports [i]
// 2: serial conn related rep [conn]
// 3: flip meas unit bool [sysSettings[1]]
// 4: flip autostart bool [sysSettings[0]]
// 5: tmrInt[0] val changed
// 6: clr str array
//
void strSelector(byte call, byte i) {
str[0] = termStr;
switch (call) {
case 0:
str[0] = checkStr[i];
break;
case 1:
str[0] = reportStr[i];
break;
case 2:
str[0] = connStr[i];
break;
case 3:
str[0] = unitStr[i];
break;
case 4:
str[0] = autostartStr[i];
break;
case 5:
str[0] = tmrInt0Str;
break;
case 6:
for (byte i = (strSize - 1); i > 0; i--) {
str[i] = termStr;
}
return;
}
TX();
for (byte i = (strSize - 1); i > 0; i--) { // roll for printout
str[i] = str[i - 1];
}
buzz(4, 1);
}
void draw(void) {
if (frame == 0) {
u8g.drawXBMP( 0, 0, 128, 64, frame0);
}
else {
if (frame == 1) {
scr();
}
}
}
void scr(void) {
if (binFlags[2]) {
#define standbySize 4
const String standbyStr[standbySize] = {"Press key >>>>", "or", "send", "start"};
u8g.setFont(u8g_font_courB10);
u8g.setPrintPos(2, 12);
u8g.print(standbyStr[0]);
u8g.setPrintPos(14, 26);
u8g.print(standbyStr[1]);
u8g.setPrintPos(30, 40);
u8g.print(standbyStr[2]);
u8g.setColorIndex(!cyclic);
u8g.drawBox(74, 28, 50, 14);
u8g.setColorIndex(cyclic);
u8g.setPrintPos(76, 40);
u8g.print(standbyStr[3]);
u8g.setColorIndex(1);
}
else {
if ((posY[0] >= 20) || (posY[13] >= 20)) {
u8g.drawBitmapP( 5, 0, 1, 12, frame1);
}
else {
u8g.drawBitmapP( 5, 25, 1, 12, frame1);
}
if ((posY[54] >= 30) || (posY[112] >= 30)) {
u8g.drawHLine(69, 40, 53);
scrTempr(72, 12);
}
else {
scrTempr(72, 44);
}
for (byte i = 0; i < posYsize; i++) {
if (posY[i] > 0) {
u8g.drawVLine(i, posY[i], 2);
}
}
}
u8g.setFont(u8g_font_6x12);
byte y = 53;
for (byte i = (strSize - 1); i > 0; i--) {
u8g.setPrintPos(0, y);
y = y + 9;
u8g.print(str[i]);
}
}
void scrTempr (byte tX, byte tY) {
u8g.drawVLine(2, 4, 39);
u8g.drawHLine(0, 40, 69);
u8g.drawHLine(0, posY[pointer - 1], 5);
byte tXrel = 2;
byte tYrel = 11;
if ((tempr < 100) && (tempr > -10)) {
u8g.drawFrame(tX - tXrel, tY - tYrel, 45, 13);
u8g.drawHLine(116, 40, 12);
}
else {
u8g.drawFrame(tX - tXrel, tY - tYrel, 51, 13);
u8g.drawHLine(122, 40, 6);
}
u8g.setFont(u8g_font_6x12);
u8g.setPrintPos(tX, tY);
u8g.print(tempr);
u8g.print(char(176));
u8g.print(unit[sysSettings[1]]);
if (trend[0]) {
return;
}
tXrel = 12;
if (trend[1]) {
u8g.drawBitmapP(tX - tXrel, tY - tYrel, 1, 4, frame2);
}
else {
u8g.drawBitmapP(tX - tXrel, tY - tYrel, 1, 4, frame3);
}
}
void scrClr(){
u8g.firstPage();
do {
} while(u8g.nextPage());
}
// ---- Maintenance of Graph Arrays
// call param
// 0: step & fill
// 1: clr
// 2: init
void graph(byte op) {
switch (op) {
case 0:
if (pointer == posYsize) {
for (byte i = 0; i < (posYsize - 1); i++) {
posY[i] = posY[i + 1];
posY[i + 1] = termByte;
}
pointer = posYsize - 1;
}
for (byte i = 0; i < refrElements; i++) {
if ((tempr <= temprRefr[i]) && (tempr >= temprRefr[i + 1])) {
posY[pointer] = i;
return;
}
}
break;
case 1:
for (byte i = 0; i < posYsize; i++) {
posY[i] = termByte;
}
break;
case 2:
float initial = graphRange[sysSettings[1]][binFlags[5]][1];
for (byte i = 0; i < refrElements; i++) {
temprRefr[i] = initial;
initial = initial - graphDiff[sysSettings[1]][binFlags[5]];
}
break;
}
}
// ---- Fill Up Arrays Program Sequence
// of reference Fahrenheit temperature values
// and calculation of graph vertical steps
// called at Startup
//
void fillArrays() {
trendSens[0] = convUnit(2, trendSens[1]);
byte i = 0;
for (i; i < 5; i++) {
temprCheck[0][i] = convUnit(2, temprCheck[1][i]);
}
for (i; i < 7 ; i++) {
temprCheck[0][i] = convUnit(1, temprCheck[1][i]);
}
temprBottomLim[0] = convUnit(1, temprBottomLim[1]);
for (byte graph = 0; graph < 2; graph++) {
for (byte j = 0; j < 2; j++) {
for (byte k = 0; k < 2; k++) {
if (graph == 0) {
graphRange[0][j][k] = convUnit(1, graphRange[1][j][k]);
}
else {
graphDiff[j][k] = (graphRange[j][k][1] - graphRange[j][k][0]) / refrElements;
}
}
}
}
}
// ---- Measurement Unit Conversion Function
// call param
// 0: F2C reference val
// 1: C2F reference val
// 2: C2F difference val
float convUnit(byte call, float val) {
switch (call) {
case 0:
return ((5 * (val - 32)) / 9);
break;
case 1:
return ((1.8 * val) + 32);
break;
case 2:
return (1.8 * val);
break;
}
}
void TX() {
sUART.println(str[0]);
}
void checkConn() {
if ((digitalRead(btState)) != conn) {
conn = digitalRead(btState);
strSelector(2, conn);
}
}
// ---- Accepted Serial Commands
//
// cmd descr
// [Auto] : enable / disable autostart with fresh water sensing (stored in EEPROM)
// [Reboot] : restart device
// [Start] : start coffee makig
// [Stop] : interrupt coffee making OR
// go back to normal working mode from Test Mode
// [Test] : enter Test Mode
// [C] / [c] : Celsius Mode (stored in EEPROM)
// [F] / [f] : Fahrenheit Mode (stored in EEPROM)
// [1]..[999999] : Temperature measurement interval - available in Test Mode only
//
void RX() {
for (byte i = 0; i < exRXSize; i++) {
exRX[i] = termCharSpc;
}
while (sUART.available()) {
for (byte i = 0; i < exRXSize; i++) {
exRX[i] = sUART.read();
}
}
if (((exRX[0] == 'A') || (exRX[0] == 'a')) && (exRX[1] == 'u') && (exRX[2] == 't') && (exRX[3] == 'o')) {
sysSettings[0] = !sysSettings[0];
memo(0);
strSelector(4, sysSettings[0]);
}
if (((exRX[0] == 'R') || (exRX[0] == 'r')) && (exRX[1] == 'e') && (exRX[2] == 'b') && (exRX[3] == 'o') && (exRX[4] == 'o') && (exRX[5] == 't')) {
if (!binFlags[1]) {
coil(0);
}
wdRst();
}
if (((exRX[0] == 'S') || (exRX[0] == 's')) && (exRX[1] == 't')) {
if ((exRX[2] == 'a') && (exRX[3] == 'r') && (exRX[4] == 't')) {
if (binFlags[5]) {
exitTest();
}
coil(1);
}
if ((exRX[2] == 'o') && (exRX[3] == 'p')) {
if (binFlags[5]) {
exitTest();
}
else {
coil(0);
}
}
}
if (((exRX[0] == 'T') || (exRX[0] == 't')) && (exRX[1] == 'e') && (exRX[2] == 's') && (exRX[3] == 't')) {
if (!binFlags[1]) {
coil(0);
}
strSelector(6, 0);
strSelector(1, 3);
binFlags[2] = 0;
binFlags[4] = 0;
binFlags[5] = 1;
graph(1);
pointer = 0;
graph(2);
}
if ((!sysSettings[1]) && ((exRX[0] == 'C') || (exRX[0] == 'c'))) {
sysSettings[1] = 1;
temprInit = convUnit(0, temprInit);
temprPrev = convUnit(0, temprPrev);
tempr = convUnit(0, tempr);
goto changeUnit;
}
if ((sysSettings[1]) && ((exRX[0] == 'F') || (exRX[0] == 'f'))) {
sysSettings[1] = 0;
temprInit = convUnit(1, temprInit);
temprPrev = convUnit(1, temprPrev);
tempr = convUnit(1, tempr);
goto changeUnit;
}
if ((binFlags[5]) && ((exRX[0] - '0') > 0) && (atof(exRX) >= 1) && (atof(exRX) <= 999999)) {
copyof_tmrInt0 = (atof(exRX) * 1000);
strSelector(5, 0);
}
for (byte i = 0; i < exRXSize; i++) {
exRX[i] = termCharSpc;
}
sUART.flush();
return;
changeUnit:
memo(1);
graph(2);
strSelector(3, sysSettings[1]);
}
The article was first published in hackster, July 7, 2021
cr: https://www.hackster.io/gyula-osi/smart-coffee-machine-with-arduino-and-bluetooth-6870d0
author: Gyula Ősi