Загрузка данных
#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;
// Пассивный динамик HY-508
const byte buzzerPin = 12;
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;
// ======================================================
// ЗВУКОВОЙ ДВИЖОК
// ======================================================
//
// Звуки проигрываются без delay().
// Поэтому Arduino продолжает выполнять основной код.
// ======================================================
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}
};
// ======================================================
// ЗАПУСК ЗВУКА
// ======================================================
void playSound(SoundType type) {
currentSound = type;
currentSoundNote = 0;
soundStartTime = millis();
soundPlaying = true;
const SoundNote* sound = getCurrentSound();
if (sound != nullptr) {
tone(
buzzerPin,
sound[0].frequency
);
}
}
// ======================================================
// ПОЛУЧИТЬ ТЕКУЩИЙ МАССИВ НОТ
// ======================================================
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 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
// ======================================================
const byte glyphA[7] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001
};
const byte glyphB[7] = {
0b11110,
0b10001,
0b10000,
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 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,
0b10000,
0b10000,
0b10000,
0b01111
};
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 glyphSh[7] = {
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b11111
};
const byte glyphYI[7] = {
0b10001,
0b10001,
0b10001,
0b11101,
0b10011,
0b10011,
0b11101
};
const byte glyphSoft[7] = {
0b10000,
0b10000,
0b10000,
0b11110,
0b10001,
0b10001,
0b11110
};
const byte* getGlyph(char c) {
switch (c) {
case 'А': return glyphA;
case 'Б': return glyphB;
case 'В': return glyphV;
case 'Г': return glyphG;
case 'Д': return glyphD;
case 'Е': return glyphE;
case 'Ж': return glyphZh;
case 'З': return glyphZ;
case 'И': return glyphI;
case 'Й': return glyphY;
case 'К': return glyphK;
case 'Л': return glyphL;
case 'М': return glyphM;
case 'Н': return glyphN;
case 'О': return glyphO;
case 'П': return glyphP;
case 'Р': return glyphR;
case 'С': return glyphS;
case 'Т': return glyphT;
case 'У': return glyphU;
case 'Ф': return glyphF;
case 'Х': return glyphH;
case 'Ц': return glyphC;
case 'Ш': return glyphSh;
case 'Ы': return glyphYI;
case 'Ь': return glyphSoft;
default:
return nullptr;
}
}
// ======================================================
// ВЫВОД РУССКОГО ТЕКСТА
// ======================================================
void printRussian(const char* text) {
while (*text) {
// Пробел
if (*text == ' ') {
display.setCursor(
display.getCursorX() + 6,
display.getCursorY()
);
text++;
continue;
}
// Обычный ASCII
if ((unsigned char)*text < 128) {
display.write(*text);
text++;
continue;
}
// UTF-8
unsigned char b1 =
(unsigned char)text[0];
unsigned char b2 =
(unsigned char)text[1];
char cyr = '?';
if (b1 == 0xD0) {
cyr =
0x40 +
(b2 - 0x90);
}
else if (b1 == 0xD1) {
cyr =
0x70 +
(b2 - 0x80);
}
const byte* glyph =
getGlyph(cyr);
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('?');
}
text += 2;
}
}
// ======================================================
// 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;
if (
millis() -
lastTemperatureRead <
TEMPERATURE_READ_TIME
) {
return;
}
lastTemperatureRead =
millis();
sensors.requestTemperatures();
float newTemperature =
sensors.getTempCByIndex(0);
// ====================================================
// ОШИБКА ДАТЧИКА
// ====================================================
if (
newTemperature ==
DEVICE_DISCONNECTED_C
) {
// Чтобы не пищать каждые 500 мс
static bool errorAlreadyReported =
false;
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;
}
// Датчик снова работает
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();
}