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


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.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
);

// =========================
// PINS
// =========================

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 timePotPin = A0;
const byte countPotPin = A1;


// =========================
// RANGES
// =========================

const unsigned int MIN_INJECTOR_TIME = 5;
const unsigned int MAX_INJECTOR_TIME = 20;

const int MIN_CYCLES = 10;
const int MAX_CYCLES = 100;


// =========================
// TIMES
// =========================

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;


// =========================
// POTENTIOMETER FILTER
// =========================

const byte POT_SAMPLES = 8;


// =========================
// INJECTOR SETTINGS
// =========================

unsigned int injectorOpenTime = MIN_INJECTOR_TIME;
int injectorCycles = MIN_CYCLES;


// =========================
// FILTERED POT VALUES
// =========================

int filteredTimeValue = 0;
int filteredCountValue = 0;


// =========================
// INJECTOR STATE
// =========================

bool injectorRunning = false;

byte currentInjector = 0;
int currentCycle = 0;

unsigned long injectorStartTime = 0;


// =========================
// INJECTOR BUTTON LOCK
// =========================

bool injectorButtonLocked = false;

unsigned long injectorUnlockTime = 0;


// =========================
// RELAY 1
// =========================

bool relay1Active = false;

unsigned long relay1StartTime = 0;


// =========================
// BUTTON STRUCT
// =========================

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


// =========================
// DISPLAY
// =========================

unsigned long lastDisplayUpdate = 0;


// ========================================================
// SETUP
// ========================================================

void setup()
{
  // -------------------------
  // Injector outputs
  // -------------------------

  for (byte i = 0; i < 4; i++)
  {
    pinMode(injectorPins[i], OUTPUT);

    digitalWrite(injectorPins[i], LOW);
  }


  // -------------------------
  // Relay outputs
  // -------------------------

  pinMode(relay1Pin, OUTPUT);
  pinMode(relay2Pin, OUTPUT);

  digitalWrite(relay1Pin, LOW);
  digitalWrite(relay2Pin, LOW);


  // -------------------------
  // Buttons
  // -------------------------

  pinMode(injectorButtonPin, INPUT_PULLUP);
  pinMode(relay1ButtonPin, INPUT_PULLUP);
  pinMode(relay2ButtonPin, INPUT_PULLUP);


  // -------------------------
  // OLED
  // -------------------------

  display.begin(
    SSD1306_SWITCHCAPVCC,
    OLED_ADDRESS
  );

  display.clearDisplay();

  display.setTextColor(SSD1306_WHITE);

  display.setTextSize(1);

  display.setCursor(0, 0);

  display.println("INJECTOR CONTROL");

  display.display();

  delay(500);
}


// ========================================================
// LOOP
// ========================================================

void loop()
{
  readSettings();

  updateButton(injectorButton);
  updateButton(relay1Button);
  updateButton(relay2Button);

  updateInjectors();

  updateInjectorLock();

  updateRelay1();

  updateRelay2();

  updateDisplay();
}


// ========================================================
// READ POTENTIOMETERS
// ========================================================

int readPotFiltered(byte pin)
{
  long sum = 0;

  for (byte i = 0; i < POT_SAMPLES; i++)
  {
    sum += analogRead(pin);
  }

  return sum / POT_SAMPLES;
}


// ========================================================
// READ SETTINGS
// ========================================================

void readSettings()
{
  int newTimeValue = readPotFiltered(timePotPin);
  int newCountValue = readPotFiltered(countPotPin);


  // ------------------------------------------------------
  // Small software hysteresis
  // ------------------------------------------------------

  if (abs(newTimeValue - filteredTimeValue) >= 3)
  {
    filteredTimeValue = newTimeValue;
  }

  if (abs(newCountValue - filteredCountValue) >= 3)
  {
    filteredCountValue = newCountValue;
  }


  // ------------------------------------------------------
  // Convert potentiometer values
  // ------------------------------------------------------

  injectorOpenTime = map(
    filteredTimeValue,
    0,
    1023,
    MIN_INJECTOR_TIME,
    MAX_INJECTOR_TIME
  );


  injectorCycles = map(
    filteredCountValue,
    0,
    1023,
    MIN_CYCLES,
    MAX_CYCLES
  );


  // ------------------------------------------------------
  // Safety limits
  // ------------------------------------------------------

  injectorOpenTime = constrain(
    injectorOpenTime,
    MIN_INJECTOR_TIME,
    MAX_INJECTOR_TIME
  );


  injectorCycles = constrain(
    injectorCycles,
    MIN_CYCLES,
    MAX_CYCLES
  );
}


// ========================================================
// BUTTON DEBOUNCE
// ========================================================

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;


      // Button pressed
      if (button.stableState == LOW)
      {
        return true;
      }
    }
  }


  return false;
}


// ========================================================
// UPDATE BUTTONS
// ========================================================

void updateButton(Button &button)
{
  // ------------------------------------------------------
  // Injector START button
  // ------------------------------------------------------

  if (&button == &injectorButton)
  {
    if (buttonPressed(button))
    {
      if (!injectorRunning && !injectorButtonLocked)
      {
        startInjectorCycle();
      }
    }
  }


  // ------------------------------------------------------
  // Relay 1 button
  // ------------------------------------------------------

  if (&button == &relay1Button)
  {
    if (buttonPressed(button))
    {
      if (!injectorRunning && !relay1Active)
      {
        relay1Active = true;

        relay1StartTime = millis();

        digitalWrite(
          relay1Pin,
          HIGH
        );
      }
    }
  }


  // R2 handled separately
}


