Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
John Bradnam
Published © GPL3+

General Purpose Relay Timer

A battery powered timer with a settable OFF time and ON time. Dual output relay connections accessible via screw terminals.

IntermediateFull instructions provided12 hours241
General Purpose Relay Timer

Things used in this project

Hardware components

Microchip ATtiny1614 microprocessor
×1
4-Digit 7-Segment Clock display
0.4in
×2
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
8mm shaft
×4
12mmx12mm tactile switch
Including round button top
×1
Buzzer
Buzzer
Passive type
×1
G6H-2F 5V Relay
×1
SMD components
1 x 2N2222 SOT-23 transistor, 1 x 1N4148 SOD80C diode, 1 x 1k 0805 resistor, 2 x 22k 0805 resistors, 1 x 33k 0805 resistor, 2 x 39k 0805 resistors, 1 x 10uF 0805 capacitor,
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

Relay_Timer_V1.ino

Arduino
/***************************************************************************
 * Relay Timer
 * by John Bradnam
 * 
 * 2022-12-01 jbrad2089@gmail.com
 *  - Created base code for ATtiny1614
 */

/**
 * ATTiny1614 Pins mapped to Ardunio Pins
 *
 *             +--------+
 *         VCC + 1   14 + GND
 * (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
 *       1 PA5 + 3   12 + PA2 9  (MISO)
 * (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
 *       3 PA7 + 5   10 + PA0 11 (UPDI)
 * (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
 * (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
 *             +--------+
 */

#ifdef __AVR_ATtiny1614__
#include <avr/eeprom.h>
#else
#include <EEPROM.h>
#endif

#include <LedControl.h>
#include "Display.h"
#include "Button.h"

#define MAX7219_DATA 2  //PA6
#define MAX7219_CLK 0   //PA4
#define MAX7219_LOAD 1  //PA5

#define SW_START 5      //PB2
#define SWITCHES 8      //PA1
#define RELAY 9         //PA2

#define SPEAKER 4       //PB3
#define BUZZER_PORT PORTB
#define BUZZER_PIN  PIN3_bm
  
//Button definitions
enum buttonEnum { BTN_NONE, BTN_STOP, BTN_START, BTN_DOWN, BTN_UP, BTN_BIG };

//void stopButtonPressed(void);
//void startButtonPressed(void);
void downButtonPressed(void);
void upButtonPressed(void);

Button* startButton;
Button* stopButton;
Button* downButton;
Button* upButton;
Button* bigButton;

//Menu definitions
enum menuEnum { TIMER, OFF_FMT, OFF_MSB, OFF_LSB, ON_FMT, ON_MSB, ON_LSB, LOOP };
enum formatEnum { HH_MM, MM_SS };
enum loopEnum { OFF, ON, DONE };

String menuText[] = { "", "OFF ", "OFF ", "ON  ", "ON  ", "LOOP" };
String formatText[] = { "hhnn", "nnss" };
String loopText[] = { "ON  ", "OFF " };

#define SETUP_FLASH_TIME 200
#define TIMER_FLASH_TIME 500
bool flashState = false;
bool flashTime = false;
bool flashColon = false;
unsigned long pauseDelay = 0;

bool inMenu = true;
menuEnum menuSelect = TIMER;
bool forceMenuUpdate = true;

//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0DAD0BAD
typedef struct {
  uint32_t magic;
  formatEnum offFormat;
  int offMsb;
  int offLsb;
  formatEnum onFormat;
  int onMsb;
  int onLsb;
  loopEnum timerLoop;
} EEPROM_DATA;

volatile EEPROM_DATA ee;       //Current EEPROM settings

volatile loopEnum timerState = OFF;
volatile int timerHours = 0;
volatile int timerMinutes = 0;
volatile int timerSeconds = 0;
volatile bool timerPaused = false;
volatile bool timerChanged = false;
volatile bool timerRelayChange = false;
bool relayIsOn = false;

//-------------------------------------------------------------------------
//Initialise Hardware

