Posts with «7-segment led» label

Single digit clock - method and apparatus

I recently found, at the bottom of a drawer, my forgotten numitron single-tube clock. It has a LiPo battery which still lights up the filaments, but no RTC to actually show time. It has a single button, which activates the display (numitron tube) when pressed. Indeed, some digits flash on, but inconsistently. And, as a clock, one would want to be able to also set the time, which is definitely not possible in this current version.

The required revision consists in:

  • adding RTC
  • adding a second button
  • updating the software (by adding the ability to set the time through buttons)

The method I devised for setting up the time follows this state-machine diagram,

where "Set time" state is part of this bigger picture:


The single digit clock has 2 buttons: "Activate", which shows the time in a sequence of 3 or 4 digits, formatted as "Hh-Mm" or "h-Mm", and "Set", which starts the process of setting up the time. This is where most of the effort was put, since the actual displaying of time is really trivial.

The whole source code file is proudly presented below (as answer to the lots of questions in the above mentioned old post).

/*************************************************************************
* Sketch for direct driving 7-segment numitron IV-9
*
* Segments are defined as follows:
*
* A
* ---
* B | | C
* --- D
* E | | F
* ---
* G
*
* Decimal point/comma is segment H.
* Common pin is wired to Vcc (would be nice to wire it to D11/MOSI
* instead, which is also PWM (for brightness)).
* To light up a segment, just connect it to GND.
*
* To display a digit, ground these Arduino pins:
* 0: 6, 7, 8, 10, 11, 13
* 1: 7, 8
* 2: 6, 8, 10, 11, 12
* 3: 6, 7, 8, 11, 12
* 4: 7, 8, 12, 13
* 5: 6, 7, 11, 12, 13
* 6: 6, 7, 10, 11, 12, 13
* 7: 6, 7, 8
* 8: 6, 7, 8, 10, 11, 12, 13
* 9: 6, 7, 8, 11, 12, 13
*
*************************************************************************/

#include <Arduino.h>
#include <Wire.h>
#include "DS1307.h"

#define _DEBUG_

// arduino pins connected to tube terminals;
// chosen based on the natural positioning of the Numitron tube on Pro Mini board;
#define segA 6 // tube pin 5
#define segB 13 // tube pin 6
#define segC 8 // tube pin 3
#define segD 12 // tube pin 7
#define segE 10 // tube pin 9
#define segF 7 // tube pin 4
#define segG 11 // tube pin 8
#define segH 9 // tube pin 2

// button to initiate the setting up of the time;
#define PIN_BUTTON_SET_TIME 4 // D4

// button to activate the display or to increment the time digit;
#define PIN_BUTTON_ACTIVATE 17 // A3


byte segmentPin[8] = {segA, segB, segC, segD, segE, segF, segG};


byte digits[10][7] =
{
// A B C D E F G
{0, 0, 0, 1, 0, 0, 0}, // 0
{1, 1, 0, 1, 1, 0, 1}, // 1
{0, 1, 0, 0, 0, 1, 0}, // 2
{0, 1, 0, 0, 1, 0, 0}, // 3
{1, 0, 0, 0, 1, 0, 1}, // 4
{0, 0, 1, 0, 1, 0, 0}, // 5
{0, 0, 1, 0, 0, 0, 0}, // 6
{0, 1, 0, 1, 1, 0, 1}, // 7
{0, 0, 0, 0, 0, 0, 0}, // 8
{0, 0, 0, 0, 1, 0, 0} // 9
};


byte state[4][7] =
{
// A B C D E F G
{1, 0, 0, 0, 0, 0, 1}, // H
{1, 0, 1, 0, 0, 0, 1}, // h
{0, 0, 0, 1, 0, 0, 1}, // M
{1, 1, 1, 0, 0, 0, 1}, // m
};


volatile boolean wasTimeEverSet = false;
volatile boolean showingTime = false;
volatile boolean settingTime = false;

byte crtIndex = 0; // 0..3, index in array timeDigits;
byte timeDigits[4] = {0, 1, 2, 3};
int hour = 0;
int minute = 0;
int second = 0;
byte crtValue = 0; // used when setting the time, one digit at a time (for HHMM);
short crtState = -1; // used when setting the time;
boolean newState = false;


