Close

UT Clock with Menu

A project log for Polling a Push Button Rotary Encoder

A push button rotary encoder is a neat input device for an Arduino. Unfortunately interfacing to it is not!

agp.cooperagp.cooper 01/30/2018 at 09:070 Comments

UT Clock with Menu

Added the menu system to the UT Clock. Had to add an abort/cancel option (i.e. 'X') to the "Done" menu option. An obvious omission.

Here is the code:

// Include RTC libraries and initialise
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;

// Include LCD library and initialise
#include <LiquidCrystal.h>
// Define Adruino pin numbers for LCD
#define en   7
#define rs   8
#define d4   9
#define d5  10
#define d6  11
#define d7  12
#define led 13
// Set LCD
LiquidCrystal lcd(rs,en,d4,d5,d6,d7);

/* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */
#define Clk 2
#define DT  3
#define SW  4
// Poll and debounces a Keyes rotary and push button switch
// Inputs:
//   Clk (Pin A with 10k pullup resistor)
//   DT  (Pin B with 10k pullup resistor)
//   SW  (Switch without a pullup resistor)
//   +   (Power for pullup resistors)
//   Gnd
// Exports:
//   UpdateSwitch (bool)
//   Switch [LOW|HIGH]
//   EncoderPos (int)
volatile bool UpdateSwitch=false;
volatile byte Switch=HIGH;
volatile int EncoderPos=0;
ISR(TIMER0_COMPB_vect) {
  static byte testClk=(PIND>>Clk)&1;
  static byte testDT=(PIND>>DT)&1;
  static byte lastClk=LOW;
  static byte lastDT=LOW;
  static bool flagClk=false;
  static bool flagDT=false;
  static int encoderDir=0;
  static byte testSW=HIGH;
  static byte statusSW=HIGH;
  static byte cntSW=0;  
  
  // Update Encoder Position
  lastClk=testClk;            // Save Clk
  lastDT=testDT;              // Save DT
  testClk=(PIND>>Clk)&1;      // Get Clk
  testDT=(PIND>>DT)&1;        // Get DT
  if (testClk!=lastClk) {     // Change in Clk?
    flagClk=true;             // Flag Clk has changed
    encoderDir=-1;            // Assume it is the last flag to change
  }
  if (testDT!=lastDT) {       // Change in DT?
    flagDT=true;              // Flag DT has changed
    encoderDir=1;             // Assume it is the last flag to change
  }
  if (flagClk&&flagDT) {      // Both flags have changed
    EncoderPos+=encoderDir;   // Update the encoder
    flagClk=false;            // Reset Clk flag
    flagDT=false;             // Reset DT flag
  }
  
  // Update switch with 10ms debounce
  testSW=(PIND>>SW)&1;
  if (testSW!=statusSW) {
    statusSW=testSW;
    cntSW=10;
  }
  if (cntSW>0) {
    cntSW--;
    if (cntSW==0) {
      Switch=statusSW;
      UpdateSwitch=true;
    }
  }
}


/* MENU SET UP */
char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second "};
int menuSize=sizeof(menuName)/sizeof(char*);
int menuValue[]=   {  1,  0,  1,  1,  0,  0,  0};
char menuNumeric[]={'N','Y','Y','Y','Y','Y','Y'};
int menuValueMin[]={  0,  0,  1,  1,  0,  0,  0};  // Non-numeric values 'N','X' and 'Y'
int menuValueMax[]={  2,999, 12, 31, 23, 59, 59};
int menuLevel=0;

void setup() {
  // Initialise rotary encoder and push button
  pinMode(Clk,INPUT_PULLUP); // Rotary Clk
  pinMode(DT,INPUT_PULLUP);  // Rotary DT
  pinMode(SW,INPUT_PULLUP);  // Rotary SW

  // Turn on polling ISR
  OCR0B=0xA0;
  TIMSK0|=1<<OCIE0B;

  // Initialise LCD
  pinMode(led,OUTPUT);
  digitalWrite(led,HIGH);
  lcd.begin(16,2);

  // Print a message to the LCD.
  lcd.setCursor(0,0);lcd.print("Universal Time");
  // Check RTC
  if (!rtc.begin()) {
    lcd.setCursor(0,1);lcd.print("RTC not found");
    while (true);
  } else if (!rtc.isrunning()) {
    lcd.setCursor(0,1);lcd.print("RTC not running");
  } else {
    lcd.setCursor(0,1);lcd.print("RTC found");
  }
  
  // Set Universal Time based on Compile Time
  int localTimeHour=-8; // Perth Western Australia
  int localTimeMin=0;   // Perth Western Australia
  int uploadTimeSec=7;  // For my computer

  rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
  DateTime now=rtc.now();
  rtc.adjust(DateTime(now+TimeSpan(0,localTimeHour,localTimeMin,uploadTimeSec)));
  
  // Set menu to current time
  now=rtc.now();
  menuLevel=-1;              // Don't enter menu on start up
  menuValue[0]='X';          // Set "done" status to abort 
  menuValue[1]=now.year();
  menuValue[2]=now.month();
  menuValue[3]=now.day();
  menuValue[4]=now.hour();
  menuValue[5]=now.minute();
  menuValue[6]=now.second();
  
  delay(1000);
  lcd.clear();
  
}

