Загрузка данных
#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);
// R1 справа внизу с обратным отсчётом
// Максимальная ширина надписи R1:15s — 36 пикселей
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);
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();
}