Showing posts with label 7-segment LED. Show all posts
Showing posts with label 7-segment LED. Show all posts

Friday, February 19, 2021

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.


Sunday, February 7, 2021

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:




Sunday, August 2, 2015

Introducing the "6-character alphanumeric LED Arduino shield"

This shield offers a quick way to add alphanumeric display capabilities to your Arduino project. It is based on the 14-segment 6-character LED display driven by two multiplexed MAX7221 drivers, through a method described in this application note.

 US$ 26 - free shipping to North America

The kit includes:
  • PCB
  • 2 x MAX7221 + 2 x 24-pin sockets
  • 6-character 14-segment common cathode LED display
  • 2 x 33k resistor
  • 2 x 100nF capacitor
  • machined header (as socket for the display)
  • male header (for the shield)


The board has a small prototyping area for adding project-specific parts, for example buttons and buzzer if you want to build an alarm clock (basic clock code for this shield here). Assembled, showing the time (from wsduino):


or, displaying text:


The shield uses Arduino pins D3, D4 and D5.


Sunday, July 12, 2015

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.

A nice feature of the chip is that the fonts are predefined, this making it an "intelligent" display driver, like Avago's HDSP-2534. To display a character, only its ASCII code needs to be supplied.

#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.

Thursday, June 11, 2015

Prototype 14-segment-display shield

Update Apr 14, 2019: The kit for this shield can be ordered here.

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 for driving 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.


Saturday, April 4, 2015

"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 :)

Tuesday, February 24, 2015

WiFiChron clock kit now available

Update Sep 18, 2017: Here is the latest revision of the PCB included in the kit.

Update Oct 8, 2015: The latest revision of the PCB (pictured here and here) has hardware support for XBee (which also covers GPSBee, WiFiBee and BTBee).

With this kit you build a clock like the ones shown in the photos below.




Last one, courtesy of Nick, features an yellow/amber display.

There are two buying options:

1. use or make your own enclosure

 (US$47, free shipping to North America)


2. enclosure included (Serpac A20, transparent front panel, screws, back panel hand-drilled with 3 holes for buttons and rectangular opening for the USB connector)


 (US$61, free shipping to North America)

The kit includes the following electronic parts:

  • main PCB
  • display adapter PCB
  • ATmega328 with 8MHz bootloader, programmed with a clock sketch + 28-pin socket
  • 74HC595 shift register + 16-pin socket
  • HDSP-2534 8-character alphanumeric display + 4 x 6-pin machined headers
  • DS3231 + battery holder + CR1220 coin battery
  • LM1117 3V3 regulator
  • 3 x right angle push button
  • 5 x100nF capacitor
  • 3 x 10k resistor
  • 220uF (or so) electrolitic capacitor
  • 2 x 12-pin right angle male header
  • miniB USB connector
  • buzzer
Schematic and board layout are shown below.



The board supports other types of displays as well, through the use of adapters. So far, beside the HDSP-2534 coming with the kit, there are adapters for HDSP-231x and QDSP-6064 (the sketch is different for this one), shown below.



Assembling the kit