void setup()
{
#ifdef _DEBUG_
Serial.begin(9600);
Serial.println("in setup");
#endif

// each of display's 7 segment is connected to an output;
for (byte i=0; i<7; i++)
{
pinMode(segmentPin[i], OUTPUT);
}

// buttons to activate tube and for setting up the time;
pinMode(PIN_BUTTON_ACTIVATE, INPUT_PULLUP);
pinMode(PIN_BUTTON_SET_TIME, INPUT_PULLUP);

blankDisplay();
}


void loop()
{
if (digitalRead(PIN_BUTTON_ACTIVATE) == LOW)
{
#ifdef _DEBUG_
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
delay(200); // debouncing;

if (settingTime)
{
newState = false;

crtValue++;
if (crtValue > 9)
crtValue = 0;
displayValue(crtValue);

#ifdef _DEBUG_
Serial.print ("crtValue=");
Serial.println(crtValue);
#endif
}
else
{
getTimeFromRTC();
splitTime();

// show time as (h)h-mm;
showingTime = true;
}
}

if (digitalRead(PIN_BUTTON_SET_TIME) == LOW)
{
delay(200); // debouncing;

#ifdef _DEBUG_
Serial.print("crtState=");
Serial.println(crtState);
#endif

if (crtState == -1)
{
// user is initiating setting up the time;
settingTime = true;
}

if (settingTime)
{
newState = true;
crtState++;
}

#ifdef _DEBUG_
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
}

if (showingTime)
{
#ifdef _DEBUG_
Serial.print ("show time, digit ");
Serial.println(crtIndex);
#endif
if (crtIndex == 0 && timeDigits[0] == 0)
{
// do not show the leading 0;
}
else
{
if (crtIndex == 2)
{
// show the dash between hours and minutes;
displayDash();
// hold it for a second;
delay(1000);
}

// make the digit flash (otherwise, if 2 consecutive digits are the same, you won't see a difference);
displayDigit(crtIndex);
// hold the digit for a second;
delay(1000);
}

crtIndex++;
if (crtIndex > 3)
{
showingTime = false; // time will show again when button is pressed;
crtIndex = 0;
blankDisplay();
}
}

if (settingTime)
{
if (newState)
{
newState = false;

// need to save the crtValue;
if (crtState > 0)
{
#ifdef _DEBUG_
Serial.print("set value ");
Serial.print(crtValue);
Serial.print(" at index ");
Serial.println(crtState-1);
#endif
timeDigits[crtState-1] = crtValue;
}

if (crtState > 3)
{
settingTime = false;
crtState = -1;
blankDisplay();

#ifdef _DEBUG_
Serial.print("saving time: ");
Serial.print(10 * timeDigits[0] + timeDigits[1]);
Serial.print(":");
Serial.println(10 * timeDigits[2] + timeDigits[3]);
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
// time is set only after all 4 digits (HhMm) were input, that is, after state "m" is left;
setTime(10 * timeDigits[0] + timeDigits[1], 10 * timeDigits[2] + timeDigits[3], 0);
}
else
{
displayCrtState(); // one of the 4: H, h, M, m
// hold it for a bit;
delay(100);

// start setting the value from 0;
crtValue = 0;
}
}
}
}


void displayDigit(byte index)
{
blankDisplay();
delay(100);

byte digit = timeDigits[index];

// turn on the necessary segments of the digit;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], digits[digit][i]);
}
}


void displayValue(byte crtValue)
{
blankDisplay();
delay(100);

// turn on the necessary segments;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], digits[crtValue][i]);
}
}


void blankDisplay()
{
// turn off all 7 segments;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], 1);
}
}


void displayDash()
{
blankDisplay();
delay(100);
digitalWrite(segD, 0);
}


void displayCrtState()
{
#ifdef _DEBUG_
Serial.print ("crt state is ");
Serial.println(crtState);
#endif

blankDisplay();
delay(100);

// turn on the necessary segments of the state/letter;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], state[crtState][i]);
}
}


