Manipulating DS3231 rtc.getTimeStr() to extract individual characters

Hi everyone - first post cherry.

I’m designing a new overhead console for my car centred around a Teensy 2.0 board and a DS3231 RTC, plus a few other bits and pieces. The console will be outputting the time from the RTC but as a series of large bitmapped characters instead of a font. This requires me to slice up or read the string returned by the RTC’s getTimeStr() as individual characters in order to select which character to use and place on the OLED. I’m currently using the Rinky Dink DS3231 and I2C_OLED libraries.

Perhaps this is more my green-ness to programming an Arduino, however I’m getting errors and brick walls when it seems I shouldn’t be. For example:

  myOLED.print(rtc.getTimeStr(FORMAT_SHORT), 0, 16);

…works fine testing that I’m getting the right string data. I am however having problems either pushing that string into a variable (declare as string or char?) or directly doing string operations based on that call.

charAt() fails to compile with using the (potentially nonsensical?):

  myOLED.print(rtc.getTimeStr(FORMAT_SHORT).charAt(2), 0, 16);

As I understand it, character operations are somewhat nightmarish.

The upshot of this is that I need to pull on digit from this string to perform a Switch Case or other logical operation to do bitmap funtimes.

Hopefully this is enough information to develop a meaningful conversation. I have hardly had time to dedicate myself to programming for a number of years, so “speak to me like I’ve forgotten”. :slight_smile: TIA

Post code. Post error messages.

Aha, it seems that I am ready to be black-bagged and taken around the back of the chemical sheds. This has produced a more favourable result:

Declare [u]S[/u]tring:

String currentClock;

This makes me guilty of jumping to the forum before fully exhausting my brain. :o

  currentClock = rtc.getTimeStr(SHORT_FORMAT);
  myOLED.print(currentClock.charAt(1), 0, 16);

Thanks AWOL.

This is at risk of showing my heavily commented testing and lack of programming grace during this brute force initial code…

#include <OLED_I2C.h>
#include <DS3231.h>

DS3231  rtc(SDA, SCL);
OLED  myOLED(SDA, SCL, 8);

float tempC;
String currentClock;

extern uint8_t char_colon[];
extern uint8_t char_0[];
extern uint8_t char_1[];
extern uint8_t char_2[];
extern uint8_t char_3[];
extern uint8_t char_4[];
extern uint8_t char_5[];
extern uint8_t char_6[];
extern uint8_t char_7[];
extern uint8_t char_8[];
extern uint8_t char_9[];
extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];

void setup()
{
  myOLED.begin();
  rtc.begin();

  // The following lines can be uncommented to set the date and time
  // rtc.setDOW(SATURDAY);     // Set Day-of-Week to SUNDAY
  // rtc.setTime(20, 01, 30);     // Set the time to 12:00:00 (24hr format)
  // rtc.setDate(29, 10, 2016);   // Set the date to January 1st, 2014
  
}

void loop() {

  myOLED.setFont(SmallFont);
  myOLED.print(rtc.getDOWStr(), LEFT, 0);
  myOLED.print(rtc.getDateStr(), RIGHT, 0);
  // myOLED.setFont(BigNumbers);
  // myOLED.print(rtc.getTimeStr(), LEFT, 16);
  delay (1000);

  currentClock = rtc.getTimeStr();
  myOLED.setFont(SmallFont);
  myOLED.print(currentClock.charAt(0), 0, 16);
  myOLED.print(currentClock.charAt(1), 10, 16);
  myOLED.print(currentClock.charAt(2), 20, 16);
  myOLED.print(currentClock.charAt(3), 30, 16);
  myOLED.print(currentClock.charAt(4), 40, 16);


/*  myOLED.setFont(SmallFont);  
  tempC = rtc.getTemp();
  myOLED.print(String(tempC)+"°C", 0, 16);
  myOLED.update();
*/

  myOLED.drawBitmap(62, 32, char_colon, 5, 15);
  myOLED.update();

  switch (int(currentClock.charAt(0))) {
  case 0:
      myOLED.drawBitmap(0, 16, char_0, 16, 48);
      myOLED.update();
      break;
  case 1:
      myOLED.drawBitmap(0, 16, char_1, 8, 48);
      myOLED.update();
      break;
  case 2:
      myOLED.drawBitmap(0, 16, char_2, 16, 48);
      myOLED.update();
      break;
  case 3:
      myOLED.drawBitmap(0, 16, char_3, 24, 48);
      myOLED.update();
      break;
  case 4:
      myOLED.drawBitmap(0, 16, char_4, 24, 48);
      myOLED.update();
      break;
  case 5:
      myOLED.drawBitmap(0, 16, char_5, 24, 48);
      myOLED.update();
      break;
  case 6:
      myOLED.drawBitmap(0, 16, char_6, 16, 48);
      myOLED.update();
      break;
  case 7:
      myOLED.drawBitmap(0, 16, char_7, 16, 48);
      myOLED.update();
      break;
  case 8:
      myOLED.drawBitmap(0, 16, char_8, 16, 48);
      myOLED.update();
      break;
  case 9:
      myOLED.drawBitmap(0, 16, char_9, 16, 48);
      myOLED.update();
      break;
  }


}