Finding the right place for the components on the board should be straightforward, since the silkscreen shows their values and their orientation.
It is very important to pay attention to a few aspects:
  • the orientation of the DS3231: the key (pin 1) must be the top-right on the board (so that you'll see the marking on the chip upside-down);
  • solder the USB mini-B connector BEFORE the capacitors surrounding it, or otherwise you'll be forced to solder the USB's ground and Vcc pins (the 2 extreme pins among the 5) in a very cramped space;
The procedure to attach the display is as follows:
  • first insert the little adapter PCB all the way into the right-angle headers, then the headers themselves into the main PCB; solder the headers to the main board first, making sure that the small PCB adapter is perpendicular on the main board;
  • solder the display adapter PCB to the male headers, after it's inserted all the way, with the so the headers' pins stick out;
  • cut the sticking pins to the PCB level; at this point you have the adapter attached and connected (perpendicular) to the board, and you are ready to solder the HDSP-2534 display;
  • insert the machined headers into the HDSP-2534 display first, then insert the HDSP-2534 display with the attached machined pins into the adapter PCB until all pins are accessible on the other side;
  • solder the machined pins, on the side facing the main board (the bottom row is just underneath the main PCB, the top row is above the main PCB). In the end, it should look like in the photo below.


Then insert the display with the key (the "cut" corner) in the left bottom of the adapter.

An assembly video can be also found in this post..

As of Oct. 8, 2015, an HCMS-29xx adapter is available (shared on oshpark), for use with the "serial"  intelligent displays (as opposed to the "parallel" HDSP-2534 coming in the kit).




Saturday, November 29, 2014

"Double Bubble Clock" shield for ProMini

I could have added a second bubble LED display to my "bubble clock" by connecting the anodes for the 4 new 7-segment digits to the only 4 pins left available: D0, D1, A6 and A7.
Instead, I preferred to use the expensive ($8+) LED driver MAX7219. Luckily, I got mine a long time ago as free sample from Maxim, and this was my chance to use it. As one can expect, the "double bubble" clock can display the seconds, plus the date and day of the week.


Below are the 2 bubble clock versions side by side. Both are ProMini shields, so ProMinis get plugged on their backs. I designed them to stand by themselves with the electronics exposed and on the open, as shown in this post. I am not sure if this is a good idea, but designing (or finding) an enclosure seems even a bigger challenge.


The sketch, included below, uses the LedControl library (communicates with MAX7219 through SPI).

#include "LedControl.h"
#include "Wire.h"
#include "DS1307.h"

#define PIN_DIN          12
#define PIN_CLK          11
#define PIN_LOAD         10
#define PIN_BTN_SETHOUR  2
#define PIN_BTN_SETMIN   9

int hour, minute, second;

// read time from DS1307 at intervals;
#define MAX_TIME_READING_COUNTER  1000
long timeReadingCounter = MAX_TIME_READING_COUNTER;

LedControl lc = LedControl(PIN_DIN, PIN_CLK, PIN_LOAD, 1);

void setup()
{
  // MAX72XX is in power-saving mode on startup, we have to do a wakeup call;
  lc.shutdown(0,false);
  lc.setIntensity(0,8);
  lc.clearDisplay(0);
  pinMode(PIN_BTN_SETHOUR, INPUT_PULLUP);
  pinMode(PIN_BTN_SETMIN,  INPUT_PULLUP);
}

void displayTime()
{
  // hours;
  lc.setDigit(0, 4, hour/10, false);
  lc.setDigit(0, 5, hour%10, false);
  // minutes;  
  lc.setDigit(0, 7, minute/10, false);
  lc.setDigit(0, 0, minute%10, false);
  // seconds;
  lc.setDigit(0, 2, second/10, false);
  lc.setDigit(0, 3, second%10, false);
  // delimiters (optional);
  lc.setChar(0, 1, ' ', true);
  lc.setChar(0, 6, ' ', true);
}

void loop()
{
  checkButtons();
  timeReadingCounter++;
  if (timeReadingCounter > MAX_TIME_READING_COUNTER)
  {
    getTimeFromRTC();
    displayTime();
    delay(5);
    timeReadingCounter = 0;
  }
}

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];
  }
}

void setTime(int hh, int mm, int ss)
{
  getTimeFromRTC();
  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 checkButtons()
{
  // increment hours and minutes;
  if (LOW == digitalRead(PIN_BTN_SETHOUR))
  {
    hour++;
    if (hour>23) hour = 0;
    setTime(hour, minute, 0);
    delay(400);
  }
  if (LOW == digitalRead(PIN_BTN_SETMIN))
  {
    minute++;
    if (minute > 59) minute = 0;
    setTime(hour, minute, 0);
    delay(400);
  }
}

Since it's a shield, the board requires a ProMini. The device takes about 50mA.
Schematic and board are shown below.




I shared the PCB layout at OSH Park, so anyone can make their own.

Sunday, September 28, 2014

Blast from the past

MikeM sent this photo he took of the guts of the revolutionary-at the-time Sinclair Black watch beside the Bubble clock shield.


Mike also noticed odd behaviour of the clock shields (both bubble and OLED) when powered from the 3.7V LiPo battery (as shown here). After a while, the RTC returns bogus time. A quick check of the DS1307 datasheet (page 3) confirmed my suspicion:
When 5V is applied within normal limits, the device is fully accessible and data can be written and read. When a 3V battery is connected to the device and VCC is below 1.25 x VBAT, reads and writes are inhibited. However, the timekeeping function continues unaffected by the lower input voltage. As VCC falls below VBAT the RAM and timekeeper are switched over to the external power supply (nominal 3.0V DC) at VBAT.

Therefore, if one really wants the clock(s) powered from LiPo, a step-up converter must be used.
Another solution is to replace the DS1307 with the (almost) pin-to-pin compatible DS1337. This one does not provide support for a backup battery, relying instead on the main power (Vcc). Which shouldn't be a problem with a LiPo. The software would need to set the ATmega328 to sleep when the LiPo voltage drops under a certain (e.g. 3.5V) value. Once the display goes blank, the user should know it's time to charge the battery. (Note that another nice feature of DS1337 is alarm capabilities.)

I actually went through a similar exercise in the past, when I had to pick DS1337 over DS1307, for the exact same reason, but I forgot about it.

Conclusion: do not use DS1307 when the design calls for powering from 3V3 or directly from 3.7V LiPo.

Saturday, August 30, 2014

ProMini clock shield with 7-segment bubble display

Update May 6, 2016: This kit is no longer offered until I get a new batch of QDSP-6064 displays (whose price seem to have jumped considerably).

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.


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.