Загрузка данных



#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
);

// =====================================================
// PINOUT
// =====================================================
const byte INJECTOR_PINS[4] = {2, 3, 4, 5};

const byte START_BUTTON_PIN = 6;
const byte RELAY1_BUTTON_PIN = 7;
const byte RELAY1_PIN = 8;
const byte RELAY2_BUTTON_PIN = 9;
const byte RELAY2_PIN = 10;

const byte TEMP_SENSOR_PIN = 11;
const byte BUZZER_PIN = 12;

const byte PULSE_POT_PIN = A0;
const byte CYCLES_POT_PIN = A1;
const byte CONTINUOUS_BUTTON_PIN = A2;

// =====================================================
// SETTINGS
// =====================================================
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;

// =====================================================
// TEMPERATURE SENSOR
// =====================================================
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;

// =====================================================
// INJECTOR TEST
// =====================================================
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;

// =====================================================
// RELAYS
// =====================================================
bool relay1Active = false;
unsigned long relay1StartTime = 0;

bool relay2Active = false;

// =====================================================
// BUTTON STATES
// =====================================================
bool startButtonWasPressed = false;
bool relay1ButtonWasPressed = false;
bool continuousButtonWasPressed = false;

bool startButtonStable = HIGH;
bool relay1ButtonStable = HIGH;
bool relay2ButtonStable = HIGH;
bool continuousButtonStable = HIGH;

bool startButtonLastReading = HIGH;
bool relay1ButtonLastReading = HIGH;
bool relay2ButtonLastReading = HIGH;
bool continuousButtonLastReading = HIGH;

unsigned long startDebounceTime = 0;
unsigned long relay1DebounceTime = 0;
unsigned long relay2DebounceTime = 0;
unsigned long continuousDebounceTime = 0;

// =====================================================
// BUZZER
// =====================================================
bool buzzerActive = false;
unsigned long buzzerStartTime = 0;
unsigned long buzzerDuration = 0;

bool temperatureErrorBeeped = false;
bool overheatBeeped = false;

// =====================================================
// TRIGGER COUNTER
// =====================================================
uint32_t triggerCount = 0;

bool triggerReferenceReady = false;
bool triggerArmed = true;

float triggerReferenceTemp = 0.0;
float triggerEventTemp = 0.0;

unsigned long triggerReferenceTime = 0;

// =====================================================
// DISPLAY
// =====================================================
unsigned long lastDisplayUpdate = 0;

// =====================================================
// BUZZER FUNCTIONS
// =====================================================
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 &&
      millis() - buzzerStartTime >= buzzerDuration) {
    noTone(BUZZER_PIN);
    buzzerActive = false;
  }
}

void errorBeep() {
  beep(1800, 180);
}

// =====================================================
// BUTTON DEBOUNCE
// =====================================================
bool readDebouncedButton(
  byte pin,
  bool &lastReading,
  bool &stableState,
  unsigned long &lastChangeTime
) {
  bool reading = digitalRead(pin);

  if (reading != lastReading) {
    lastChangeTime = millis();
    lastReading = reading;
  }

  if (millis() - lastChangeTime >= DEBOUNCE_DELAY) {
    stableState = reading;
  }

  return stableState == LOW;
}

// =====================================================
// POTENTIOMETERS
// =====================================================

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;
  }

  // Экспоненциальное сглаживание
  filteredP += (rawP - filteredP) * 0.08f;
  filteredN += (rawN - filteredN) * 0.08f;

  // Преобразование сглаженных значений
  int newP = 5 + (int)(filteredP * 20.0f / 1023.0f + 0.5f);
  int newN = 10 + (int)(filteredN * 90.0f / 1023.0f + 0.5f);

  pulseDuration = constrain(newP, 5, 25);
  targetCycles = constrain(newN, 10, 100);
}

// =====================================================
// INJECTOR CONTROL
// =====================================================
void allInjectorsOff() {
  for (byte i = 0; i < 4; i++) {
    digitalWrite(INJECTOR_PINS[i], LOW);
  }

  injectorPulseActive = false;
}

bool safetyConditionsOK() {
  return !temperatureFault &&
         !overheatLock &&
         !emergencyLock;
}

void startInjectorTest(bool continuous) {
  if (injectorRunning || !safetyConditionsOK()) {
    errorBeep();
    return;
  }

  readPotentiometers();

  cyclesCompleted = 0;
  currentInjector = 0;

  injectorRunning = true;
  injectorPulseActive = false;

  continuousMode = continuous;
  continuousStopRequested = false;

  beep(1500, 80);
}

void stopInjectorTest(bool emergency) {
  allInjectorsOff();

  injectorRunning = false;
  continuousMode = false;
  continuousStopRequested = false;

  if (emergency) {
    emergencyLock = true;
    beep(2200, 350);
  } else {
    beep(1200, 100);
  }
}