//**********************************************************************************
// Read the entire RTC buffer
//
void getTimeFromRTC()
{
int rtc[7];
RTC_DS1307.get(rtc, true);

// check to avoid glitches;
if (rtc[DS1307_MIN] < 60 && rtc[DS1307_HR] < 24 && rtc[DS1307_SEC] < 60)
{
second = rtc[DS1307_SEC];
minute = rtc[DS1307_MIN];
hour = rtc[DS1307_HR];
}
/*
// check to avoid glitches;
if (rtc[DS1307_YR] <= 2050 && rtc[DS1307_MTH] <= 12 && rtc[DS1307_DATE] <= 31)
{
day = rtc[DS1307_DATE];
month = rtc[DS1307_MTH];
year = rtc[DS1307_YR];
}
*/
// The RTC may have a dead battery or may have never been initialized
// If so, the RTC doesn't run until it is set.
// Here we check once to see if it is running and start it if not.
if (!wasTimeEverSet) {
wasTimeEverSet = true;
if (hour == 0 && minute == 0 && second == 0)
{
// set an arbitrary time to get the RTC going;
setTime(10,23,45);
}
}

#ifdef _DEBUG_
Serial.print("Time is ");
Serial.print(rtc[DS1307_HR]);
Serial.print(":");
Serial.print(rtc[DS1307_MIN]);
Serial.print(":");
Serial.println(rtc[DS1307_SEC]);
#endif

}


//**********************************************************************************
//
void setTime(int hh, int mm, int ss)
{
RTC_DS1307.stop();
RTC_DS1307.set(DS1307_SEC, ss);
RTC_DS1307.set(DS1307_MIN, mm);
RTC_DS1307.set(DS1307_HR, hh);
RTC_DS1307.start();
}


void splitTime()
{
timeDigits[0] = hour / 10;
timeDigits[1] = hour % 10;
timeDigits[2] = minute/10;
timeDigits[3] = minute%10;
}


The single digit numitron clock is the simplest possible clock, in terms of the number of components included. The numitron tube is connected directly to the processor's outputs, the common electrode being wired to Vcc. I noticed that the 3.7V LiPo battery is not reliably capable to light up the filaments. USB's 5V gives the tube a stable functionality.

Below are some photos.




I was actually able to design the improvised RTC "shield" for ProMini shown in the above photo. Ordered from oshpark and shared here:


The "second" simplest single digit clock would be the one using a 7-segment LED display. It is the "second" simplest just because it requires 7 more current-limiting resistors. Otherwise, if the wiring (numitron tube and 7-segment display) is similar, the above code works with no changes (tried and proven).




Shown above is another ProMini-based prototype of a single digit clock, without the RTC shield. I expect this single digit 7-segment clock to work with the LiPo battery shield (unlike the single numitron clock). Note that the display is common anode, with the anode wired to Vcc and each cathode connected to processor's outputs. The segment is lit when the output is grounded (set to digital 0), similar to the numitron's driving.


Single digit clock - method and apparatus

I recently found, at the bottom of a drawer, my forgotten numitron single-tube clock. It has a LiPo battery which still lights up the filaments, but no RTC to actually show time. It has a single button, which activates the display (numitron tube) when pressed. Indeed, some digits flash on, but inconsistently. And, as a clock, one would want to be able to also set the time, which is definitely not possible in this current version.

The required revision consists in:

  • adding RTC
  • adding a second button
  • updating the software (by adding the ability to set the time through buttons)

The method I devised for setting up the time follows this state-machine diagram,

where "Set time" state is part of this bigger picture:


The single digit clock has 2 buttons: "Activate", which shows the time in a sequence of 3 or 4 digits, formatted as "Hh-Mm" or "h-Mm", and "Set", which starts the process of setting up the time. This is where most of the effort was put, since the actual displaying of time is really trivial.

The whole source code file is proudly presented below (as answer to the lots of questions in the above mentioned old post).

/*************************************************************************
* Sketch for direct driving 7-segment numitron IV-9
*
* Segments are defined as follows:
*
* A
* ---
* B | | C
* --- D
* E | | F
* ---
* G
*
* Decimal point/comma is segment H.
* Common pin is wired to Vcc (would be nice to wire it to D11/MOSI
* instead, which is also PWM (for brightness)).
* To light up a segment, just connect it to GND.
*
* To display a digit, ground these Arduino pins:
* 0: 6, 7, 8, 10, 11, 13
* 1: 7, 8
* 2: 6, 8, 10, 11, 12
* 3: 6, 7, 8, 11, 12
* 4: 7, 8, 12, 13
* 5: 6, 7, 11, 12, 13
* 6: 6, 7, 10, 11, 12, 13
* 7: 6, 7, 8
* 8: 6, 7, 8, 10, 11, 12, 13
* 9: 6, 7, 8, 11, 12, 13
*
*************************************************************************/