Currently I am having trouble developing a working Switch Case off a charAt(). I'm sure this is a simple syntax issue thanks to my lack of exposure and working experience programming in this environment.

The current Switch Case code:

  switch (currentClock.charAt(0)) {
  case 0:
      myOLED.drawBitmap(90, 16, char_0, 16, 48);
      myOLED.update();
      break;
  case 1:
      myOLED.drawBitmap(90, 16, char_1, 8, 48);
      myOLED.update();
      break;
  case 2:
      myOLED.drawBitmap(90, 16, char_2, 16, 48);
      myOLED.update();
      break;
  case 3:
      myOLED.drawBitmap(90, 16, char_3, 24, 48);
      myOLED.update();
      break;
  case 4:
      myOLED.drawBitmap(90, 16, char_4, 24, 48);
      myOLED.update();
      break;
  case 5:
      myOLED.drawBitmap(90, 16, char_5, 24, 48);
      myOLED.update();
      break;
  case 6:
      myOLED.drawBitmap(90, 16, char_6, 16, 48);
      myOLED.update();
      break;
  case 7:
      myOLED.drawBitmap(90, 16, char_7, 16, 48);
      myOLED.update();
      break;
  case 8:
      myOLED.drawBitmap(90, 16, char_8, 16, 48);
      myOLED.update();
      break;
  case 9:
      myOLED.drawBitmap(90, 16, char_9, 16, 48);
      myOLED.update();
      break;
  default:
      myOLED.drawBitmap(90, 16, char_colon, 5, 15);
      myOLED.update();
      break;
  }

....is outputting the colon rather than any of the cases. Hmm.

You should post your whole code, then it is clear(er) what data types you are working with.
try:

case '0':

Prostheta: Hi everyone - first post cherry.

I'm designing a new overhead console for my car centred around a Teensy 2.0 board and a DS3231 RTC, plus a few other bits and pieces. The console will be outputting the time from the RTC but as a series of large bitmapped characters instead of a font. This requires me to slice up or read the string returned by the RTC's getTimeStr() as individual characters in order to select which character to use and place on the OLED.

It does not, in fact, require you to deal with character strings at all.

Once I made an Arduino-based clock for my grandmother, using huge bitmapped digits for the time. I did not in any way deal with characters (in the sense you mean) or character strings.

You are dealing with some sort of time library. It must have some means of getting the hours, minutes, and seconds of the current time, as numbers, not as character strings. Once you have those numbers, choose the correct bitmaps based on the numbers.

If your problem is splitting a two-digit number into its digits, just use arithmetic. Something like this should work:

// declare some variables
int tens; int ones;

// here is the number you want to convert
// (it can be any number from 0 to 99)
int num = 23; 

// here is how we do the conversion
ones = num;
tens = 0;
while (ones > 9) {
  // more than 9 ones is too many
  // so we trade 10 ones for 1 ten
  // (the same as in pen-and-paper arithmetic)
  ones = ones - 10;
  tens = tens + 1;
}

// now, do whatever you want with the ones and tens variables

Gentlemen, thank you. In spite of this being somewhat of a simpler issue for the more hands-on Arduino wranglers, it was a real time sink for me having to go through the drawn-out process of identifying where I was going wrong. I genuinely appreciate your input and it’s made my Sunday that much nicer. Thank you.

In spite of my code currently being somewhat graceless and brute force, this is the end product:

#include <OLED_I2C.h>
#include <DS3231.h>

DS3231  rtc(SDA, SCL);
OLED  myOLED(SDA, SCL, 8);

int xOff;
int charSpacing = 2;
int clockCentre = 62;
String currentClock, oldClock;

extern uint8_t char_colon[];
extern uint8_t char_0[];
extern uint8_t char_1[];
extern uint8_t char_2[];
extern uint8_t char_3[];
extern uint8_t char_4[];
extern uint8_t char_5[];
extern uint8_t char_6[];
extern uint8_t char_7[];
extern uint8_t char_8[];
extern uint8_t char_9[];
extern uint8_t SmallFont[];

void setup()
{
  myOLED.begin();
  rtc.begin();
  myOLED.drawBitmap(clockCentre, 32, char_colon, 5, 15);

  // pre-set clock string to get valid initial update condition
  currentClock = rtc.getTimeStr(FORMAT_SHORT);

  // The following lines can be uncommented to set the date and time
  // rtc.setDOW(SUNDAY);          // Set Day-of-Week
  // rtc.setTime(12, 00, 00);     // Set the time to xx:xx:xx (24hr format)
  // rtc.setDate(30, 10, 2016);   // Set the date to dd:mm:yyyy
  
}

void loop() {

  myOLED.setFont(SmallFont);
  myOLED.print(rtc.getDOWStr(), LEFT, 0);
  myOLED.print(rtc.getDateStr(), RIGHT, 0);
  delay (1000);

  currentClock = rtc.getTimeStr();
  if ( oldClock != currentClock ) { myOLED.update(); }
  oldClock = currentClock;

//
// Switch Case for second hour character and set offset for first hour character
//
  switch (currentClock.charAt(1)) {
  case '0':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_0, 16, 48);
      break;
  case '1':
      xOff = 8;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_1, 8, 48);
      break;
  case '2':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_2, 16, 48);
      break;
  case '3':
      xOff = 19;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_3, 19, 48);
      break;
  case '4':
      xOff = 24;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_4, 24, 48);
      break;
  case '5':
      xOff = 17;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_5, 17, 48);
      break;
  case '6':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_6, 16, 48);
      break;
  case '7':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_7, 16, 48);
      break;
  case '8':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_8, 16, 48);
      break;
  case '9':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_9, 16, 48);
      break;
  }

  
//
// Switch Case for first hour character offset from second hour character
//
  switch (currentClock.charAt(0)) {
  case '0':
      myOLED.drawBitmap(clockCentre-xOff-16-(charSpacing*2), 16, char_0, 16, 48);
      break;
  case '1':
      myOLED.drawBitmap(clockCentre-xOff-8-(charSpacing*2), 16, char_1, 8, 48);
      break;
  case '2':
      myOLED.drawBitmap(clockCentre-xOff-16-(charSpacing*2), 16, char_2, 16, 48);
      break;
  }


//
// Switch Case for first minute character and set offset for second minute character
//
  switch (currentClock.charAt(3)) {
  case '0':
      xOff = 16;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_0, 16, 48);
      break;
  case '1':
      xOff = 8;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_1, 8, 48);
      break;
  case '2':
      xOff = 16;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_2, 16, 48);
      break;
  case '3':
      xOff = 19;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_3, 19, 48);
      break;
  case '4':
      xOff = 24;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_4, 24, 48);
      break;
  case '5':
      xOff = 17;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_5, 24, 48);
      break;
  }

  
//
// Switch Case for first hour character offset from second hour character
//
  switch (currentClock.charAt(4)) {
  case '0':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_0, 16, 48);
      break;
  case '1':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_1, 8, 48);
      break;
  case '2':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_2, 16, 48);
      break;
  case '3':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_3, 19, 48);
      break;
  case '4':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_4, 24, 48);
      break;
  case '5':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_5, 24, 48);
      break;
  case '6':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_6, 16, 48);
      break;
  case '7':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_7, 16, 48);
      break;
  case '8':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_8, 16, 48);
      break;
  case '9':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_9, 16, 48);
      break;
  }

}

odometer: It does not, in fact, require you to deal with character strings at all.

Once I made an Arduino-based clock for my grandmother, using huge bitmapped digits for the time. I did not in any way deal with characters (in the sense you mean) or character strings.

