Pedestal Clock Sketch

Declarations

/*

   LauraClock.ino. Program for the long 14-segment clock for Laura to verify clock adjusting

  The clock Display has 6, 4-character 14-segmeny yellow modules from SparkFun.

  Each module is so;dered to an "Backpack" from SparkFun.which provides I2C connectivity.

  The display is powered via a DC-DC converter with nominal 9-12V input.

  The display is connected to the SparkFun "MiniPro", 5V 16MHZ Atmel P238.

  This is powered from the same 7-12V input tp its Raw input.

  Contro is by an incremental encoder with push-button.

  It has a RTC clock module with a DS1307. It used 2 rechargarbable batteries fo backup.

  There are also 4 analog inputs:a 1.2V reference diode, a temperature sensor,

  a photocell and an external input from an augio jack.

  John Saunders 10/27/2022

*/

// Libraries:

#include <EncoderStepCounter.h>   //Better than Encoder

#include "SegDisp.h"              //For the Spark Fun 4-character display with I2C backpack

#include <Wire.h>                 //For I2C

#include "RTClib.h"               //For the old RTC with a DS1307

#include <EEPROM.h>

//Hardware definitions

#define NUMDISP 6                 //The display has 6 modules with addresses 70 - 75

#define rtcAddr 0x68

#define secPort 2                 //The one-Hz output of the DS1307

#define buttPort 3                //Pressing the incremental encoder knob to ground

#define lightPort A0              //A CDS phtocell embedded in the box botton, conected to Vcc

#define voltPort A1               //A 3mm audio jack at the botton of the base with 2:1 divider 2k ohm

#define tempPort A2               //A LM335 sticking out of the back of the box

#define refPort A3                //A 1.2V LM385 to avoid dependind on Vcc for ADVC reference

#define ENCODER_USE_INTERRUPTS

#define ENTRYLEN 30


// Instanstaning devices

DS1307 RTC;

EncoderStepCounter controlKnob(4, 5);


// Global variables:

bool buttFlag = false;            //Button pressed state, down = true

bool secFlag;

volatile byte darkCount = 0;

volatile byte dotAddr;

int dispTemp;

byte nextEvent;

byte currWD;

byte numEvents = 19;

uint8_t darkThreshold;     //Photocell output, less is dark, stored in RTC address 11

uint8_t messageSpeed;     // In units of 100 milliseconds, stored in RTC address 10

uint8_t darkCountMax;      //Seconds of display on after button pressed, stored in RTC address 12


//Constants:

const byte measSampleCount = 25;   //Number of samples for measuring temperature and external voltage

const float refVal = 1.233;       //LM385

const float zeroC = 2731.5;       //Convert LM335 deg K to Deg C

const float zeroF = 4596.7;       //Convert LM335 deg K to Deg F

const int mainPosMax = 6;

const int mainPosMin = 1;

const int adjPosMax = 17;

const int adjPosMin = 7;

const int numMsgs = 25;


//Strings:

#define welcomeMesgLen 4


struct msg_t {

  String msg;

  byte len;

};


const struct msg_t messages[numMsgs] = {

  {"Hello, I am Walnut Clock", 24}, {"John Saunders made me   ", 24},                  // 0-1 main

  {"I live at 40641 178 St E", 24}, {"Lake Los Angeles, CA    ", 24},                  // 2-3 main

  {"LIGHT=", 6}, {"Press for Clock Adjust", 22}, {"Press to review dates", 21},         // 4-6 main

  {"minutes", 7}, {"hours", 5}, {"date", 4}, {"month", 5}, {"year", 4},                 // 0-4 adjust, add 7

  {"Press for Temp in deg", 21}, {"Press for", 9}, {"Hour Mode", 9},                    // 5-7 adjust, add 7

  {"Press to exit adjust", 20}, {"Press to Adjust ", 16}, {"Press to store", 14},       // 8-10 adjust, add 7

  {"Dial to display dates ", 21}, {"on-time", 7}, {"Darkness", 8},                      // 11-14 adjust, add 7

  {"Interval", 8}, {" ", 1}, {"Dare to be wise", 15}, {"Go by the other road", 20}

};