// ========================================================
// START INJECTOR SEQUENCE
// ========================================================

void startInjectorCycle()
{
  injectorRunning = true;

  currentInjector = 0;

  currentCycle = 0;


  // Lock START button
  injectorButtonLocked = true;


  // Start injector №1
  digitalWrite(
    injectorPins[currentInjector],
    HIGH
  );

  injectorStartTime = micros();
}


// ========================================================
// UPDATE INJECTORS
// ========================================================

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


  unsigned long now = micros();


  unsigned long requiredTime =
    (unsigned long)injectorOpenTime * 1000UL;


  // ------------------------------------------------------
  // Injector pulse finished
  // ------------------------------------------------------

  if (now - injectorStartTime >= requiredTime)
  {
    // Turn current injector OFF
    digitalWrite(
      injectorPins[currentInjector],
      LOW
    );


    // Move to next injector
    currentInjector++;


    // ----------------------------------------------------
    // End of 4-injector sequence
    // ----------------------------------------------------

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

      currentCycle++;


      // --------------------------------------------------
      // All cycles completed
      // --------------------------------------------------

      if (currentCycle >= injectorCycles)
      {
        injectorRunning = false;

        // Start 10-second lock NOW
        injectorUnlockTime = millis();

        return;
      }
    }


    // ----------------------------------------------------
    // Start next injector
    // ----------------------------------------------------

    digitalWrite(
      injectorPins[currentInjector],
      HIGH
    );

    injectorStartTime = micros();
  }
}


// ========================================================
// INJECTOR BUTTON LOCK
// ========================================================

void updateInjectorLock()
{
  if (!injectorButtonLocked)
  {
    return;
  }


  // Still injecting
  if (injectorRunning)
  {
    return;
  }


  // 10 seconds after final injector pulse
  if (millis() - injectorUnlockTime >= INJECTOR_LOCK_TIME)
  {
    injectorButtonLocked = false;
  }
}


// ========================================================
// RELAY 1
// ========================================================

void updateRelay1()
{
  if (!relay1Active)
  {
    return;
  }


  if (millis() - relay1StartTime >= RELAY1_TIME)
  {
    digitalWrite(
      relay1Pin,
      LOW
    );

    relay1Active = false;
  }
}


// ========================================================
// RELAY 2
// ========================================================

void updateRelay2()
{
  if (relay2Button.stableState == LOW)
  {
    digitalWrite(
      relay2Pin,
      HIGH
    );
  }
  else
  {
    digitalWrite(
      relay2Pin,
      LOW
    );
  }
}


// ========================================================
// OLED DISPLAY
// ========================================================

void updateDisplay()
{
  if (millis() - lastDisplayUpdate < DISPLAY_UPDATE_TIME)
  {
    return;
  }


  lastDisplayUpdate = millis();


  display.clearDisplay();

  display.setTextColor(SSD1306_WHITE);


  // ------------------------------------------------------
  // STATUS
  // ------------------------------------------------------

  display.setTextSize(1);

  display.setCursor(0, 0);


  if (injectorRunning)
  {
    display.print("INJECTING");
  }
  else if (injectorButtonLocked)
  {
    display.print("LOCKED");
  }
  else
  {
    display.print("READY");
  }


  // ------------------------------------------------------
  // Injector time
  // ------------------------------------------------------

  display.setTextSize(2);

  display.setCursor(0, 14);

  display.print("T:");

  display.print(injectorOpenTime);

  display.print("ms");


  // ------------------------------------------------------
  // Number of cycles
  // ------------------------------------------------------

  display.setCursor(0, 35);

  display.print("N:");

  display.print(injectorCycles);


  // ------------------------------------------------------
  // Injection information
  // ------------------------------------------------------

  display.setTextSize(1);


  if (injectorRunning)
  {
    display.setCursor(78, 16);

    display.print("INJ:");

    display.print(currentInjector + 1);


    display.setCursor(78, 28);

    display.print("CY:");

    // Human-readable cycle number
    display.print(currentCycle + 1);
  }


  // ------------------------------------------------------
  // Lock countdown
  // ------------------------------------------------------

  else if (injectorButtonLocked)
  {
    unsigned long elapsed =
      millis() - injectorUnlockTime;


    unsigned long remaining =
      (INJECTOR_LOCK_TIME - elapsed + 999) / 1000;


    display.setCursor(78, 16);

    display.print("LOCK");


    display.setCursor(78, 28);

    display.print(remaining);

    display.print("s");
  }


  // ------------------------------------------------------
  // Relay status
  // ------------------------------------------------------

  display.setCursor(0, 56);

  display.print("R1:");

  if (relay1Active)
  {
    display.print("ON");
  }
  else
  {
    display.print("OFF");
  }


  display.print(" R2:");

  if (relay2Button.stableState == LOW)
  {
    display.print("ON");
  }
  else
  {
    display.print("OFF");
  }


  display.display();
}