You are dealing with some sort of time library. It must have some means of getting the hours, minutes, and seconds of the current time, as numbers, not as character strings. Once you have those numbers, choose the correct bitmaps based on the numbers.

If your problem is splitting a two-digit number into its digits, just use arithmetic. Something like this should work:

// declare some variables
int tens; int ones;

// here is the number you want to convert // (it can be any number from 0 to 99) int num = 23;

// here is how we do the conversion ones = num; tens = 0; while (ones > 9) {   // more than 9 ones is too many   // so we trade 10 ones for 1 ten   // (the same as in pen-and-paper arithmetic)   ones = ones - 10;   tens = tens + 1; }

// now, do whatever you want with the ones and tens variables

Sure. The time library I opted to use doesn't have simple access to the individual hours/minutes/seconds, only as UNIX time, a long HH:MM:SS or short HH:MM string, or a third format which I'm not currently sure about. It's abundantly obvious that there's many more graceful ways of achieving this end - perhaps using a different time library as you suggest - however this seemed to be the simplest way forward. At least to me...it might seem that I've decided to wade through the dumb string swamp instead of taking the dry integer high road. Well, I say "seem"....I did....

Aside from the remnants of previous integers being left in the character spacing between updates, this works wonderfully for its end use. A bit more tweaking and it should look fantastic in the overhead console!

I'm trying to attach a photo of the working end product....the image has "failed security checks"?

Prostheta: Sure. The time library I opted to use doesn't have simple access to the individual hours/minutes/seconds, only as UNIX time, a long HH:MM:SS or short HH:MM string, or a third format which I'm not currently sure about.

What time library is that? Where did you get it?

Aside from the remnants of previous integers being left in the character spacing between updates, this works wonderfully for its end use. A bit more tweaking and it should look fantastic in the overhead console!

Why would anything be "left in the character spacing"? Wouldn't the new digit be put in exactly the same spot as the old digit? If not, why not?

The library I am using for the RTC is from Rinky Dink Electronics:

http://www.rinkydinkelectronics.com/library.php?id=74

It's the same source as the I2C_OLED library. I chose that one simply because the Adafruit library decides to miss out every other line on my OLEDs, leaving me with 32 spaced lines rather than 64 contiguous ones.

More than likely it was a case of me choosing the library based on the availability of good documentation and working examples. This isn't my day rodeo, so on some level I chose expediency of results (in spite of hiccups) rather than what might have been a more logical choice.

odometer: What time library is that? Where did you get it? Why would anything be "left in the character spacing"? Wouldn't the new digit be put in exactly the same spot as the old digit? If not, why not?

The bitmapped numbers I am using are not monospaced. The code was set up to place the second hours character to the left of the centralised spacer colon leaving a 2-pixel gap, then setting it's width as an offset (xOff). The first hours character is then placed to the left of the second hours character, again with a 2-pixel gap. The minutes are done in more or less the same manner, but in reverse.

For example, when the minutes roll over from 19 to 20, the "2" is wider than the "1", pushing that 2-pixel gap along a bit. Whatever remnants are on the display from the "9" are left in the new gap placement, and the "0" is placed further along to the right. Does that make sense?

Remnants of previous characters are being left in those 2-pixel gaps when the character widths alter. It's a simple fix where I need to force a full display refresh. It's only changing once per minute anyway, so flicker shouldn't be that much of an issue, if at all.

Prostheta: More than likely it was a case of me choosing the library based on the availability of good documentation and working examples. This isn't my day rodeo, so on some level I chose expediency of results (in spite of hiccups) rather than what might have been a more logical choice.

The documentation left something to be desired, as it did not mention the getTime() method.

No reason not to dig around in the code, really, if you want to know what it is you have to work with.

BTW, what is a "day rodeo"?

Prostheta: The bitmapped numbers I am using are not monospaced.

Non-monospaced numbers? For a clock?

Day rodeo…what I do to earn money or for “more relevant enjoyment”. By trade I’m a professional manufacturing woodworker and CAD engineer. I studied a BSc (hons) in Computing back in the early 90s, grew up on the Z80 and discrete electronics. I’ve allowed my programming to fall by the wayside simply because I haven’t dedicated constant time to it, so I’m picking up rusty tools and wondering why they’re not cutting it any more. :smiley:

Monospaced would have been an option however I converted the characters I am using from a TTF called “Downtown” which matches the typeface around my PT Cruiser. I figured that it wouldn’t be too much of a push to kern the characters around a centralised colon separator. It looks pretty good as it’s chugging away on my desk right now. I fixed the remnant screen data, but I’m now wondering why the RTC is running approximately seven minutes behind time.

I’m going to push the current time to the RTC and see if the constant reboots have been adding delays to the clock. Can’t see why they would, however it would be a problem if the RTC paused every time the board was powered up/down as part of a car install…

Code as it stands:

#include <OLED_I2C.h>
#include <DS3231.h>

DS3231  rtc(SDA, SCL);
OLED  myOLED(SDA, SCL, 8);

int xOff;
int charSpacing = 2;
int clockCentre = 62;
String currentClock, oldClock;

extern uint8_t char_colon[];
extern uint8_t char_0[];
extern uint8_t char_1[];
extern uint8_t char_2[];
extern uint8_t char_3[];
extern uint8_t char_4[];
extern uint8_t char_5[];
extern uint8_t char_6[];
extern uint8_t char_7[];
extern uint8_t char_8[];
extern uint8_t char_9[];
extern uint8_t SmallFont[];

void setup()
{
  myOLED.begin();
  rtc.begin();

  // pre-set clock string to get valid initial update condition
  currentClock = rtc.getTimeStr(FORMAT_SHORT);

  // The following lines can be uncommented to set the date and time
  // rtc.setDOW(SUNDAY);          // Set Day-of-Week
  // rtc.setTime(12, 00, 00);     // Set the time to xx:xx:xx (24hr format)
  // rtc.setDate(30, 10, 2016);   // Set the date to dd:mm:yyyy
  
}

void loop()
{
  myOLED.setFont(SmallFont);
  myOLED.print(rtc.getDOWStr(), LEFT, 0);
  myOLED.print(rtc.getDateStr(), RIGHT, 0);
  myOLED.drawBitmap(clockCentre, 32, char_colon, 5, 15);
  delay (1000);

  currentClock = rtc.getTimeStr();
  if ( oldClock != currentClock ) { myOLED.update(); myOLED.clrScr(); }
  oldClock = currentClock;

//
// Switch Case for second hour character and set offset for first hour character
//
  switch (currentClock.charAt(1)) {
  case '0':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_0, 16, 48);
      break;
  case '1':
      xOff = 8;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_1, 8, 48);
      break;
  case '2':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_2, 16, 48);
      break;
  case '3':
      xOff = 19;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_3, 24, 48);
      break;
  case '4':
      xOff = 24;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_4, 24, 48);
      break;
  case '5':
      xOff = 17;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_5, 24, 48);
      break;
  case '6':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_6, 16, 48);
      break;
  case '7':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_7, 16, 48);
      break;
  case '8':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_8, 16, 48);
      break;
  case '9':
      xOff = 16;
      myOLED.drawBitmap(clockCentre-xOff-charSpacing, 16, char_9, 16, 48);
      break;
  }

  
//
// Switch Case for first hour character offset from second hour character
//
  switch (currentClock.charAt(0)) {
  case '0':
      myOLED.drawBitmap(clockCentre-xOff-16-(charSpacing*2), 16, char_0, 16, 48);
      break;
  case '1':
      myOLED.drawBitmap(clockCentre-xOff-8-(charSpacing*2), 16, char_1, 8, 48);
      break;
  case '2':
      myOLED.drawBitmap(clockCentre-xOff-16-(charSpacing*2), 16, char_2, 16, 48);
      break;
  }


//
// Switch Case for first minute character and set offset for second minute character
//
  switch (currentClock.charAt(3)) {
  case '0':
      xOff = 16;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_0, 16, 48);
      break;
  case '1':
      xOff = 8;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_1, 8, 48);
      break;
  case '2':
      xOff = 16;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_2, 16, 48);
      break;
  case '3':
      xOff = 19;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_3, 24, 48);
      break;
  case '4':
      xOff = 24;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_4, 24, 48);
      break;
  case '5':
      xOff = 17;
      myOLED.drawBitmap(clockCentre+5+charSpacing, 16, char_5, 24, 48);
      break;
  }

  