struct event_t {

  char nme[25];

  uint8_t typ;

  uint8_t mnth;

  uint8_t dte;

  uint16_t yr;

};


const char *eventItem[] = {"Anniversary" , "Birthday"};

event_t event;



struct edit_t {

  uint8_t msgInx;

  uint8_t addr;

  uint8_t editMin;

  uint8_t editMax;

};


const struct edit_t edits[] = {

  //   exit      Degree select   Hour Mode

  {8, 0, 0, 1}, {15, 8, 0, 1}, {15, 2, 0, 0x40},

  //  minutes        hours          date           month           year        Dark On Time   Dark Threshold      Line Delay

  {0, 1, 0, 59}, {1, 2, 0, 23}, {2, 4, 1, 31}, {3, 5, 1, 12}, {4, 6, 22, 60}, {12, 12, 5, 100}, {13, 11, 5, 100}, {14, 10, 4, 80}

};


const char weekdays[] = {"SUNMONTUEWEDTHUFRISAT"};

const char months[] = {"JANFEBMARAPRMAYJUNJLYAUGSEPOCTNOVDEC"};

const uint16_t monthDays[13] = {

  //  J F  M   A  M   J  J    A   S   O   N   D

  0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334

};

Interrupt


// -------------------  Interrupt service Routines -----------------------

void showTime(void) {                       //Called by the one-Hz interrupt

  if (dotAddr < 23) {

    dotAddr++;

  }

  else {

    dotAddr = 0;

  }

  if (darkCount > 0) {

    darkCount--;

  }

  secFlag = true;

}

void setButt(void) {                     //Called by the push-button

  buttFlag = true;

}

Utility Programs

// -------------------  Utilities -----------------------

uint8_t bcd2bin (uint8_t bcdval) {

  return bcdval - 6 * (bcdval >> 4);

}

uint8_t bin2bcd (uint8_t binval) {

  return binval + 6 * (binval / 10);

}


uint16_t daysInYear(uint8_t mnth, uint8_t dte) {

  return monthDays[mnth] + dte;

}

byte getNextEvent(byte mnth, byte dte) {

  int currDays, eventDays, inx;

  currDays = daysInYear(mnth, dte);

  for (inx = 0; inx < numEvents; inx++) {

    EEPROM.get((inx * ENTRYLEN), event);

    eventDays = daysInYear(event.mnth, event.dte);

    if (eventDays >= currDays) {

      break;

    }

  }

  return inx;

}

nt getTemperature(void) {                    //LM335

  static float tempAcc = 0.0;

  static byte sampleCount = 0;

  int refCount, tempCount;

  int degVal;

  uint8_t bcdVal;

  if (sampleCount == 0) {

    bcdVal = RTC.read(8);

    if (bcdVal == 'C') {

      degVal = int((1000 * tempAcc / measSampleCount) - zeroC);

    }

    else {

      degVal = int((1800 * tempAcc / measSampleCount) - zeroF);

    }

    tempAcc = 0;

  }

  refCount = analogRead(refPort);

  tempCount = analogRead(tempPort) ;

  tempAcc += (refVal * tempCount / refCount);

  sampleCount++;

  if (sampleCount >= measSampleCount) {

    sampleCount = 0;

  }

  return degVal;

}


bool setDarkFlag(void) {                        //Photocell

  int lightCount = analogRead(lightPort);

  if ((lightCount < darkThreshold) && (darkCount == 0)) {

    return true;

  }

  else {

    return false;

  }

}

Display Programs


// -------------------  Display Functions -----------------------

//Up to 9999, blank leadimg zeroes,insert decimal points after last 3 digits