void setup() 
{
  readEepromData();
  
  pinMode(SW_START, INPUT_PULLUP);
  pinMode(SWITCHES, INPUT);
  
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, LOW);
  relayIsOn = false;
  
  setupDisplay(MAX7219_DATA,MAX7219_CLK,MAX7219_LOAD);
 
  //Initialise buttons
  startButton = new Button(BTN_START, SWITCHES, 570, 669, false);
  startButton->Repeat(startButtonPressed);
  stopButton = new Button(BTN_STOP, SWITCHES, 670, 750, false);
  stopButton->Repeat(stopButtonPressed);
  downButton = new Button(BTN_DOWN, SWITCHES, 0, 100, false);
  downButton->Repeat(downButtonPressed);
  upButton = new Button(BTN_UP, SWITCHES, 460, 569, false);
  upButton->Repeat(upButtonPressed);
  bigButton = new Button(BTN_BIG, SW_START);

  // Initialize RTC
  while (RTC.STATUS > 0);                           // Wait until registers synchronized
  RTC.PER = 1023;                                   // Set period 1 second
  RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                // 32.768kHz Internal Oscillator  
  RTC.INTCTRL = RTC_OVF_bm;                         // Enable overflow interrupt
  RTC.CTRLA = RTC_PRESCALER_DIV32_gc | RTC_RTCEN_bm;// Prescaler /32 and enable

  timerPaused = true;       //Stop timer
  timerChanged = true;      //Force repaint
  inMenu = true;            //Menu mode
  pauseDelay = millis() + SETUP_FLASH_TIME;         //Used to flash active setting

  //Enable interrupts
  sei();
  
}

//-------------------------------------------------------------------------
// Handle main loop

void loop() 
{
  //No need to test buttons, callback routines will be invoked if pressed
  startButton->Pressed();
  stopButton->Pressed();
  upButton->Pressed();
  downButton->Pressed();

  if (inMenu)
  {
    if (millis() > pauseDelay)
    {
      flashColon = !flashColon;
      pauseDelay = millis() + SETUP_FLASH_TIME;
      forceMenuUpdate = true;  
    }
    
    if (forceMenuUpdate)
    {
      displayMenu();
    }

    if (menuSelect == TIMER && bigButton->Pressed())
    {
      if (ee.offMsb != 0 || ee.offLsb != 0)
      {
        writeEepromData();
        timerState = OFF;
        timerChanged = true;
        if (ee.offFormat == HH_MM)
        {
          timerSeconds = 0;
          timerMinutes = ee.offLsb;
          timerHours = ee.offMsb;
        }
        else
        {
          timerSeconds = ee.offLsb;
          timerMinutes = ee.offMsb;
          timerHours = 0;
        }
        inMenu = false;
        timerPaused = false;
        timerRelayChange = true;
      }
      else if (ee.onMsb != 0 || ee.onLsb != 0)
      {
        writeEepromData();
        timerState = ON;
        timerChanged = true;
        if (ee.onFormat == HH_MM)
        {
          timerSeconds = 0;
          timerMinutes = ee.onLsb;
          timerHours = ee.onMsb;
        }
        else
        {
          timerSeconds = ee.onLsb;
          timerMinutes = ee.onMsb;
          timerHours = 0;
        }
        inMenu = false;
        timerPaused = false;
        timerRelayChange = true;
      }
      else
      {
        displayString("Err ", false, true);
      }
    }
  }
  else
  {
    //Handle coundown
    processTimer();

    if ((timerPaused || timerState == DONE) && bigButton->Pressed())
    {
      timerPaused = true;       //Stop timer
      timerChanged = true;      //Force repaint
      inMenu = true;            //Menu mode
      timerRelayChange = true;  //Force relay change
      pauseDelay = millis() + SETUP_FLASH_TIME;         //Used to flash active setting
    }
  }

  if (timerRelayChange)
  {
    bool on = (!inMenu && (timerState == ON));
    if (relayIsOn != on)
    {
      digitalWrite(RELAY, (on) ? HIGH : LOW);
      relayIsOn = on;
      beepRelay();
    }
    timerRelayChange = false;
  }
  
  delay(100);
}

//-------------------------------------------------------------------------
// In timer mode 

void processTimer()
{
  if (timerChanged || millis() > pauseDelay)
  {
    if (timerPaused)
    {
      flashState = !flashState;
      flashTime = !flashTime;
    }
    else
    {
      flashState = true;
      flashTime = true;
    }
    flashColon = !flashColon;
    pauseDelay = millis() + TIMER_FLASH_TIME;

    displayString((timerState == OFF) ? "OFF " : (timerState == ON) ? "ON  " : "dONE", true, flashState);
    if ((timerState == OFF && ee.offFormat == HH_MM) || (timerState == ON && ee.onFormat == HH_MM))
    {
      displayDigits(timerHours, false, true, false, flashColon, flashTime);
      displayDigits(timerMinutes, false, false, true, false, flashTime);
    }
    else
    {
      displayDigits(timerMinutes, false, true, false, flashColon, flashTime);
      displayDigits(timerSeconds, false, false, true, false, flashTime);
    }
    timerChanged = false;
  }
}

