// @libraries https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library/archive/master.zip
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// === Pin Definitions ===
namespace Pin {
constexpr uint8_t BUTTON_START = 16;
constexpr uint8_t BUTTON_STOP = 17;
constexpr uint8_t MAGNETRON_CONTROL = 22;
constexpr uint8_t ROTATOR_MOTOR = 15;
constexpr uint8_t ENCODER_CLK = 18;
constexpr uint8_t ENCODER_DT = 19;
constexpr uint8_t ENCODER_SW = 20;
};
// === System Constants ===
namespace Constants {
constexpr uint8_t POWER_LEVELS[] = {100, 80, 60, 40, 20};
constexpr size_t NUM_POWER_LEVELS = sizeof(POWER_LEVELS);
constexpr uint16_t CYCLE_DURATION_MS = 10000;
constexpr uint16_t TIME_STEP = 10;
constexpr uint16_t DEBOUNCE_DELAY_MS = 20; // For SW only
constexpr uint16_t LCD_REFRESH_INTERVAL_MS = 500; // Increased to reduce refreshes
constexpr uint32_t TICK_INTERVAL_MS = 1000;
const char VERSION[] PROGMEM = "v4.1";
};
// === Microwave Class ===
class Microwave {
private:
// States
uint32_t cookingTime = 0;
bool isCooking = false;
bool isPaused = false;
uint8_t currentPowerLevelIndex = 0;
// Timing
uint32_t cookStartTime = 0;
uint32_t currentCycleStartTime = 0;
bool magnetronIsOn = false;
uint32_t lastTickTime = 0;
uint32_t lastLcdUpdateTime = 0;
// LCD
LiquidCrystal_I2C lcd{0x27, 16, 2}; // Use 0x3F if 0x27 doesn't work
// Display States
uint32_t lastDisplayedTime = -1; // Initialize with a value that guarantees initial update
uint8_t lastDisplayedPowerIndex = -1; // Initialize with a value that guarantees initial update
bool lastDisplayedCooking = false;
bool lastDisplayedPaused = false;
bool lcdNeedsUpdate = true;
// Input States
struct InputState {
bool lastState = HIGH;
uint32_t lastChangeTime = 0;
};
InputState startButton, stopButton, encoderSW;
// Encoder State
int8_t lastClkState = HIGH;
// Private Methods
bool debounceInput(InputState& state, bool currentState) {
uint32_t now = millis();
if (currentState != state.lastState) {
state.lastChangeTime = now;
state.lastState = currentState;
}
return (now - state.lastChangeTime) > Constants::DEBOUNCE_DELAY_MS && currentState == LOW;
}
void manageMagnetronCycling() {
if (!isCooking) return;
uint32_t now = millis();
uint8_t power = Constants::POWER_LEVELS[currentPowerLevelIndex];
if (power == 100) {
if (!magnetronIsOn) {
digitalWrite(Pin::MAGNETRON_CONTROL, HIGH);
magnetronIsOn = true;
}
return;
}
if (power == 0) {
if (magnetronIsOn) {
digitalWrite(Pin::MAGNETRON_CONTROL, LOW);
magnetronIsOn = false;
}
return;
}
uint32_t onTime = (Constants::CYCLE_DURATION_MS * power) / 100;
uint32_t elapsed = now - currentCycleStartTime;
if (magnetronIsOn && elapsed >= onTime) {
digitalWrite(Pin::MAGNETRON_CONTROL, LOW);
magnetronIsOn = false;
currentCycleStartTime = now;
} else if (!magnetronIsOn && elapsed >= (Constants::CYCLE_DURATION_MS - onTime)) {
digitalWrite(Pin::MAGNETRON_CONTROL, HIGH);
magnetronIsOn = true;
currentCycleStartTime = now;
}
}
void updateTimeDisplay() {
// Only update if time has changed or LCD needs a full update
if (cookingTime == lastDisplayedTime && !lcdNeedsUpdate) return;
char buffer[8];
snprintf(buffer, sizeof(buffer), "T:%02u:%02u", cookingTime / 60, cookingTime % 60);
lcd.setCursor(0, 0);
lcd.print(buffer);
lcd.print(" "); // Clear residual characters
lastDisplayedTime = cookingTime;
}
void updatePowerDisplay() {
// Only update if power level has changed or LCD needs a full update
if (currentPowerLevelIndex == lastDisplayedPowerIndex && !lcdNeedsUpdate) return;
char buffer[6];
snprintf(buffer, sizeof(buffer), "P:%3u%%", Constants::POWER_LEVELS[currentPowerLevelIndex]);
lcd.setCursor(10, 0);
lcd.print(buffer);
lastDisplayedPowerIndex = currentPowerLevelIndex;
}
void updateStatusDisplay() {
// Only update if status has changed or LCD needs a full update
if (isCooking == lastDisplayedCooking && isPaused == lastDisplayedPaused &&
(cookingTime == lastDisplayedTime) && !lcdNeedsUpdate) return;
lcd.setCursor(0, 1);
if (isCooking) {
lcd.print(F("Cocinando "));
} else if (isPaused) {
lcd.print(F("Pausa "));
} else if (cookingTime == 0) {
// Check if it's the initial state (not cooking, not paused, time is 0)
bool isInitialState = (!isCooking && !isPaused && cookingTime == 0);
// Use a flag or check for initial state conditions if needed more robustly
// For simplicity, assuming if not cooking/paused and time is 0, it's either initial or finished
if (!lastDisplayedCooking && !lastDisplayedPaused && lastDisplayedTime == 0 && cookingTime == 0) {
lcd.print(F("Set Tiempo y Pot"));
} else {
lcd.print(F("Listo "));
}
} else {
lcd.print(F("Set Tiempo "));
}
lastDisplayedCooking = isCooking;
lastDisplayedPaused = isPaused;
// lastDisplayedTime is updated in updateTimeDisplay
}
public:
void begin() {
Serial.begin(115200);
Serial.println(F("Microwave initializing..."));
// Initialize pins
pinMode(Pin::BUTTON_START, INPUT_PULLUP);
pinMode(Pin::BUTTON_STOP, INPUT_PULLUP);
pinMode(Pin::ENCODER_CLK, INPUT_PULLUP);
pinMode(Pin::ENCODER_DT, INPUT_PULLUP);
pinMode(Pin::ENCODER_SW, INPUT_PULLUP);
pinMode(Pin::MAGNETRON_CONTROL, OUTPUT);
pinMode(Pin::ROTATOR_MOTOR, OUTPUT);
digitalWrite(Pin::MAGNETRON_CONTROL, LOW);
digitalWrite(Pin::ROTATOR_MOTOR, LOW);
// Initialize LCD
Wire.begin();
Serial.println(F("Initializing LCD..."));
lcd.begin(16, 2);
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Microondas "));
lcd.print(PSTR(Constants::VERSION));
lcd.setCursor(0, 1);
lcd.print(F("Starting..."));
Serial.println(F("LCD initialized"));
// Reduced delay for welcome message
delay(1000); // Reduced from 2000ms
// Clear LCD and show initial state for time and power selection
lcd.clear();
lastClkState = digitalRead(Pin::ENCODER_CLK);
lastTickTime = millis();
lcdNeedsUpdate = true; // Ensure initial display update happens
updateDisplay(true); // Force initial display update
}
void loop() {
// Handle 1-second tick
uint32_t now = millis();
if (now - lastTickTime >= Constants::TICK_INTERVAL_MS) {
if (isCooking && cookingTime > 0) {
cookingTime--;
lcdNeedsUpdate = true; // Mark for update when time changes
if (cookingTime == 0) {
stopCooking();
// The stopCooking() call sets lcdNeedsUpdate = true, which will trigger
// the "Listo" message or the initial "Set Tiempo y Pot" message if time is reset.
Serial.println(F("Cooking finished!"));
}
}
lastTickTime = now;
}
// Handle buttons
if (debounceInput(startButton, digitalRead(Pin::BUTTON_START))) {
startCooking();
}
if (debounceInput(stopButton, digitalRead(Pin::BUTTON_STOP))) {
stopCooking();
}
readRotaryEncoder();
manageCookingProcess();
updateDisplay();
}
void readRotaryEncoder() {
int clk = digitalRead(Pin::ENCODER_CLK);
int dt = digitalRead(Pin::ENCODER_DT);
// Process falling edge on CLK (no debouncing, as in original)
if (clk == LOW && lastClkState == HIGH) {
// Double-read DT to stabilize Wokwi simulation
dt = digitalRead(Pin::ENCODER_DT);
// Re-read DT
delayMicroseconds(100); // Brief delay for stability
dt = digitalRead(Pin::ENCODER_DT); // Confirm DT
if (dt == LOW) {
// Clockwise rotation
adjustTime(true);
} else {
// Counter-clockwise rotation
adjustTime(false);
}
lcdNeedsUpdate = true; // Mark for update after encoder action
}
lastClkState = clk;
// Process switch
if (debounceInput(encoderSW, digitalRead(Pin::ENCODER_SW))) {
Serial.println(F("Encoder SW pressed"));
changePower();
lcdNeedsUpdate = true; // Mark for update after encoder action
}
}
void updateDisplay(bool force = false) {
if (!force && !lcdNeedsUpdate &&
(millis() - lastLcdUpdateTime < Constants::LCD_REFRESH_INTERVAL_MS)) {
return;
}
lastLcdUpdateTime = millis();
lcdNeedsUpdate = false; // Reset the flag after updating
updateTimeDisplay();
updatePowerDisplay();
updateStatusDisplay();
}
void startCooking() {
if (cookingTime == 0) {
lcd.setCursor(0, 1);
lcd.print(F("Set Tiempo ")); // Indicate that time needs to be set
lcdNeedsUpdate = false; // Avoid unnecessary full refresh
// Keep last displayed state as is
return;
}
if (!isCooking) {
isCooking = true;
isPaused = false;
cookStartTime = millis();
currentCycleStartTime = millis();
magnetronIsOn = true; // Start with magnetron on for the first cycle
digitalWrite(Pin::MAGNETRON_CONTROL, HIGH);
digitalWrite(Pin::ROTATOR_MOTOR, HIGH);
lcdNeedsUpdate = true; // Status changed, requires display update
Serial.println(F("Cooking started"));
}
}
void pauseCooking() {
if (isCooking) {
isCooking = false;
isPaused = true;
digitalWrite(Pin::MAGNETRON_CONTROL, LOW);
digitalWrite(Pin::ROTATOR_MOTOR, LOW);
lcdNeedsUpdate = true; // Status changed, requires display update
Serial.println(F("Cooking paused"));
}
}
void stopCooking() {
if (isCooking || isPaused) {
isCooking = false;
isPaused = false;
cookingTime = 0; // Reset time on stop
digitalWrite(Pin::MAGNETRON_CONTROL, LOW);
digitalWrite(Pin::ROTATOR_MOTOR, LOW);
magnetronIsOn = false;
Serial.println(F("Cooking stopped"));
} else if (cookingTime > 0) {
// If not cooking/paused but time is set, stop button resets time
cookingTime = 0;
Serial.println(F("Time reset"));
}
lcdNeedsUpdate = true; // Status or time changed, requires display update
}
void adjustTime(bool increase) {
if (isCooking || isPaused) {
Serial.println(F("Cannot adjust time: Cooking or Paused"));
return;
}
uint32_t prevTime = cookingTime;
cookingTime = increase ?
min(cookingTime + Constants::TIME_STEP, 3600U) : // Max 60 minutes
(cookingTime >= Constants::TIME_STEP ? cookingTime - Constants::TIME_STEP : 0);
lcdNeedsUpdate = true; // Time changed, requires display update
Serial.print(F("Time adjusted: ")); Serial.print(prevTime);
Serial.print(F(" -> ")); Serial.println(cookingTime);
}
void changePower() {
currentPowerLevelIndex = (currentPowerLevelIndex + 1) % Constants::NUM_POWER_LEVELS;
lcdNeedsUpdate = true; // Power changed, requires display update
if (isCooking) {
// If cooking, reset cycle time to apply new power level immediately
currentCycleStartTime = millis();
// Re-evaluate magnetron state based on new power level and cycle time
manageMagnetronCycling();
}
Serial.print(F("Power changed: "));
Serial.println(Constants::POWER_LEVELS[currentPowerLevelIndex]);
}
void manageCookingProcess() {
if (isCooking) manageMagnetronCycling();
}
};
// === Global Instances ===
Microwave microwave;
// === Arduino Main Functions ===
void setup() {
microwave.begin();
}
void loop() {
microwave.loop();
delay(5); // Small delay to prevent watchdog timer issues and reduce loop speed
}Magnetron
Motor Plato