Загрузка данных
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
// =====================================================
// OLED
// =====================================================
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(
SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET
);
// =====================================================
// PINS — ARDUINO NANO
// =====================================================
// Injector driver inputs
const byte INJ1_PIN = 2;
const byte INJ2_PIN = 3;
const byte INJ3_PIN = 4;
const byte INJ4_PIN = 5;
// Buttons — INPUT_PULLUP, button connects pin to GND
const byte START_BUTTON_PIN = 6;
const byte RELAY1_BUTTON_PIN = 7;
const byte RELAY2_BUTTON_PIN = 9;
const byte CONTINUOUS_BUTTON_PIN = A2;
// Relay control outputs
const byte RELAY1_PIN = 8;
const byte RELAY2_PIN = 10;
// Temperature sensor
const byte TEMP_PIN = 11;
// Buzzer
const byte BUZZER_PIN = 12;
// Potentiometers
const byte PULSE_POT_PIN = A0;
const byte CYCLE_POT_PIN = A1;
// =====================================================
// DS18B20
// =====================================================
OneWire oneWire(TEMP_PIN);
DallasTemperature sensors(&oneWire);
// =====================================================
// INJECTOR SETTINGS
// =====================================================
const int MIN_PULSE_MS = 5;
const int MAX_PULSE_MS = 25;
const int MIN_CYCLES = 10;
const int MAX_CYCLES = 100;
// =====================================================
// TEMPERATURE SETTINGS
// =====================================================
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 TEMP_READ_INTERVAL = 500UL;
const unsigned long TEMP_CONVERSION_TIME = 750UL;
// =====================================================
// TIMING
// =====================================================
const unsigned long R1_TIME = 15000UL;
const unsigned long DISPLAY_INTERVAL = 100UL;
const unsigned long DEBOUNCE_TIME = 30UL;
const byte POT_SAMPLES = 8;
// EEPROM write throttle
const unsigned long EEPROM_SAVE_INTERVAL = 10000UL;
// =====================================================
// EEPROM
// =====================================================
const int EEPROM_MAGIC_ADDR = 0;
const int EEPROM_COUNTER_ADDR = 4;
const uint32_t EEPROM_MAGIC = 0x4A544553UL;
// =====================================================
// INJECTOR STATE
// =====================================================
byte injectorPins[4] = {
INJ1_PIN, INJ2_PIN, INJ3_PIN, INJ4_PIN
};
bool injectorRunning = false;
bool continuousMode = false;
byte currentInjector = 0;
int currentCycle = 0;
int pulseMs = MIN_PULSE_MS;
int cycleCount = MIN_CYCLES;
unsigned long injectorStartTime = 0;
// =====================================================
// BUTTON STATE
// =====================================================
bool lastStartState = HIGH;
bool lastR1State = HIGH;
bool lastR2State = HIGH;
bool lastContinuousState = HIGH;
unsigned long lastStartChange = 0;
unsigned long lastR1Change = 0;
unsigned long lastR2Change = 0;
unsigned long lastContinuousChange = 0;
// =====================================================
// RELAYS
// =====================================================
bool relay1Active = false;
bool relay2Active = false;
unsigned long relay1StartTime = 0;
// =====================================================
// TEMPERATURE STATE
// =====================================================
float temperature = 0.0;
bool temperatureValid = false;
bool temperatureFault = true;
bool overheatLock = false;
bool emergencyLock = true;
bool temperatureConversionPending = false;
unsigned long temperatureConversionStart = 0;
unsigned long lastTemperatureRequest = 0;
// =====================================================
// TEMPERATURE TRIGGER COUNTER
// =====================================================
uint32_t triggerCount = 0;
bool triggerArmed = true;
bool triggerAlreadyCounted = false;
float triggerStartTemperature = 0.0;
unsigned long triggerStartTime = 0;
bool eepromDirty = false;
unsigned long lastEepromSave = 0;
// =====================================================
// POTENTIOMETERS
// =====================================================
int lastPulseRaw = -1000;
int lastCycleRaw = -1000;
// =====================================================
// DISPLAY
// =====================================================
unsigned long lastDisplayUpdate = 0;
// =====================================================
// SOUND ENGINE
// =====================================================
enum SoundType {
SOUND_NONE,
SOUND_STARTUP,
SOUND_BUTTON,
SOUND_STOP,
SOUND_OVERHEAT,
SOUND_ERROR
};
SoundType currentSound = SOUND_NONE;
byte soundStep = 0;
unsigned long soundStepStart = 0;
bool soundPlaying = false;
struct SoundNote {
int frequency;
unsigned long duration;
unsigned long pause;
};
const SoundNote startupSound[] = {
{523, 100, 25},
{659, 100, 25},
{784, 100, 25},
{1047, 180, 40}
};
const byte startupSoundLength =
sizeof(startupSound) / sizeof(startupSound[0]);
const SoundNote buttonSound[] = {
{880, 70, 30},
{1175, 110, 40}
};
const byte buttonSoundLength =
sizeof(buttonSound) / sizeof(buttonSound[0]);
const SoundNote stopSound[] = {
{988, 70, 30},
{784, 100, 30},
{523, 160, 50}
};
const byte stopSoundLength =
sizeof(stopSound) / sizeof(stopSound[0]);
const SoundNote overheatSound[] = {
{988, 120, 40},
{784, 120, 40},
{523, 200, 60},
{988, 120, 40},
{523, 250, 80}
};
const byte overheatSoundLength =
sizeof(overheatSound) / sizeof(overheatSound[0]);
const SoundNote errorSound[] = {
{330, 140, 50},
{330, 140, 50},
{220, 220, 80}
};
const byte errorSoundLength =
sizeof(errorSound) / sizeof(errorSound[0]);
// =====================================================
// GET SOUND NOTES
// =====================================================
const SoundNote* getSoundNotes(byte &length) {
switch (currentSound) {
case SOUND_STARTUP:
length = startupSoundLength;
return startupSound;
case SOUND_BUTTON:
length = buttonSoundLength;
return buttonSound;
case SOUND_STOP:
length = stopSoundLength;
return stopSound;
case SOUND_OVERHEAT:
length = overheatSoundLength;
return overheatSound;
case SOUND_ERROR:
length = errorSoundLength;
return errorSound;
default:
length = 0;
return nullptr;
}
}
// =====================================================
// PLAY SOUND
// =====================================================
void playSound(SoundType sound) {
noTone(BUZZER_PIN);
currentSound = sound;
soundStep = 0;
soundStepStart = millis();
soundPlaying = true;
}
// =====================================================
// UPDATE SOUND
// =====================================================
void updateSound() {
if (!soundPlaying) return;
byte length = 0;
const SoundNote* notes = getSoundNotes(length);
if (notes == nullptr || length == 0) {
soundPlaying = false;
noTone(BUZZER_PIN);
currentSound = SOUND_NONE;
return;
}
if (soundStep >= length) {
soundPlaying = false;
noTone(BUZZER_PIN);
currentSound = SOUND_NONE;
return;
}
const SoundNote ¬e = notes[soundStep];
unsigned long elapsed = millis() - soundStepStart;
unsigned long totalTime = note.duration + note.pause;
if (elapsed < note.duration) {
if (elapsed < 5) {
tone(BUZZER_PIN, note.frequency);
}
} else {
noTone(BUZZER_PIN);
if (elapsed >= totalTime) {
soundStep++;
soundStepStart = millis();
if (soundStep < length) {
tone(BUZZER_PIN, notes[soundStep].frequency);
}
}
}
}
// =====================================================
// ALL INJECTORS OFF
// =====================================================
void allInjectorsOff() {
for (byte i = 0; i < 4; i++) {
digitalWrite(injectorPins[i], LOW);
}
}
// =====================================================
// STOP INJECTORS
// =====================================================
void stopInjectorTest(bool emergency) {
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
currentInjector = 0;
currentCycle = 0;
if (emergency) {
emergencyLock = true;
playSound(SOUND_STOP);
}
}
// =====================================================
// EEPROM LOAD
// =====================================================
void loadTriggerCounter() {
uint32_t magic = 0;
EEPROM.get(EEPROM_MAGIC_ADDR, magic);
if (magic != EEPROM_MAGIC) {
triggerCount = 0;
EEPROM.put(EEPROM_MAGIC_ADDR, EEPROM_MAGIC);
EEPROM.put(EEPROM_COUNTER_ADDR, triggerCount);
} else {
EEPROM.get(EEPROM_COUNTER_ADDR, triggerCount);
}
eepromDirty = false;
lastEepromSave = millis();
}
// =====================================================
// EEPROM SAVE — THROTTLED
// =====================================================
void saveTriggerCounterIfNeeded() {
if (!eepromDirty) return;
if (millis() - lastEepromSave < EEPROM_SAVE_INTERVAL) {
return;
}
EEPROM.put(EEPROM_COUNTER_ADDR, triggerCount);
eepromDirty = false;
lastEepromSave = millis();
}
// =====================================================
// RESET TRIGGER COUNTER
// =====================================================
void resetTriggerCounter() {
triggerCount = 0;
EEPROM.put(EEPROM_COUNTER_ADDR, triggerCount);
eepromDirty = false;
lastEepromSave = millis();
}
// =====================================================
// READ FILTERED POTENTIOMETER
// =====================================================
int readFilteredPot(byte pin, int &lastRaw) {
long sum = 0;
for (byte i = 0; i < POT_SAMPLES; i++) {
sum += analogRead(pin);
}
int raw = sum / POT_SAMPLES;
if (raw <= 3) raw = 0;
if (raw >= 1020) raw = 1023;
if (lastRaw >= 0 && abs(raw - lastRaw) < 3) {
raw = lastRaw;
}
lastRaw = raw;
return raw;
}
// =====================================================
// UPDATE POTENTIOMETERS
// =====================================================
void updatePots() {
int rawPulse = readFilteredPot(PULSE_POT_PIN, lastPulseRaw);
int rawCycles = readFilteredPot(CYCLE_POT_PIN, lastCycleRaw);
pulseMs = map(rawPulse, 0, 1023, MIN_PULSE_MS, MAX_PULSE_MS);
cycleCount = map(rawCycles, 0, 1023, MIN_CYCLES, MAX_CYCLES);
pulseMs = constrain(pulseMs, MIN_PULSE_MS, MAX_PULSE_MS);
cycleCount = constrain(cycleCount, MIN_CYCLES, MAX_CYCLES);
}
// =====================================================
// START INJECTOR TEST
// =====================================================
void startInjectorTest() {
if (temperatureFault || overheatLock || emergencyLock) return;
if (!temperatureValid || temperature >= OVERHEAT_TEMP) {
if (temperatureValid && temperature >= OVERHEAT_TEMP) {
overheatLock = true;
emergencyLock = true;
playSound(SOUND_OVERHEAT);
}
return;
}
injectorRunning = true;
continuousMode = false;
currentInjector = 0;
currentCycle = 1;
injectorStartTime = micros();
digitalWrite(injectorPins[currentInjector], HIGH);
playSound(SOUND_BUTTON);
}
// =====================================================
// START CONTINUOUS TEST
// =====================================================
void startContinuousTest() {
if (temperatureFault || overheatLock || emergencyLock) return;
if (!temperatureValid || temperature >= OVERHEAT_TEMP) {
if (temperatureValid && temperature >= OVERHEAT_TEMP) {
overheatLock = true;
emergencyLock = true;
playSound(SOUND_OVERHEAT);
}
return;
}
injectorRunning = true;
continuousMode = true;
currentInjector = 0;
currentCycle = 1;
injectorStartTime = micros();
digitalWrite(injectorPins[currentInjector], HIGH);
playSound(SOUND_BUTTON);
}
// =====================================================
// UPDATE INJECTORS
// =====================================================
void updateInjectors() {
if (!injectorRunning) return;
if (temperatureFault || overheatLock || emergencyLock) {
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
currentInjector = 0;
currentCycle = 0;
return;
}
if (temperature >= OVERHEAT_TEMP) {
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
currentInjector = 0;
currentCycle = 0;
overheatLock = true;
emergencyLock = true;
playSound(SOUND_OVERHEAT);
return;
}
unsigned long elapsed = micros() - injectorStartTime;
if (elapsed < (unsigned long)pulseMs * 1000UL) return;
// Current injector pulse finished
digitalWrite(injectorPins[currentInjector], LOW);
currentInjector++;
// All four injectors completed
if (currentInjector >= 4) {
currentInjector = 0;
if (continuousMode) {
// Continue only if the button is still held
if (digitalRead(CONTINUOUS_BUTTON_PIN) == LOW) {
currentCycle++;
injectorStartTime = micros();
digitalWrite(injectorPins[currentInjector], HIGH);
} else {
injectorRunning = false;
continuousMode = false;
currentCycle = 0;
}
return;
}
if (currentCycle >= cycleCount) {
injectorRunning = false;
currentCycle = 0;
return;
}
currentCycle++;
injectorStartTime = micros();
digitalWrite(injectorPins[currentInjector], HIGH);
return;
}
// Start the next injector
injectorStartTime = micros();
digitalWrite(injectorPins[currentInjector], HIGH);
}
// =====================================================
// TEMPERATURE TRIGGER LOGIC
// =====================================================
void updateTemperatureTrigger() {
unsigned long now = millis();
if (triggerArmed) {
triggerStartTemperature = temperature;
triggerStartTime = now;
triggerArmed = false;
triggerAlreadyCounted = false;
return;
}
float rise = temperature - triggerStartTemperature;
unsigned long elapsed = now - triggerStartTime;
// Count once if temperature rises by strictly MORE than 5 C
// within the 2-second observation window.
if (!triggerAlreadyCounted &&
rise > TRIGGER_TEMP_RISE &&
elapsed <= TRIGGER_WINDOW) {
triggerCount++;
triggerAlreadyCounted = true;
eepromDirty = true;
}
// Rearm only after cooling at least 1 C below the baseline.
if (temperature <= triggerStartTemperature - 1.0) {
triggerArmed = true;
triggerAlreadyCounted = false;
return;
}
// If no trigger occurred, begin a fresh observation window.
if (elapsed > TRIGGER_WINDOW && !triggerAlreadyCounted) {
triggerStartTemperature = temperature;
triggerStartTime = now;
}
}
// =====================================================
// UPDATE TEMPERATURE — ASYNCHRONOUS
// =====================================================
void updateTemperature() {
unsigned long now = millis();
// Start conversion when none is pending.
if (!temperatureConversionPending) {
if (now - lastTemperatureRequest >= TEMP_READ_INTERVAL) {
sensors.requestTemperatures();
temperatureConversionPending = true;
temperatureConversionStart = now;
lastTemperatureRequest = now;
}
return;
}
// Wait without blocking the main loop.
if (now - temperatureConversionStart < TEMP_CONVERSION_TIME) {
return;
}
temperatureConversionPending = false;
float newTemperature = sensors.getTempCByIndex(0);
if (newTemperature == DEVICE_DISCONNECTED_C ||
newTemperature < -55.0 ||
newTemperature > 125.0) {
temperatureValid = false;
temperatureFault = true;
emergencyLock = true;
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
currentInjector = 0;
currentCycle = 0;
static bool errorAlreadyReported = false;
if (!errorAlreadyReported) {
playSound(SOUND_ERROR);
errorAlreadyReported = true;
}
return;
}
temperature = newTemperature;
temperatureValid = true;
temperatureFault = false;
// Reset the one-shot error notification after recovery.
static bool errorAlreadyReported = false;
errorAlreadyReported = false;
// Overheat protection
if (temperature >= OVERHEAT_TEMP) {
if (!overheatLock) {
overheatLock = true;
emergencyLock = true;
allInjectorsOff();
injectorRunning = false;
continuousMode = false;
currentInjector = 0;
currentCycle = 0;
playSound(SOUND_OVERHEAT);
}
updateTemperatureTrigger();
return;
}
// Release the temperature lock after sufficient cooling.
// Emergency lock remains until manual acknowledgement.
if (overheatLock && temperature <= COOL_DOWN_TEMP) {
overheatLock = false;
}
updateTemperatureTrigger();
}
// =====================================================
// BUTTON DEBOUNCE
// Returns true after a stable LOW has lasted long enough.
// =====================================================
bool buttonPressed(byte pin,
bool &lastState,
unsigned long &lastChange) {
bool state = digitalRead(pin);
if (state != lastState) {
lastChange = millis();
lastState = state;
}
return state == LOW &&
millis() - lastChange >= DEBOUNCE_TIME;
}
// =====================================================
// START BUTTON
// =====================================================
void updateStartButton() {
static bool handled = false;
bool pressed = buttonPressed(
START_BUTTON_PIN, lastStartState, lastStartChange
);
if (!pressed) {
handled = false;
return;
}
if (handled) return;
handled = true;
// Do not acknowledge an emergency while a fault persists.
if (temperatureFault || overheatLock) {
playSound(SOUND_ERROR);
return;
}
if (emergencyLock) {
emergencyLock = false;
playSound(SOUND_BUTTON);
return;
}
if (injectorRunning) {
stopInjectorTest(true);
return;
}
startInjectorTest();
}
// =====================================================
// CONTINUOUS BUTTON
// =====================================================
void updateContinuousButton() {
bool pressed = buttonPressed(
CONTINUOUS_BUTTON_PIN,
lastContinuousState,
lastContinuousChange
);
static bool wasPressed = false;
if (pressed && !wasPressed) {
wasPressed = true;
if (!injectorRunning) {
startContinuousTest();
}
}
if (!pressed && wasPressed) {
wasPressed = false;
// Do not stop immediately. The active 4-injector cycle
// is allowed to finish by updateInjectors().
}
}
// =====================================================
// R1 BUTTON
// =====================================================
void updateRelay1() {
bool pressed = buttonPressed(
RELAY1_BUTTON_PIN, lastR1State, lastR1Change
);
static bool wasPressed = false;
if (pressed && !wasPressed) {
wasPressed = true;
if (!injectorRunning && !emergencyLock &&
!temperatureFault && !overheatLock) {
relay1Active = true;
relay1StartTime = millis();
digitalWrite(RELAY1_PIN, HIGH);
}
}
if (!pressed) {
wasPressed = false;
}
if (relay1Active &&
millis() - relay1StartTime >= R1_TIME) {
relay1Active = false;
digitalWrite(RELAY1_PIN, LOW);
}
}
// =====================================================
// R2 BUTTON — ACTIVE WHILE HELD
// =====================================================
void updateRelay2() {
bool pressed = buttonPressed(
RELAY2_BUTTON_PIN, lastR2State, lastR2Change
);
relay2Active = pressed;
digitalWrite(RELAY2_PIN, relay2Active ? HIGH : LOW);
}
// =====================================================
// OLED STATUS
// =====================================================
void printStatus() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// Line 1
display.setCursor(0, 0);
display.print(F("PULSE:"));
display.print(pulseMs);
display.print(F("ms"));
display.setCursor(70, 0);
display.print(F("N:"));
display.print(cycleCount);
// Temperature
display.setCursor(0, 11);
display.print(F("TEMP:"));
if (temperatureFault || !temperatureValid) {
display.print(F("ERROR"));
} else {
display.print(temperature, 1);
display.print(F(" C"));
}
// Main status
display.setCursor(0, 23);
if (temperatureFault || !temperatureValid) {
display.print(F("SENSOR ERROR"));
} else if (overheatLock) {
display.print(F("OVERHEAT"));
} else if (emergencyLock) {
display.print(F("EMERGENCY LOCK"));
} else if (injectorRunning) {
display.print(continuousMode ? F("CONTINUOUS") : F("RUN"));
} else {
display.print(F("READY"));
}
// Injector / cycle
display.setCursor(0, 35);
if (injectorRunning) {
display.print(F("INJ:"));
display.print(currentInjector + 1);
display.setCursor(58, 35);
display.print(F("CYCLE:"));
display.print(currentCycle);
if (!continuousMode) {
display.print('/');
display.print(cycleCount);
}
} else {
display.print(F("INJ:OFF"));
}
// Relays
display.setCursor(0, 47);
display.print(F("R1:"));
if (relay1Active) {
unsigned long elapsed = millis() - relay1StartTime;
long remaining = (long)((R1_TIME - elapsed + 999) / 1000);
if (remaining < 0) remaining = 0;
display.print(remaining);
display.print('s');
} else {
display.print(F("OFF"));
}
display.setCursor(65, 47);
display.print(F("R2:"));
display.print(relay2Active ? F("ON") : F("OFF"));
// Counter — shifted up to fit the 64-pixel screen
display.setCursor(0, 56);
display.print(F("TRIG:"));
display.print(triggerCount);
display.display();
}
// =====================================================
// STARTUP SCREEN
// =====================================================
void startupScreen() {
unsigned long startTime = millis();
while (millis() - startTime < 5000UL) {
unsigned long elapsed = millis() - startTime;
int percent = (elapsed * 100UL) / 5000UL;
percent = constrain(percent, 0, 100);
int barWidth = map(percent, 0, 100, 0, 108);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(30, 8);
display.print(F("INJECTOR TEST"));
display.setCursor(45, 22);
display.print(F("STARTING..."));
display.drawRect(10, 38, 108, 10, SSD1306_WHITE);
if (barWidth > 0) {
display.fillRect(12, 40, barWidth - 2, 6, SSD1306_WHITE);
}
display.setCursor(52, 52);
display.print(percent);
display.print('%');
display.display();
delay(20);
}
playSound(SOUND_STARTUP);
}
// =====================================================
// SETUP
// =====================================================
void setup() {
// Injector outputs
pinMode(INJ1_PIN, OUTPUT);
pinMode(INJ2_PIN, OUTPUT);
pinMode(INJ3_PIN, OUTPUT);
pinMode(INJ4_PIN, OUTPUT);
allInjectorsOff();
// Relay outputs
pinMode(RELAY1_PIN, OUTPUT);
pinMode(RELAY2_PIN, OUTPUT);
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, LOW);
// Buttons
pinMode(START_BUTTON_PIN, INPUT_PULLUP);
pinMode(RELAY1_BUTTON_PIN, INPUT_PULLUP);
pinMode(RELAY2_BUTTON_PIN, INPUT_PULLUP);
pinMode(CONTINUOUS_BUTTON_PIN, INPUT_PULLUP);
// Buzzer
pinMode(BUZZER_PIN, OUTPUT);
noTone(BUZZER_PIN);
// OLED
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
// If the OLED fails, keep outputs in a safe state.
allInjectorsOff();
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, LOW);
while (true) {
noTone(BUZZER_PIN);
}
}
display.clearDisplay();
display.display();
// Temperature sensor — non-blocking conversion
sensors.begin();
sensors.setResolution(12);
sensors.setWaitForConversion(false);
// EEPROM counter
loadTriggerCounter();
// Hold R1 + R2 during power-on to reset TRIG.
delay(100);
if (digitalRead(RELAY1_BUTTON_PIN) == LOW &&
digitalRead(RELAY2_BUTTON_PIN) == LOW) {
resetTriggerCounter();
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 25);
display.print(F("TRIGGER RESET"));
display.display();
while (digitalRead(RELAY1_BUTTON_PIN) == LOW ||
digitalRead(RELAY2_BUTTON_PIN) == LOW) {
delay(10);
}
delay(800);
}
// Request the first temperature conversion.
sensors.requestTemperatures();
temperatureConversionPending = true;
temperatureConversionStart = millis();
lastTemperatureRequest = millis();
// Stay locked until a valid temperature has been read
// and the operator acknowledges readiness with START.
temperatureValid = false;
temperatureFault = true;
emergencyLock = true;
startupScreen();
}
// =====================================================
// LOOP
// =====================================================
void loop() {
updateSound();
updateTemperature();
updatePots();
updateStartButton();
updateContinuousButton();
updateRelay1();
updateRelay2();
updateInjectors();
saveTriggerCounterIfNeeded();
if (millis() - lastDisplayUpdate >= DISPLAY_INTERVAL) {
lastDisplayUpdate = millis();
printStatus();
}
}