#define BUTTON_SET 2
#define BUTTON_UP 10
#define BUTTON_DOWN 16
#define LED_1_PIN 9
#define NTC_PIN A1
#define EEPROM_ADDR 1
#define EEPROM_FLAG 0
#include <LiquidCrystal.h>
#include <GyverNTC.h>
#include <EEPROM.h>
const int rs = 8, en = 7, d4 = 6, d5 = 5, d6 = 4, d7 = 3;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
GyverNTC therm(NTC_PIN, 10000, 3950);
char celsius = 223;
uint8_t scr_state; // Состояние меню
int16_t setpoint, new_setpoint;
int16_t disp_temp, current_temp;
uint32_t menu_tmr, meas_tmr, current_millis;
boolean flag_setbutton_state, scr_update_flag = true;
float Kp = 5.0, Ki = 1.0, Kd = 0.0;
void setup() {
Serial.begin(9600);
pinMode (BUTTON_SET, INPUT_PULLUP);
pinMode (BUTTON_UP, INPUT_PULLUP);
pinMode (BUTTON_DOWN, INPUT_PULLUP);
pinMode (LED_1_PIN, OUTPUT);
pinMode (NTC_PIN, INPUT);
lcd.begin (16, 2);
lcd.clear();
if (EEPROM.read(EEPROM_FLAG) != 'w') { // Если это первое включение (по адресу 0 нет флага w)
EEPROM.write(EEPROM_FLAG, 'w'); // Установим флаг
EEPROM.put(EEPROM_ADDR, setpoint); // Запишем уставку
} else {
EEPROM.get(EEPROM_ADDR, setpoint); // Читаем уставку из EEPROM по адресу 1 и записываем в setpoint
}
new_setpoint = setpoint;
// Вывод названий графиков в порт
Serial.println("Temperature, PWM");
}
class My_button {
public:
My_button(uint8_t pin) {
_pin = pin;
}
// ==========Опрос кнопки==========
void tick() {
now_millis = millis();
btn_state = !digitalRead(_pin);
// нажали (с антидребезгом)
if (btn_state && !btn_flag && now_millis - btn_timer > debounce) {
btn_flag = 1; // флаг
btn_HO = 1; // флаг hold once
btn_press = 1;
btn_timer = now_millis;
}
// если отпустили до hold, считать отпущенной
if (!btn_state && btn_flag && now_millis - btn_timer < hold) {
btn_flag = 0;
btn_timer = now_millis;
}
// Если удерживается более hold, то считать удержанием
if (btn_flag && !btn_H && now_millis - btn_timer > hold) {
btn_H = 1;
}
// Если отпущена после hold, то считать, что была удержана
if (!btn_state && btn_flag && now_millis - btn_timer > hold) {
btn_flag = 0;
btn_H = 0;
btn_timer = now_millis;
}
}
boolean is_press() {
tick(); // Опрос во время вызова
if (btn_press) {
btn_press = false;
return true;
}
return false;
}
boolean is_hold_once() {
tick(); // Опрос во время вызова
if (btn_H && btn_HO) {
btn_HO = false;
return true;
}
return false;
}
boolean is_step() {
tick(); // Опрос во время вызова
if (btn_H && now_millis - step_timer > step) {
step_timer = now_millis;
btn_HO = false;
return true;
}
return false;
}
private:
boolean btn_state = false; // храним состояния кнопок
boolean btn_flag = false; // флажки кнопок
boolean btn_press = false; // флажки кнопок на нажатие
boolean btn_H = false; // флажки кнопок на удержание (многократный вызов) (H - Hold)
boolean btn_HO = false; // флажки кнопок на удержание (один вызов при нажатии) (HO - Hold Once)
uint8_t _pin = 0;
uint16_t step = 250;
uint16_t hold = 500; // время (мс), после которого кнопка считается зажатой
uint16_t debounce = 80; // (мс), антидребезг
uint32_t btn_timer = 0; // таймер последнего нажатия кнопки
uint32_t step_timer = 0; // таймер периода step
uint32_t now_millis = 0; // текущее значение millis
};
My_button btn_set(BUTTON_SET);
My_button btn_up(BUTTON_UP);
My_button btn_down(BUTTON_DOWN);
//____________________________________________________________________
void print_zero(uint16_t num) {
if (num < 10) {
lcd.print(0);
lcd.print(0);
} else if (num < 100) {
lcd.print(0);
}
}
// (вход, установка, п, и, д, период в секундах, мин.выход, макс. выход)
int computePID(float input, float setpoint, float kp, float ki, float kd, float dt, int minOut, int maxOut) {
float err = setpoint - input;
static float integral = 0, prevErr = 0;
integral = constrain(integral + (float)err * dt * ki, minOut, maxOut);
float D = (err - prevErr) / dt;
prevErr = err;
return constrain(err * kp + integral + D * kd, minOut, maxOut);
}
void loop() {
current_millis = millis();
if (current_millis - meas_tmr > 250) {
meas_tmr = current_millis;
current_temp = therm.getTempAverage();
int res = computePID(current_temp, setpoint, Kp, Ki, Kd, 0.25, 0, 255);
analogWrite(LED_1_PIN, res);
// ========== Вывод в порт графиков ==========
//Serial.print(current_temp);
//Serial.print(',');
//Serial.println(res);
// ========== Вывод в порт графиков ==========
Serial.print(Kp);
Serial.print(',');
Serial.print(Ki);
Serial.print(',');
Serial.println(Kd);
}
// Чтение из буфера Serial (настройка коэффициентов)
if (Serial.available() > 1) { // Если в буфере больше 1 байта данных
char incoming = Serial.read(); // Читаем (и удаляем) первый байт
float val = Serial.parseFloat(); // Читаем числовое значение
switch (incoming) {
case 'p' : Kp = val;
break;
case 'i' : Ki = val;
break;
case 'd' : Kd = val;
break;
case 's' : {
//setpoint = val;
new_setpoint = val;
}
break;
}
}
// Главный экран
if (scr_state == 0) {
if (scr_update_flag) {
scr_update_flag = false;
lcd.clear();
// Printing setpoint
lcd.setCursor(0, 0);
print_zero(setpoint);
lcd.print(setpoint);
lcd.setCursor(4, 0);
lcd.print(celsius);
lcd.print("C Setpoint");
// Printing disp_temp
lcd.setCursor(0, 1);
print_zero(disp_temp);
lcd.print(disp_temp);
lcd.setCursor(4, 1);
lcd.print(celsius);
lcd.print("C Current");
}
// ===== Начало только для тестов =====
if (setpoint != new_setpoint) {
setpoint = new_setpoint;
lcd.setCursor(0, 0);
print_zero(setpoint);
lcd.print(setpoint);
}
// ===== Конец только для тестов =====
if (disp_temp != current_temp) {
disp_temp = current_temp;
lcd.setCursor(0, 1);
print_zero(disp_temp);
lcd.print(disp_temp);
}
}
// Экран меню
if (scr_state == 1) {
if (scr_update_flag) {
scr_update_flag = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Temperature:");
lcd.setCursor(0, 1);
print_zero(setpoint);
lcd.print(setpoint);
lcd.setCursor(4, 1);
lcd.print(celsius);
lcd.print("C");
}
if (setpoint != new_setpoint) {
setpoint = new_setpoint;
lcd.setCursor(0, 1);
print_zero(setpoint);
lcd.print(setpoint);
}
// Автоматический выход из меню через 10 сек
if (current_millis - menu_tmr > 10000) {
scr_state = 0;
scr_update_flag = true;
}
}
// __________Кнопка Set__________
if (btn_set.is_press()) {
switch (scr_state) {
case 0 : {
scr_state = 1;
menu_tmr = current_millis; // Сброс счетчика меню
}
break;
case 1 : {
scr_state = 0;
EEPROM.put(EEPROM_ADDR, setpoint); // Сохранение setpoint в EEPROM при выходе из настроек
}
break;
}
scr_update_flag = true;
}
// ===== Обработка кнопок на экране меню =====
if (scr_state == 1) {
// __________Кнопка ВВЕРХ__________
if (btn_up.is_press() || btn_up.is_step()) {
new_setpoint += 5;
if (new_setpoint > 500) new_setpoint = 500;
menu_tmr = current_millis;
}
// __________Кнопка ВНИЗ__________
if (btn_down.is_press() || btn_down.is_step()) {
new_setpoint -= 5;
if (new_setpoint < 0) new_setpoint = 0;
menu_tmr = current_millis;
}
}
}