daniel23
Published © LGPL

Digital countdown timer on mains Outlet

This timer controls the main power outlet. The operating time is adjustable from 1 second to 24 hours with 3 buttons.

AdvancedFull instructions provided3 hours80
Digital countdown timer on mains Outlet

Things used in this project

Hardware components

Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
CN3903 3A Mini DC-DC Step Down 5V-30V
×1
zener 24V
×1
General Purpose Transistor NPN
General Purpose Transistor NPN
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
×3
Resistor 150 ohm 1/2W
×2
Capacitor X2 0.22
×1
Electrolytic Capacitor, 220 µF, 35 V
×1
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
×5
Resistor 1k ohm
Resistor 1k ohm
×5
Through Hole Resistor, 4.7 kohm
Through Hole Resistor, 4.7 kohm
×1
Through Hole Resistor, 27 kohm
Through Hole Resistor, 27 kohm
×1
Multilayer Ceramic Capacitor, 22000 pF
Multilayer Ceramic Capacitor, 22000 pF
×3
Capacitor 100 nF
Capacitor 100 nF
×1
Capacitor 100 µF
Capacitor 100 µF
×1
Monochrome 0.91”128x32 I2C OLED Display with Chip Pad
DFRobot Monochrome 0.91”128x32 I2C OLED Display with Chip Pad
×1
LED bicolor R+G
×1

Story

Read more

Schematics

Schematic

Code

Minuterie_EN.ino

Arduino
Compile on arduino IDE 2.3.2 and burn on arduino pro-mini or nano
//        *****************************************************
//        *   Digital countdown timer on mains Outlet         *
//        *   with relay output                               *
//        *                                                   *
//        *    Daniel Engel                    11/07/2024     *
//        *                                                   *
//        *   Translated to english            15/07/2024     *
//        *****************************************************
//
/*
   Many thanks to Oliver Kraus for his U8glib library
      https://github.com/olikraus/U8glib_Arduino
*/


#include <Arduino.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include "U8glib.h"

U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);

// Pin definitions
#define buttonIncrementPin 7
#define buttonDecrementPin 8
#define buttonOnOff 9
#define relayOutput 10
#define frequencyMeasureOutput 12  // Used to measure the exact frequency of the 1Hz clock
#define OLEDTimeout 30000          // After this time (ms), the OLED becomes dark

// Timing constants
const long timeVal_1 = 7200;       // 2h   (button + and -) => Power on default duration
const long timeVal_2 = 1800;       // 30mn (button ON and -)
const long timeVal_3 = 36000;      // 10h  (button ON and +)
const long timeValMax = 86400;     // maximal countdown value (24h in seconds)
const int debounce = 2;            // could be increased if key bouces are not enough filtered
                                   // but this should not arose: they are already filtered by capacitors
const int SHORT_PRESS_TIME = 500;  // If longer will considered as long press (in milliseconds)
const int loopDelay = 200;         // Loop duration for comfortable time setting (in ms)
const int incrementCnt = 10;       // Number of consecutive increments before increment increase
const int maxIncrement = 900;      // Corresponds to 15mn time increase each 200ms while the + button is pressed

// Variables
long timeVal = timeVal_1;
int increment = 1;
int nbIncrements = 0;
bool changeFlag = false;  //  or display only if something was changed
bool timerFlag = false;   // This flag is set by timer interrupt each second
bool flagRun = false;     // Enable the output as long the countdown is not elapsedme = 0;
unsigned long lastButtonPressTime = 0;
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
unsigned long previousTime = 0;
unsigned long currentTime = 0;

void setup() {
  // turn off everything we can to save power
  power_adc_disable();
  power_spi_disable();
  // power_twi_disable();  // I2C
  // power_timer0_disable();
  // power_timer1_disable();
  power_timer2_disable();


  pinMode(buttonIncrementPin, INPUT_PULLUP);
  pinMode(buttonDecrementPin, INPUT_PULLUP);
  pinMode(buttonOnOff, INPUT_PULLUP);
  pinMode(relayOutput, OUTPUT);
  // Serial.begin(115200);	// for tests

  u8g.setColorIndex(1);
  u8g.setContrast(0);
  /* You can choose between different fonts to display on the OLED */
  // u8g2.setFont(u8g2_font_ncenB10_tr);
  // u8g2.setFont(u8g2_font_fub35_tn);
  // u8g.setFont(u8g_font_unifont);
  // u8g.setFont(u8g_font_tpssb);
  // u8g.setFont(u8g_font_osb21);
  // u8g.setFont(u8g_font_10x20);
  // u8g.setFont(u8g_font_9x18);
  //u8g.setFont(u8g_font_courR14);
  u8g.setFont(u8g_font_helvR18);  // This seems to me to be the best choice.
  clearOLED();

  // set the timer interrupts - timer 1 is used (16 bits timer)
  noInterrupts();  // disable interrupts
  TCCR1A = 0;
  TCCR1B = 0b00001100;
  TIMSK1 = 0b00000010;
  TCNT1 = 0;
  OCR1A = 62536;  // settings for mine two Arduino boards @ VCC = 4.4V
                  // mesured with HP 55181A frequency counter at pin "frequencyMeasureOutput"
  /* 
  The formula is: [OCR2A=(Crystal frequency/(prescaler*ISR-calling-frequency))-1 ]
     =>   OCR2A = (16.000.000 / (256 * 1 )) -1 = 62499 (theoretical value)
  */
  interrupts();  // enable interrupts
}

