Close

Still Testing

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 02/02/2018 at 13:280 Comments

Still Testing

Those rotary encoders are still causing trouble, occasionally they get confused. I can't work out why but t must be a memory clash or an interrupt problem. I am using volatile variable without turning off interrupts so I should look at that first. But once it gets confused it stays that way (more likely a memory clash).

I have added a sampling and datalogging code using the RTC AT24C32 EEPROM. Why not, its come for free with the RTC I got. At the moment the sampler just records a random number that can be downloaded to the serial port:

/* 
 * Universal Time Clock with Periodic Sampler 
 */
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;

#include <AT24Cxx.h>
AT24Cxx AT24C32(0x50); 
// An anonymous union/structure to read/write RTC AT24C32 data
union {
  struct{
    char Year;
    char Month;
    char Day;
    char Hour;
    char Minute;
    char Second;
    int Value;
  };
  char data[8]; 
} AT24C32_Slot;
union {
  struct{
    int Value;
  };
  char data[2]; 
} AT24C32_Int;
  
// 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 PinA 3
#define PinB 2
#define SW   4
volatile bool UpdateSwitch=false;
volatile byte Switch=HIGH;
volatile int EncoderPos=0;
ISR(TIMER0_COMPB_vect) {
  static byte testPinA=(PIND>>PinA)&1;
  static byte testPinB=(PIND>>PinB)&1;
  static byte lastPinA=LOW;
  static byte lastPinB=LOW;
  static bool flagPinA=false;
  static bool flagPinB=false;
  static bool encoderFlag=true;
  static int encoderDir=0;
  static byte testSW=HIGH;
  static byte statusSW=HIGH;
  static byte cntSW=0;  
  
  // Update Encoder Position
  lastPinA=testPinA;             // Save PinA
  lastPinB=testPinB;             // Save PinB
  testPinA=(PIND>>PinA)&1;       // Get PinA
  testPinB=(PIND>>PinB)&1;       // Get PinB
  if (testPinA!=lastPinA) {      // Change in PinA?
    flagPinA=true;               // Flag PinA has changed
    encoderDir=-1;               // Assume it is the last flag to change
  }
  if (testPinB!=lastPinB) {      // Change in PinB?
    flagPinB=true;               // Flag PinB has changed
    encoderDir=1;                // Assume it is the last flag to change
  }
  if (flagPinA&&flagPinB) {      // Both flags have changed
    // This rotary encoder updates twice per detent!
    if (encoderFlag) {           // First transition only
      EncoderPos+=encoderDir;
    }
    encoderFlag=(!encoderFlag);    
    flagPinA=false;              // Reset PinA flag
    flagPinB=false;              // Reset PinB flag
  }
  
  // Update switch with 10 ms debounce
  testSW=(PIND>>SW)&1;
  if (testSW!=statusSW) {
    encoderFlag=true;            // Reset encoder flag (precaution)
    statusSW=testSW;
    cntSW=10;
  }
  if (cntSW>0) {
    cntSW--;
    if (cntSW==0) {
      Switch=statusSW;
      UpdateSwitch=true;
    }
  }
}


/* MENU SET UP */
char* menuName[]={
  "Exit Menu ",    //  0 - Exit menu system
  "UT Year ",      //  1
  "UT Month ",     //  2
  "UT Day ",       //  3
  "UT Hour ",      //  4
  "UT Minute ",    //  5
  "UT Second ",    //  6
  "UT Save ",      //  7
  "Local Hour ",   //  8
  "Local Min ",    //  9
  "Upload Sec ",   // 10
  "Sample Min ",   // 11 - Minutes between samples
  "Upload Data "   // 12 - Upload data to serial port 
};
int menuSize=sizeof(menuName)/sizeof(char*);
int menuValue[]=   {  1,2000,  1,  1,  0,  0,  0,  0,  8,  0,  7,  6,  1};
// Non-numeric values are 'N'=0 and 'Y'=1
char menuNumeric[]={'N', 'Y','Y','Y','Y','Y','Y','N','Y','Y','Y','Y','N'};
int menuValueMin[]={  0,1000,  1,  1,  0,  0,  0,  0,-59,  0,  0,  0};
int menuValueMax[]={  1,2999, 12, 31, 23, 59, 59,  1, 59, 59, 60,  1};
int menuLevel=0;