void updateInjectorTest() {
  if (!injectorRunning) {
    return;
  }

  unsigned long now = millis();

  if (!injectorPulseActive) {
    digitalWrite(INJECTOR_PINS[currentInjector], HIGH);

    injectorPulseActive = true;
    injectorPulseStart = now;
    return;
  }

  if (now - injectorPulseStart >=
      (unsigned long)pulseDuration) {

    digitalWrite(INJECTOR_PINS[currentInjector], LOW);

    injectorPulseActive = false;
    currentInjector++;

    if (currentInjector >= 4) {
      currentInjector = 0;
      cyclesCompleted++;

      // После отпускания кнопки заканчиваем текущий цикл
      if (continuousStopRequested) {
        stopInjectorTest(false);
        return;
      }

      // Конечный тест
      if (!continuousMode &&
          cyclesCompleted >= (unsigned long)targetCycles) {
        stopInjectorTest(false);
        return;
      }
    }
  }
}

// =====================================================
// START BUTTON
// =====================================================
void updateStartButton() {
  bool pressed = readDebouncedButton(
    START_BUTTON_PIN,
    startButtonLastReading,
    startButtonStable,
    startDebounceTime
  );

  if (pressed && !startButtonWasPressed) {

    if (injectorRunning) {
      stopInjectorTest(true);
    }
    else if (temperatureFault || overheatLock) {
      errorBeep();
    }
    else if (emergencyLock) {
      emergencyLock = false;
      beep(1400, 100);
    }
    else {
      startInjectorTest(false);
    }
  }

  startButtonWasPressed = pressed;
}

// =====================================================
// CONTINUOUS BUTTON
// =====================================================
void updateContinuousButton() {
  bool pressed = readDebouncedButton(
    CONTINUOUS_BUTTON_PIN,
    continuousButtonLastReading,
    continuousButtonStable,
    continuousDebounceTime
  );

  if (pressed && !continuousButtonWasPressed) {
    if (!injectorRunning) {
      startInjectorTest(true);
    }
  }

  if (!pressed && continuousButtonWasPressed) {
    if (injectorRunning && continuousMode) {
      continuousStopRequested = true;
    }
  }

  continuousButtonWasPressed = pressed;
}

// =====================================================
// RELAY 1 BUTTON
// =====================================================
void updateRelay1() {
  bool pressed = readDebouncedButton(
    RELAY1_BUTTON_PIN,
    relay1ButtonLastReading,
    relay1ButtonStable,
    relay1DebounceTime
  );

  // R1 не блокируется из-за температуры или emergencyLock
  if (pressed && !relay1ButtonWasPressed) {
    if (!injectorRunning && !relay1Active) {
      relay1Active = true;
      relay1StartTime = millis();

      digitalWrite(RELAY1_PIN, HIGH);
      beep(1500, 60);
    }
  }

  relay1ButtonWasPressed = pressed;

  if (relay1Active &&
      millis() - relay1StartTime >= RELAY1_DURATION) {

    relay1Active = false;
    digitalWrite(RELAY1_PIN, LOW);
  }
}

// =====================================================
// RELAY 2 BUTTON
// =====================================================
void updateRelay2() {
  bool pressed = readDebouncedButton(
    RELAY2_BUTTON_PIN,
    relay2ButtonLastReading,
    relay2ButtonStable,
    relay2DebounceTime
  );

  // R2 работает при удержании кнопки.
  // Блокировка по температуре и emergencyLock отсутствует.
  if (!injectorRunning && pressed) {
    relay2Active = true;
    digitalWrite(RELAY2_PIN, HIGH);
  } else {
    relay2Active = false;
    digitalWrite(RELAY2_PIN, LOW);
  }
}

// =====================================================
// TRIGGER COUNTER
// =====================================================

void loadTriggerCounter() {
  EEPROM.get(EEPROM_TRIGGER_ADDRESS, triggerCount);

  // Защита от случайного или повреждённого значения
  if (triggerCount == 0xFFFFFFFFUL ||
      triggerCount > 1000000UL) {
    triggerCount = 0;
    EEPROM.put(EEPROM_TRIGGER_ADDRESS, triggerCount);
  }
}


void resetTriggerCounterIfRequested() {
  if (digitalRead(RELAY1_BUTTON_PIN) == LOW &&
      digitalRead(RELAY2_BUTTON_PIN) == LOW) {

    triggerCount = 0;
    EEPROM.put(EEPROM_TRIGGER_ADDRESS, triggerCount);

    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 20);
    display.println(F("TRIG RESET"));
    display.display();

    beep(1600, 200);
    delay(300);
  }
}

