Еще не собирал жду некоторые детали, если есть какие-то ошибки готов к критике
Эта прошивка для Arduino предназначена для управления вентилятором охлаждения двигателя, с поддержкой:
Измерение температуры
Управление вентилятором
BOOST режим
Часы и дисплей
Меню настроек (через кнопки)
Вход — кнопка Enter
Навигация — Up / Down
Все параметры сохраняются в EEPROM при выходе из меню.
Аварийный режим
Включается, если:
Действия:
Эта прошивка для 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%, включается зуммер.

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

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

Вход — кнопка Enter
Навигация — Up / Down
Параметр | Диапазон | EEPROM адрес |
---|---|---|
minTempOn | 50.0–120.0 °C | 0 |
minTempOff | 50.0–minTempOn | 4 |
minDutyCycle | 10–100 % | 8 |
overheatTemp | minTempOn–150.0 °C | 10 |
Все параметры сохраняются в EEPROM при выходе из меню.

Включается, если:
- Ошибка одного из датчиков
- Перегрев (температура выше overheatTemp)
Действия:
- Вентилятор включается на 100%
- Зуммер моргает с частотой 0.5 секунды
- На дисплее сообщение об ошибке или перегреве

Вложения
-
13.3 KB Просмотры: 1
-
180 KB Просмотры: 1
Изменено: