352 lines
7.7 KiB
C++
352 lines
7.7 KiB
C++
/* Copyright (c) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
|
|
/*
|
|
* ATmega328p
|
|
* +---------+
|
|
* (PCINT14/RESET) PC6 -|1 \_/ 28|- PC5 (ADC5/SCL/PCINT13)
|
|
* (PCINT16/RXD) PD0 -|2 27|- PC4 (ADC4/SDA/PCINT12)
|
|
* (PCINT17/TXD) PD1 -|3 26|- PC3 (ADC3/PCINT11)
|
|
* (PCINT18/INT0) PD2 -|4 25|- PC2 (ADC2/PCINT10)
|
|
* (PCINT19/OC2B/INT1) PD3 -|5 24|- PC1 (ADC1/PCINT9)
|
|
* (PCINT20/XCK/T0) PD4 -|6 23|- PC0 (ADC0/PCINT8)
|
|
* VCC -|7 22|- GND
|
|
* GND -|8 21|- AREF
|
|
* (PCINT6/XTAL1/TOSC1) PB6 -|9 20|- AVCC
|
|
* (PCINT7/XTAL2/TOSC2) PB7 -|10 19|- PB5 (SCK/PCINT5)
|
|
* (PCINT21/OC0B/T1) PD5 -|11 18|- PB4 (MISO/PCINT4)
|
|
* (PCINT22/OC0A/AIN0) PD6 -|12 17|- PB3 (MOSI/OC2A/PCINT3)
|
|
* (PCINT23/AIN1) PD7 -|13 16|- PB2 (SS/OC1B/PCINT2)
|
|
* (PCINT0/CLKO/ICP1) PB0 -|14 15|- PB1 (OC1A/PCINT1)
|
|
* +---------+
|
|
*
|
|
*
|
|
* ATMEL ATMEGA8 & 168 / ARDUINO
|
|
*
|
|
* +-\/-+
|
|
* PC6 1| |28 PC5 (AI 5)
|
|
* (D 0) PD0 2| |27 PC4 (AI 4)
|
|
* (D 1) PD1 3| |26 PC3 (AI 3)
|
|
* (D 2) PD2 4| |25 PC2 (AI 2)
|
|
* PWM+ (D 3) PD3 5| |24 PC1 (AI 1)
|
|
* (D 4) PD4 6| |23 PC0 (AI 0)
|
|
* VCC 7| |22 GND
|
|
* GND 8| |21 AREF
|
|
* PB6 9| |20 AVCC
|
|
* PB7 10| |19 PB5 (D 13)
|
|
* PWM+ (D 5) PD5 11| |18 PB4 (D 12)
|
|
* PWM+ (D 6) PD6 12| |17 PB3 (D 11) PWM
|
|
* (D 7) PD7 13| |16 PB2 (D 10) PWM
|
|
* (D 8) PB0 14| |15 PB1 (D 9) PWM
|
|
* +----+
|
|
*
|
|
*
|
|
*/
|
|
|
|
enum pinout {
|
|
/* Inputs */
|
|
PIN_POWER_ON = 8,
|
|
PIN_HOT = 9,
|
|
//PIN_FLOW = PIN_D5,
|
|
|
|
/* Outputs */
|
|
PIN_LED_GREEN = 5,
|
|
PIN_LED_RED = 6,
|
|
PIN_HEAT = 7,
|
|
PIN_PUMP = 12,
|
|
PIN_BUZZ = 10,
|
|
|
|
/* Analog */
|
|
PIN_NTC = PIN_A0,
|
|
};
|
|
|
|
enum logic {
|
|
ON = 1,
|
|
OFF = 0,
|
|
};
|
|
|
|
#define DEBOUNCE_TIME 20L /* ms */
|
|
#define TEMP_MIN 600
|
|
#define TEMP_MAX 700
|
|
|
|
#define LED_MIN_VALUE 0
|
|
|
|
enum state {
|
|
SLEEPING,
|
|
DEBOUNCE,
|
|
HEATING,
|
|
HOT,
|
|
BREWING,
|
|
COOLING,
|
|
};
|
|
|
|
long debounce_t0 = 0;
|
|
int state = SLEEPING;
|
|
|
|
enum button {
|
|
BUTTON_ON = 0,
|
|
BUTTON_HOT,
|
|
MAX_BUTTON,
|
|
};
|
|
|
|
enum button_state {
|
|
RESTING = 0,
|
|
PRESSING,
|
|
PRESSED,
|
|
RELEASING,
|
|
RELEASED,
|
|
};
|
|
|
|
enum buzz_state {
|
|
BUZZ_OFF = 0,
|
|
BUZZ_HEY,
|
|
BUZZ_STOP,
|
|
};
|
|
|
|
int buzz_state;
|
|
|
|
int button_pin[MAX_BUTTON] = {
|
|
[BUTTON_ON] = PIN_POWER_ON,
|
|
[BUTTON_HOT] = PIN_HOT,
|
|
};
|
|
|
|
int button_state[MAX_BUTTON];
|
|
unsigned long button_press_t0[MAX_BUTTON];
|
|
unsigned long button_release_t0[MAX_BUTTON];
|
|
|
|
int read_input(int pin)
|
|
{
|
|
return !digitalRead(pin);
|
|
}
|
|
|
|
void relay(int pin, enum logic st)
|
|
{
|
|
/* Relays are active low */
|
|
if (st == ON)
|
|
digitalWrite(pin, 0);
|
|
else
|
|
digitalWrite(pin, 1);
|
|
}
|
|
|
|
void setled(int pin, enum logic st)
|
|
{
|
|
/* LEDs are active high */
|
|
if (st == ON)
|
|
digitalWrite(pin, 1);
|
|
else
|
|
digitalWrite(pin, 0);
|
|
}
|
|
|
|
void update_buttons()
|
|
{
|
|
for (int i = 0; i < MAX_BUTTON; i++) {
|
|
int st = button_state[i];
|
|
int pin = button_pin[i];
|
|
if (st == RESTING) {
|
|
if (read_input(pin) == ON) {
|
|
button_state[i] = PRESSING;
|
|
button_press_t0[i] = millis();
|
|
}
|
|
} else if (st == PRESSING) {
|
|
if (read_input(pin) != ON) {
|
|
button_state[i] = RESTING;
|
|
} else if (millis() - button_press_t0[i] > DEBOUNCE_TIME) {
|
|
button_state[i] = PRESSED;
|
|
}
|
|
} else if (st == PRESSED) {
|
|
if (read_input(pin) != ON) {
|
|
button_state[i] = RELEASING;
|
|
button_release_t0[i] = millis();
|
|
}
|
|
} else if (st == RELEASING) {
|
|
if (read_input(pin) == ON) {
|
|
button_state[i] = PRESSED;
|
|
} else if (millis() - button_release_t0[i] > DEBOUNCE_TIME) {
|
|
button_state[i] = RELEASED;
|
|
}
|
|
} else if (st == RELEASED) {
|
|
button_state[i] = RESTING;
|
|
}
|
|
}
|
|
}
|
|
|
|
int red_min = 50;
|
|
int red_state = red_min;
|
|
unsigned long brewing_time = 3000UL; /* 3 seconds */
|
|
unsigned long cooling_time = 3000UL; /* 3 seconds */
|
|
unsigned long max_heating_time = 10000UL; /* 10 seconds */
|
|
unsigned long max_idle_time = 10000UL; /* 10 seconds */
|
|
|
|
void progress()
|
|
{
|
|
int temp = analogRead(PIN_NTC);
|
|
int on = (button_state[BUTTON_ON] == RELEASED);
|
|
int hot = (button_state[BUTTON_HOT] == PRESSED);
|
|
|
|
static unsigned long brewing_t0 = 0;
|
|
static unsigned long cooling_t0 = 0;
|
|
static unsigned long heating_t0 = 0;
|
|
static unsigned long hot_t0 = 0;
|
|
|
|
Serial.print("state=");
|
|
Serial.print(state);
|
|
Serial.print(" on=");
|
|
Serial.print(on);
|
|
Serial.print(" temp=");
|
|
Serial.println(temp);
|
|
|
|
/* Pressing ON cancels any operation */
|
|
if (state != SLEEPING && on) {
|
|
state = SLEEPING;
|
|
return;
|
|
}
|
|
|
|
if (state == SLEEPING) {
|
|
if (on) {
|
|
state = HEATING;
|
|
heating_t0 = millis();
|
|
Serial.println("heating");
|
|
}
|
|
} else if (state == HEATING) {
|
|
if (temp > TEMP_MAX) {
|
|
state = HOT;
|
|
hot_t0 = millis();
|
|
buzz_state = BUZZ_HEY;
|
|
Serial.println("hot");
|
|
} else if (millis() - heating_t0 > max_heating_time) {
|
|
/* TODO: Add alarm state */
|
|
state = SLEEPING;
|
|
Serial.println("cannot heat, going to sleep");
|
|
}
|
|
} else if (state == HOT) {
|
|
if (hot) {
|
|
state = BREWING;
|
|
brewing_t0 = millis();
|
|
Serial.println("brewing");
|
|
} else if (millis() - hot_t0 > max_idle_time) {
|
|
state = SLEEPING;
|
|
Serial.println("idle timeout, going to sleep");
|
|
}
|
|
} else if (state == BREWING) {
|
|
if (millis() - brewing_t0 > brewing_time) {
|
|
state = COOLING;
|
|
cooling_t0 = millis();
|
|
Serial.println("cooling");
|
|
}
|
|
} else if (state == COOLING) {
|
|
/* TODO: Wait a bit and go back to heating */
|
|
if (millis() - cooling_t0 > cooling_time) {
|
|
state = HEATING;
|
|
heating_t0 = millis();
|
|
Serial.println("heating");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
update_leds()
|
|
{
|
|
static int r = 0;
|
|
static int g = 0;
|
|
|
|
if (state == HEATING || state == COOLING) {
|
|
analogWrite(PIN_LED_RED, r);
|
|
setled(PIN_LED_GREEN, 0);
|
|
if (r >= 255)
|
|
r = 0;
|
|
else
|
|
r += 3;
|
|
} else if (state == HOT) {
|
|
setled(PIN_LED_RED, 0);
|
|
setled(PIN_LED_GREEN, 1);
|
|
r = 0;
|
|
} else if (state == BREWING) {
|
|
setled(PIN_LED_RED, 0);
|
|
analogWrite(PIN_LED_GREEN, g);
|
|
if (g >= 255)
|
|
g = 0;
|
|
else
|
|
g += 3;
|
|
} else {
|
|
setled(PIN_LED_RED, 0);
|
|
setled(PIN_LED_GREEN, 0);
|
|
r = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
update_heater()
|
|
{
|
|
if (state == HEATING || state == HOT || state == BREWING) {
|
|
int temp = analogRead(PIN_NTC);
|
|
if (temp < TEMP_MIN)
|
|
relay(PIN_HEAT, ON);
|
|
else if (temp > TEMP_MAX)
|
|
relay(PIN_HEAT, OFF);
|
|
} else {
|
|
relay(PIN_HEAT, OFF);
|
|
}
|
|
}
|
|
|
|
void
|
|
update_pump()
|
|
{
|
|
if (state == BREWING) {
|
|
relay(PIN_PUMP, ON);
|
|
} else {
|
|
relay(PIN_PUMP, OFF);
|
|
}
|
|
}
|
|
|
|
void
|
|
update_buzz()
|
|
{
|
|
static unsigned long started = 0;
|
|
|
|
if (buzz_state == BUZZ_HEY) {
|
|
tone(PIN_BUZZ, 1500);
|
|
if (started == 0)
|
|
started = millis();
|
|
else if (millis() - started > 20) {
|
|
buzz_state = BUZZ_OFF;
|
|
}
|
|
} else if (buzz_state == BUZZ_STOP) {
|
|
tone(PIN_BUZZ, 220);
|
|
} else if (buzz_state == BUZZ_OFF) {
|
|
noTone(PIN_BUZZ);
|
|
started = 0;
|
|
}
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(9600);
|
|
Serial.println("Booting");
|
|
|
|
pinMode(PIN_POWER_ON, INPUT);
|
|
pinMode(PIN_HOT, INPUT);
|
|
|
|
pinMode(PIN_LED_RED, OUTPUT);
|
|
pinMode(PIN_LED_GREEN, OUTPUT);
|
|
pinMode(PIN_HEAT, OUTPUT);
|
|
pinMode(PIN_PUMP, OUTPUT);
|
|
|
|
/* Turn all relays off */
|
|
relay(PIN_HEAT, OFF);
|
|
relay(PIN_PUMP, OFF);
|
|
|
|
Serial.println("Ready");
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
//Serial.println("Looping...");
|
|
update_buttons();
|
|
progress();
|
|
update_leds();
|
|
update_heater();
|
|
update_pump();
|
|
update_buzz();
|
|
delay(5);
|
|
}
|