From 9c114b136aae0db626dbac4afb235061613ee120 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Mon, 13 Oct 2025 21:09:06 +0200 Subject: [PATCH] Add NTC coefficients to measure celsius --- barista/.gitignore | 2 + barista/Makefile | 22 ++- barista/barista.ino | 322 +++++++++++++++++++--------------------- barista/ntc.c | 44 ++++++ barista/ntc.h | 18 +++ barista/pinout.h | 78 ++++++++++ barista/test/Makefile | 1 + barista/test/test_ntc.c | 15 ++ 8 files changed, 329 insertions(+), 173 deletions(-) create mode 100644 barista/ntc.c create mode 100644 barista/ntc.h create mode 100644 barista/pinout.h create mode 100644 barista/test/Makefile create mode 100644 barista/test/test_ntc.c diff --git a/barista/.gitignore b/barista/.gitignore index 567609b..46a295a 100644 --- a/barista/.gitignore +++ b/barista/.gitignore @@ -1 +1,3 @@ build/ +test_ntc +*.o diff --git a/barista/Makefile b/barista/Makefile index a2dbec4..86a1b33 100644 --- a/barista/Makefile +++ b/barista/Makefile @@ -1,13 +1,21 @@ +# Copyright (c) 2025 Rodrigo Arias Mallo +# SPDX-License-Identifier: GPL-3.0-or-later + PORT=/dev/ttyUSB0 FQBN=arduino:avr:atmega328bb -OPTS=--no-color --log-level=info -v +OPTS=--no-color --log-level=info +#OPTS+=-v PROJ=barista BUILD=build/$(subst :,.,$(FQBN)) HEX=$(BUILD)/$(PROJ).ino.hex +# For host test programs +CPPFLAGS=-I. +LIBS=-lm + all: $(HEX) -$(HEX): barista.ino +$(HEX): barista.ino ntc.c ntc.h pinout.h arduino-cli compile $(OPTS) -e --fqbn $(FQBN) upload: $(HEX) @@ -15,3 +23,13 @@ upload: $(HEX) serial: picocom -b 115200 --lower-rts --lower-dtr /dev/ttyUSB0 --imap lfcrlf + +test: test_ntc + +test_ntc: test/test_ntc.o ntc.o + gcc $^ -o $@ $(LIBS) + +clean: + rm -f test/test_ntc.o ntc.o + +.PHONY: test all clean diff --git a/barista/barista.ino b/barista/barista.ino index 4204a63..d014ea8 100644 --- a/barista/barista.ino +++ b/barista/barista.ino @@ -1,64 +1,8 @@ /* Copyright (c) 2025 Rodrigo Arias Mallo * 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, -}; +#include "ntc.h" +#include "pinout.h" enum logic { ON = 1, @@ -66,8 +10,8 @@ enum logic { }; #define DEBOUNCE_TIME 20L /* ms */ -#define TEMP_MIN 600 -#define TEMP_MAX 700 +#define TEMP_MIN 80.0f +#define TEMP_MAX 90.0f #define LED_MIN_VALUE 0 @@ -98,9 +42,9 @@ enum button_state { }; enum buzz_state { - BUZZ_OFF = 0, - BUZZ_HEY, - BUZZ_STOP, + BUZZ_OFF = 0, + BUZZ_HEY, + BUZZ_STOP, }; int buzz_state; @@ -114,6 +58,10 @@ int button_state[MAX_BUTTON]; unsigned long button_press_t0[MAX_BUTTON]; unsigned long button_release_t0[MAX_BUTTON]; +float ntc_R; +float ntc_T; + + int read_input(int pin) { return !digitalRead(pin); @@ -121,20 +69,20 @@ int read_input(int pin) void relay(int pin, enum logic st) { - /* Relays are active low */ - if (st == ON) - digitalWrite(pin, 0); - else - digitalWrite(pin, 1); + /* 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); + /* LEDs are active high */ + if (st == ON) + digitalWrite(pin, 1); + else + digitalWrite(pin, 0); } void update_buttons() @@ -170,6 +118,13 @@ void update_buttons() } } +void update_ntc() +{ + int Vo = analogRead(PIN_NTC); + ntc_R = ntc_resistance(Vo); + ntc_T = ntc_temp(ntc_R); +} + int red_min = 50; int red_state = red_min; unsigned long brewing_time = 3000UL; /* 3 seconds */ @@ -183,10 +138,10 @@ void progress() 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; + 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); @@ -195,97 +150,111 @@ void progress() Serial.print(" temp="); Serial.println(temp); - /* Pressing ON cancels any operation */ + /* Pressing ON cancels any operation */ if (state != SLEEPING && on) { - state = SLEEPING; - return; - } + state = SLEEPING; + return; + } if (state == SLEEPING) { if (on) { state = HEATING; - heating_t0 = millis(); + 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"); - } + 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"); - } + 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"); - } - } + 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; + 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; - } + 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; + } +} + +/* Return the temperature in celsisus */ +float +measure_temp() +{ + int Vo = analogRead(PIN_NTC); + float c1 = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07; + float R1 = 10e3; /* 10 kOhm resistor */ + float R2 = R1 * (1023.0 / (float)Vo - 1.0); + float logR2 = log(R2); + float T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2)); + float Tc = T - 273.15; + return Tc; } 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); - } + if (state == HEATING || state == HOT || state == BREWING) { + int temp = measure_temp(); + if (temp < TEMP_MIN) + relay(PIN_HEAT, ON); + else if (temp > TEMP_MAX) + relay(PIN_HEAT, OFF); + } else { + relay(PIN_HEAT, OFF); + } } void @@ -293,29 +262,43 @@ update_pump() { if (state == BREWING) { relay(PIN_PUMP, ON); - } else { + } else { relay(PIN_PUMP, OFF); - } + } } void update_buzz() { - static unsigned long started = 0; + 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; - } + 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 inputs() +{ + update_buttons(); + update_ntc(); +} + +void outputs() +{ + update_leds(); + update_heater(); + update_pump(); + update_buzz(); } void setup() @@ -331,21 +314,18 @@ void setup() pinMode(PIN_HEAT, OUTPUT); pinMode(PIN_PUMP, OUTPUT); - /* Turn all relays off */ - relay(PIN_HEAT, OFF); - relay(PIN_PUMP, OFF); + /* Turn all relays off */ + relay(PIN_HEAT, OFF); + relay(PIN_PUMP, OFF); Serial.println("Ready"); } void loop() { - //Serial.println("Looping..."); - update_buttons(); + inputs(); progress(); - update_leds(); - update_heater(); - update_pump(); - update_buzz(); - delay(5); + outputs(); + + delay(5); } diff --git a/barista/ntc.c b/barista/ntc.c new file mode 100644 index 0000000..4d7c287 --- /dev/null +++ b/barista/ntc.c @@ -0,0 +1,44 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include + +/* Steinhart-Hart Thermistor Coefficients, used to convert resistance into + * temperature. + * + * The current NTC sensor has 102kOhm at 24C but I don't know the specific + * model, so the coefficients are computed for this NTC sensor instead: + * https://www.tme.eu/Document/f9d2f5e38227fc1c7d979e546ff51768/NTCM-100K-B3950.pdf + * + * The table seems to match what I would expect. Their R2 resistor is 6.8 kOhm, + * which yields a cuttof temperature of around 98.5 C at exactly half voltage + * (where the ADC would have more precision). + * + * In any case, we can calibrate the original NTC sensor by taking three + * temperature points. See: + * https://www.thinksrs.com/downloads/programs/therm%20calc/ntccalibrator/ntccalculator.html + */ +#define C1 0.7740577674e-3 +#define C2 2.073449619e-4 +#define C3 1.263502259e-7 + +/* Return the temperature in celsisus */ +float +ntc_temp(float R) +{ + /* Computing the log is slow, we may want to build a table */ + float logR = log(R); + float T = (1.0 / (C1 + C2*logR + C3*logR*logR*logR)); + float Tc = T - 273.15; + return Tc; +} + +/* Return resistance in Ohms */ +float +ntc_resistance(int Vo) +{ + float R1 = 6.8e3; /* Resistor for voltage divider */ + float R2 = R1 * (1023.0 / (float)Vo - 1.0); + + return R2; +} diff --git a/barista/ntc.h b/barista/ntc.h new file mode 100644 index 0000000..cf341b3 --- /dev/null +++ b/barista/ntc.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef BARISTA_NTC_H +#define BARISTA_NTC_H + +#ifdef __cplusplus +extern "C" { +#endif + +float ntc_resistance(int Vo); +float ntc_temp(float omhs); + +#ifdef __cplusplus +} +#endif + +#endif /* BARISTA_NTC_H */ diff --git a/barista/pinout.h b/barista/pinout.h new file mode 100644 index 0000000..2457c87 --- /dev/null +++ b/barista/pinout.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef BARISTA_PINOUT_H +#define BARISTA_PINOUT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * 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, +}; + + +#ifdef __cplusplus +} +#endif + +#endif /* BARISTA_PINOUT_H */ diff --git a/barista/test/Makefile b/barista/test/Makefile new file mode 100644 index 0000000..f605230 --- /dev/null +++ b/barista/test/Makefile @@ -0,0 +1 @@ +test_ntc: test_ntc.o ../ntc.c diff --git a/barista/test/test_ntc.c b/barista/test/test_ntc.c new file mode 100644 index 0000000..ef16497 --- /dev/null +++ b/barista/test/test_ntc.c @@ -0,0 +1,15 @@ +#include +#include "ntc.h" + +int main(void) +{ + for (int i = 0; i <= 1023; i++) { + float R = ntc_resistance(i); + float T = ntc_temp(R); + if (T < 96.0 || T > 100.2) + continue; + + printf("%6d %12.1f Ohm %12.1f C\n", i, R, T); + } + return 0; +}