byte dispNum(byte addr, int pos, bool blz = false, byte dp = 0) {

  byte c;

  bool plz = false;

  bool setDp = false;

  if (pos < 0) {

    pos = -pos;

    SegDisp_drawAscii(addr++, 0x2D, false);

  }

  if (pos > 9999) {

    pos = pos % 10000;

  }

  c = (pos / 1000) + 0x30;

  if ((blz == true) && (c == 0x30)) {

    plz = true;

  }

  else {

    SegDisp_drawAscii(addr++, c, false);

    plz = false;

  }

  pos = pos % 1000;

  c = (pos / 100) + 0x30;

  if ((blz == true) && (c == 0x30) && (plz == true)) {

    plz = true;

  }

  else {

    if (dp == 2) {

      setDp = true;

    }

    SegDisp_drawAscii(addr++, c, setDp);

    plz = false;

    setDp = false;

  }

  pos = pos % 100;

  c = (pos / 10) + 0x30;

  if ((blz == true) && (c == 0x30) && (plz == true)) {

    plz = true;

    SegDisp_drawAscii(addr++, 0x20, false);

  }

  else {

    if (dp == 1) {

      setDp = true;

    }

    SegDisp_drawAscii(addr++, c, setDp);

    setDp = false;

  }

  c = (pos % 10) + 0x30;

  if (dp == 3) {

    setDp = true;

  }

  SegDisp_drawAscii(addr++, c, setDp);

  SegDisp_writeAlpha();

  return addr;

}


void dispWeekday(byte p, byte wd) {

  char c;

  byte indx;

  byte addr = p;

  for (byte i = 0; i < 3; i++)  {

    indx = (3 * wd ) + i;

    c = weekdays[indx];

    SegDisp_drawAscii(addr++, c, false);

  }

}


uint8_t dispMonth(byte p, byte mth) {

  char c;

  byte indx;

  byte addr = p;

  for (byte i = 0; i < 3; i++)  {

    indx = (3 * (mth - 1)) + i;

    c = months[indx];

    SegDisp_drawAscii(addr++, c, false);

  }

  return addr;

}


void dispWelcome(void) {

  char c;

  for (int i = 0; i < 4; i++) {

    for (int j = 0; j < messages[i].len; j++) {

      c = messages[i].msg.charAt(j);

      SegDisp_drawAscii( j, c, false);

    }

    SegDisp_writeAlpha();

    delay(100 * messageSpeed );

  }

}


int dispMsg(byte addr, byte inx) {

  char c;

  for (int i = 0; i < messages[inx].len; i++) {

    c = messages[inx].msg.charAt(i);

    SegDisp_drawAscii(addr++, c, buttFlag);

  }

  return addr;

}


int dispName(byte addr) {

  char c;

  byte pos = 0;

  do {

    c = event.nme[pos++];

    SegDisp_drawAscii(addr++, c, false);

  } while (c > 0x1F);

  SegDisp_drawAscii((addr - 1), 0x20, false);

  return addr;

}


int dispType(uint8_t addr, byte index) {

  char c;

  byte pos = 0;

  do {

    c = eventItem[index][pos++];

    SegDisp_drawAscii(addr++, c, false);

  } while (c > 0x1F);

  SegDisp_drawAscii((addr - 1), 0x20, false);

  return addr;

}


void dispDark(void) {

  static byte oldDotAddr;

  if (dotAddr > 0) {

    oldDotAddr = dotAddr - 1;

  }

  else {

    oldDotAddr = 23;

  }

  SegDisp_drawAscii(oldDotAddr, 0x20, false);

  SegDisp_drawAscii(dotAddr, 0x20, true);

  SegDisp_writeAlpha();

}


// -------------------  Analog Measurement Functions -----------------------