void loop() {
  handleButtonInputs();
  handleOnOffButton();
  updateRelayOutput();
  manageOLEDTimeout();

  if (changeFlag) {
    printTimerVal(timeVal);
    if (!digitalRead(buttonOnOff) || digitalRead(!buttonIncrementPin) || digitalRead(!buttonDecrementPin)) {
      previousTime = millis();  // to reset "clearOLED" time
    }
  }
  delay(loopDelay);
}

void handleButtonInputs() {
  if (digitalRead(buttonIncrementPin) == HIGH && digitalRead(buttonDecrementPin) == HIGH) {
    nbIncrements = 0;
    increment = 1;
    changeFlag = false;
  }
  if (digitalRead(buttonIncrementPin) == LOW && digitalRead(buttonDecrementPin) == HIGH) {
    calculateVal(true);
    changeFlag = true;
  }
  if (digitalRead(buttonDecrementPin) == LOW && digitalRead(buttonIncrementPin) == HIGH) {
    calculateVal(false);
    changeFlag = true;
  }
  handleSimultaneousPress();
}

void handleSimultaneousPress() {
  if (digitalRead(buttonIncrementPin) == LOW && digitalRead(buttonDecrementPin) == LOW) {
    timeVal = timeVal_1;
    changeFlag = true;
  } else if (digitalRead(buttonOnOff) == LOW && digitalRead(buttonDecrementPin) == LOW) {
    timeVal = timeVal_2;
    changeFlag = true;
  } else if (digitalRead(buttonOnOff) == LOW && digitalRead(buttonIncrementPin) == LOW) {
    timeVal = timeVal_3;
    changeFlag = true;
  }
}

void handleOnOffButton() {
  static bool buttonStateOnOffPrec = HIGH;  // Previous button state
  bool buttonStateOnOff = digitalRead(buttonOnOff);

  if (buttonStateOnOffPrec == HIGH && buttonStateOnOff == LOW) {
    pressedTime = millis();
  } else if (buttonStateOnOffPrec == LOW && buttonStateOnOff == HIGH) {
    releasedTime = millis();
    long pressDuration = releasedTime - pressedTime;
    if (pressDuration < SHORT_PRESS_TIME) {
      flagRun = true;
    } else {
      flagRun = false;
    }
  }
  buttonStateOnOffPrec = buttonStateOnOff;
}

void updateRelayOutput() {
  if (timerFlag && flagRun) {
    timeVal--;
    timerFlag = false;
    changeFlag = true;
    previousTime = millis();
  }

  if (timeVal > 0 && flagRun) {
    digitalWrite(relayOutput, HIGH);
  } else {
    digitalWrite(relayOutput, LOW);
    if (flagRun) {
      previousTime = millis();
      flagRun = false;
      timeVal = 0;
    }
  }
  lastButtonPressTime;
}

void manageOLEDTimeout() {
  if (!flagRun) {
    currentTime = millis();
    if (currentTime - previousTime > OLEDTimeout) {  // OLED goes dark after timeout to save its lifespan
      clearOLED();
    }
  }
}

void calculateVal(bool increase) {
  unsigned long currentTime = millis();
  if (currentTime - lastButtonPressTime > debounce) {
    lastButtonPressTime = currentTime;
    nbIncrements++;
    if (nbIncrements >= incrementCnt) {
      increment = min(increment * 5, maxIncrement);
      nbIncrements = 0;
    }
    if (increase) {
      timeVal = min(timeVal + increment, timeValMax);
    } else {
      timeVal = max(timeVal - increment, 1);
    }
  }
}

void printTimerVal(unsigned long x) {
  // Display on OLED in format "hours, minutes, seconds"
  int second = x % 60;
  int minute = (x % 3600) / 60;
  int hour = x / 3600;
  char buffer[20];
  sprintf(buffer, "%3dh %02d:%02d   ", hour, minute, second);

  u8g.firstPage();
  do {
    u8g.setPrintPos(5, 30);
    u8g.print(buffer);
  } while (u8g.nextPage());
  changeFlag = false;
}

void clearOLED() {
  u8g.firstPage();
  do {
  } while (u8g.nextPage());
}

ISR(TIMER1_COMPA_vect) {
  timerFlag = true;
  // Following is only used to measure the exact frequency of the 1Hz clock
  // This line can be commented out if you don't need it
  digitalWrite(frequencyMeasureOutput, !digitalRead(frequencyMeasureOutput));  // toggle state
}

Credits

daniel23
9 projects • 12 followers
Contact

Comments

Please log in or sign up to comment.