bool processMenu(void) {
  static int lastPos=0;
  static int lastMenuLevel=-1;

  // Pre-empt menu level display 
  if (menuLevel!=lastMenuLevel) {
    lastMenuLevel=menuLevel;
    lcd.clear();
    lcd.setCursor(0,0);
    if (menuLevel==0) {
      lcd.print("Menu:");
      lcd.setCursor(0,1);
      lcd.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]=='Y') {
        lcd.print(menuValue[EncoderPos]);
      } else {       
        if (menuValue[EncoderPos]==0) {
          lcd.print("N");
        } else if (menuValue[EncoderPos]==1) {
          lcd.print("X");      
        } else {
          lcd.print("Y");      
        }
      }          
    } else if (menuLevel==1) {
      lcd.print("Set:");
      lcd.setCursor(0,1);
      lcd.print(menuName[lastPos]);
      if (menuNumeric[lastPos]=='Y') {
        lcd.print(menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]==0) {
          lcd.print("N");
        } else if (menuValue[lastPos]==2) {
          lcd.print("Y");      
        } else {
          lcd.print("X");      
        }
      }      
    } else {
      lcd.print("Rotary Encoder?");      
    }
  }
  
  // If push button pushed toggle menuLevel
  if (UpdateSwitch) {
    UpdateSwitch=false;
    if (Switch==LOW) {
      // Re-enter menu if button pushed (for long enough)
      if (menuLevel==-1) {
        menuLevel=0;    // Item menu
        lastPos=1;      // Trigger menu update 
        EncoderPos=0;   // Done
        menuValue[0]=1; // 'X'
      } else {
        // Toggle menu level
        menuLevel=1-menuLevel;
        if (menuLevel==0) {
          // Save item menu position
          EncoderPos=lastPos;
          // Exit menu if done!
          if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1;
          // Any final work to be done? Set the time!
          if (menuValue[EncoderPos]==2) {
            rtc.adjust(DateTime(menuValue[1],menuValue[2],menuValue[3],menuValue[4],menuValue[5],menuValue[6]));
          }
        } else {
          // Set value for edit menu
          EncoderPos=menuValue[lastPos];
        }
      }
    }
  }
  
  // If encoder turned
  if (menuLevel==0) { // Select menu item
    if (lastPos!=EncoderPos) {
      if (EncoderPos>=menuSize) EncoderPos=0;
      if (EncoderPos<0) EncoderPos=menuSize-1;
      lastPos=EncoderPos;
      
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Menu:");
      lcd.setCursor(0,1);
      lcd.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]=='Y') {
        lcd.print(menuValue[EncoderPos]);
      } else {       
        if (menuValue[EncoderPos]==0) {
          lcd.print("N");
        } else if (menuValue[EncoderPos]==2) {
          lcd.print("Y");      
        } else {
          lcd.print("X");      
        }
      }

    }
  } else if (menuLevel==1) { // Edit menu item value
    if (menuValue[lastPos]!=EncoderPos) {
      if (EncoderPos>menuValueMax[lastPos]) EncoderPos=menuValueMin[lastPos];
      if (EncoderPos<menuValueMin[lastPos]) EncoderPos=menuValueMax[lastPos];
      menuValue[lastPos]=EncoderPos;
      
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Set:");
      lcd.setCursor(0,1);
      lcd.print(menuName[lastPos]);
      if (menuNumeric[lastPos]=='Y') {
        lcd.print(menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]==0) {
          lcd.print("N");
        } else if (menuValue[lastPos]==2) {
          lcd.print("Y");      
        } else {
          lcd.print("X");      
        }
      }
      
    }
  }

  return (menuLevel!=-1); // Return true if menu active
}

void loop() {

  if (!processMenu()) { // Menu not active
    // Present the results

    DateTime now=rtc.now();
    // Print date on first line of LCD
    lcd.clear();
    lcd.setCursor(0,0);
      lcd.print(now.year());
      lcd.print('/');
      if (now.month()<10) lcd.print(0);
      lcd.print(now.month());
      lcd.print('/');
      if (now.day()<10) lcd.print(0);
      lcd.print(now.day());
    
    // Print time on second line of LCD
    lcd.setCursor(0,1);lcd.print("UT: ");
      if (now.hour()<10) lcd.print(0);
      lcd.print(now.hour());
      lcd.print(':');
      if (now.minute()<10) lcd.print(0);
      lcd.print(now.minute());
      lcd.print(':');
      if (now.second()<10) lcd.print(0);
      lcd.print(now.second());
    
    delay(1000);
  }

}

I am happy with the code. Time for the front panel.

Magic

Discussions