Add NTC coefficients to measure celsius

This commit is contained in:
Rodrigo Arias Mallo 2025-10-13 21:09:06 +02:00
parent 695aa4b3c2
commit 9c114b136a
8 changed files with 329 additions and 173 deletions

2
barista/.gitignore vendored
View File

@ -1 +1,3 @@
build/
test_ntc
*.o

View File

@ -1,13 +1,21 @@
# Copyright (c) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
# 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

View File

@ -1,64 +1,8 @@
/* 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,
};
#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);
}

44
barista/ntc.c Normal file
View File

@ -0,0 +1,44 @@
/* Copyright (c) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later */
#include <math.h>
/* 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;
}

18
barista/ntc.h Normal file
View File

@ -0,0 +1,18 @@
/* Copyright (c) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
* 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 */

78
barista/pinout.h Normal file
View File

@ -0,0 +1,78 @@
/* Copyright (c) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef BARISTA_PINOUT_H
#define BARISTA_PINOUT_H
#include <Arduino.h>
#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 */

1
barista/test/Makefile Normal file
View File

@ -0,0 +1 @@
test_ntc: test_ntc.o ../ntc.c

15
barista/test/test_ntc.c Normal file
View File

@ -0,0 +1,15 @@
#include <stdio.h>
#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;
}