/*
  Скетч к проекту "Терморегулятор"
  - Страница проекта (схемы, описания): https://alexgyver.ru/thermocontrol/
  - Исходники на GitHub:
  Проблемы с загрузкой? Читай гайд для новичков: https://alexgyver.ru/arduino-first/
  Нравится, как написан код? Поддержи автора! https://alexgyver.ru/support_alex/
  Автор: AlexGyver, AlexGyver Technologies, 2020
  https://www.youtube.com/c/alexgyvershow
  https://github.com/AlexGyver
  https://AlexGyver.ru/
  [email protected]
*/
// v1.1: улучшена стабильность
/*
  Терморегулятор с выходом ШИМ 20 кГц 0-100%
  Датчик температуры: термистор или DS18b20
  Управление кнопкой:
  - 1x клик: вкл/выкл вентиль
  - 2x клик: задать минимальную температуру
  - 3x клик: задать максимальную температуру
  - 4x клик - сброс на "стандартные" MIN_TEMP и MAX_TEMP
  Поддерживается ATmega328 (Arduino NANO) и ATtiny85 (Digispark)
  ШИМ пин:
  - D3 на Arduino (PD3 чип)
  - P1 на Digispark (PB1 чип)
  Пин термистора/далласа:
  - A0 на Arduino (PC0 чип)
  - A1 (P2) на Digispark (PB2 чип)
  Пин кнопки:
  - D2 на Arduino (PD2 чип)
  - P3 на Digispark (PB3 чип)
  Пин пищалки:
  - D4 на Arduino (PD2 чип)
  - P0 на Digispark (PB0 чип)
*/
// ========= ДАТЧИК ========
#define SENSOR_TYPE 1   // 0 - термистор, 1 - ds18b20
// для термистора!
#define T_RESIST 10000  // сопротивление термистора при 25 градусах
#define R_RESIST 10000  // сопротивление резистора
#define B_COEF 4300     // B коэффициент
// серый 4300
// проводной 3950
// ===== РЕЖИМ РАБОТЫ =====
#define MIN_TEMP 30     // мин. температура
#define MAX_TEMP 45     // макс. температура
#define MIN_SPEED 30    // (0-255) мин скорость
#define MAX_SPEED 255   // (0-255) макс скорость
#define ALARM_TEMP 50   // температура тревоги
#define BUZZER_TYPE 0   // тип пищалки: 0 пассивный, 1 активный
// ========= КНОПКА ========
#define BTN_CONTROL 1   // настройка кнопкой, 0 вкл, 1 выкл
// если выкл, режим работы будет такой, как задан выше
// если вкл, то настройки можно будет изменить кнопкой
// ========= СИГНАЛ ========
// ===== ДЛЯ РАЗРАБОТЧИКОВ =====
// отладка
#define DEBUG_ENABLE 0
#if (DEBUG_ENABLE == 1)
#define DEBUG(x) Serial.println(x)
#else
#define DEBUG(x)
#endif
// выбор пинов
#if defined([B]AVR_ATtiny85[/B])
#pragma message ("tiny85")
#define BTN_PIN PB3   // кнопка
#define SENS_PIN A1   // датчик
#define OUT_PIN PB1   // выход
#define BUZ_PIN PB0   // пищалка
#else
#define BTN_PIN 2     // кнопка
#define SENS_PIN A0   // датчик
#define OUT_PIN 3     // выход
#define BUZ_PIN 4     // пищалка
#endif
// либы
#include "timerMinim.h"
timerMinim fanTimer(60);
timerMinim sensorTimer(2000);
#if (SENSOR_TYPE == 0)
#include "thermistorMinim.h"
// (пин, R термистора, B термистора, базовая температура, R резистора)
thermistor therm(SENS_PIN, T_RESIST, B_COEF, 25, R_RESIST);
#elif (SENSOR_TYPE == 1)
#include <microDS18B20.h>;
MicroDS18B20 dallas(SENS_PIN);
#endif
#if (BTN_CONTROL == 1)
#include <avr/eeprom.h>
// макросы для епрома
#define EEPROM_UPD_INT(addr, val) eeprom_update_word((uint16_t*)(addr), (uint16_t)(val))
#define EEPROM_READ_INT(addr) eeprom_read_word((uint16_t*)(addr))
#include <GyverButton.h>
GButton btn(BTN_PIN);
#endif
// дата
int16_t minTemp = MIN_TEMP, maxTemp = MAX_TEMP;
bool systemState = true;
int newFanSpeed = 0;
int currentTemp = 0;
int fanSpeed = 0;
uint32_t thisTime;
bool buzFlag = false;
bool alarmFlag = false;
void setup() {
#if (DEBUG_ENABLE == 1)
  Serial.begin(9600);
#endif
  // пинаем далласа
#if (SENSOR_TYPE == 1)
  dallas.requestTemp();
#endif
  // восстанавливаем настройки (если с кнопкой)
#if (BTN_CONTROL == 1)
  if (eeprom_read_byte(0) != 50) { // первый запуск
    eeprom_write_byte(0, 50);
    EEPROM_UPD_INT(1, minTemp);
    EEPROM_UPD_INT(3, maxTemp);
  }
  minTemp = (int)EEPROM_READ_INT(1);
  maxTemp = (int)EEPROM_READ_INT(3);
#endif
  initPWM();
  pinMode(BUZ_PIN, OUTPUT);
  pinMode(OUT_PIN, OUTPUT);
}
void loop() {
  tempToSpeed();
  fanTick();
  buttonTick();
}
// преобразуем температуру в скорость
void tempToSpeed() {
  if (sensorTimer.isReady()) {
#if (SENSOR_TYPE == 0)
    currentTemp = therm.getTempAverage();
#elif (SENSOR_TYPE == 1)
    currentTemp = dallas.getTemp(); // получить с далласа
    dallas.requestTemp();           // запросить измерение
#endif
    // преобразовать диапазон и ограничить значение
    if (!systemState) newFanSpeed = 0;          // если выкл - скорость 0
    else {
      newFanSpeed = map(currentTemp, minTemp, maxTemp, MIN_SPEED, MAX_SPEED);
      newFanSpeed = constrain(newFanSpeed, MIN_SPEED, MAX_SPEED);
    }
    initPWM();    // заново инициализируем шим, тон его ломает
    if (currentTemp > ALARM_TEMP) {
      alarmFlag = !alarmFlag;
#if (BUZZER_TYPE == 0)
      if (alarmFlag) tone(BUZ_PIN, 400, 900);
#else
      digitalWrite(BUZ_PIN, alarmFlag);
#endif
    } else {
      digitalWrite(BUZ_PIN, 0);
    }
    //DEBUG(currentTemp);
  }
}
// плавный контроль вентилятора
void fanTick() {
  if (fanTimer.isReady()) {
    if (newFanSpeed > fanSpeed) fanSpeed += 2;  // плавно управляем
    if (newFanSpeed < fanSpeed) fanSpeed -= 2;  // плавно управляем
    fanSpeed = constrain(fanSpeed, 0, 255);  // ограничить
#if defined([B]AVR_ATtiny85[/B])
    tiny85_PWM20kHz(fanSpeed);    // ШИМ для тини
#else
    mega328_PWM20kHz_D3(fanSpeed);  // ШИМ для меги
#endif
    //DEBUG(fanSpeed);
  }
}
// отработка кнопки
void buttonTick() {
#if (BTN_CONTROL == 1)
  btn.tick();
  if (btn.hasClicks()) {
    switch (btn.getClicks()) {
      case 1:
        systemState = !systemState;
        buzzer(1);
        break;
      case 2:
        minTemp = currentTemp;
        EEPROM_UPD_INT(1, minTemp);
        buzzer(2);
        break;
      case 3:
        maxTemp = currentTemp;
        EEPROM_UPD_INT(3, maxTemp);
        buzzer(3);
        break;
      case 4:
        minTemp = MIN_TEMP;
        maxTemp = MAX_TEMP;
        EEPROM_UPD_INT(1, minTemp);
        EEPROM_UPD_INT(3, maxTemp);
        buzzer(4);
        break;
    }
  }
#endif
}
// пищатель
void buzzer(byte count) {
  for (byte i = 0; i < count; i++) {
#if (BUZZER_TYPE == 0)
    tone(BUZ_PIN, 400, 200);
    delay(200);
#else
    digitalWrite(BUZ_PIN, 1);
    delay(200);
    digitalWrite(BUZ_PIN, 0);
#endif
    delay(70);
  }
  initPWM();  // заново инициализируем шим, тон его ломает
}
void initPWM() {
#if defined([B]AVR_ATtiny85[/B])
  // инициализация ШИМ 20 кГц для tiny85
  TCCR0A = _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
  TCCR0B = _BV(WGM02) | 0x02;
  OCR0A = 99;
#else
  // инициализация ШИМ 20 кГц для mega328
  TCCR2A = 0b10100011;
  TCCR2B = 0b00001010;
  OCR2A = 99;
#endif
}
// ШИМ 20 кГц
#if defined([B]AVR_ATtiny85[/B])
void tiny85_PWM20kHz(uint8_t duty) {
  OCR0B = (float)duty * 0.39;
}
#else
void mega328_PWM20kHz_D3(uint8_t duty) {
  if (duty == 0) {
    bitClear(TCCR2A, COM2B1);
    bitClear(PORTD, 3);
  } else {
    bitSet(TCCR2A, COM2B1);
    OCR2B = map(duty, 0, 255, 0, 99);
  }
}
#endif