//---------------------------------------------------------------
// Countdown the timer. 
// This is invoked by RTC ISR every second
void countDownTimer()
{
  bool testAgain;
  if ((timerState == OFF && ee.offFormat == HH_MM) || (timerState == ON && ee.onFormat == HH_MM))
  {
    //HH-MM format
    if (timerSeconds > 0)
    {
      timerSeconds--;
    }
    else if (timerMinutes > 0)
    {
      timerSeconds = 59;
      timerMinutes--;
      timerChanged = true;
    }
    else if (timerHours > 0)
    {
      timerSeconds = 59;
      timerMinutes = 59;
      timerHours--;
      timerChanged = true;
    }
    testAgain = true;
    while (testAgain)
    {
      testAgain = false;
      if (timerSeconds == 0 && timerMinutes == 0 && timerHours == 0)
      {
        if (timerState == OFF)
        {
          timerState = ON;
          timerSeconds = 0;
          timerMinutes = ee.onLsb;
          timerHours = ee.onMsb;
          timerChanged = true;
          timerRelayChange = true;
          testAgain = true;
        }
        else if (ee.timerLoop == ON)
        {
          timerState = OFF;
          timerSeconds = 0;
          timerMinutes = ee.offLsb;
          timerHours = ee.offMsb;
          timerChanged = true;
          timerRelayChange = true;
          testAgain = true;
        }
        else
        {
          timerState = DONE;
          timerRelayChange = true;
          stopButtonPressed();
        }
      }
    }
  }
  else
  {
    //MM-SS format
    if (timerSeconds > 0)
    {
      timerSeconds--;
      timerChanged = true;
    }
    else if (timerMinutes > 0)
    {
      timerSeconds = 59;
      timerMinutes--;
      timerChanged = true;
    }
    testAgain = true;
    while (testAgain)
    {
      testAgain = false;
      if (timerSeconds == 0 && timerMinutes == 0 && timerHours == 0)
      {
        if (timerState == OFF)
        {
          timerState = ON;
          timerSeconds = ee.onLsb;
          timerMinutes = ee.onMsb;
          timerHours = 0;
          timerChanged = true;
          timerRelayChange = true;
          testAgain = true;
        }
        else if (ee.timerLoop == ON)
        {
          timerState = OFF;
          timerSeconds = ee.offLsb;
          timerMinutes = ee.offMsb;
          timerHours = 0;
          timerChanged = true;
          timerRelayChange = true;
          testAgain = true;
        }
        else
        {
          timerState = DONE;
          timerRelayChange = true;
          stopButtonPressed();
        }
      }
    }
  }
}

//---------------------------------------------------------------
// RTC Interrupt Handler
ISR(RTC_CNT_vect)
{
  if (!timerPaused)
  {
    countDownTimer();
  }
  RTC.INTFLAGS = RTC_OVF_bm;                         // Reset overflow interrupt
}

//-------------------------------------------------------------------------
// Callback for when START button is pressed

void startButtonPressed()
{
  if (inMenu)
  {
    beepDigit();
    menuSelect = (menuSelect == LOOP) ? TIMER : (menuEnum)((int)menuSelect + 1);
    forceMenuUpdate = true;
  }
  else if (timerPaused && timerState != DONE)
  {
    timerChanged = true;
    timerPaused = false;
  }
}

//-------------------------------------------------------------------------
// Callback for when STOP button is pressed

void stopButtonPressed()
{
  if (inMenu)
  {
    beepDigit();
    menuSelect = (menuSelect == TIMER) ? LOOP : (menuEnum)((int)menuSelect - 1);
    forceMenuUpdate = true;
  }
  else if (!timerPaused)
  {
    timerChanged = true;
    timerPaused = true;
    flashState = true;
    flashTime = true;
    flashColon = true;
  }
}

//-------------------------------------------------------------------------
// Callback for when + button is pressed