// AT24C32 usage
char settings[8];              // 32752-32759
unsigned int sampleSlot=0; // Slot 0 to 4093 (2 Slots reserved)

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 {
          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 {
          lcd.print("Y");      
        }
      }      
    } else {
      lcd.print("Rotary Encoder?");      
    }
  }
  
  // If push button pushed toggle menu level
  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;    // 'Y'
      } else {
        // Toggle menu level
        menuLevel=1-menuLevel;
        if (menuLevel==0) {
          // Restore item menu position
          EncoderPos=lastPos;
          // Exit menu if done!
          if ((EncoderPos==0)&&(menuValue[0]!=0)) {
            menuLevel=-1;
            // Save Local Time and Sample settings
            strcpy(settings,"UTC");
            settings[4]=menuValue[8];
            settings[5]=menuValue[9];
            settings[6]=menuValue[10];
            settings[7]=menuValue[11];
            AT24C32.WriteMem(32752,settings,8);
          }
          // Check for save new UT
          if ((EncoderPos==7)&&(menuValue[7]!=0)) {
            rtc.adjust(DateTime(menuValue[1],menuValue[2],menuValue[3],menuValue[4],menuValue[5],menuValue[6]));
          }
          // Check for data download
          if ((EncoderPos==12)&&(menuValue[12]!=0)) {
            menuValue[11]=1; // Reset to 'X'
            lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("Upload Data: ");
            lcd.setCursor(0,1);
            lcd.print("Slots used ");
            lcd.print(sampleSlot);
            delay(1000);
            Serial.begin(9600);
            while (!Serial);
            for (int i=0;i<sampleSlot;i++) {
              AT24C32.ReadMem(i*8,AT24C32_Slot.data,8);
              Serial.print(AT24C32_Slot.Year+2000,DEC);
              Serial.print("/");
              if (AT24C32_Slot.Month<10) Serial.print(0);       
              Serial.print(AT24C32_Slot.Month,DEC);     
              Serial.print("/");
              if (AT24C32_Slot.Day<10) Serial.print(0);         
              Serial.print(AT24C32_Slot.Day,DEC);     
              Serial.print(" "); 
              if (AT24C32_Slot.Hour<10) Serial.print(0);                        
              Serial.print(AT24C32_Slot.Hour,DEC);     
              Serial.print(":"); 
              if (AT24C32_Slot.Minute<10) Serial.print(0);                        
              Serial.print(AT24C32_Slot.Minute,DEC);     
              Serial.print(":");    
              if (AT24C32_Slot.Second<10) Serial.print(0);     
              Serial.print(AT24C32_Slot.Second,DEC);
              Serial.print(" = ");        
              Serial.println(AT24C32_Slot.Value);  
            }
            Serial.end();
            lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("Upload Data: ");
            lcd.setCursor(0,1);
            lcd.print("Done ");
            delay(1000);
          }
        } 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 {
          lcd.print("Y");      
        }
      }

    }
  } 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 {
          lcd.print("Y");      
        }
      }
      
    }
  }

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

