Загрузка данных
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <string.h>
// =====================================================
// OLED
// =====================================================
#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 INJECTOR_PINS[4] = {2, 3, 4, 5};
// Кнопки
const byte START_BUTTON_PIN = 6;
const byte RELAY1_BUTTON_PIN = 7;
const byte RELAY2_BUTTON_PIN = 9;
const byte CONTINUOUS_BUTTON_PIN = A2;
// Реле
const byte RELAY1_PIN = 8;
const byte RELAY2_PIN = 10;
// Температурный датчик DS18B20
const byte TEMP_SENSOR_PIN = 11;
// Динамик
const byte BUZZER_PIN = 12;
// Потенциометры
const byte PULSE_POT_PIN = A0;
const byte CYCLES_POT_PIN = A1;
// =====================================================
// НАСТРОЙКИ
// =====================================================
const unsigned long RELAY1_DURATION = 15000UL;
const unsigned long TEMP_CONVERSION_TIME = 750UL;
const unsigned long TEMP_READ_INTERVAL = 1000UL;
const float OVERHEAT_LIMIT = 65.0;
const float OVERHEAT_RELEASE = 60.0;
const float TRIGGER_RISE = 5.0;
const float TRIGGER_REARM_DROP = 1.0;
const unsigned long TRIGGER_WINDOW = 2000UL;
const unsigned long DISPLAY_INTERVAL = 200UL;
const unsigned long DEBOUNCE_DELAY = 35UL;
const int EEPROM_TRIGGER_ADDRESS = 0;
// =====================================================
// ТЕМПЕРАТУРА
// =====================================================
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature sensors(&oneWire);
float temperatureC = DEVICE_DISCONNECTED_C;
bool temperatureConversionPending = false;
unsigned long temperatureRequestTime = 0;
unsigned long lastTemperatureRequest = 0;
bool temperatureFault = false;
bool overheatLock = false;
bool emergencyLock = false;
bool temperatureErrorBeeped = false;
bool overheatBeeped = false;
// =====================================================
// ФОРСУНКИ
// =====================================================
int pulseDuration = 5;
int targetCycles = 10;
unsigned long cyclesCompleted = 0;
byte currentInjector = 0;
bool injectorRunning = false;
bool injectorPulseActive = false;
unsigned long injectorPulseStart = 0;
bool continuousMode = false;
bool continuousStopRequested = false;
// =====================================================
// РЕЛЕ
// =====================================================
bool relay1Active = false;
unsigned long relay1StartTime = 0;
bool relay2Active = false;
// =====================================================
// КНОПКИ И АНТИДРЕБЕЗГ
// =====================================================
struct ButtonState {
byte pin;
bool stableState;
bool lastReading;
unsigned long lastChangeTime;
};
ButtonState startButton = {
START_BUTTON_PIN, HIGH, HIGH, 0
};
ButtonState relay1Button = {
RELAY1_BUTTON_PIN, HIGH, HIGH, 0
};
ButtonState relay2Button = {
RELAY2_BUTTON_PIN, HIGH, HIGH, 0
};
ButtonState continuousButton = {
CONTINUOUS_BUTTON_PIN, HIGH, HIGH, 0
};
// =====================================================
// ЗВУК
// =====================================================
bool buzzerActive = false;
unsigned long buzzerStartTime = 0;
unsigned long buzzerDuration = 0;
// =====================================================
// СЧЁТЧИК TRIG
// =====================================================
uint32_t triggerCount = 0;
bool triggerReferenceReady = false;
bool triggerArmed = true;
float triggerReferenceTemp = 0.0;
float triggerEventTemp = 0.0;
unsigned long triggerReferenceTime = 0;
// =====================================================
// ДИСПЛЕЙ
// =====================================================
unsigned long lastDisplayUpdate = 0;
// =====================================================
// ЗВУКОВОЙ СИГНАЛ
// =====================================================
void beep(unsigned int frequency, unsigned long duration) {
if (buzzerActive) {
return;
}
tone(BUZZER_PIN, frequency);
buzzerActive = true;
buzzerStartTime = millis();
buzzerDuration = duration;
}
void updateBuzzer() {
if (!buzzerActive) {
return;
}
if (millis() - buzzerStartTime >= buzzerDuration) {
noTone(BUZZER_PIN);
buzzerActive = false;
}
}
// =====================================================
// МЕЛОДИЯ ПРИ ЗАПУСКЕ
// =====================================================
void playStartupMelody() {
const unsigned int notes[] = {
523, 659, 784, 1047, 784, 1047
};
const unsigned int durations[] = {
140, 140, 140, 260, 140, 350
};
for (byte i = 0; i < 6; i++) {
tone(BUZZER_PIN, notes[i]);
delay(durations[i]);
noTone(BUZZER_PIN);
delay(45);
}
noTone(BUZZER_PIN);
}
// =====================================================
// АНТИДРЕБЕЗГ КНОПОК
// Возвращает true при новом нажатии
// =====================================================
bool readDebouncedButton(ButtonState &button) {
bool reading = digitalRead(button.pin);
unsigned long now = millis();
if (reading != button.lastReading) {
button.lastReading = reading;
button.lastChangeTime = now;
}
if (now - button.lastChangeTime >= DEBOUNCE_DELAY) {
if (reading != button.stableState) {
button.stableState = reading;
if (button.stableState == LOW) {
return true;
}
}
}
return false;
}
// =====================================================
// ПОТЕНЦИОМЕТРЫ
// P: 5–25 мс
// N: 10–100 циклов
// =====================================================
void readPotentiometers() {
static float filteredP = 0;
static float filteredN = 0;
static bool initialized = false;
int rawP = analogRead(PULSE_POT_PIN);
int rawN = analogRead(CYCLES_POT_PIN);
if (!initialized) {
filteredP = rawP;
filteredN = rawN;
initialized = true;
}
const float alpha = 0.08;
filteredP += alpha * (rawP - filteredP);
filteredN += alpha * (rawN - filteredN);
pulseDuration = map(
(int)(filteredP + 0.5),
0, 1023,
5, 25
);
targetCycles = map(
(int)(filteredN + 0.5),
0, 1023,
10, 100
);
pulseDuration = constrain(pulseDuration, 5, 25);
targetCycles = constrain(targetCycles, 10, 100);
}
// =====================================================
// УПРАВЛЕНИЕ ВЫХОДАМИ ФОРСУНОК
// =====================================================
void allInjectorsOff() {
for (byte i = 0; i < 4; i++) {
digitalWrite(INJECTOR_PINS[i], LOW);
}
injectorPulseActive = false;
}
bool safetyConditionsOK() {
return !temperatureFault && !overheatLock;
}
void stopInjectorTest() {
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
continuousStopRequested = false;
currentInjector = 0;
cyclesCompleted = 0;
}
void startInjectorTest(bool continuous) {
if (!safetyConditionsOK() || emergencyLock) {
beep(300, 200);
return;
}
allInjectorsOff();
cyclesCompleted = 0;
currentInjector = 0;
continuousMode = continuous;
continuousStopRequested = false;
injectorRunning = true;
injectorPulseActive = false;
digitalWrite(INJECTOR_PINS[currentInjector], HIGH);
injectorPulseStart = millis();
injectorPulseActive = true;
}
// =====================================================
// ЦИКЛЫ ФОРСУНОК
// =====================================================
void updateInjectorTest() {
if (!injectorRunning) {
return;
}
// Защита на случай появления ошибки во время работы
if (!safetyConditionsOK() || emergencyLock) {
stopInjectorTest();
return;
}
if (!injectorPulseActive) {
return;
}
if (millis() - injectorPulseStart < (unsigned long)pulseDuration) {
return;
}
// Завершаем импульс текущей форсунки
digitalWrite(INJECTOR_PINS[currentInjector], LOW);
injectorPulseActive = false;
// Переходим к следующей форсунке
currentInjector++;
if (currentInjector >= 4) {
currentInjector = 0;
cyclesCompleted++;
// Останов после завершения полного цикла
if (continuousMode && continuousStopRequested) {
stopInjectorTest();
return;
}
if (!continuousMode && cyclesCompleted >= (unsigned long)targetCycles) {
stopInjectorTest();
beep(1500, 100);
return;
}
}
// Запускаем следующую форсунку
digitalWrite(INJECTOR_PINS[currentInjector], HIGH);
injectorPulseStart = millis();
injectorPulseActive = true;
}
// =====================================================
// КНОПКА START
// Повторное нажатие во время теста — аварийная остановка
// =====================================================
void updateStartButton() {
if (!readDebouncedButton(startButton)) {
return;
}
if (injectorRunning) {
stopInjectorTest();
emergencyLock = true;
beep(350, 250);
return;
}
if (!safetyConditionsOK()) {
beep(300, 200);
return;
}
if (emergencyLock) {
emergencyLock = false;
beep(1000, 100);
return;
}
startInjectorTest(false);
}
// =====================================================
// НЕПРЕРЫВНЫЙ РЕЖИМ
// Нажатие — запуск, отпускание — останов после цикла
// =====================================================
void updateContinuousButton() {
bool pressed = readDebouncedButton(continuousButton);
if (pressed && !injectorRunning) {
if (safetyConditionsOK() && !emergencyLock) {
startInjectorTest(true);
} else {
beep(300, 200);
}
}
// Считываем фактическое стабильное состояние кнопки
if (continuousMode && injectorRunning) {
if (continuousButton.stableState == HIGH) {
continuousStopRequested = true;
}
}
}
// =====================================================
// РЕЛЕ R1
// Включение на 15 секунд
// =====================================================
void updateRelay1() {
if (readDebouncedButton(relay1Button)) {
if (!injectorRunning && !relay1Active) {
relay1Active = true;
relay1StartTime = millis();
digitalWrite(RELAY1_PIN, HIGH);
}
}
if (relay1Active) {
if (millis() - relay1StartTime >= RELAY1_DURATION) {
relay1Active = false;
digitalWrite(RELAY1_PIN, LOW);
}
}
}
// =====================================================
// РЕЛЕ R2
// Работает, пока кнопка нажата
// Заблокировано во время теста форсунок
// =====================================================
void updateRelay2() {
readDebouncedButton(relay2Button);
bool shouldBeActive =
(relay2Button.stableState == LOW) && !injectorRunning;
if (shouldBeActive != relay2Active) {
relay2Active = shouldBeActive;
digitalWrite(
RELAY2_PIN,
relay2Active ? HIGH : LOW
);
}
}
// =====================================================
// СЧЁТЧИК TRIG — EEPROM
// =====================================================
void loadTriggerCounter() {
EEPROM.get(EEPROM_TRIGGER_ADDRESS, triggerCount);
// Проверка на незаписанную или повреждённую EEPROM
if (triggerCount == 0xFFFFFFFFUL) {
triggerCount = 0;
}
}
void saveTriggerCounter() {
EEPROM.put(EEPROM_TRIGGER_ADDRESS, triggerCount);
}
// Сброс TRIG при удержании R1 и R2 во время включения
void resetTriggerCounterIfRequested() {
if (digitalRead(RELAY1_BUTTON_PIN) == LOW &&
digitalRead(RELAY2_BUTTON_PIN) == LOW) {
triggerCount = 0;
saveTriggerCounter();
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(12, 20);
display.print(F("TRIG RESET"));
display.display();
delay(700);
}
}
void updateTriggerCounter() {
if (temperatureC == DEVICE_DISCONNECTED_C ||
temperatureFault) {
return;
}
unsigned long now = millis();
// После запуска ждём первое достоверное значение
if (!triggerReferenceReady) {
triggerReferenceTemp = temperatureC;
triggerReferenceTime = now;
triggerReferenceReady = true;
return;
}
// Повторное разрешение счётчика после охлаждения
if (!triggerArmed) {
if (temperatureC <= triggerEventTemp - TRIGGER_REARM_DROP) {
triggerArmed = true;
triggerReferenceTemp = temperatureC;
triggerReferenceTime = now;
}
return;
}
// Обнаружение скачка температуры в пределах 2 секунд
if (now - triggerReferenceTime <= TRIGGER_WINDOW) {
if (temperatureC - triggerReferenceTemp >= TRIGGER_RISE) {
triggerCount++;
saveTriggerCounter();
triggerEventTemp = temperatureC;
triggerArmed = false;
return;
}
} else {
// Обновляем точку отсчёта для следующего окна
triggerReferenceTemp = temperatureC;
triggerReferenceTime = now;
}
}
// =====================================================
// ОБРАБОТКА ТЕМПЕРАТУРЫ
// =====================================================
void processTemperature() {
if (temperatureC == DEVICE_DISCONNECTED_C) {
temperatureFault = true;
emergencyLock = true;
stopInjectorTest();
if (!temperatureErrorBeeped) {
beep(300, 400);
temperatureErrorBeeped = true;
}
return;
}
// Датчик снова отвечает
temperatureFault = false;
temperatureErrorBeeped = false;
// Защита от перегрева
if (temperatureC >= OVERHEAT_LIMIT) {
overheatLock = true;
emergencyLock = true;
stopInjectorTest();
if (!overheatBeeped) {
beep(250, 500);
overheatBeeped = true;
}
}
// Автоматическое снятие блокировки перегрева
if (overheatLock && temperatureC <= OVERHEAT_RELEASE) {
overheatLock = false;
overheatBeeped = false;
}
updateTriggerCounter();
}
void updateTemperature() {
unsigned long now = millis();
// Запрашиваем новое измерение
if (!temperatureConversionPending &&
now - lastTemperatureRequest >= TEMP_READ_INTERVAL) {
sensors.requestTemperatures();
temperatureConversionPending = true;
temperatureRequestTime = now;
lastTemperatureRequest = now;
}
// Получаем результат после завершения преобразования
if (temperatureConversionPending &&
now - temperatureRequestTime >= TEMP_CONVERSION_TIME) {
temperatureC = sensors.getTempCByIndex(0);
temperatureConversionPending = false;
processTemperature();
}
}
// =====================================================
// ЗАГРУЗОЧНАЯ АНИМАЦИЯ — 5 СЕКУНД
// =====================================================
void startupAnimation() {
unsigned long startTime = millis();
const unsigned long animationDuration = 5000UL;
while (millis() - startTime < animationDuration) {
unsigned long elapsed = millis() - startTime;
int progress = map(
elapsed,
0,
animationDuration,
0,
100
);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(22, 8);
display.print(F("INJECTOR TESTER"));
display.drawRect(9, 29, 110, 13, SSD1306_WHITE);
int barWidth = map(progress, 0, 100, 0, 106);
display.fillRect(11, 31, barWidth, 9, SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(43, 47);
display.print(progress);
display.print(F("%"));
display.display();
delay(30);
}
display.clearDisplay();
display.display();
}
// =====================================================
// ОБНОВЛЕНИЕ ДИСПЛЕЯ
// =====================================================
void updateDisplay() {
if (millis() - lastDisplayUpdate < DISPLAY_INTERVAL) {
return;
}
lastDisplayUpdate = millis();
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Заголовки P и N
display.setTextSize(1);
display.setCursor(0, 0);
display.print(F("P"));
display.setCursor(76, 0);
display.print(F("N"));
// Крупные значения параметров
display.setTextSize(3);
display.setCursor(0, 10);
display.print(pulseDuration);
char nBuffer[4];
snprintf(
nBuffer,
sizeof(nBuffer),
"%d",
targetCycles
);
int nWidth = strlen(nBuffer) * 18;
int nX = 124 - nWidth;
if (nX < 64) {
nX = 64;
}
display.setCursor(nX, 10);
display.print(nBuffer);
// Температура
display.setTextSize(1);
display.setCursor(0, 39);
display.print(F("TEMP:"));
if (temperatureFault) {
display.print(F("ERROR"));
} else if (overheatLock) {
display.print(F("OVERHEAT"));
} else if (temperatureC != DEVICE_DISCONNECTED_C) {
display.print(temperatureC, 1);
display.print(F(" C"));
} else {
display.print(F("--.- C"));
}
// TRIG слева внизу
display.setCursor(0, 51);
display.print(F("TRIG:"));
display.print(triggerCount);
// Обратный отсчёт R1 справа внизу
display.setCursor(92, 51);
if (relay1Active) {
unsigned long elapsed = millis() - relay1StartTime;
unsigned long remaining = 0;
if (elapsed < RELAY1_DURATION) {
remaining =
(RELAY1_DURATION - elapsed + 999UL) / 1000UL;
}
display.print(F("R1:"));
display.print(remaining);
display.print(F("s"));
} else {
display.print(F("R1:OFF"));
}
display.display();
}
// =====================================================
// SETUP
// =====================================================
void setup() {
// Форсунки
for (byte i = 0; i < 4; i++) {
pinMode(INJECTOR_PINS[i], OUTPUT);
digitalWrite(INJECTOR_PINS[i], LOW);
}
// Кнопки
pinMode(START_BUTTON_PIN, INPUT_PULLUP);
pinMode(RELAY1_BUTTON_PIN, INPUT_PULLUP);
pinMode(RELAY2_BUTTON_PIN, INPUT_PULLUP);
pinMode(CONTINUOUS_BUTTON_PIN, INPUT_PULLUP);
// Реле
pinMode(RELAY1_PIN, OUTPUT);
pinMode(RELAY2_PIN, OUTPUT);
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, LOW);
// Динамик
pinMode(BUZZER_PIN, OUTPUT);
noTone(BUZZER_PIN);
// OLED
Wire.begin();
if (!display.begin(
SSD1306_SWITCHCAPVCC,
OLED_ADDRESS
)) {
while (true) {
// Остановка при ошибке инициализации дисплея
}
}
display.clearDisplay();
display.display();
// Температурный датчик
sensors.begin();
sensors.setWaitForConversion(false);
// Загружаем счётчик
loadTriggerCounter();
// При удержании R1 + R2 сбрасываем TRIG
resetTriggerCounterIfRequested();
// Пятисекундная загрузочная анимация
startupAnimation();
// НОВОЕ: мелодия после загрузочной анимации
playStartupMelody();
// После мелодии динамик остаётся выключенным
noTone(BUZZER_PIN);
buzzerActive = false;
}
// =====================================================
// ОСНОВНОЙ ЦИКЛ
// =====================================================
void loop() {
updateBuzzer();
readPotentiometers();
updateTemperature();
updateStartButton();
updateContinuousButton();
updateRelay1();
updateRelay2();
updateInjectorTest();
updateDisplay();
}