ARDUINO FAN CONTROLLER для автомобиля тойота

reven

✩✩✩✩✩✩✩
28 Май 2025
1
1
Еще не собирал жду некоторые детали, если есть какие-то ошибки готов к критике
Эта прошивка для Arduino предназначена для управления вентилятором охлаждения двигателя, с поддержкой:
  • Двух температурных датчиков DS18B20 (ДВС и внешний)
  • Плавного включения вентилятора по температуре
  • Режима BOOST с сигналом от кондиционера (5В на A0)
  • Аварийного зуммера при перегреве или ошибке датчиков
  • Меню настроек на дисплее LCD 20x4 с сохранением параметров в EEPROM
  • Реального времени с помощью модуля часов DS3231
  • FAN:
    #include <OneWire.h>
    #include <DallasTemperature.h>
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    #include <EEPROM.h>
    #include <RTClib.h>  // Библиотека для часов DS3231
    
    #define ONE_WIRE_BUS_1 2      
    #define ONE_WIRE_BUS_2 8       // Второй датчик на пине 8
    
    #define FAN_PWM_PIN 3      
    #define BUZZER_PIN 4        
    
    #define BUTTON_ENTER 5      
    #define BUTTON_UP 6        
    #define BUTTON_DOWN 7      
    
    #define BOOST_PIN A0          // Пин для сигнала 5В (аналоговый)
    
    // DS18B20 внутренний
    OneWire oneWire1(ONE_WIRE_BUS_1);
    DallasTemperature sensors1(&oneWire1);
    
    // DS18B20 внешний
    OneWire oneWire2(ONE_WIRE_BUS_2);
    DallasTemperature sensors2(&oneWire2);
    
    LiquidCrystal_I2C lcd(0x27, 20, 4);
    
    RTC_DS3231 rtc;  // Объект для работы с часами DS3231
    
    #define EEPROM_MIN_TEMP_ON_ADDR 0      
    #define EEPROM_MIN_TEMP_OFF_ADDR 4    
    #define EEPROM_MIN_DUTY_ADDR 8        
    #define EEPROM_OVERHEAT_TEMP_ADDR 10  
    
    float minTempOn = 93.0;        
    float minTempOff = 89.0;        
    int minDutyCycle = 38;          
    float overheatTemp = 108.0;    
    
    float currentTemp1 = 0.0;
    float currentTemp2 = 0.0;
    bool fanOn = false;
    bool alarmOn = false;
    bool sensorError = false;         // Флаг ошибки датчика
    int currentDutyCycle = 0;
    
    enum MenuState { MENU_NONE, MENU_MIN_ON, MENU_MIN_OFF, MENU_DUTY, MENU_OVERHEAT };
    MenuState menuState = MENU_NONE;
    
    unsigned long lastDebounceTime = 0;
    const unsigned long debounceDelay = 200;
    bool lastEnterState = HIGH;
    bool lastUpState = HIGH;
    bool lastDownState = HIGH;
    
    // Для аварийного зуммера (моргание звуком)
    unsigned long buzzerLastToggle = 0;
    const unsigned long buzzerInterval = 500; // 0.5 секунды
    bool buzzerState = false;
    
    // Для плавного включения вентилятора при 5В на A0
    bool boostActive = false;
    unsigned long boostStartTime = 0;
    int boostStage = 0; // 0 - нет, 1 - 30%, 2 - 60%, 3 - 100%
    
    // Новые переменные для плавного включения вентилятора при достижении minTempOn
    bool rampingUpFan = false;
    unsigned long rampStartTime = 0;
    const unsigned long rampDuration = 3000; // время плавного включения вентилятора, мс (3 секунды)
    
    void EEPROM_writeFloat(int address, float value) {
      byte [I]p = (byte[/I])(void*)&value;
      for (int i = 0; i < 4; i++) {
        EEPROM.write(address + i, *p++);
      }
    }
    float EEPROM_readFloat(int address) {
      float value = 0.0;
      byte [I]p = (byte[/I])(void*)&value;
      for (int i = 0; i < 4; i++) {
        *p++ = EEPROM.read(address + i);
      }
      return value;
    }
    void EEPROM_writeInt(int address, int value) {
      EEPROM.write(address, (value >> 8) & 0xFF);
      EEPROM.write(address + 1, value & 0xFF);
    }
    int EEPROM_readInt(int address) {
      int value = EEPROM.read(address) << 8 | EEPROM.read(address + 1);
      return value;
    }
    
    void saveSettings() {
      EEPROM_writeFloat(EEPROM_MIN_TEMP_ON_ADDR, minTempOn);
      EEPROM_writeFloat(EEPROM_MIN_TEMP_OFF_ADDR, minTempOff);
      EEPROM_writeInt(EEPROM_MIN_DUTY_ADDR, minDutyCycle);
      EEPROM_writeFloat(EEPROM_OVERHEAT_TEMP_ADDR, overheatTemp);
    }
    
    void loadSettings() {
      float val;
    
      val = EEPROM_readFloat(EEPROM_MIN_TEMP_ON_ADDR);
      if (val > 50 && val < 150) minTempOn = val;
    
      val = EEPROM_readFloat(EEPROM_MIN_TEMP_OFF_ADDR);
      if (val > 50 && val < minTempOn) minTempOff = val;
    
      int intval = EEPROM_readInt(EEPROM_MIN_DUTY_ADDR);
      if (intval >= 10 && intval <= 100) minDutyCycle = intval;
    
      val = EEPROM_readFloat(EEPROM_OVERHEAT_TEMP_ADDR);
      if (val > minTempOn && val < 150) overheatTemp = val;
    }
    
    bool isButtonPressed(int pin, bool &lastState) {
      bool reading = digitalRead(pin);
      if (reading != lastState) {
        lastDebounceTime = millis();
      }
      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading == LOW) {
          lastState = reading;
          return true;
        }
      }
      lastState = reading;
      return false;
    }
    
    void setup() {
      Serial.begin(9600);
      sensors1.begin();
      sensors2.begin();
    
      pinMode(FAN_PWM_PIN, OUTPUT);
      pinMode(BUZZER_PIN, OUTPUT);
    
      pinMode(BUTTON_ENTER, INPUT_PULLUP);
      pinMode(BUTTON_UP, INPUT_PULLUP);
      pinMode(BUTTON_DOWN, INPUT_PULLUP);
    
      pinMode(BOOST_PIN, INPUT);
    
      lcd.init();
      lcd.backlight();
      lcd.clear();
    
      loadSettings();
    
        // Инициализация RTC с проверкой
      if (rtc.begin()) {
        // Безопасная попытка прочитать время
        // Если выбросит исключение — значит модуля нет
        // Этот блок защищён, чтобы избежать зависаний
        bool rtcPresent = true;
        if (rtc.lostPower()) {
          rtc.adjust(DateTime(F([B]DATE[/B]), F([B]TIME[/B])));
        }
      }
      lcd.setCursor(0,0);
      lcd.print("FAN CONTROL REVVL          VER 1.0");
      delay(1500);
    }
    
    void loop() {
      sensors1.requestTemperatures();
      sensors2.requestTemperatures();
    
      currentTemp1 = sensors1.getTempCByIndex(0);
      currentTemp2 = sensors2.getTempCByIndex(0);
    
      // Проверяем ошибку датчиков: NAN или температура <= -45 (часто  -127)
      sensorError = (isnan(currentTemp1) || currentTemp1 <= -45.0) || (isnan(currentTemp2) || currentTemp2 <= -45.0);
    
      // Проверяем сигнал на BOOST_PIN (A0)
      int boostSignal = analogRead(BOOST_PIN);
      bool boostDetected = (boostSignal > 800); // примерно выше ~4В
    
      unsigned long currentMillis = millis();
    
      if (sensorError) {
        // Аварийный режим: датчик не отвечает или отказ
        analogWrite(FAN_PWM_PIN, 255);  // Вентилятор 100%
    
        // Зуммер моргает с интервалом buzzerInterval
        if (currentMillis - buzzerLastToggle >= buzzerInterval) {
          buzzerLastToggle = currentMillis;
          buzzerState = !buzzerState;
          digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW);
        }
    
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print(" SENSOR ERROR! ");
        lcd.setCursor(0,1);
        lcd.print(" FAN 100% ON ");
        lcd.setCursor(0,2);
        lcd.print(" Check DS18B20 ");
    
        boostActive = false; // сброс boost
        boostStage = 0;
    
        rampingUpFan = false; // сброс плавного включения вентилятора
      } else {
        digitalWrite(BUZZER_PIN, LOW); // Выключаем зуммер, если нет аварии
    
        // Если пришёл сигнал 5В (boostDetected), запускаем плавное включение вентилятора
        if (boostDetected) {
          if (!boostActive) {
            boostActive = true;
            boostStartTime = currentMillis;
            boostStage = 1;
            analogWrite(FAN_PWM_PIN, map(30, 0, 100, 0, 255));
          } else {
            // Проверяем переходы по времени
            if (boostStage == 1 && currentMillis - boostStartTime >= 1500) {
              boostStage = 2;
              analogWrite(FAN_PWM_PIN, map(60, 0, 100, 0, 255));
            } else if (boostStage == 2 && currentMillis - boostStartTime >= 3500) {
              boostStage = 3;
              analogWrite(FAN_PWM_PIN, 255);
            }
          }
    
          lcd.setCursor(0,0);
          lcd.print("A\\C FAN ON  ");
          lcd.setCursor(0,1);
          lcd.print("Boost FAN: ");
          lcd.print(boostStage == 1 ? 30 : (boostStage == 2 ? 60 : 100));
          lcd.print("%       ");
    
          // Показ времени
          DateTime now = rtc.now();
          lcd.setCursor(15, 3);
          char timeStr[6];
          sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
          lcd.print(timeStr);
    
          // Не даём основной логике менять pwm, выходим из loop для предотвращения конфликта
          return;
    
        } else {
          // Если boost не активен, сбрасываем
          if (boostActive) {
            boostActive = false;
            boostStage = 0;
          }
    
          if (menuState == MENU_NONE) {
    
            if (currentTemp1 >= overheatTemp || currentTemp2 >= overheatTemp) {
              analogWrite(FAN_PWM_PIN, 255);
              digitalWrite(BUZZER_PIN, HIGH);
              alarmOn = true;
              lcd.clear();
              lcd.setCursor(0,0);
              lcd.print("!!! WARNING !!!");
              lcd.setCursor(0,1);
              lcd.print("OVERHEAT ");
              float maxTemp = max(currentTemp1, currentTemp2);
              lcd.print(maxTemp,1);
              lcd.print(" C");
              lcd.setCursor(0,2);
              lcd.print("Fan PWM: 100%");
              rampingUpFan = false; // сброс плавного включения
            } else {
              if (alarmOn) {
                digitalWrite(BUZZER_PIN, LOW);
                alarmOn = false;
                lcd.clear();
              }
    
              float maxTemp = max(currentTemp1, currentTemp2);
    
              // Плавное включение вентилятора при достижении minTempOn
              if (maxTemp >= minTempOn) {
                if (!rampingUpFan) {
                  rampingUpFan = true;
                  rampStartTime = currentMillis;
                }
                unsigned long rampElapsed = currentMillis - rampStartTime;
                int pwmValue = 0;
                if (rampElapsed < rampDuration) {
                  float progress = (float)rampElapsed / rampDuration;
                  pwmValue = (int)(progress * minDutyCycle);
                  pwmValue = constrain(pwmValue, 0, minDutyCycle);
                } else {
                  int duty = map(maxTemp * 10, minTempOn * 10, overheatTemp * 10, minDutyCycle * 10, 1000);
                  duty = constrain(duty / 10, minDutyCycle, 100);
                  pwmValue = duty;
                }
                currentDutyCycle = pwmValue;
                analogWrite(FAN_PWM_PIN, map(pwmValue, 0, 100, 0, 255));
                fanOn = true;
              } else if (maxTemp <= minTempOff) {
                fanOn = false;
                currentDutyCycle = 0;
                analogWrite(FAN_PWM_PIN, 0);
                rampingUpFan = false; // сброс плавного включения при выключении вентилятора
              }
    
              lcd.setCursor(0,0);
              lcd.print("Temp EGN: ");
              lcd.print(currentTemp1, 1);
              lcd.print(" C     ");
    
              lcd.setCursor(0,1);
              lcd.print("Temp OUT: ");
              lcd.print(currentTemp2, 1);
              lcd.print(" C     ");
    
              lcd.setCursor(0,2);
              lcd.print("Fan: ");
              if (fanOn) {
                lcd.print("ON ");
              } else {
                lcd.print("OFF");
              }
              lcd.print("         ");
    
              lcd.setCursor(0,3);
              lcd.print("PWM: ");
              lcd.print(currentDutyCycle);
              lcd.print("%      ");
    
              // Вывод времени в нижнем правом углу
              DateTime now = rtc.now();
              lcd.setCursor(15, 3);
              char timeStr[6];
              sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
              lcd.print(timeStr);
            }
    
            if (isButtonPressed(BUTTON_ENTER, lastEnterState)) {
              menuState = MENU_MIN_ON;
              lcd.clear();
            }
          } else {
            // Меню настройки
            lcd.setCursor(0,0);
            lcd.print("Set parameters:");
    
            switch (menuState) {
              case MENU_MIN_ON:
                lcd.setCursor(0,1);
                lcd.print("Min ON Temp: ");
                lcd.print(minTempOn);
                lcd.print(" C   ");
                if (isButtonPressed(BUTTON_UP, lastUpState)) minTempOn += 0.5;
                if (isButtonPressed(BUTTON_DOWN, lastDownState)) minTempOn -= 0.5;
                if (minTempOn < 50) minTempOn = 50;
                if (minTempOn > 120) minTempOn = 120;
                break;
              case MENU_MIN_OFF:
                lcd.setCursor(0,1);
                lcd.print("Min OFF Temp:");
                lcd.print(minTempOff);
                lcd.print(" C   ");
                if (isButtonPressed(BUTTON_UP, lastUpState)) minTempOff += 0.5;
                if (isButtonPressed(BUTTON_DOWN, lastDownState)) minTempOff -= 0.5;
                if (minTempOff < 50) minTempOff = 50;
                if (minTempOff > minTempOn) minTempOff = minTempOn;
                break;
              case MENU_DUTY:
                lcd.setCursor(0,1);
                lcd.print("Min Duty: ");
                lcd.print(minDutyCycle);
                lcd.print("%    ");
                if (isButtonPressed(BUTTON_UP, lastUpState)) minDutyCycle++;
                if (isButtonPressed(BUTTON_DOWN, lastDownState)) minDutyCycle--;
                if (minDutyCycle < 10) minDutyCycle = 10;
                if (minDutyCycle > 100) minDutyCycle = 100;
                break;
              case MENU_OVERHEAT:
                lcd.setCursor(0,1);
                lcd.print("Overheat Temp:");
                lcd.print(overheatTemp);
                lcd.print(" C   ");
                if (isButtonPressed(BUTTON_UP, lastUpState)) overheatTemp += 0.5;
                if (isButtonPressed(BUTTON_DOWN, lastDownState)) overheatTemp -= 0.5;
                if (overheatTemp < minTempOn) overheatTemp = minTempOn + 1;
                if (overheatTemp > 150) overheatTemp = 150;
                break;
              default:
                break;
            }
    
            lcd.setCursor(0,3);
            lcd.print("Enter=Next Save Exit");
    
            if (isButtonPressed(BUTTON_ENTER, lastEnterState)) {
              if (menuState == MENU_OVERHEAT) {
                saveSettings();
                menuState = MENU_NONE;
                lcd.clear();
              } else {
                menuState = static_cast<MenuState>(menuState + 1);
              }
            }
          }
        }
      }
      delay(100);
    }