int dispExtVolt(byte addr) {                  //Bottom Read Audio Jack

  float voltAcc = 0.0;

  int refCount, voltCount;

  int voltVal;

  for (int i = 0; i < measSampleCount; i ++ ) {

    refCount = analogRead(refPort);

    voltCount = analogRead(voltPort) ;

    voltAcc += (refVal * voltCount / refCount);

    delayMicroseconds(200);

  }

  voltVal = int((198 * voltAcc / measSampleCount));

  addr = dispNum(addr, voltVal, true, 2);

  SegDisp_drawAscii(addr++, 0x56, false);

  return addr;

}


void dispEvent(bool line) {

  uint8_t addr;

  if (line) {

    addr = dispType(0, event.typ);

    addr = dispMonth(addr, event.mnth);

    addr = dispNum(addr, event.dte, true, 0);

    SegDisp_drawAscii(addr++, 0x20, false);

    addr = dispNum(addr, event.yr);

  }

  else {

    dispName(0);

  }

  SegDisp_writeAlpha();

}

Group Programs


// -------------------  Group Functions -----------------------

int dispMain(void) {

  static int mainPos = mainPosMin;                      //Encoder output

  static int oldMainPos = mainPosMax + 1;

  uint8_t addr, bcdVal, wd, mm, dd, newGroup = 0;

  int temp, inx;

  static bool line = 0;

  static int intervalCount = 0;

  if (oldMainPos >= mainPosMax) {

    controlKnob.setPosition(mainPos);

  }

  controlKnob.tick();

  mainPos = controlKnob.getPosition();

  if (mainPos > mainPosMax) {

    mainPos = mainPosMin;

    controlKnob.setPosition(mainPosMin);

  }

  if (mainPos < mainPosMin) {

    mainPos = mainPosMax;

    controlKnob.setPosition(mainPosMax);

  }

  DateTime now = RTC.now();

  if (mainPos != oldMainPos) {

    SegDisp_setAll(0);

    oldMainPos = mainPos;

    EEPROM.get((nextEvent * ENTRYLEN), event);

  }

  inx = mainPos - mainPosMin;

  switch (inx) {

    case 0:

      bcdVal = RTC.read(2);

      if (bcdVal > 0x2F) {

        bcdVal = bcdVal & 0x1F;

      }

      if (bcdVal < 0x10) {

        SegDisp_drawAscii(0, 0x20, false);        // Blank a leading zero

      }

      else {

        SegDisp_drawAscii(0, ((bcdVal / 16) + 0x30), false);

      }

      SegDisp_drawAscii(1, ((bcdVal & 0x0F) + 0x30), true);

      addr = dispNum(2, now.minute(), true, 3);

      addr = dispNum(addr, now.second(), true);

      wd = now.dayOfWeek();

      dispWeekday(8, wd);

      mm = now.month();

      dispMonth(12, mm);

      dd = now.day();

      dispNum(16, dd , true);

      dispNum(19, now.year(), false);

      if (wd != currWD) {

        nextEvent = getNextEvent(mm, dd);

        currWD = wd;

      }

      if (buttFlag) {

        buttFlag = false;

        SegDisp_setAll(0);

        dispMsg(0, 23);

        SegDisp_writeAlpha();

        delay(messageSpeed * 100);

        SegDisp_setAll(0);

        newGroup = 0;

      }

      break;

    case 1:

      addr = dispNum(0, dispTemp, true, 1);

      SegDisp_drawAscii(addr++, 10, false);

      bcdVal = RTC.read(8);

      SegDisp_drawAscii(addr++, bcdVal, false);

      addr++;

      addr = dispMsg(addr, 4);

      temp = analogRead(lightPort);

      addr = dispNum(addr++, temp, true, 0);

      addr++;

      addr = dispExtVolt(addr);

      if (buttFlag) {

        buttFlag = false;

        SegDisp_setAll(0);

        dispMsg(0, 24);

        SegDisp_writeAlpha();

        delay(messageSpeed * 100);

        SegDisp_setAll(0);

        newGroup = 0;

      }

      break;

    case 2:

      addr = dispMsg(0, 5);

      if (buttFlag) {

        SegDisp_setAll(0);

        dispMsg(0, 16);

        newGroup = 1;

        buttFlag = false;

      }

      break;

    case 3:

      addr = dispMsg(0, 6);

      if (buttFlag) {

        newGroup = 2;

        buttFlag = false;

      }

      break;

    case 4:

      dispDark();

      break;

    case 5:

      intervalCount++;

      delay(50);

      if (intervalCount > messageSpeed) {

        intervalCount = 0;

        line = !line;

        SegDisp_setAll(0);

      }

      dispEvent(line);

      if (buttFlag) {

        newGroup = 0;

        buttFlag = 0;

      }

      break;

  }

  SegDisp_writeAlpha();

  oldMainPos = mainPos;

  while (digitalRead(buttPort) == 0);

  return newGroup;

}