#include <Arduino.h>
#include <Wire.h>
#include "DS1307.h"

#define _DEBUG_

// arduino pins connected to tube terminals;
// chosen based on the natural positioning of the Numitron tube on Pro Mini board;
#define segA 6 // tube pin 5
#define segB 13 // tube pin 6
#define segC 8 // tube pin 3
#define segD 12 // tube pin 7
#define segE 10 // tube pin 9
#define segF 7 // tube pin 4
#define segG 11 // tube pin 8
#define segH 9 // tube pin 2

// button to initiate the setting up of the time;
#define PIN_BUTTON_SET_TIME 4 // D4

// button to activate the display or to increment the time digit;
#define PIN_BUTTON_ACTIVATE 17 // A3


byte segmentPin[8] = {segA, segB, segC, segD, segE, segF, segG};


byte digits[10][7] =
{
// A B C D E F G
{0, 0, 0, 1, 0, 0, 0}, // 0
{1, 1, 0, 1, 1, 0, 1}, // 1
{0, 1, 0, 0, 0, 1, 0}, // 2
{0, 1, 0, 0, 1, 0, 0}, // 3
{1, 0, 0, 0, 1, 0, 1}, // 4
{0, 0, 1, 0, 1, 0, 0}, // 5
{0, 0, 1, 0, 0, 0, 0}, // 6
{0, 1, 0, 1, 1, 0, 1}, // 7
{0, 0, 0, 0, 0, 0, 0}, // 8
{0, 0, 0, 0, 1, 0, 0} // 9
};


byte state[4][7] =
{
// A B C D E F G
{1, 0, 0, 0, 0, 0, 1}, // H
{1, 0, 1, 0, 0, 0, 1}, // h
{0, 0, 0, 1, 0, 0, 1}, // M
{1, 1, 1, 0, 0, 0, 1}, // m
};


volatile boolean wasTimeEverSet = false;
volatile boolean showingTime = false;
volatile boolean settingTime = false;

byte crtIndex = 0; // 0..3, index in array timeDigits;
byte timeDigits[4] = {0, 1, 2, 3};
int hour = 0;
int minute = 0;
int second = 0;
byte crtValue = 0; // used when setting the time, one digit at a time (for HHMM);
short crtState = -1; // used when setting the time;
boolean newState = false;


void setup()
{
#ifdef _DEBUG_
Serial.begin(9600);
Serial.println("in setup");
#endif

// each of display's 7 segment is connected to an output;
for (byte i=0; i<7; i++)
{
pinMode(segmentPin[i], OUTPUT);
}

// buttons to activate tube and for setting up the time;
pinMode(PIN_BUTTON_ACTIVATE, INPUT_PULLUP);
pinMode(PIN_BUTTON_SET_TIME, INPUT_PULLUP);

blankDisplay();
}