Основные функции
Измерение температуры
  • Получение температуры от двух датчиков DS18B20.
  • При ошибке (NAN или ≤ -45°C) включается аварийный режим.
🔄 Управление вентилятором

  • Вентилятор включается, если температура выше minTempOn.
  • Выключается, если температура ниже minTempOff.
  • Плавный старт при включении — за 3 секунды выходит на минимальный скважный цикл (minDutyCycle).
  • При перегреве (> overheatTemp) — вентилятор включается на 100%, включается зуммер.
🌀 BOOST режим

  • При наличии 5В на A0 (кондиционер включен):
    • Вентилятор запускается ступенчато: 30% → 60% → 100%.
    • Основная логика отключается, пока активен BOOST.
⏰ Часы и дисплей

  • Отображаются:
    • Температуры
    • Статус вентилятора
    • Скважность ШИМ
    • Время (с DS3231)

🛠 Меню настроек (через кнопки)
Вход — кнопка Enter
Навигация — Up / Down


ПараметрДиапазонEEPROM адрес
minTempOn50.0–120.0 °C0
minTempOff50.0–minTempOn4
minDutyCycle10–100 %8
overheatTempminTempOn–150.0 °C10


Все параметры сохраняются в EEPROM при выходе из меню.


🚨 Аварийный режим

Включается, если:
  • Ошибка одного из датчиков
  • Перегрев (температура выше overheatTemp)

Действия:
  • Вентилятор включается на 100%
  • Зуммер моргает с частотой 0.5 секунды
  • На дисплее сообщение об ошибке или перегреве

ПЛАТА.png
 

Вложения

Изменено:
  • Лойс +1
Реакции: MastarRak