uint8_t adjClock(uint8_t index) {          // input is to edits[], returns new value

  uint8_t itemAddr = edits[index].addr;

  static uint8_t editPos;

  uint8_t editPosMin = edits[index].editMin;

  uint8_t  editPosMax = edits[index].editMax;

  uint8_t editVal = RTC.read(itemAddr);

  while (digitalRead(buttPort) == 0);

  editPos = bcd2bin(editVal);

  controlKnob.setPosition(editPos);

  while (digitalRead(buttPort) == 1) {

    controlKnob.tick();

    editPos = controlKnob.getPosition();

    if (editPos > editPosMax) {

      editPos = editPosMin;

      controlKnob.setPosition(editPosMin);

    }

    if (editPos < editPosMin) {

      editPos = editPosMax;

      controlKnob.setPosition(editPosMax);

    }

    dispNum(16, editPos, true, 0);

    SegDisp_writeAlpha();

  };

  editVal = bin2bcd(editPos);

  SegDisp_drawAscii(23, (itemAddr + 0x30), false);

  SegDisp_writeAlpha();

  while (digitalRead(buttPort) == 0);

  return editVal;

}


int dispAdjust(void) {

  static int adjPos;                      //Encoder output, in the display

  static int oldAdjPos = adjPosMax + 1;

  uint8_t addr;                                       //Of the display

  uint8_t bcdVal, newVal, inx;                        //of the messages table relative to adjPosMin

  byte newGroup = 1;

  adjPos = 1;

  controlKnob.tick();

  if (oldAdjPos > adjPosMax) {

    controlKnob.setPosition(adjPos);

  }

  adjPos = controlKnob.getPosition();

  if (adjPos > adjPosMax) {

    adjPos = adjPosMin;

    controlKnob.setPosition(adjPosMin);

  }

  if (adjPos < adjPosMin) {

    adjPos = adjPosMax;

    controlKnob.setPosition(adjPosMax);

  }

  if (adjPos != oldAdjPos) {

    SegDisp_setAll(0);

    oldAdjPos = adjPos;

  }

  inx = adjPos - adjPosMin;

  switch (inx) {

    case 0:

      addr = dispMsg(addr, (adjPosMin + 8));

      if (buttFlag) {                                         // exit

        newGroup = 0;

        buttFlag = false;

      }

      break;

    case 1:                                           //temperature mode selection

      addr = dispMsg(0, (adjPosMin + 5));

      bcdVal = RTC.read(8);

      if (bcdVal == 'C') {

        newVal = 'F';

      }

      else {

        newVal = 'C';

      }

      SegDisp_drawAscii(addr++, newVal, false);

      if (buttFlag) {

        RTC.write(8, newVal);

        buttFlag = false;

        newGroup = 0;

      }

      break;

    case 2:

      addr = dispMsg(0, (adjPosMin + 6));                   //12 or 24 hour seletion

      addr++;

      bcdVal = RTC.read(2);

      if (bcdVal > 0x2F) {                                       //12-hour mode, go to 24

        addr = dispNum(addr, 24, true);

        newVal = bitClear(bcdVal, 6);

      }

      else {

        addr = dispNum(addr, 12, true);

        newVal = bitSet(bcdVal, 6);;

      }

      if (buttFlag) {

        RTC.write(2, newVal);

        buttFlag = false;

        newGroup = 0;

      }

      addr += 2;

      addr = dispMsg(addr, (adjPosMin + 7));

      break;

    case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:           //Time and date adjustment

      addr = dispMsg(0, (adjPosMin + 9));

      addr = dispMsg(addr, edits[inx].msgInx + 7);

      if (buttFlag) {

        buttFlag = false;

        SegDisp_setAll(0);

        addr = dispMsg(0, (adjPosMin + 10));

        newVal = adjClock(inx);

        newGroup = 0;

        RTC.write(edits[inx].addr, newVal);

      }

      break;

  }

  SegDisp_writeAlpha();

  while (digitalRead(buttPort) == 0);

  return newGroup;

}