void loop()
{
if (digitalRead(PIN_BUTTON_ACTIVATE) == LOW)
{
#ifdef _DEBUG_
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
delay(200); // debouncing;

if (settingTime)
{
newState = false;

crtValue++;
if (crtValue > 9)
crtValue = 0;
displayValue(crtValue);

#ifdef _DEBUG_
Serial.print ("crtValue=");
Serial.println(crtValue);
#endif
}
else
{
getTimeFromRTC();
splitTime();

// show time as (h)h-mm;
showingTime = true;
}
}

if (digitalRead(PIN_BUTTON_SET_TIME) == LOW)
{
delay(200); // debouncing;

#ifdef _DEBUG_
Serial.print("crtState=");
Serial.println(crtState);
#endif

if (crtState == -1)
{
// user is initiating setting up the time;
settingTime = true;
}

if (settingTime)
{
newState = true;
crtState++;
}

#ifdef _DEBUG_
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
}

if (showingTime)
{
#ifdef _DEBUG_
Serial.print ("show time, digit ");
Serial.println(crtIndex);
#endif
if (crtIndex == 0 && timeDigits[0] == 0)
{
// do not show the leading 0;
}
else
{
if (crtIndex == 2)
{
// show the dash between hours and minutes;
displayDash();
// hold it for a second;
delay(1000);
}

// make the digit flash (otherwise, if 2 consecutive digits are the same, you won't see a difference);
displayDigit(crtIndex);
// hold the digit for a second;
delay(1000);
}

crtIndex++;
if (crtIndex > 3)
{
showingTime = false; // time will show again when button is pressed;
crtIndex = 0;
blankDisplay();
}
}

if (settingTime)
{
if (newState)
{
newState = false;

// need to save the crtValue;
if (crtState > 0)
{
#ifdef _DEBUG_
Serial.print("set value ");
Serial.print(crtValue);
Serial.print(" at index ");
Serial.println(crtState-1);
#endif
timeDigits[crtState-1] = crtValue;
}

if (crtState > 3)
{
settingTime = false;
crtState = -1;
blankDisplay();

#ifdef _DEBUG_
Serial.print("saving time: ");
Serial.print(10 * timeDigits[0] + timeDigits[1]);
Serial.print(":");
Serial.println(10 * timeDigits[2] + timeDigits[3]);
Serial.print("settingTime=");
Serial.println(settingTime);
#endif
// time is set only after all 4 digits (HhMm) were input, that is, after state "m" is left;
setTime(10 * timeDigits[0] + timeDigits[1], 10 * timeDigits[2] + timeDigits[3], 0);
}
else
{
displayCrtState(); // one of the 4: H, h, M, m
// hold it for a bit;
delay(100);

// start setting the value from 0;
crtValue = 0;
}
}
}
}


void displayDigit(byte index)
{
blankDisplay();
delay(100);

byte digit = timeDigits[index];

// turn on the necessary segments of the digit;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], digits[digit][i]);
}
}


void displayValue(byte crtValue)
{
blankDisplay();
delay(100);

// turn on the necessary segments;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], digits[crtValue][i]);
}
}


void blankDisplay()
{
// turn off all 7 segments;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], 1);
}
}


void displayDash()
{
blankDisplay();
delay(100);
digitalWrite(segD, 0);
}


void displayCrtState()
{
#ifdef _DEBUG_
Serial.print ("crt state is ");
Serial.println(crtState);
#endif

blankDisplay();
delay(100);

// turn on the necessary segments of the state/letter;
for (byte i=0; i<7; i++)
{
digitalWrite(segmentPin[i], state[crtState][i]);
}
}


//**********************************************************************************
// Read the entire RTC buffer
//
void getTimeFromRTC()
{
int rtc[7];
RTC_DS1307.get(rtc, true);

// check to avoid glitches;
if (rtc[DS1307_MIN] < 60 && rtc[DS1307_HR] < 24 && rtc[DS1307_SEC] < 60)
{
second = rtc[DS1307_SEC];
minute = rtc[DS1307_MIN];
hour = rtc[DS1307_HR];
}
/*
// check to avoid glitches;
if (rtc[DS1307_YR] <= 2050 && rtc[DS1307_MTH] <= 12 && rtc[DS1307_DATE] <= 31)
{
day = rtc[DS1307_DATE];
month = rtc[DS1307_MTH];
year = rtc[DS1307_YR];
}
*/
// The RTC may have a dead battery or may have never been initialized
// If so, the RTC doesn't run until it is set.
// Here we check once to see if it is running and start it if not.
if (!wasTimeEverSet) {
wasTimeEverSet = true;
if (hour == 0 && minute == 0 && second == 0)
{
// set an arbitrary time to get the RTC going;
setTime(10,23,45);
}
}

#ifdef _DEBUG_
Serial.print("Time is ");
Serial.print(rtc[DS1307_HR]);
Serial.print(":");
Serial.print(rtc[DS1307_MIN]);
Serial.print(":");
Serial.println(rtc[DS1307_SEC]);
#endif

}


//**********************************************************************************
//
void setTime(int hh, int mm, int ss)
{
RTC_DS1307.stop();
RTC_DS1307.set(DS1307_SEC, ss);
RTC_DS1307.set(DS1307_MIN, mm);
RTC_DS1307.set(DS1307_HR, hh);
RTC_DS1307.start();
}