void upButtonPressed()
{
  if (inMenu)
  {
    forceMenuUpdate = true;
    switch (menuSelect)
    {
      case OFF_FMT: ee.offFormat = (ee.offFormat == HH_MM) ? MM_SS : HH_MM; break;
      case OFF_MSB: ee.offMsb = min(ee.offMsb + 1, 99); break;
      case OFF_LSB: ee.offLsb = min(ee.offLsb + 1, 59); break;
      case ON_FMT:  ee.onFormat = (ee.onFormat == HH_MM) ? MM_SS : HH_MM; break;
      case ON_MSB:  ee.onMsb = min(ee.onMsb + 1, 99); break;
      case ON_LSB:  ee.onLsb = min(ee.onLsb + 1, 59); break;
      case LOOP:    ee.timerLoop = (ee.timerLoop == ON) ? OFF : ON; break;
      default: forceMenuUpdate = false; break;
    }
    if (forceMenuUpdate)
    {
      beepDigit();
      flashColon = true;
      displayMenu();
    }
  }
}

//-------------------------------------------------------------------------
// Callback for when - button is pressed

void downButtonPressed()
{
  if (inMenu)
  {
    forceMenuUpdate = true;
    switch (menuSelect)
    {
      case OFF_FMT: ee.offFormat = (ee.offFormat == HH_MM) ? MM_SS : HH_MM; break;
      case OFF_MSB: ee.offMsb = max(ee.offMsb - 1, 0); break;
      case OFF_LSB: ee.offLsb = max(ee.offLsb - 1, 0); break;
      case ON_FMT:  ee.onFormat = (ee.onFormat == HH_MM) ? MM_SS : HH_MM; break;
      case ON_MSB:  ee.onMsb = max(ee.onMsb - 1, 0); break;
      case ON_LSB:  ee.onLsb = max(ee.onLsb - 1, 0); break;
      case LOOP:    ee.timerLoop = (ee.timerLoop == ON) ? OFF : ON; break;
      default: forceMenuUpdate = false; break;
    }
    if (forceMenuUpdate)
    {
      beepDigit();
      flashColon = true;
      displayMenu();
    }
  }
}

//-------------------------------------------------------------------------
// Display the current menu

void displayMenu()
{
  switch (menuSelect)
  {
    case TIMER: displayString("PLAY", true, true); displayString("   ", false, true); break;
    case OFF_FMT: displayFormat("OF F", ee.offFormat); break;
    case OFF_MSB: displayTime("OF t", ee.offMsb, ee.offLsb, ee.offFormat, true); break;
    case OFF_LSB: displayTime("OF t", ee.offLsb, ee.offMsb, ee.offFormat, false); break;
    case ON_FMT:  displayFormat("ON F", ee.onFormat); break;
    case ON_MSB: displayTime("ON t", ee.onMsb, ee.onLsb, ee.onFormat, true); break;
    case ON_LSB: displayTime("ON t", ee.onLsb, ee.onMsb, ee.onFormat, false); break;
    case LOOP: displayLoop(ee.timerLoop); break;
  }
  forceMenuUpdate = false;
}

//-------------------------------------------------------------------------
// Display the time format
//  s - String to display on top line
//  fmt - Format
void displayFormat(String s, formatEnum fmt)
{
  displayString(s, true, true);
  displayString((fmt == HH_MM) ? "hhnn" : "nnSS", false, flashColon);
}

//-------------------------------------------------------------------------
// Display the time format
//  s - String to display on top line
//  dyn - Dynamic variable to set
//  sta - Static variable to show
//  fmt - Format
//  left - true if adjusting left digit
void displayTime(String s, int dyn, int sta, formatEnum fmt, bool left)
{
  displayString(s, true, true);
  if (left)
  {
    displayDigits(dyn, false, true, true, flashColon, flashColon);
    displayDigits(sta, false, false, true, false, true);
  }
  else
  {
    displayDigits(sta, false, true, true, flashColon, true);
    displayDigits(dyn, false, false, true, false, flashColon);
  }
}

//-------------------------------------------------------------------------
// Display the time format
void displayLoop(loopEnum fmt)
{
  displayString("LOOP", true, true);
  displayString((fmt == ON) ? "ON  " : "OFF ", false, flashColon);
}

//---------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData()
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  #ifdef __AVR_ATtiny1614__
    eeprom_update_block (( void *) &ee , ( void *) EEPROM_ADDRESS, sizeof(ee));  
  #else
    EEPROM.put(EEPROM_ADDRESS,ee);
  #endif
}