//
// Switch Case for first hour character offset from second hour character
//
  switch (currentClock.charAt(4)) {
  case '0':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_0, 16, 48);
      break;
  case '1':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_1, 8, 48);
      break;
  case '2':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_2, 16, 48);
      break;
  case '3':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_3, 24, 48);
      break;
  case '4':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_4, 24, 48);
      break;
  case '5':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_5, 24, 48);
      break;
  case '6':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_6, 16, 48);
      break;
  case '7':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_7, 16, 48);
      break;
  case '8':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_8, 16, 48);
      break;
  case '9':
      myOLED.drawBitmap(clockCentre+5+xOff+(charSpacing*2), 16, char_9, 16, 48);
      break;
  }

}

External graphics.c file for the bitmaps:

#include <avr/pgmspace.h>

const uint8_t char_colon[] PROGMEM={
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 
};

const uint8_t char_0[] PROGMEM={
0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0,   // 0x0010 (16) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0030 (48) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0040 (64) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0050 (80) pixels
0x07, 0x1F, 0x3F, 0x7F, 0x7F, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0xFC, 0x7F, 0x7F, 0x3F, 0x1F, 0x07,   // 0x0060 (96) pixels
};

const uint8_t char_1[] PROGMEM={
0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0010 (16) pixels
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0030 (48) pixels
};

const uint8_t char_2[] PROGMEM={
0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0,   // 0x0010 (16) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0030 (48) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0xF8, 0xFE, 0xFF, 0xFF, 0x7F, 0x1F, 0x07, 0x00,   // 0x0040 (64) pixels
0x00, 0x00, 0x00, 0xC0, 0xF8, 0xFE, 0xFF, 0xFF, 0xFF, 0x3F, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00,   // 0x0050 (80) pixels
0xC0, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,   // 0x0060 (96) pixels
};

const uint8_t char_3[] PROGMEM={
0x00, 0x00, 0x00, 0x1F, 0x3F, 0x3E, 0x7E, 0x7C, 0xFC, 0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00,   // 0x0010 (16) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0020 (32) pixels
0x00, 0x01, 0x03, 0x07, 0x1F, 0xFF, 0xFF, 0xFE, 0xFC, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0030 (48) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,   // 0x0040 (64) pixels
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0050 (80) pixels
0x00, 0x80, 0xC0, 0xE0, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0060 (96) pixels
0x02, 0x0E, 0x1E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x7F, 0xFF, 0xFC,   // 0x0070 (112) pixels
0xF8, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,   // 0x0080 (128) pixels
0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xFC, 0xFF, 0x7F, 0x7F, 0x3F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0090 (144) pixels
};

const uint8_t char_4[] PROGMEM={
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0,   // 0x0010 (16) pixels
0xF8, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0020 (32) pixels
0x00, 0x00, 0x00, 0xC0, 0xF0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0030 (48) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0xFC, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF,   // 0x0040 (64) pixels
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF0, 0xFC, 0xFF, 0xFF,   // 0x0050 (80) pixels
0xFF, 0x3F, 0x0F, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0060 (96) pixels
0x20, 0x38, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0xFF, 0xFF, 0xFF,   // 0x0070 (112) pixels
0xFF, 0xFF, 0x3E, 0x3E, 0x3E, 0x3E, 0x1E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0080 (128) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0090 (144) pixels
};

const uint8_t char_5[] PROGMEM={
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,   // 0x0010 (16) pixels
0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,   // 0x0020 (32) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0030 (48) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0040 (64) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,   // 0x0050 (80) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0060 (96) pixels
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFF, 0xFE, 0xFC, 0xF8,   // 0x0070 (112) pixels
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,   // 0x0080 (128) pixels
0xF8, 0xF8, 0xF8, 0xFC, 0xFF, 0x7F, 0x7F, 0x3F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0090 (144) pixels
};

const uint8_t char_6[] PROGMEM={
0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0,   // 0x0010 (16) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, 0x07,   // 0x0030 (48) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0040 (64) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFF, 0xFE, 0xFE, 0xFC, 0xF0,   // 0x0050 (80) pixels
0x07, 0x1F, 0x3F, 0x7F, 0xFF, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xFF, 0x7F, 0x7F, 0x3F, 0x0F,   // 0x0060 (96) pixels
};