void splitTime()
{
timeDigits[0] = hour / 10;
timeDigits[1] = hour % 10;
timeDigits[2] = minute/10;
timeDigits[3] = minute%10;
}



Enclosure ideas for WiFiChron and other clocks

It turns out that most electronics, even prototypes, can be easily enclosed with Lego. And that means no screws, no glue, no fasteners, zero tools, just the bricks and some imagination.

This is the HDSP clock variant with 1" displays driven by HT16K33 (introduced here). The board was cut and filed (0.5mm on each side) to fit snug between the walls (see this).


Next is a HDSP clock variant with two Adafruit Quad Alphanumeric displays.


Similarly, the PCB was cut and filed a bit. The assembly fits solidly between the bricks (no movement when shaken). As in the previous build, the exposed PCB is kind-of-required to allow access to the two buttons (set hours, set minutes).

Both of the above can be mounted on a Lego wall (as found in schools) or they can desk-stand on their own.

Here is an example of a Lego-encapsulated WifiChron.


The PCB was also filed about 0.5mm on each side to fit between the lateral brick walls. It did not have to be fastened in any other way. The ESP8266 module fits inside nicely. The 3 buttons and the USB mini B connector are all easily accessible from the back.

Below is the Lego version of the Axiris clock.



Since it does not have any buttons, the time is set through Bluetooth (command "SET TIME=hh:mm", sent from Terminal app while BT paired).

And finally, a couple of OLED clocks, both running the same software on similar hardware: pro-mini + OLED shield and wsduino + 2.42" OLED shield, respectively.



Note that this is the prototype version, using a LiPo battery with charger (similar to the one shown here).


Again, all the above enclosures feel solid: nothing moves or rattles when upside down or even shaken. I did not try dropping them though :)

And lastly, the WiFiChron with Adafruit quad 0.56" displays from the previous post, sandwiched between scrap plexiglass plates:




Experimenting with MAX6955

MAX6955 is an interesting LED driver chip. It is the primordial charlieplexing device, being the materialization of a technique invented by Charlie Allen of Maxim Integrated. Without understanding how charelieplexing works, it is actually counter-intuitive to wire multiple (up to 8) 16-segment displays to such a driver chip. Fortunately, Maxim has great documentation on how to do it.


My experimenting actually started with MAX6954. After many failed tries due to SPI issues (Maxim uses a special interpretation of the protocol, I read), I switched to MAX6955.

MAX6955 is the I2C sibling of MAX6954 (which uses SPI). They both have identical LED driving abilities, only the microcontroller interface part of the chips differ. Once, both chips were available in DIP-40 package. Now, MAX6955 only comes in SSOP-36 (MAX6954 is still available in DIP-40). Luckily, the pin configurations for the two chips are compatible, which allows for easy swap. For this reason, I designed a breakout board (shared at oshpark), so I can use the same setup I built for MAX6954.


Beside Maxim's, there isn't much documentation on how to control these chips. One of the reasons they are rarely used by hobbyists is probably their price (about $20 at digikey), although in the same range as the wildly popular MAX72xx LED driver (available at around $10 from digikey). In "reality", for some reason, MAX72xx could be had for $1 or less on ebay, unlike MAX6954/6955. Therefore, a hobby kit designed around MAX6955 would be "extremely" expensive compared with others using different LED driving chips (e.g. MAX72xx).

MAX6954/6955 was designed to drive common cathode LED displays, like MAX72xx. But unlike MAX72xx, it cannot be used for displays with common segments (which require multiplexing), like the ones below (and used here). MAX6954/6955 requires the 14/16-segment displays to be single digit/character, because not all segments will be wired together.


Below is the Arduino sketch I wrote that works very well with MAX6955. Although it uses a minimal set of commands, it is capable of displaying a character at a given position, light up the dot, and even scroll a long message.

#include "Wire.h"

#define ID_MAX6955 B1100000 
#define NUM_DISPLAYS 6

#define max6955_reg_decodeMode      0x01
#define max6955_reg_globalIntensity 0x02
#define max6955_reg_scanLimit       0x03
#define max6955_reg_configuration   0x04
#define max6955_reg_displayTest     0x07
#define max6955_reg_digitType       0x0C

