Термостат, терморегулятор, термореле – призначені для контролю і підтримки заданої температури з заданою точністю. Вони в автоматичному режимі керують різного виду навантаженням для нагрівання чи охолодження. В склад терморегулятора входить датчик температури, мікроконтролер та актуатор, найчастіше реле або транзистор.
В даній статті розглянемо термостат на Arduino з NTC датчиком температури та семисегментним індикатором або LCD1602/LCD2004 для відображення температури. Керувати навантаженням будем за допомогою електромеханічного реле.
Підключення NTC терморезистора до Arduino
Підключення LCD1602 по I2C до Arduino
Підключення семисегментних індикаторів до Arduino
Підключення декількох тактових кнопок на один вхід Arduino
Керування налаштуваннями будемо виконувати за допомогою двох кнопок. Термореле буде мати два режими
- Нагрівання – реле вмикається якщо температура нижче заданої
- Охолодження – реле вмикається коли температура вище заданої
Також за допогою кнопок будем налаштовувати температуру для спрацювання, та гестирезис в градусах.
Схема підключення з семисигментиними індикаторами
Код програми
#include <Arduino.h>
#include <EEPROM.h>
#define MIN_TEMP -50
#define MAX_TEMP 120
#define MIN_HIST 0
#define MAX_HIST 10
#define R_NTC 100000
#define B 3950
#define SAMPLES 10
#define VCC 5.0
#define R1 100000
#define NOMINAL_T 25
#define KELVIN_T 273.15
#define NTC A0
#define BUTTON_SET 15
#define BUTTON_MINUS 16
#define RELAY 13
int click;
bool buttton_finished = false;
#define GENERAL_ANOD 1
#define GENERAL_CATOD 0
#define TYPE GENERAL_ANOD
#define A_1 2
#define B_1 3
#define C_1 4
#define D_1 5
#define E_1 6
#define F_1 7
#define G_1 8
#define DP_1 9
#define DIG_1 10
#define DIG_2 11
#define DIG_3 12
volatile uint8_t dig1;
volatile uint8_t dig2;
volatile uint8_t dig3;
bool heat = true;
uint8_t __heat;
int action_temp = 30;
int _action_temp = 300;
int __act;
int hysteresis = 2;
int _hysteresis = 20;
int __hyst;
bool change = true;
bool change_dig1 = false;
bool change_dig2 = false;
bool change_dig3 = false;
bool change_dp = false;
bool flag_float = false;
bool _flag_float = false;
bool flag_setting = false;
bool flag_hist_setting = false;
bool flag_direct = false;
unsigned long timer_setting = 0;
uint8_t nums[13][7] = {{1, 1, 1, 1, 1, 1, 0}, // 0
{0, 1, 1, 0, 0, 0, 0}, // 1
{1, 1, 0, 1, 1, 0, 1}, // 2
{1, 1, 1, 1, 0, 0, 1}, // 3
{0, 1, 1, 0, 0, 1, 1}, // 4
{1, 0, 1, 1, 0, 1, 1}, // 5
{1, 0, 1, 1, 1, 1, 1}, // 6
{1, 1, 1, 0, 0, 0, 0}, // 7
{1, 1, 1, 1, 1, 1, 1}, // 8
{1, 1, 1, 1, 0, 1, 1}, // 9
{0, 0, 0, 0, 0, 0, 1}, // -
{0, 1, 1, 0, 1, 1, 1}, // H
{1, 0, 0, 1, 1, 1, 0}}; // C
void disp_num(uint8_t *num, bool dp)
{
if (TYPE)
{
digitalWrite(A_1, !num[0]);
digitalWrite(B_1, !num[1]);
digitalWrite(C_1, !num[2]);
digitalWrite(D_1, !num[3]);
digitalWrite(E_1, !num[4]);
digitalWrite(F_1, !num[5]);
digitalWrite(G_1, !num[6]);
digitalWrite(DP_1, !dp);
}
else
{
digitalWrite(A_1, num[0]);
digitalWrite(B_1, num[1]);
digitalWrite(C_1, num[2]);
digitalWrite(D_1, num[3]);
digitalWrite(E_1, num[4]);
digitalWrite(F_1, num[5]);
digitalWrite(G_1, num[6]);
digitalWrite(DP_1, dp);
}
}
void display_h()
{
change_dig1 = false;
change_dig2 = false;
change_dig3 = true;
dig3 = 11;
}
void display_c()
{
change_dig1 = false;
change_dig2 = false;
change_dig3 = true;
dig3 = 12;
}
void display(int num)
{
if (!_flag_float)
{
flag_float = false;
change_dp = false;
}
else
{
_flag_float = false;
}
bool minus = false;
if (num < -99)
{
dig1 = 10;
dig2 = 9;
dig3 = 9;
change_dig1 = true;
change_dig2 = true;
change_dig3 = true;
flag_float = false;
change_dp = false;
return;
}
if (num < 0)
{
minus = true;
num = num * -1;
}
if (num > 999)
{
dig1 = 9;
dig2 = 9;
dig3 = 9;
change_dig1 = true;
change_dig2 = true;
change_dig3 = true;
change_dp = false;
flag_float = false;
return;
}
if (num < 10)
{
dig3 = num;
change_dig3 = true;
if (minus)
{
if (flag_float)
{
change_dig1 = true;
dig1 = 10;
dig2 = 0;
}
else
{
dig2 = 10;
change_dig1 = false;
}
change_dig2 = true;
}
else
{
if (flag_float)
{
change_dig2 = true;
dig2 = 0;
}
else
{
change_dig2 = false;
}
change_dig1 = false;
}
}
else if (num < 100)
{
change_dig2 = true;
change_dig3 = true;
dig2 = num / 10;
dig3 = num - dig2 * 10;
if (minus)
{
dig1 = 10;
change_dig1 = true;
}
else
{
change_dig1 = false;
}
}
else
{
dig1 = num / 100;
int _dig2 = num % 100;
dig2 = _dig2 / 10;
dig3 = (_dig2 % 10);
change_dig1 = true;
change_dig2 = true;
change_dig3 = true;
}
}
void display(float num)
{
_flag_float = true;
if ((num > -10) && (num < 100))
{
change_dp = true;
flag_float = true;
display(int(num * 10));
}
else
{
change_dp = false;
flag_float = false;
display(int(num));
}
}
void output()
{
static uint8_t _dig = 1;
static unsigned long timer = 0;
if ((millis() - timer) >= 7)
{
timer = millis();
if (_dig > 3)
{
_dig = 1;
}
switch (_dig)
{
case 1:
if (change_dig1)
{
digitalWrite(DIG_1, HIGH);
digitalWrite(DIG_2, LOW);
digitalWrite(DIG_3, LOW);
disp_num(nums[dig1], false);
}
break;
case 2:
if (change_dig2)
{
digitalWrite(DIG_1, LOW);
digitalWrite(DIG_2, HIGH);
digitalWrite(DIG_3, LOW);
disp_num(nums[dig2], change_dp);
}
break;
case 3:
if (change_dig3)
{
digitalWrite(DIG_1, LOW);
digitalWrite(DIG_2, LOW);
digitalWrite(DIG_3, HIGH);
disp_num(nums[dig3], false);
}
break;
default:
break;
}
_dig++;
}
}
float read_ntc()
{
int raw = 0;
for (int i = 0; i < SAMPLES; i++)
{
raw += analogRead(NTC);
}
raw = raw / SAMPLES;
float Vread = (VCC / 1023.00) * raw;
float R_ntc = ((VCC / (VCC - Vread)) - 1) * R1;
float steinhart;
steinhart = log(R_ntc / R_NTC);
steinhart = steinhart / B;
steinhart = steinhart + 1.0 / (NOMINAL_T + KELVIN_T);
steinhart = 1.0 / steinhart;
return steinhart - KELVIN_T;
}
void read_button_set()
{
static bool flag_read_button = true;
static unsigned long timer = 0;
static unsigned long timer_counter = 0;
static unsigned long timer_long_press = 0;
static int _click = 0;
if (flag_read_button)
{
if (_click)
{
if ((millis() - timer_counter) > 400)
{
click = _click;
buttton_finished = true;
_click = 0;
}
}
if (digitalRead(BUTTON_SET))
{
flag_read_button = false;
timer = millis();
timer_long_press = timer;
}
}
else
{
if (digitalRead(BUTTON_SET))
{
timer = millis();
}
else
{
if ((millis() - timer) > 30)
{
if ((millis() - timer_long_press) > 1000)
{
_click = 99;
}
else
{
_click = 1;
}
timer_counter = millis();
flag_read_button = true;
}
}
}
}
void calc_action_temp()
{
_action_temp = action_temp * 10;
_hysteresis = (hysteresis * 10) / 2;
if (_hysteresis == 0)
{
_hysteresis = 1;
}
}
void read_setting()
{
int _at;
EEPROM.get(11, _at);
if (_at < -50 || _at > 120)
{
EEPROM.put(13, action_temp);
}
else
{
action_temp = _at;
}
int _hs;
EEPROM.get(13, _hs);
if (_hs < 0 || _hs > 10)
{
EEPROM.put(13, hysteresis);
}
else
{
hysteresis = _hs;
}
uint8_t _heat = EEPROM.read(10);
if (_heat != 0 && _heat != 1)
{
EEPROM.update(10, 1);
}
else
{
if (_heat)
{
heat = true;
}
else
{
heat = false;
}
}
calc_action_temp();
__heat = heat;
__act = action_temp;
__hyst = hysteresis;
}
void save_setting()
{
EEPROM.update(10, __heat);
EEPROM.put(11, __act);
EEPROM.put(13, __hyst);
calc_action_temp();
}
void button_action()
{
if (buttton_finished)
{
buttton_finished = false;
if ((click == 99) && flag_setting && flag_hist_setting && flag_direct)
{
flag_direct = false;
flag_hist_setting = false;
flag_setting = false;
save_setting();
}
else if ((click == 99) && flag_setting && flag_hist_setting)
{
flag_direct = true;
if (__heat)
{
display_h();
}
else
{
display_c();
}
save_setting();
}
else if ((click == 99) && flag_setting)
{
flag_hist_setting = true;
save_setting();
timer_setting = millis();
display(__hyst);
}
else if (click == 99)
{
flag_setting = true;
timer_setting = millis();
display(__act);
}
if (flag_setting)
{
if (click == 1)
{
if (flag_hist_setting && flag_direct)
{
if (__heat)
{
display_c();
__heat = false;
}
else
{
display_h();
__heat = true;
}
}
else if (flag_hist_setting)
{
if (__hyst < MAX_HIST)
{
__hyst += 1;
}
display(__hyst);
}
else
{
if (__act < MAX_TEMP)
{
__act += 1;
}
display(__act);
}
}
}
}
}
bool read_button_minus()
{
static bool flag_read_button = true;
static unsigned long timer = 0;
if (flag_read_button)
{
if (digitalRead(BUTTON_MINUS))
{
flag_read_button = false;
timer = millis();
}
}
else
{
if (digitalRead(BUTTON_MINUS))
{
timer = millis();
}
else
{
if ((millis() - timer) > 50)
{
flag_read_button = true;
return true;
}
}
}
return false;
}
void setting()
{
if ((millis() - timer_setting) > 60000)
{
flag_direct = false;
flag_setting = false;
flag_hist_setting = false;
}
if (read_button_minus())
{
if (flag_hist_setting && flag_direct)
{
if (__heat)
{
display_c();
__heat = false;
}
else
{
display_h();
__heat = true;
}
}
else if (flag_hist_setting)
{
if (__hyst > MIN_HIST)
{
__hyst -= 1;
display(__hyst);
}
}
else
{
if (__act > MIN_TEMP)
{
__act -= 1;
display(__act);
}
}
}
}
void relay(bool action)
{
digitalWrite(RELAY, action);
}
void relay_action(float temperature)
{
int temp = temperature * 10;
if (heat)
{
if (temp <= _action_temp - _hysteresis)
{
relay(true);
}
else if (temp >= _action_temp + _hysteresis)
{
relay(false);
}
}
else
{
if (temp >= _action_temp + _hysteresis)
{
relay(true);
}
else if (temp <= _action_temp - _hysteresis)
{
relay(false);
}
}
}
void setup()
{
pinMode(A_1, OUTPUT);
pinMode(B_1, OUTPUT);
pinMode(C_1, OUTPUT);
pinMode(D_1, OUTPUT);
pinMode(E_1, OUTPUT);
pinMode(F_1, OUTPUT);
pinMode(G_1, OUTPUT);
pinMode(DP_1, OUTPUT);
pinMode(DIG_1, OUTPUT);
pinMode(DIG_2, OUTPUT);
pinMode(DIG_3, OUTPUT);
pinMode(BUTTON_SET, INPUT);
pinMode(BUTTON_MINUS, INPUT);
pinMode(RELAY, OUTPUT);
read_setting();
Serial.begin(9600);
}
void loop()
{
static float temperature;
static unsigned long timer = 0;
if ((millis() - timer) > 1000)
{
temperature = read_ntc();
relay_action(temperature);
timer = millis();
if (!flag_setting)
{
display(temperature);
}
}
if (flag_setting)
{
setting();
}
output();
read_button_set();
button_action();
}
Схема підключення з LCD 1602 по I2C
Код програми
#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
#define TIME_BACKLIGHT_LCD 10000
#define MIN_TEMP -50
#define MAX_TEMP 120
#define MIN_HIST 0
#define MAX_HIST 10
#define R_NTC 100000
#define B 3950
#define SAMPLES 20
#define VCC 5.0
#define R1 100000
#define NOMINAL_T 25
#define KELVIN_T 273.15
#define NTC A0
#define BUTTON_SET 15
#define BUTTON_MINUS 16
#define RELAY 13
int click;
bool buttton_finished = false;
bool heat = true;
uint8_t __heat;
int action_temp = 30;
int _action_temp = 300;
int __act;
int hysteresis = 2;
int _hysteresis = 20;
int __hyst;
bool flag_backlight = true;
bool flag_setting = false;
bool flag_hist_setting = false;
bool flag_direct = false;
unsigned long timer_setting = 0;
unsigned long timer_backlight = 0;
void display_settings()
{
static unsigned long timer = 0;
static bool veiw = true;
String _dis = " ";
if ((millis() - timer) >= 500)
{
timer = millis();
veiw = !veiw;
if (flag_hist_setting && flag_direct)
{
lcd.setCursor(15, 1);
if (__heat)
{
_dis = "H";
}
else
{
_dis = "C";
}
}
else if (flag_hist_setting)
{
lcd.setCursor(10, 1);
_dis = __hyst;
}
else
{
lcd.setCursor(3, 1);
_dis = __act;
}
if (veiw)
{
lcd.print(_dis);
}
else
{
lcd.print(" ");
}
}
}
void display(float num)
{
int point = 1;
if (num > 0)
{
point += 1;
}
if (num < 10 && num > -10)
{
point += 2;
}
else if (num < 100 && num > -100)
{
point += 1;
}
for (int i = 0; i <= point; i++)
{
lcd.setCursor(i, 0);
lcd.print(" ");
}
lcd.setCursor(point, 0);
lcd.print(num, 1);
}
float read_ntc()
{
int raw = 0;
for (int i = 0; i < SAMPLES; i++)
{
raw += analogRead(NTC);
}
raw = raw / SAMPLES;
float Vread = (VCC / 1023.00) * raw;
float R_ntc = ((VCC / (VCC - Vread)) - 1) * R1;
float steinhart;
steinhart = log(R_ntc / R_NTC);
steinhart = steinhart / B;
steinhart = steinhart + 1.0 / (NOMINAL_T + KELVIN_T);
steinhart = 1.0 / steinhart;
return steinhart - KELVIN_T;
}
void read_button_set()
{
static bool flag_read_button = true;
static unsigned long timer = 0;
static unsigned long timer_counter = 0;
static unsigned long timer_long_press = 0;
static int _click = 0;
if (flag_read_button)
{
if (_click)
{
if ((millis() - timer_counter) > 300)
{
click = _click;
buttton_finished = true;
_click = 0;
}
}
if (digitalRead(BUTTON_SET))
{
if (!flag_backlight)
{
flag_backlight = true;
lcd.backlight();
}
timer_backlight = millis();
flag_read_button = false;
timer = millis();
timer_long_press = timer;
}
}
else
{
if (digitalRead(BUTTON_SET))
{
timer = millis();
}
else
{
if ((millis() - timer) > 30)
{
if ((millis() - timer_long_press) > 1000)
{
_click = 99;
}
else
{
_click = 1;
}
timer_counter = millis();
flag_read_button = true;
}
}
}
}
void calc_action_temp()
{
_action_temp = action_temp * 10;
_hysteresis = (hysteresis * 10) / 2;
if (_hysteresis == 0)
{
_hysteresis = 1;
}
}
void read_setting()
{
int _at;
EEPROM.get(11, _at);
if (_at < -50 || _at > 120)
{
EEPROM.put(13, action_temp);
}
else
{
action_temp = _at;
}
int _hs;
EEPROM.get(13, _hs);
if (_hs < 0 || _hs > 10)
{
EEPROM.put(13, hysteresis);
}
else
{
hysteresis = _hs;
}
uint8_t _heat = EEPROM.read(10);
if (_heat != 0 && _heat != 1)
{
EEPROM.update(10, 1);
}
else
{
if (_heat)
{
heat = true;
}
else
{
heat = false;
}
}
calc_action_temp();
__heat = heat;
__act = action_temp;
__hyst = hysteresis;
}
void display_header()
{
lcd.setCursor(3, 1);
lcd.print(action_temp, 1);
lcd.setCursor(15, 1);
if (heat)
{
lcd.print("H");
}
else
{
lcd.print("C");
}
lcd.setCursor(10, 1);
lcd.print(hysteresis, 1);
}
void save_setting()
{
EEPROM.update(10, __heat);
EEPROM.put(11, __act);
EEPROM.put(13, __hyst);
heat = __heat;
action_temp = __act;
hysteresis = __hyst;
calc_action_temp();
display_header();
}
void button_action()
{
if (buttton_finished)
{
buttton_finished = false;
if ((click == 99) && flag_setting && flag_hist_setting && flag_direct)
{
flag_direct = false;
flag_hist_setting = false;
flag_setting = false;
save_setting();
}
else if ((click == 99) && flag_setting && flag_hist_setting)
{
flag_direct = true;
save_setting();
}
else if ((click == 99) && flag_setting)
{
flag_hist_setting = true;
save_setting();
timer_setting = millis();
}
else if (click == 99)
{
flag_setting = true;
timer_setting = millis();
}
if (flag_setting)
{
if (click == 1)
{
if (flag_hist_setting && flag_direct)
{
__heat = !__heat;
}
else if (flag_hist_setting)
{
if (__hyst < MAX_HIST)
{
__hyst += 1;
}
}
else
{
if (__act < MAX_TEMP)
{
__act += 1;
}
}
}
}
}
}
bool read_button_minus()
{
static bool flag_read_button = true;
static unsigned long timer = 0;
if (flag_read_button)
{
if (digitalRead(BUTTON_MINUS))
{
if (!flag_backlight)
{
flag_backlight = true;
lcd.backlight();
}
timer_backlight = millis();
flag_read_button = false;
timer = millis();
}
}
else
{
if (digitalRead(BUTTON_MINUS))
{
timer = millis();
}
else
{
if ((millis() - timer) > 50)
{
flag_read_button = true;
return true;
}
}
}
return false;
}
void setting()
{
if ((millis() - timer_setting) > 60000)
{
flag_direct = false;
flag_setting = false;
flag_hist_setting = false;
display_header();
}
if (read_button_minus())
{
if (flag_hist_setting && flag_direct)
{
__heat = !__heat;
}
else if (flag_hist_setting)
{
if (__hyst > MIN_HIST)
{
__hyst -= 1;
}
}
else
{
if (__act > MIN_TEMP)
{
__act -= 1;
}
}
}
}
void relay(bool action)
{
lcd.setCursor(11, 0);
if (action)
{
lcd.print("run");
}
else
{
lcd.print(" ");
}
digitalWrite(RELAY, action);
}
void relay_action(float temperature)
{
int temp = temperature * 10;
if (heat)
{
if (temp <= _action_temp - _hysteresis)
{
relay(true);
}
else if (temp >= _action_temp + _hysteresis)
{
relay(false);
}
}
else
{
if (temp >= _action_temp + _hysteresis)
{
relay(true);
}
else if (temp <= _action_temp - _hysteresis)
{
relay(false);
}
}
}
void setup()
{
read_setting();
lcd.init();
lcd.backlight();
display_header();
pinMode(BUTTON_SET, INPUT);
pinMode(BUTTON_MINUS, INPUT);
pinMode(RELAY, OUTPUT);
Serial.begin(9600);
}
void loop()
{
static float temperature;
static unsigned long timer = 0;
if (flag_backlight && (millis() - timer_backlight) > TIME_BACKLIGHT_LCD)
{
flag_backlight = false;
lcd.noBacklight();
}
if ((millis() - timer) > 1000)
{
temperature = read_ntc();
relay_action(temperature);
timer = millis();
display(temperature);
}
if (flag_setting)
{
setting();
display_settings();
}
read_button_set();
button_action();
}
Керування
Діапазон тепператури керування від -50 до +120 градусів цельсія.
Діапазон гістирезиса від 0 до 10 градусів.
H режим нагрівання.
С режим охолодження.
Для налашування потрібно натиснути та утримувати кнопку “++” більше секунди, на дсиплеї відобразиця температура яку потрібно підтримувати. Корткими натисканнями “++” або “—“, встановіть свою температуру. Повторне довге натискання кнопки “++“, відобразить температуру гістирезису, короткими натисканнями “++” або “—“, встановіть значення гістирезису. Третє довге натискання відобразить “H” або “C“. Залежно від заданого режиму, Корткими натисканнями “++” або “—“, встановіть режим роботи нагрівання чи охолдження. Наступне довге натискання завершить режим налаштування та відброзать актуальну температуру. Значення вступають в силу та зберігаються кожного разу при довгому натисканні. При відсутності натискань через 60 секунд атоматичний вихід з режиму налаштування без збереження.
Для дисплея LCD1602 курування не відрізняється.
Схема відображення
Актуальна температура | Індикація увімкнення реле | |
Задана температура | Гістирезис | Режим роботи |
При налаштуванні значення яке налаштовується буде миготіти. Підсвітка автоматично вимикається через 10 секунд, та вмикається пр и натискання на кнопку “++“.
Якщо в скетчі поміняти значення, можна підняти діапазон температури до 280 градусів.