//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData()
{
  //Eprom
  #ifdef __AVR_ATtiny1614__
    eeprom_read_block (( void *) &ee , ( const void *) EEPROM_ADDRESS, sizeof(ee));  
  #else
    EEPROM.get(EEPROM_ADDRESS,ee);
  #endif
  #ifndef RESET_EEPROM
  if (ee.magic != EEPROM_MAGIC)
  #endif
  {
    ee.magic = EEPROM_MAGIC;
    ee.offFormat = MM_SS;
    ee.offMsb = 0;
    ee.offLsb = 0;
    ee.onFormat = MM_SS;
    ee.onMsb = 0;
    ee.onLsb = 0;
    ee.timerLoop = OFF;
    writeEepromData();
  }
}

//-----------------------------------------------------------------------------------
//Turn on and off buzzer quickly
void beepDigit() 
{
  //PB3
#define BUZZER_PORT PORTB
#define BUZZER_PIN  PIN3_bm
  
  BUZZER_PORT.OUTSET = BUZZER_PIN;   // turn on buzzer
  delay(5);
  BUZZER_PORT.OUTCLR = BUZZER_PIN;  // turn off the buzzer
}

//-----------------------------------------------------------------------------------
//Turn on and off buzzer quickly
void beepRelay() 
{
  BUZZER_PORT.OUTSET = BUZZER_PIN;   // turn on buzzer
  delay(20);
  BUZZER_PORT.OUTCLR = BUZZER_PIN;  // turn off the buzzer
}

Display.h

C Header File
#pragma once
// Relay Timer - Display Routines
// jbrad2089@gmail.com

//LED order 
// 1, 5, 7, 6 - Top left to right
// 0, 3, 2, 4 - Bottom left to right
int8_t digitMap[] = {6,7,5,1,4,2,3,0};
int8_t charMap[] = {1,5,7,6,0,3,2,4};

//The actual pins are set in setupDisplay. This instance will be deleted
LedControl lc = LedControl(12,11,10,1);

const static byte myCharTable [] PROGMEM  = {
    //pabcdefg
    B01111110,B00110000,B01101101,B01111001,B00110011,B01011011,B01011111,B01110000,
    B01111111,B01111011,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,
    B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
    B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
    B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
    B00000000,B00000000,B00000000,B00000000,B10000000,B00000001,B10000000,B00000000,
    //0        1         2         3         4         5         6         7
    B01111110,B00110000,B01101101,B01111001,B00110011,B01011011,B01011111,B01110000,
    //8        9         :         ;         <         =         >         ?
    B01111111,B01111011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
    //@        A         B         C         D         E         F         G
    B00000000,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,B01111011,
    //H        I         J         K         L         M         N         O
    B00110111,B00110000,B00000000,B00000000,B00001110,B00000000,B01110110,B01111110,
    //P        Q         R         S         T         U         V         W
    B01100111,B00000000,B00000000,B01011011,B00001111,B00111110,B00000000,B00000000,
    //X        Y         Z         [         /         ]         ^         _
    B00000000,B00111011,B00000000,B00000000,B00000000,B00000000,B00000000,B00001000,
    //`        a         b         c         d         e         f         g
    B00000000,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,B01111011,
    //h        i         j         k         l         m         n         o
    B00110111,B00010000,B00000000,B00000000,B00001110,B00000000,B00010101,B00011101,
    //p        q         r         s         t         u         v         w
    B01100111,B00000000,B00000101,B01011011,B00001111,B00011100,B00000000,B00000000,
    //x        y         z         {         |         }         ~         DEL
    B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000
};

//-----------------------------------------------------------------------------------
// Functions
void setupDisplay(int dataPin, int clockPin, int loadPin);
void displayNumber(int num, bool top, bool leading);
void displayDigits(int num, bool top, bool left, bool leading, bool colon, bool on);
void displayString(String s, bool top, bool on);
void displayChar(int digit, char value, bool colon); 

//-----------------------------------------------------------------------------------
// Setup speaker
void setupDisplay(int dataPin, int clockPin, int loadPin)
{
  lc = LedControl(dataPin,clockPin,loadPin,1);
  
  lc.shutdown(0,false);     //Wakeup MAX7219
  lc.setIntensity(0,3);     //Set brightness (0 - 15)
  lc.clearDisplay(0);       //Clear display
}

//-----------------------------------------------------------------------------------
// Display four digit number on display
// num - Number to display
// top - true to display on top segment array
// leading - true to show leading zeros
void displayNumber(int num, bool top, bool leading)
{
  int ofs = (top) ? 0 : 4;
  for (int i = 0; i < 4; i++)
  {
    if (num > 0 || i == 0 || leading)
    {
      displayChar(ofs + 3 - i, '0' + num % 10, false);
    }
    else
    {
      displayChar(ofs + 3 - i, ' ', false);
    }
    num = num / 10;
  }
}