int writeMAX6955(char command, char data)
{
    Wire.beginTransmission(ID_MAX6955);
    Wire.write(command);
    Wire.write(data);
    Wire.endTransmission();
}

void initMAX6955()
{
    Wire.begin();
    // ascii decoding for all digits;
    writeMAX6955(max6955_reg_decodeMode, 0xFF);
    // brightness: 0x00 =  1/16 (min on)  2.5 mA/seg;
    // 0x0F = 15/16 (max on) 37.5 mA/segment
    writeMAX6955(max6955_reg_globalIntensity, 0x07);
    // active displays: 0x07 -> all;
    writeMAX6955(max6955_reg_scanLimit, 0x07);
    // set normal operation;
    writeMAX6955(max6955_reg_configuration, 0x01);
    // segments/display: 0xFF=14-seg; 0=16 or 7-seg;
    writeMAX6955(max6955_reg_digitType, 0x00);
    // display test off;
    writeMAX6955(max6955_reg_displayTest, 0x00);
}

void setup()
{
  // set D8 and D9 to GND (for I2C address);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);

  initMAX6955();
  delay(100);
  clear();
//  writeDisplay("HI");
//  writeChar(0, 'A', true);
//  scrollDisplay("     HELLO WORLD", 300);
  writeTime(15, 24, 39);
}

void loop()
{
}

void writeDisplay(char* msg)
{
  for (byte i=0; i < NUM_DISPLAYS; i++)
  {
    if (i < strlen(msg))
      writeMAX6955(0x25-i, msg[i]);
    else
      writeMAX6955(0x25-i, ' ');
  }
}

void writeChar(byte pos, char letter, boolean dotLit)
{
  writeMAX6955(0x25-pos, (dotLit? 0x80 : 0) | letter);
}

void clear()
{
  for (byte i=0; i < NUM_DISPLAYS; i++)
    writeMAX6955(0x25-i, ' ');
}

void scrollDisplay(char* msg, int delayMs)
{
  for (int i=0; i<=strlen(msg); i++)
  {
    writeDisplay(msg+i);
    delay(delayMs);
  }
}

void writeTime(int hours, int minutes, int seconds)
{
  char h1 = (hours/10)? '0' + hours/10 : ' ';
  writeChar(0, h1, false);
  char h2 = '0' + (hours%10);
  writeChar(1, h2, true);
  char m1 = '0' + (minutes/10);
  writeChar(2, m1, false);
  char m2 = '0' + (minutes%10);
  writeChar(3, m2, true);
  char s1 = '0' + (seconds/10);
  writeChar(4, s1, false);
  char s2 = '0' + (seconds%10);
  writeChar(5, s2, false);
}

The I2C address of B1100000 was set by grounding AD0 and AD1 (see table 5 in the datasheet on how to change that address). In my setup, these 2 pins are connected to D8 and D9. Don't forget to pull-up SDA and SCL with 4k7-10k resistors.

The left-most digit is at position 0, accessible at address 0x25. "Digit 1" is the second from left, accessible at address 0x24, and so on. This is determined by the wiring. In my case, "CC0" (in the table 1 of this application note) represents the right-most display, and it is accessible at address 0x20.

Prototype 14-segment-display shield

There are many ways (*) to drive the 6-digit 14-segment common cathode display from Seeed Studio.
This time I chose to multiplex two MAX7221, a method described here (but used to drive a bi-color 8x8 LED matrix).


The code is based on LedControl library, which I extended to cover the definition and display of 14-segment characters (digits, upper case letters, and a few specials). Below is a relevant fragment of the code I added:

/*
* Segment names in the 14-segment (plus DP) display:
*
*     -     A
*   |\|/|   F,I,J,K,B
*    - -    G,H
*   |/|\|   E,N,M,L,C
*     -  .  D,P
*/
// my wiring:
//            GFEDCBAx
// 1st byte: B11111111
//
//            NHJIKMLP
// 2nd byte: B11111111