void setup() {
  // Initialise rotary encoder and push button
  pinMode(PinA,INPUT_PULLUP); // Rotary PinA
  pinMode(PinB,INPUT_PULLUP); // Rotary PinB
  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 welcome 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");
  }
  // Check if AT24C32 is present
  if (!AT24C32.isPresent()) {
    lcd.setCursor(0,1);lcd.print("AT24C32 not installed"); 
    while (true);
  }  

  // Set Universal Time based on saved local time
  int localTimeHour=8;   // 8 hours for Perth Western Australia
  int localTimeMin=0;    // 0 minutes for Perth Western Australia
  int uploadTimeSec=7;   // 7 seconds for my computer
  int samplePeriodMin=1; // 1 minutes between samples

  // Read saved settings
  AT24C32.ReadMem(32752,settings,8);
  if (strcmp(settings,"UTC")==0) {
    localTimeHour=settings[4];            
    localTimeMin=settings[5];     
    uploadTimeSec=settings[6];
    samplePeriodMin=settings[7];
  }
  
  rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
  DateTime now=rtc.now();
  rtc.adjust(DateTime(now+(uploadTimeSec-localTimeHour*3600-localTimeMin*60)));
  
  // Set menu to current time
  now=rtc.now();
  menuLevel=-1;                  // Don't enter menu on start up
  menuValue[0]='Y';              // Set "Exit Menu" status 
  menuValue[1]=now.year();
  menuValue[2]=now.month();
  menuValue[3]=now.day();
  menuValue[4]=now.hour();
  menuValue[5]=now.minute();
  menuValue[6]=now.second();
  menuValue[7]='Y';              // Set "UT Save" status 
  menuValue[8]=localTimeHour;
  menuValue[9]=localTimeMin;
  menuValue[10]=uploadTimeSec;
  menuValue[11]=samplePeriodMin;
  delay(1000);
  lcd.clear();
  
}

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("Date ");
      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("Time   ");
      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());

    // Save data to RTC ATC32 (4094 slots) every samplePeriodMin
    if ((now.second()==0)&&(now.minute()%menuValue[11]==0)) {
      AT24C32_Slot.Year=now.year()-2000;
      AT24C32_Slot.Month=now.month();
      AT24C32_Slot.Day=now.day();
      AT24C32_Slot.Hour=now.hour();
      AT24C32_Slot.Minute=now.minute();
      AT24C32_Slot.Second=0;
      AT24C32_Slot.Value=random(32)*random(32);
      AT24C32.WriteMem(sampleSlot*8,AT24C32_Slot.data,8);      
      // Update next address
      sampleSlot++;
      if (sampleSlot>=4094) sampleSlot=1;
      AT24C32_Int.Value=sampleSlot;
      AT24C32.WriteMem(32760,AT24C32_Int.data,2);
      lcd.clear();
      lcd.setCursor(0,0);
        lcd.print("Saved data ");
        lcd.print(AT24C32_Slot.Value);
      lcd.setCursor(0,1);
        lcd.print("Slots used ");
        lcd.print(sampleSlot);
      delay(2000);

    }
    
    delay(1000);
  }
}

Each sample (16 bit integer) is date stamped. The record (or slot as I have called it) is 8 byte long. One char for Year, Month, Day, Hour, Minute, Second and one 16 bit int for Value. I used this anonymous union/structure convert between formats for read and write to the AT24C32:

union {
  struct {
    char Year;
    char Month;
    char Day;
    char Hour;
    char Minute;
    char Second;
    int Value;
  };
  char data[8];
 } AT24C32_Slot;

Here is an example writing to the AT24C32:

      AT24C32_Slot.Year=now.year()-2000;
      AT24C32_Slot.Month=now.month();
      AT24C32_Slot.Day=now.day();
      AT24C32_Slot.Hour=now.hour();
      AT24C32_Slot.Minute=now.minute();
      AT24C32_Slot.Second=0;
      AT24C32_Slot.Value=random(32)*random(32);
      AT24C32.WriteMem(sampleSlot*8,AT24C32_Slot.data,8);      

Reading data from the AT24C32 is similar:

      AT24C32.ReadMem(sampleSlot*8,AT24C32_Slot.data,8);
      Year=AT24C32_Slot.Year+2000;
      Month=AT24C32_Slot.Month;
      Day=AT24C32_Slot.Day;
      Hour=AT24C32_Slot.Hour;
      Minute=AT24C32_Slot.Minute;
      Second=AT24C32_Slot.Second;
      Value=AT24C32_Slot.Value;

So no explicit data conversions, neat.

At 1 minute between samples the AT24C32 can hold 68 hours of data. The sample period is programable from the menu (current range is 1 to 60 minutes).

Magic

Discussions