//-----------------------------------------------------------------------------------
// Display two digit number on display
// num - Number to display
// top - true to display on top segment array
// left - true for left two digits, false for right two digits
// leading - true to show leading zeros
// colon - true to show colon (left only)
// on - true to show digits, false to blank digits
void displayDigits(int num, bool top, bool left, bool leading, bool colon, bool on)
{
  int ofs = ((top) ? 0 : 4) + ((left) ? 0 : 2);
  for (int i = 0; i < 2; i++)
  {
    if (on && (num > 0 || i == 0 || leading))
    {
      displayChar(ofs + 1 - i, '0' + num % 10, colon);
    }
    else
    {
      displayChar(ofs + 1 - i, ' ', false);
    }
    num = num / 10;
  }
}

//-----------------------------------------------------------------------------------
// Display string on display
// s - String (truncated to 4 characters and space filled)
// top - true to display on top segment array
// on - true to display string, false to print with spaces
void displayString(String s, bool top, bool on)
{
  for (unsigned int i=0; i<4; i++)
  {
    char c = (i < s.length() && on) ? s[i] : ' ';
    displayChar((top) ? i : i + 4, c, false);
  }
}

//----------------------------------------------------------------------------------
//Display Character
// digit - position to display (0 to 7)
// value - character (0 .. 127)
void displayChar(int digit, char value, bool colon) 
{
  byte v = pgm_read_byte_near(&myCharTable[(int)value & 0x7F]);
  if (colon)
  {
    v = v | B10000000;
  }
  lc.setRow(0,charMap[digit & 0x07],v);
}

  

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 10

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50

class Button
{
	public:
	//Simple constructor
	Button(int pin);
	Button(int name, int pin);
	Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true);

  //Background function called when in a wait or repeat loop
  void Background(void (*pBackgroundFunction)());
	//Repeat function called when button is pressed
  void Repeat(void (*pRepeatFunction)());
	//Test if button is pressed
	bool IsDown(void);
	//Test whether button is pressed and released
	//Will call repeat function if one is provided
	bool Pressed();
	//Return button state (HIGH or LOW) - LOW = Pressed
	int State();
  //Return button name
  int Name();

	private:
		int _name;
		int _pin;
		bool _range;
		int _low;
		int _high;
		bool _activeLow;
		void (*_repeatCallback)(void);
		void (*_backgroundCallback)(void);
};

Button.cpp

C/C++
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"

Button::Button(int pin)
{
  _name = pin;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin)
{
  _name = name;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow)
{
  _name = name;
  _pin = pin;
  _range = true;
  _low = analogLow;
  _high = analogHigh;
  _activeLow = activeLow;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
  _backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
  _repeatCallback = pRepeatFunction;
}

  bool Button::IsDown()
{
	if (_range)
	{
		int value = analogRead(_pin);
		return (value >= _low && value < _high);
	}
	else
	{
		return (digitalRead(_pin) == LOW);
	}
}

//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
  bool pressed = false;
  if (IsDown())
  {
    unsigned long wait = millis() + DEBOUNCE_DELAY;
    while (millis() < wait)
    {
      if (_backgroundCallback != NULL)
      {
        _backgroundCallback();
      }
    }
    if (IsDown())
    {
  	  //Set up for repeat loop
  	  if (_repeatCallback != NULL)
  	  {
  	    _repeatCallback();
  	  }
  	  unsigned long speed = REPEAT_START_SPEED;
  	  unsigned long time = millis() + speed;
      while (IsDown())
      {
        if (_backgroundCallback != NULL)
        {
          _backgroundCallback();
        }
    		if (_repeatCallback != NULL && millis() >= time)
    		{
    		  _repeatCallback();
    		  unsigned long faster = speed - REPEAT_INCREASE_SPEED;
    		  if (faster >= REPEAT_MAX_SPEED)
    		  {
    			  speed = faster;
    		  }
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		int value = analogRead(_pin);
		if (_activeLow)
		{
			return (value >= _low && value < _high) ? LOW : HIGH;
		}
		else 
		{
			return (value >= _low && value < _high) ? HIGH : LOW;
		}
	}
	else
	{
		return digitalRead(_pin);
	}
}

//Return current button name
int Button::Name()
{
	return _name;
}

Credits

John Bradnam

John Bradnam

147 projects • 181 followers

Comments