const static byte charTable14Seg[43][2] = {
    {B01111110,B10001000},  // 0
    {B00001100,B00001000},  // 1
    {B10110110,B01000000},  // 2
    {B00011110,B01000000},  // 3
    {B11001100,B01000000},  // 4
    {B11010010,B00000010},  // 5
    {B11111010,B01000000},  // 6
    {B00000010,B00001100},  // 7
    {B11111110,B01000000},  // 8
    {B11011110,B01000000},  // 9
    {B00000000,B01000000},  // :
    {B00000000,B01000000},  // ;
    {B00000000,B01000000},  // <
    {B00000000,B01000000},  // =
    {B00000000,B01000000},  // >
    {B00000000,B01000000},  // ?
    {B00000000,B01000000},  // @
    {B11101110,B01000000},  // A
    {B00011110,B01100100},  // B
    {B01110010,B00000000},  // C
    {B00011110,B00100100},  // D
    {B11110010,B01000000},  // E
    {B11100010,B01000000},  // F
    {B01111010,B01000000},  // G
    {B11101100,B01000000},  // H
    {B00000000,B00100100},  // I
    {B00111100,B00000000},  // J
    {B11100000,B00001010},  // K
    {B01110000,B00000000},  // L
    {B01101100,B00011000},  // M
    {B01101100,B000100L0},  // N
    {B01111110,B00000000},  // 0
    {B11100110,B01000000},  // P
    {B01111110,B00000010},  // Q
    {B11100110,B01000010},  // R
    {B11011010,B01000000},  // S
    {B00000010,B00100100},  // T
    {B01111100,B00000000},  // U
    {B01100000,B10001000},  // V
    {B01101100,B10000010},  // W
    {B00000000,B10011010},  // X
    {B00000000,B00011100},  // Y
    {B00010010,B10001000},  // Z
};
...
void setChar14Seg(byte pos, byte ascii)
{
  if (pos>7)
    return;

  if (ascii>90 || ascii<48)
    return;

  byte index = ascii - 48;
  for(byte seg=0; seg < 8; seg++)
  {
    SetLed(SEG_AG, pos, seg, charTable14Seg[index][0] & 1 << seg);
    SetLed(SEG_GN, pos, seg, charTable14Seg[index][1] & 1 << seg);
  }
}

This method (hardware and software) can be used for up to 8 14/16-segment displays.


(*) Should be the topic of a future post.


Home-made Wise Clock-based Alpha Clock Five

I saw Justin's recent post on Alpha Clock Five and I just couldn't resist not to try it myself too. Since I didn't have that clock, I thought of improvising one by making a 5-character display that would plug into my Wise Clock 4 board. The idea was easy, the implementation not so. After many hours of hand-wiring, this is how it looks like.


The displays are 1" single digit alphanumeric (common anode) from sparkfun, now retired. They came with non-functional dots, probably the reason they were less than $2 each.
The spacing between the individual displays is forced by the protoboard.


The 2 boards are connected through the pairs of 2x8 headers. All pins used by Alpha Clock 5 to drive the displays are wired to the unused header on the Wise Clock 4 board.


Not to mention  that the software compilation and upload worked without any glitch (after downloading the very nice DS1307RTC library)

The only regret is that this clock lacks seconds. One extra display would have added lots of extra value, but probably lacked the cool factor (the "6-letter clock" requires a lot more memory to store all 6-letter words than the approx 50k required by the 5-letter-word collection).

You should try this at home :)

ProMini clock shield with 7-segment bubble display

This clock was designed as a ProMini shield. It comes as a mostly-SMD kit, based on DS1307 with battery backup and the QDSP-6064 7-segment LED "bubble" display.


   US$18, free shipping to North America

The kit includes the following:
  • PCB
  • QDSP-6064
  • DS1307 SMD
  • 32kHz crystal
  • CR1220 coin battery
  • battery holder
  • 330 ohm resistor 0805 (8x)
  • tactile switch SMD (2x)
  • machined female pins



The assembled clock can be fitted with a LiPo battery shield for ProMini, as shown in this post (source code also provided there).
The current draw (measured at 20mA with an unmodified ProMini) can be minimized by removing the 2 LEDs on the ProMini board, as well as dimming the 7-segment bubble display through software (SevSeg library). One other way of maximizing the LiPo battery life cycle is by waking the clock from sleep mode at the press of the "minutes" button (on D2).

Schematic and board layout are shown below.