Загрузка данных
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C
Adafruit_SSD1306 display(
SCREEN_WIDTH,
SCREEN_HEIGHT,
&Wire,
OLED_RESET
);
// ======================================================
// ПИНЫ
// ======================================================
const byte injectorPins[4] = {2, 3, 4, 5};
const byte injectorButtonPin = 6;
const byte relay1ButtonPin = 7;
const byte relay1Pin = 8;
const byte relay2ButtonPin = 9;
const byte relay2Pin = 10;
const byte temperaturePin = 11;
// Динамик
// Можно использовать A2 как цифровой выход.
// Если хочешь вернуть D12 — просто поставь 12.
const byte buzzerPin = A2;
const byte timePotPin = A0;
const byte countPotPin = A1;
// ======================================================
// DS18B20
// ======================================================
OneWire oneWire(temperaturePin);
DallasTemperature sensors(&oneWire);
float currentTemperature = 0.0;
bool temperatureValid = false;
bool temperatureFault = false;
// ======================================================
// НАСТРОЙКИ ФОРСУНОК
// ======================================================
const unsigned int MIN_INJECTOR_TIME = 5;
const unsigned int MAX_INJECTOR_TIME = 25;
const int MIN_CYCLES = 10;
const int MAX_CYCLES = 100;
// ======================================================
// ТЕМПЕРАТУРНАЯ ЗАЩИТА
// ======================================================
const float OVERHEAT_TEMP = 65.0;
const float COOL_DOWN_TEMP = 60.0;
// ======================================================
// СЧЁТЧИК РЕЗКОГО ПОВЫШЕНИЯ ТЕМПЕРАТУРЫ
// ======================================================
const float TRIGGER_TEMP_RISE = 5.0;
const unsigned long TRIGGER_WINDOW = 2000UL;
const unsigned long TEMPERATURE_READ_TIME = 500UL;
float triggerStartTemperature = 0.0;
unsigned long triggerStartTime = 0;
unsigned long triggerCount = 0;
bool triggerArmed = true;
// ======================================================
// ВРЕМЕНА
// ======================================================
const unsigned long INJECTOR_LOCK_TIME = 10000UL;
const unsigned long RELAY1_TIME = 10000UL;
const unsigned long DEBOUNCE_TIME = 25UL;
const unsigned long DISPLAY_UPDATE_TIME = 100UL;
// ======================================================
// ПОТЕНЦИОМЕТРЫ
// ======================================================
const byte POT_SAMPLES = 8;
unsigned int injectorOpenTime = MIN_INJECTOR_TIME;
int injectorCycles = MIN_CYCLES;
int filteredTimeValue = 0;
int filteredCountValue = 0;
// ======================================================
// ФОРСУНКИ
// ======================================================
bool injectorRunning = false;
byte currentInjector = 0;
int currentCycle = 0;
unsigned long injectorStartTime = 0;
// ======================================================
// БЛОКИРОВКА КНОПКИ
// ======================================================
bool injectorButtonLocked = false;
unsigned long injectorUnlockTime = 0;
bool overheatLock = false;
// ======================================================
// РЕЛЕ 1
// ======================================================
bool relay1Active = false;
unsigned long relay1StartTime = 0;
// ======================================================
// КНОПКИ
// ======================================================
struct Button {
byte pin;
bool stableState;
bool lastReading;
unsigned long lastChangeTime;
};
Button injectorButton = {
injectorButtonPin,
HIGH,
HIGH,
0
};
Button relay1Button = {
relay1ButtonPin,
HIGH,
HIGH,
0
};
Button relay2Button = {
relay2ButtonPin,
HIGH,
HIGH,
0
};
// ======================================================
// OLED
// ======================================================
unsigned long lastDisplayUpdate = 0;
// ======================================================
// ЗВУКОВОЙ ДВИЖОК
// ======================================================
struct SoundNote {
unsigned int frequency;
unsigned int duration;
unsigned int pause;
};
enum SoundType {
SOUND_NONE,
SOUND_STARTUP,
SOUND_BUTTON,
SOUND_OVERHEAT,
SOUND_ERROR
};
SoundType currentSound = SOUND_NONE;
byte currentSoundNote = 0;
unsigned long soundStartTime = 0;
bool soundPlaying = false;
// ======================================================
// НОТЫ
// ======================================================
// Запуск:
// ДО5 → МИ5 → СОЛЬ5 → ДО6
const SoundNote startupSound[] = {
{523, 100, 25},
{659, 100, 25},
{784, 100, 25},
{1047, 160, 20}
};
// Кнопка
const SoundNote buttonSound[] = {
{880, 75, 45},
{1175, 110, 20}
};
// Перегрев
const SoundNote overheatSound[] = {
{988, 120, 40},
{784, 120, 40},
{523, 200, 30}
};
// Ошибка датчика
const SoundNote errorSound[] = {
{330, 140, 50},
{330, 140, 50},
{220, 220, 30}
};
// ======================================================
// ПОЛУЧИТЬ ТЕКУЩИЙ ЗВУК
// ======================================================
const SoundNote* getCurrentSound() {
switch (currentSound) {
case SOUND_STARTUP:
return startupSound;
case SOUND_BUTTON:
return buttonSound;
case SOUND_OVERHEAT:
return overheatSound;
case SOUND_ERROR:
return errorSound;
default:
return nullptr;
}
}
// ======================================================
// КОЛИЧЕСТВО НОТ
// ======================================================
byte getSoundLength() {
switch (currentSound) {
case SOUND_STARTUP:
return sizeof(startupSound) /
sizeof(startupSound[0]);
case SOUND_BUTTON:
return sizeof(buttonSound) /
sizeof(buttonSound[0]);
case SOUND_OVERHEAT:
return sizeof(overheatSound) /
sizeof(overheatSound[0]);
case SOUND_ERROR:
return sizeof(errorSound) /
sizeof(errorSound[0]);
default:
return 0;
}
}
// ======================================================
// ЗАПУСК ЗВУКА
// ======================================================
void playSound(SoundType type) {
currentSound = type;
currentSoundNote = 0;
soundStartTime = millis();
soundPlaying = true;
const SoundNote* sound =
getCurrentSound();
if (sound != nullptr) {
tone(
buzzerPin,
sound[0].frequency
);
}
}
// ======================================================
// ОБНОВЛЕНИЕ ЗВУКА
// ======================================================
void updateSound() {
if (!soundPlaying)
return;
const SoundNote* sound =
getCurrentSound();
if (sound == nullptr) {
noTone(buzzerPin);
soundPlaying = false;
currentSound = SOUND_NONE;
return;
}
unsigned long elapsed =
millis() - soundStartTime;
unsigned long noteTotal =
sound[currentSoundNote].duration +
sound[currentSoundNote].pause;
if (
elapsed >=
sound[currentSoundNote].duration
) {
noTone(buzzerPin);
}
if (
elapsed >= noteTotal
) {
currentSoundNote++;
if (
currentSoundNote >=
getSoundLength()
) {
noTone(buzzerPin);
soundPlaying = false;
currentSound = SOUND_NONE;
return;
}
soundStartTime = millis();
tone(
buzzerPin,
sound[currentSoundNote].frequency
);
}
}
// ======================================================
// РУССКИЙ ШРИФТ 5×7
// ======================================================
// Каждая буква:
// 5 пикселей шириной
// 7 пикселей высотой
const byte glyphA[7] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001
};
const byte glyphB[7] = {
0b11110,
0b10001,
0b10001,
0b11110,
0b10001,
0b10001,
0b11110
};
const byte glyphV[7] = {
0b11110,
0b10001,
0b10001,
0b11110,
0b10001,
0b10001,
0b11110
};
const byte glyphG[7] = {
0b11111,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000
};
const byte glyphD[7] = {
0b01110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b11111
};
const byte glyphE[7] = {
0b11111,
0b10000,
0b10000,
0b11110,
0b10000,
0b10000,
0b11111
};
// Ё
const byte glyphYO[7] = {
0b01010,
0b00000,
0b11111,
0b10000,
0b11110,
0b10000,
0b11111
};
const byte glyphZh[7] = {
0b10101,
0b10101,
0b01110,
0b00100,
0b01110,
0b10101,
0b10101
};
const byte glyphZ[7] = {
0b01110,
0b00001,
0b00001,
0b00110,
0b00001,
0b00001,
0b01110
};
const byte glyphI[7] = {
0b10001,
0b10001,
0b10011,
0b10101,
0b11001,
0b10001,
0b10001
};
const byte glyphY[7] = {
0b01010,
0b00000,
0b10001,
0b10001,
0b10011,
0b10101,
0b11001
};
const byte glyphK[7] = {
0b10001,
0b10010,
0b10100,
0b11000,
0b10100,
0b10010,
0b10001
};
const byte glyphL[7] = {
0b00111,
0b01001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001
};
const byte glyphM[7] = {
0b10001,
0b11011,
0b10101,
0b10101,
0b10001,
0b10001,
0b10001
};
const byte glyphN[7] = {
0b10001,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001
};
const byte glyphO[7] = {
0b01110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110
};
const byte glyphP[7] = {
0b11111,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001
};
const byte glyphR[7] = {
0b11110,
0b10001,
0b10001,
0b11110,
0b10000,
0b10000,
0b10000
};
const byte glyphS[7] = {
0b01111,
0b10000,
0b10000,
0b01110,
0b00001,
0b00001,
0b11110
};
const byte glyphT[7] = {
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100
};
const byte glyphU[7] = {
0b10001,
0b10001,
0b01010,
0b00100,
0b00100,
0b01000,
0b10000
};
const byte glyphF[7] = {
0b00100,
0b01110,
0b10101,
0b10101,
0b01110,
0b00100,
0b00100
};
const byte glyphH[7] = {
0b10001,
0b10001,
0b01010,
0b00100,
0b01010,
0b10001,
0b10001
};
const byte glyphC[7] = {
0b10010,
0b10010,
0b10010,
0b10010,
0b10010,
0b10010,
0b11111
};
const byte glyphCh[7] = {
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01010,
0b00100
};
// Ш
const byte glyphSh[7] = {
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b11111
};
// Щ
const byte glyphShch[7] = {
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b11111
};
// Ъ
const byte glyphHard[7] = {
0b11000,
0b10000,
0b10000,
0b11110,
0b10001,
0b10001,
0b11110
};
// Ы
const byte glyphYI[7] = {
0b10001,
0b10001,
0b10001,
0b11101,
0b10011,
0b10011,
0b11101
};
// Ь
const byte glyphSoft[7] = {
0b10000,
0b10000,
0b10000,
0b11110,
0b10001,
0b10001,
0b11110
};
// Э
const byte glyphEReverse[7] = {
0b11110,
0b00001,
0b00001,
0b01111,
0b00001,
0b00001,
0b11110
};
// Ю
const byte glyphYu[7] = {
0b10111,
0b10101,
0b10101,
0b11101,
0b10101,
0b10101,
0b10111
};
// Я
const byte glyphYa[7] = {
0b01111,
0b10001,
0b10001,
0b01111,
0b00101,
0b01001,
0b10001
};
// ======================================================
// ПОЛУЧИТЬ GLYPH ПО UNICODE
// ======================================================
const byte* getGlyphUnicode(unsigned int code) {
switch (code) {
case 0x0410: return glyphA; // А
case 0x0411: return glyphB; // Б
case 0x0412: return glyphV; // В
case 0x0413: return glyphG; // Г
case 0x0414: return glyphD; // Д
case 0x0415: return glyphE; // Е
case 0x0401: return glyphYO; // Ё
case 0x0416: return glyphZh; // Ж
case 0x0417: return glyphZ; // З
case 0x0418: return glyphI; // И
case 0x0419: return glyphY; // Й
case 0x041A: return glyphK; // К
case 0x041B: return glyphL; // Л
case 0x041C: return glyphM; // М
case 0x041D: return glyphN; // Н
case 0x041E: return glyphO; // О
case 0x041F: return glyphP; // П
case 0x0420: return glyphR; // Р
case 0x0421: return glyphS; // С
case 0x0422: return glyphT; // Т
case 0x0423: return glyphU; // У
case 0x0424: return glyphF; // Ф
case 0x0425: return glyphH; // Х
case 0x0426: return glyphC; // Ц
case 0x0427: return glyphCh; // Ч
case 0x0428: return glyphSh; // Ш
case 0x0429: return glyphShch; // Щ
case 0x042A: return glyphHard; // Ъ
case 0x042B: return glyphYI; // Ы
case 0x042C: return glyphSoft; // Ь
case 0x042D: return glyphEReverse; // Э
case 0x042E: return glyphYu; // Ю
case 0x042F: return glyphYa; // Я
// Строчные буквы
// автоматически превращаем в заглавные
case 0x0430: return glyphA;
case 0x0431: return glyphB;
case 0x0432: return glyphV;
case 0x0433: return glyphG;
case 0x0434: return glyphD;
case 0x0435: return glyphE;
case 0x0451: return glyphYO;
case 0x0436: return glyphZh;
case 0x0437: return glyphZ;
case 0x0438: return glyphI;
case 0x0439: return glyphY;
case 0x043A: return glyphK;
case 0x043B: return glyphL;
case 0x043C: return glyphM;
case 0x043D: return glyphN;
case 0x043E: return glyphO;
case 0x043F: return glyphP;
case 0x0440: return glyphR;
case 0x0441: return glyphS;
case 0x0442: return glyphT;
case 0x0443: return glyphU;
case 0x0444: return glyphF;
case 0x0445: return glyphH;
case 0x0446: return glyphC;
case 0x0447: return glyphCh;
case 0x0448: return glyphSh;
case 0x0449: return glyphShch;
case 0x044A: return glyphHard;
case 0x044B: return glyphYI;
case 0x044C: return glyphSoft;
case 0x044D: return glyphEReverse;
case 0x044E: return glyphYu;
case 0x044F: return glyphYa;
default:
return nullptr;
}
}
// ======================================================
// UTF-8 → UNICODE
// ======================================================
unsigned int decodeUTF8(
const char* &text
) {
unsigned char b1 =
(unsigned char)text[0];
// ASCII
if (b1 < 0x80) {
text++;
return b1;
}
// 2-байтовый UTF-8
if (
(b1 & 0xE0) ==
0xC0
) {
unsigned char b2 =
(unsigned char)text[1];
unsigned int code =
((unsigned int)(b1 & 0x1F) << 6) |
(b2 & 0x3F);
text += 2;
return code;
}
// 3-байтовый UTF-8
if (
(b1 & 0xF0) ==
0xE0
) {
unsigned char b2 =
(unsigned char)text[1];
unsigned char b3 =
(unsigned char)text[2];
unsigned int code =
((unsigned int)(b1 & 0x0F) << 12) |
((unsigned int)(b2 & 0x3F) << 6) |
(b3 & 0x3F);
text += 3;
return code;
}
// Неизвестный символ
text++;
return '?';
}
// ======================================================
// ВЫВОД РУССКОГО / UTF-8 ТЕКСТА
// ======================================================
void printRussian(const char* text) {
while (*text) {
unsigned int code =
decodeUTF8(text);
// ==================================================
// ПРОБЕЛ
// ==================================================
if (code == ' ') {
display.setCursor(
display.getCursorX() + 6,
display.getCursorY()
);
continue;
}
// ==================================================
// ASCII
// ==================================================
if (code < 128) {
display.write(
(char)code
);
continue;
}
// ==================================================
// РУССКАЯ БУКВА
// ==================================================
const byte* glyph =
getGlyphUnicode(code);
if (glyph != nullptr) {
int x =
display.getCursorX();
int y =
display.getCursorY();
for (
byte row = 0;
row < 7;
row++
) {
for (
byte col = 0;
col < 5;
col++
) {
if (
glyph[row] &
(1 << (4 - col))
) {
display.drawPixel(
x + col,
y + row,
SSD1306_WHITE
);
}
}
}
display.setCursor(
x + 6,
y
);
}
else {
display.write('?');
}
}
}
// ======================================================
// SETUP
// ======================================================
void setup() {
// ====================================================
// ФОРСУНКИ
// ====================================================
for (
byte i = 0;
i < 4;
i++
) {
pinMode(
injectorPins[i],
OUTPUT
);
digitalWrite(
injectorPins[i],
LOW
);
}
// ====================================================
// РЕЛЕ
// ====================================================
pinMode(
relay1Pin,
OUTPUT
);
pinMode(
relay2Pin,
OUTPUT
);
digitalWrite(
relay1Pin,
LOW
);
digitalWrite(
relay2Pin,
LOW
);
// ====================================================
// КНОПКИ
// ====================================================
pinMode(
injectorButtonPin,
INPUT_PULLUP
);
pinMode(
relay1ButtonPin,
INPUT_PULLUP
);
pinMode(
relay2ButtonPin,
INPUT_PULLUP
);
// ====================================================
// ДИНАМИК
// ====================================================
pinMode(
buzzerPin,
OUTPUT
);
noTone(
buzzerPin
);
// ====================================================
// OLED
// ====================================================
display.begin(
SSD1306_SWITCHCAPVCC,
OLED_ADDRESS
);
display.clearDisplay();
display.setTextColor(
SSD1306_WHITE
);
display.setTextSize(1);
display.setCursor(
0,
0
);
printRussian(
"ФОРСУНКИ"
);
display.display();
// ====================================================
// DS18B20
// ====================================================
sensors.begin();
sensors.setResolution(
10
);
sensors.setWaitForConversion(
true
);
// ====================================================
// ПЕРВОЕ ИЗМЕРЕНИЕ
// ====================================================
sensors.requestTemperatures();
float t =
sensors.getTempCByIndex(0);
if (
t !=
DEVICE_DISCONNECTED_C
) {
currentTemperature =
t;
temperatureValid =
true;
triggerStartTemperature =
t;
triggerStartTime =
millis();
}
else {
temperatureValid =
false;
temperatureFault =
true;
}
// ====================================================
// ПАУЗА ПОСЛЕ СТАРТА
// ====================================================
delay(500);
// ====================================================
// ЗВУК ЗАПУСКА
// ====================================================
playSound(
SOUND_STARTUP
);
}
// ======================================================
// LOOP
// ======================================================
void loop() {
updateSound();
readSettings();
updateButton(
injectorButton
);
updateButton(
relay1Button
);
updateButton(
relay2Button
);
updateTemperature();
updateInjectors();
updateInjectorLock();
updateRelay1();
updateRelay2();
updateDisplay();
}
// ======================================================
// ПОТЕНЦИОМЕТРЫ
// ======================================================
int readPotFiltered(
byte pin
) {
long sum = 0;
for (
byte i = 0;
i < POT_SAMPLES;
i++
) {
sum += analogRead(pin);
}
return sum / POT_SAMPLES;
}
void readSettings() {
int newTimeValue =
readPotFiltered(
timePotPin
);
int newCountValue =
readPotFiltered(
countPotPin
);
// ====================================================
// ВРЕМЯ
// ====================================================
if (
newTimeValue <= 3
) {
filteredTimeValue =
0;
}
else if (
newTimeValue >= 1020
) {
filteredTimeValue =
1023;
}
else if (
abs(
newTimeValue -
filteredTimeValue
) >= 3
) {
filteredTimeValue =
newTimeValue;
}
// ====================================================
// КОЛИЧЕСТВО ЦИКЛОВ
// ====================================================
if (
newCountValue <= 3
) {
filteredCountValue =
0;
}
else if (
newCountValue >= 1020
) {
filteredCountValue =
1023;
}
else if (
abs(
newCountValue -
filteredCountValue
) >= 3
) {
filteredCountValue =
newCountValue;
}
// ====================================================
// ПРЕОБРАЗОВАНИЕ
// ====================================================
injectorOpenTime =
map(
filteredTimeValue,
0,
1023,
MIN_INJECTOR_TIME,
MAX_INJECTOR_TIME
);
injectorCycles =
map(
filteredCountValue,
0,
1023,
MIN_CYCLES,
MAX_CYCLES
);
injectorOpenTime =
constrain(
injectorOpenTime,
MIN_INJECTOR_TIME,
MAX_INJECTOR_TIME
);
injectorCycles =
constrain(
injectorCycles,
MIN_CYCLES,
MAX_CYCLES
);
}
// ======================================================
// КНОПКИ
// ======================================================
bool buttonPressed(
Button &button
) {
bool reading =
digitalRead(
button.pin
);
unsigned long now =
millis();
if (
reading !=
button.lastReading
) {
button.lastChangeTime =
now;
button.lastReading =
reading;
}
if (
now -
button.lastChangeTime >=
DEBOUNCE_TIME
) {
if (
reading !=
button.stableState
) {
button.stableState =
reading;
if (
button.stableState ==
LOW
) {
return true;
}
}
}
return false;
}
void updateButton(
Button &button
) {
// ====================================================
// КНОПКА ФОРСУНОК
// ====================================================
if (
&button ==
&injectorButton
) {
if (
buttonPressed(button)
) {
if (
!injectorRunning &&
!injectorButtonLocked &&
!overheatLock &&
temperatureValid &&
!temperatureFault
) {
playSound(
SOUND_BUTTON
);
startInjectorCycle();
}
else if (
overheatLock
) {
playSound(
SOUND_OVERHEAT
);
}
}
}
// ====================================================
// РЕЛЕ 1
// ====================================================
if (
&button ==
&relay1Button
) {
if (
buttonPressed(button)
) {
if (
!injectorRunning &&
!relay1Active
) {
relay1Active =
true;
relay1StartTime =
millis();
digitalWrite(
relay1Pin,
HIGH
);
}
}
}
}
// ======================================================
// ЗАПУСК ФОРСУНОК
// ======================================================
void startInjectorCycle() {
if (!temperatureValid)
return;
if (temperatureFault)
return;
if (overheatLock)
return;
if (
currentTemperature >=
OVERHEAT_TEMP
) {
overheatLock =
true;
injectorButtonLocked =
true;
playSound(
SOUND_OVERHEAT
);
return;
}
injectorRunning =
true;
currentInjector =
0;
currentCycle =
0;
injectorButtonLocked =
true;
digitalWrite(
injectorPins[
currentInjector
],
HIGH
);
injectorStartTime =
micros();
}
// ======================================================
// ФОРСУНКИ
// ======================================================
void updateInjectors() {
if (!injectorRunning)
return;
if (
overheatLock ||
temperatureFault ||
!temperatureValid
) {
for (
byte i = 0;
i < 4;
i++
) {
digitalWrite(
injectorPins[i],
LOW
);
}
injectorRunning =
false;
injectorUnlockTime =
millis();
return;
}
unsigned long now =
micros();
unsigned long requiredTime =
(unsigned long)
injectorOpenTime *
1000UL;
if (
now -
injectorStartTime >=
requiredTime
) {
digitalWrite(
injectorPins[
currentInjector
],
LOW
);
currentInjector++;
if (
currentInjector >= 4
) {
currentInjector =
0;
currentCycle++;
if (
currentCycle >=
injectorCycles
) {
injectorRunning =
false;
injectorUnlockTime =
millis();
return;
}
}
digitalWrite(
injectorPins[
currentInjector
],
HIGH
);
injectorStartTime =
micros();
}
}
// ======================================================
// БЛОКИРОВКА КНОПКИ
// ======================================================
void updateInjectorLock() {
if (
!injectorButtonLocked
)
return;
if (overheatLock)
return;
if (injectorRunning)
return;
if (
millis() -
injectorUnlockTime >=
INJECTOR_LOCK_TIME
) {
injectorButtonLocked =
false;
}
}
// ======================================================
// РЕЛЕ 1
// ======================================================
void updateRelay1() {
if (!relay1Active)
return;
if (
millis() -
relay1StartTime >=
RELAY1_TIME
) {
digitalWrite(
relay1Pin,
LOW
);
relay1Active =
false;
}
}
// ======================================================
// РЕЛЕ 2
// ======================================================
void updateRelay2() {
if (
relay2Button.stableState ==
LOW
) {
digitalWrite(
relay2Pin,
HIGH
);
}
else {
digitalWrite(
relay2Pin,
LOW
);
}
}
// ======================================================
// ТЕМПЕРАТУРА
// ======================================================
void updateTemperature() {
static unsigned long
lastTemperatureRead = 0;
static bool
errorAlreadyReported = false;
if (
millis() -
lastTemperatureRead <
TEMPERATURE_READ_TIME
) {
return;
}
lastTemperatureRead =
millis();
sensors.requestTemperatures();
float newTemperature =
sensors.getTempCByIndex(0);
// ====================================================
// ОШИБКА ДАТЧИКА
// ====================================================
if (
newTemperature ==
DEVICE_DISCONNECTED_C
) {
temperatureValid =
false;
temperatureFault =
true;
for (
byte i = 0;
i < 4;
i++
) {
digitalWrite(
injectorPins[i],
LOW
);
}
injectorRunning =
false;
injectorButtonLocked =
true;
if (
!errorAlreadyReported
) {
playSound(
SOUND_ERROR
);
errorAlreadyReported =
true;
}
return;
}
// ====================================================
// ДАТЧИК СНОВА РАБОТАЕТ
// ====================================================
errorAlreadyReported =
false;
temperatureValid =
true;
temperatureFault =
false;
currentTemperature =
newTemperature;
// ====================================================
// ПЕРЕГРЕВ
// ====================================================
if (
currentTemperature >=
OVERHEAT_TEMP
) {
if (!overheatLock) {
playSound(
SOUND_OVERHEAT
);
}
overheatLock =
true;
injectorButtonLocked =
true;
for (
byte i = 0;
i < 4;
i++
) {
digitalWrite(
injectorPins[i],
LOW
);
}
injectorRunning =
false;
}
// ====================================================
// ОХЛАЖДЕНИЕ
// ====================================================
if (
overheatLock &&
currentTemperature <=
COOL_DOWN_TEMP
) {
overheatLock =
false;
injectorButtonLocked =
false;
}
// ====================================================
// СЧЁТЧИК РЕЗКОГО ПОВЫШЕНИЯ ТЕМПЕРАТУРЫ
// ====================================================
if (!triggerArmed) {
if (
currentTemperature <
triggerStartTemperature - 1.0
) {
triggerArmed =
true;
triggerStartTemperature =
currentTemperature;
triggerStartTime =
millis();
}
return;
}
unsigned long now =
millis();
float rise =
currentTemperature -
triggerStartTemperature;
// Более 5 °C за 2 секунды
if (
rise > TRIGGER_TEMP_RISE &&
now -
triggerStartTime <=
TRIGGER_WINDOW
) {
triggerCount++;
triggerArmed =
false;
triggerStartTemperature =
currentTemperature;
triggerStartTime =
now;
return;
}
// ====================================================
// ОКНО ЗАКОНЧИЛОСЬ
// ====================================================
if (
now -
triggerStartTime >
TRIGGER_WINDOW
) {
triggerStartTemperature =
currentTemperature;
triggerStartTime =
now;
}
}
// ======================================================
// OLED
// ======================================================
void updateDisplay() {
if (
millis() -
lastDisplayUpdate <
DISPLAY_UPDATE_TIME
) {
return;
}
lastDisplayUpdate =
millis();
display.clearDisplay();
display.setTextColor(
SSD1306_WHITE
);
display.setTextSize(1);
// ====================================================
// НАСТРОЙКИ
// ====================================================
display.setCursor(
0,
0
);
printRussian(
"ИМПУЛЬС:"
);
display.print(
injectorOpenTime
);
display.print(
"ms"
);
display.setCursor(
70,
0
);
printRussian(
"ЦИКЛЫ:"
);
display.print(
injectorCycles
);
// ====================================================
// ТЕМПЕРАТУРА
// ====================================================
display.setCursor(
0,
12
);
printRussian(
"ТЕМП:"
);
if (temperatureValid) {
display.print(
currentTemperature,
1
);
display.print(
"C"
);
}
else {
printRussian(
"ОШИБКА"
);
}
// ====================================================
// СОСТОЯНИЕ
// ====================================================
display.setCursor(
0,
23
);
if (temperatureFault) {
printRussian(
"ОШИБКА ДАТЧИКА"
);
}
else if (overheatLock) {
printRussian(
"ПЕРЕГРЕВ"
);
}
else if (injectorRunning) {
printRussian(
"РАБОТА"
);
display.setCursor(
70,
23
);
display.print(
currentInjector + 1
);
display.print(
"/"
);
display.print(
currentCycle + 1
);
}
else if (injectorButtonLocked) {
printRussian(
"БЛОК:"
);
unsigned long elapsed =
millis() -
injectorUnlockTime;
unsigned long remaining =
0;
if (
elapsed <
INJECTOR_LOCK_TIME
) {
remaining =
(
INJECTOR_LOCK_TIME -
elapsed +
999
) / 1000;
}
display.print(
remaining
);
display.print(
"s"
);
}
else {
printRussian(
"ГОТОВ"
);
}
// ====================================================
// РЕЛЕ
// ====================================================
display.setCursor(
0,
35
);
display.print(
"R1:"
);
display.print(
relay1Active ?
"ON" :
"OFF"
);
display.setCursor(
65,
35
);
display.print(
"R2:"
);
display.print(
relay2Button.stableState == LOW ?
"ON" :
"OFF"
);
// ====================================================
// СЧЁТЧИК
// ====================================================
display.setCursor(
0,
48
);
printRussian(
"СРАБАТЫВАНИЙ:"
);
display.setCursor(
0,
56
);
display.print(
triggerCount
);
display.display();
}