const uint8_t char_7[] PROGMEM={
0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07,   // 0x0010 (16) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00,   // 0x0020 (32) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00,   // 0x0030 (48) pixels
0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0040 (64) pixels
0x00, 0x00, 0xE0, 0xFE, 0xFF, 0xFF, 0xFF, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0050 (80) pixels
0xC0, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0060 (96) pixels
};

const uint8_t char_8[] PROGMEM={
0xC0, 0xF8, 0xFC, 0xFE, 0xFE, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFE, 0xFE, 0xFC, 0xF0, 0xC0,   // 0x0010 (16) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0030 (48) pixels
0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x80, 0x00, 0x00, 0x80, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F,   // 0x0040 (64) pixels
0xE0, 0xF8, 0xFD, 0xFF, 0xFF, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFF, 0xFF, 0xFD, 0xF8, 0xE0,   // 0x0050 (80) pixels
0x07, 0x1F, 0x3F, 0x7F, 0x7F, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0xFC, 0x7F, 0x7F, 0x3F, 0x1F, 0x07,   // 0x0060 (96) pixels
};

const uint8_t char_9[] PROGMEM={
0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x3F, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0,   // 0x0010 (16) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0020 (32) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0030 (48) pixels
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0040 (64) pixels
0x00, 0x03, 0x07, 0x0F, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0050 (80) pixels
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0x03,   // 0x0060 (96) pixels
};

Prostheta: Monospaced would have been an option however I converted the characters I am using from a TTF called "Downtown" which matches the typeface around my PT Cruiser. I figured that it wouldn't be too much of a push to kern the characters around a centralised colon separator. It looks pretty good as it's chugging away on my desk right now. I fixed the remnant screen data, but I'm now wondering why the RTC is running approximately seven minutes behind time.

Is the clock "running" behind? Or is the display frozen?

You would be better off losing the Strings (I hear Strings have problems) and using arithmetic instead.

As for the components of the time: in the relevant library, I found this

class Time
{
public:
    uint8_t     hour;
    uint8_t     min;
    uint8_t     sec;
    uint8_t     date;
    uint8_t     mon;
    uint16_t    year;
    uint8_t     dow;

    Time();
};

and this

class DS3231
{
    public:
        DS3231(uint8_t data_pin, uint8_t sclk_pin);
        void    begin();
        Time    getTime();

        // et cetera, et cetera

so you can use getTime() to get at the time.

Gotcha, nice find! Thanks. So:

getTime(hour)

....is what I should be using for an arithmetic-based routine instead?

If I come to rewriting this Sketch, I'll peel it all back and use a less roundabout method. For the moment its been running nicely on the desk for a few hours since the last reboot. I might artificially create a test loop to accelerate time to run it "around the clock" however all seems fine, silly String games excepted.

Not sure why the clock was running behind. Rebooting showed that the RTC was reporting time a few minutes behind. It's on the dot right now though. I highly suspect that constant reboots were the problem. I'm wondering if they set the RTC back a little each time....I was doing a lot of modification and testing to the Sketch this afternoon....

Prostheta: Gotcha, nice find! Thanks. So:

getTime(hour)

....is what I should be using for an arithmetic-based routine instead?

No, more something like this:

Time t = getTime();
int h = t.hour;
int m = t.min;

but you don't really need the last two lines as you can just write t.hour and t.min. At least I think that's how it works.

Does your clock have provisions for you to adjust the time? What about Daylight Saving Time? Is that a concern where you live?

Thanks Odometer. DST just kicked in today, so yes it's a concern. The objective is that this install will have an accessible USB port in case I want to update the Sketch or re-align the RTC. One would prefer a maintenance-free end product, however plugging in a laptop twice a year (worst case) or adding in a DST routine to a future edit shouldn't be too onerous a task.

From what I can make out, the DS3231 is highly-reliable so it should keep good time over the period of a year. Certainly nothing that a quick RTC set can't cure.

I totally forgot to include an image of the end product since I couldn't attach....

|500x413

The constants defined at the head of the code allow the clock positioning (default is centred) plus character spacing to be set. Once I can figure out why I can't get these UTFT fonts working for the day/date, I think this is good to go to be installed. Next is the 3D compass and figuring out the resistance gradient of the Chrysler external thermistor (no datasheet).

I'm thinking that re-coding from string to numeric logic might be a good idea sooner. I just thought about how to code a loop creating a test string to push into currentTime and thought...yeah...strings...not good...

There's only one way to find out if that's how it works, right?