void updateTriggerCounter(float temp) {
  unsigned long now = millis();

  if (!triggerReferenceReady) {
    triggerReferenceTemp = temp;
    triggerReferenceTime = now;
    triggerReferenceReady = true;
    triggerArmed = true;
    return;
  }

  if (!triggerArmed) {
    if (temp <= triggerEventTemp - TRIGGER_REARM_DROP) {
      triggerArmed = true;
      triggerReferenceTemp = temp;
      triggerReferenceTime = now;
    }

    return;
  }

  unsigned long elapsed = now - triggerReferenceTime;

  if (elapsed <= TRIGGER_WINDOW &&
      temp - triggerReferenceTemp > TRIGGER_RISE) {

    triggerCount++;
    EEPROM.put(EEPROM_TRIGGER_ADDRESS, triggerCount);

    triggerEventTemp = temp;
    triggerArmed = false;

    beep(2000, 100);
  }
  else if (elapsed > TRIGGER_WINDOW) {
    triggerReferenceTemp = temp;
    triggerReferenceTime = now;
  }
}

// =====================================================
// TEMPERATURE PROCESSING
// =====================================================
void processTemperature(float temp) {
  if (temp == DEVICE_DISCONNECTED_C ||
      temp < -55.0 ||
      temp > 125.0) {

    temperatureFault = true;
    emergencyLock = true;

    if (!temperatureErrorBeeped) {
      errorBeep();
      temperatureErrorBeeped = true;
    }

    allInjectorsOff();

    injectorRunning = false;
    continuousMode = false;
    continuousStopRequested = false;

    return;
  }

  temperatureFault = false;
  temperatureErrorBeeped = false;

  temperatureC = temp;

  if (temperatureC >= OVERHEAT_LIMIT) {
    overheatLock = true;
    emergencyLock = true;

    if (!overheatBeeped) {
      beep(2300, 400);
      overheatBeeped = true;
    }

    allInjectorsOff();

    injectorRunning = false;
    continuousMode = false;
    continuousStopRequested = false;
  }

  if (overheatLock && temperatureC <= OVERHEAT_RELEASE) {
    overheatLock = false;
    overheatBeeped = false;
    // emergencyLock снимается кнопкой START
  }

  updateTriggerCounter(temperatureC);
}

void updateTemperature() {
  unsigned long now = millis();

  if (temperatureConversionPending &&
      now - temperatureRequestTime >= TEMP_CONVERSION_TIME) {

    float temp = sensors.getTempCByIndex(0);

    temperatureConversionPending = false;

    processTemperature(temp);
  }

  if (!temperatureConversionPending &&
      now - lastTemperatureRequest >= TEMP_READ_INTERVAL) {

    sensors.requestTemperatures();

    temperatureRequestTime = now;
    lastTemperatureRequest = now;

    temperatureConversionPending = true;
  }
}

// =====================================================
// STARTUP ANIMATION
// =====================================================
void startupAnimation() {
  unsigned long startTime = millis();

  while (millis() - startTime < 5000UL) {
    unsigned long elapsed = millis() - startTime;

    int progress = map(elapsed, 0, 5000, 0, 100);
    progress = constrain(progress, 0, 100);

    display.clearDisplay();

    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);

    display.setCursor(16, 8);
    display.println(F("INJECTOR TESTER"));

    display.drawRect(8, 29, 112, 12, SSD1306_WHITE);

    int barWidth = map(progress, 0, 100, 0, 108);
    display.fillRect(10, 31, barWidth, 8, SSD1306_WHITE);

    display.setCursor(48, 47);
    display.print(progress);
    display.println(F("%"));

    display.display();

    delay(40);
  }
}

// =====================================================
// OLED 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);

  // Состояния реле
  display.setCursor(76, 51);
  display.print(relay1Active ? F("R1+") : F("R1-"));

  display.setCursor(100, 51);
  display.print(relay2Active ? F("R2+") : F("R2-"));

  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);
  pinMode(BUZZER_PIN, OUTPUT);

  digitalWrite(RELAY1_PIN, LOW);
  digitalWrite(RELAY2_PIN, LOW);

  noTone(BUZZER_PIN);

  Wire.begin();

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    while (true) {
      // Ошибка инициализации OLED
    }
  }

  display.clearDisplay();
  display.display();

  // Сначала загружаем счётчик, затем при необходимости сбрасываем
  loadTriggerCounter();
  resetTriggerCounterIfRequested();

  sensors.begin();
  sensors.setWaitForConversion(false);

  sensors.requestTemperatures();

  temperatureRequestTime = millis();
  lastTemperatureRequest = millis();
  temperatureConversionPending = true;

  startupAnimation();

  beep(1500, 100);
}

// =====================================================
// MAIN LOOP
// =====================================================
void loop() {
  updateBuzzer();

  readPotentiometers();

  updateTemperature();

  updateStartButton();
  updateContinuousButton();

  updateRelay1();
  updateRelay2();

  updateInjectorTest();

  updateDisplay();
}