int dispDates(void) {

  static int eventPos = nextEvent;                      //Encoder output, in the display

  static int prevEventPos;

  byte inx;

  uint16_t eepromAddr;

  byte newGroup = 2;

  byte numSteps = 2 * numEvents + 1;

  do {

    controlKnob.tick();

    eventPos = controlKnob.getPosition();

    if (eventPos > numSteps) {

      eventPos = 0;

      controlKnob.setPosition(0);

    }

    if (eventPos < 0) {

      eventPos = numSteps;

      controlKnob.setPosition(numSteps);

    }

    inx = eventPos / 2;

    eepromAddr = inx * ENTRYLEN;

    if (eventPos != prevEventPos) {

      prevEventPos = eventPos;

      SegDisp_setAll(0);

      SegDisp_drawAscii(23, 0X20, (bitRead(inx, 0) == 0));

      if (bitRead(eventPos, 0) == 0) {

        EEPROM.get(eepromAddr, event);

        dispEvent(false);

      }

      else {

        dispEvent(true);

      }

    }

    if (buttFlag) {                                         // exit

      newGroup = 0;

      buttFlag = false;

      break;

    }

  } while (digitalRead(buttPort) == 1);

  return newGroup;

}

Setup and Loop

// -------------------  Control Functions -----------------------


void setup() {

  Wire.begin();

  RTC.begin();

  SegDisp_init();

  controlKnob.begin();

  pinMode(buttPort, INPUT_PULLUP);              //Others have resistors

  RTC.write(7, 0x10);                           //Enable 1 Hz square wave

  numEvents = RTC.read(9);

  messageSpeed = RTC.read(10);

  darkThreshold = RTC.read(11);

  darkCountMax = RTC.read(12);

  attachInterrupt(digitalPinToInterrupt(secPort), showTime, RISING);

  attachInterrupt(digitalPinToInterrupt(buttPort), setButt, FALLING);

  dispWelcome();

  dispTemp = 720;

  currWD = 0;

  darkCount = darkCountMax;

  dotAddr = 0;

  buttFlag = false;

}


void loop() {

  static byte group = 0;       //0=main,1-adjust,2-review,3=dark

  static byte prevGroup = 9;

  dispTemp = getTemperature();

  if (setDarkFlag()) {

    group = 3;

  }

  switch (group) {

    case 0:

      group = dispMain();

      break;

    case 1:

      group = dispAdjust();

      break;

    case 2:

      dispDates();

      group = 0;

      break;

    case 3:

      if (!setDarkFlag()) {

        group = 0;

      }

      else {

        dispDark();

      }

      if (buttFlag) {

        darkCount = darkCountMax;

        buttFlag = false;

      }

      break;

  }

  if (prevGroup != group) {

    SegDisp_setAll(0);

    prevGroup = group;

  }

  while (digitalRead(buttPort) == 0);

}