The Anelace "Powers of 2" clock is a favorite of mine. It displays code in a BCD (binary coded decimal) format.
0 0 0
0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
H H M M S S
0 * 0
0 * 0 0 0
0 * 0 0 * 0
* 0 * * * 0
1 2:5 9:3 0 = 12:59 and 30 seconds
Using BCD format makes the clock easy to read, which is quite important. However, I personally am not a fan of the inefficiency of BCD. Plus, any bit-banger worth their salt should be able to read binary numbers at least up to 2^8, right?
Note that the total information conveyable by the clock is
2^(number of leds) = 2^20 = 1,048,576 possible values.
In the specific BCD format used, the total values (going from the left-most column to the right-most column) are
4*10*8*10*8*10 = 256,000 possible values.
Now, there are (24*60*60) = 86,400 seconds in a day. So (obviously) the 24 hour time can be perfectly represented within this scheme.
However, if we go to true binary, then we know that we can represent seconds and minutes by 6 LED's (2^6=64) and the 24 hour time by 5 LED's (2^5 = 32). Conveniently enough we can use columns instead of rows and get
0 0 0
0 0 0 0 0 H
0 0 0 0 0 0 M
0 0 0 0 0 0 S
so our 12:59:30 time becomes
0 0 0
0 * * 0 0 12
* * * 0 * * 59
0 * * * * 0 30 = 12:59 and 30 seconds
The Arduino sketch below uses that mode as a default. There are a few other modes though, including a random mode and a grey code mode.
// Arduino code for "Powers of 2" clock by Anelace.
// Remove microcontroller, wire lines from microcontroller pads to Arduino
/*
2 - col 1
3 - col 2
4 - col 3
5 - col 4
6 - col 5
7 - 1st row
8 - 2nd row
9 - 3rd row
10 - 4th row
11 - ?
TODO: HANDLE WRAP
*/
#define CLOCK_STATE_RUNNING (0)
#define CLOCK_STATE_SET_MINUTES (1)
#define CLOCK_STATE_SET_HOURS (2)
#define CLOCK_STATE_SET_DAYS (3)
#define CLOCK_STATE_SET_MODE (4)
#define CLOCK_NUM_STATES (5)
#define CLOCK_MODE_BINARY (0)
#define CLOCK_MODE_BCD (1)
#define CLOCK_MODE_GRAY (2)
#define CLOCK_MODE_SYMMETRIC_GRAY (3)
#define CLOCK_MODE_RANDOM (4)
#define CLOCK_NUM_MODES (5)
#define TIME_SECONDS (3)
#define TIME_MINUTES (2)
#define TIME_HOURS (1)
#define TIME_DAYS (0)
#define TIME_NUM_PARTS (4)
#define NUM_SECONDS (60)
#define NUM_MINUTES (60)
#define NUM_HOURS (24)
#define NUM_DAYS (7)
int clock_mode = CLOCK_MODE_SYMMETRIC_GRAY;
int col0 = 2;
int numcols = 5;
int row0 = 7;
int numrows = 4;
int switch0 = 12;
int last_switch0 = 0;
int last_switch1 = 0;
unsigned short time[TIME_NUM_PARTS] = {1,18,30,00}; // 3 = seconds... 0 = days
int clock_state = CLOCK_STATE_RUNNING;
unsigned long last_seconds_from_millis = 0;
#define BRIGHTNESS_DELAY_NUM (3)
unsigned int brightness_delay_index = 2;
unsigned int brightness_delay[BRIGHTNESS_DELAY_NUM] = {10,1,500};
void update_time() {
unsigned long seconds_from_millis = millis()/1000;
time[TIME_SECONDS] += (seconds_from_millis - last_seconds_from_millis);
last_seconds_from_millis = seconds_from_millis;
if (time[TIME_SECONDS] >= NUM_SECONDS) {
time[TIME_MINUTES]++; // assume we didn't miss a whole minute w/o update
time[TIME_SECONDS] = 0;
if (time[TIME_MINUTES] >= NUM_MINUTES) {
time[TIME_HOURS]++;
time[TIME_MINUTES] = 0;
if (time[TIME_HOURS] >= NUM_HOURS) {
time[TIME_DAYS]++;
time[TIME_HOURS] = 0;
if (time[TIME_DAYS] >= NUM_DAYS) {
time[TIME_DAYS] = 0;
}
}
}
}
}
int led_display_random(int row, int col) {
return rand()&0x1;
}
int led_display_bcd(int row, int col) {
if (row == 0) {
switch (col) {
case 1: // most significant hours 10's place
return (time[TIME_HOURS]/10)&(0x1<<1);
case 3: // least significant hours 10's place
return (time[TIME_HOURS]/10)&(0x1);
case 0: // most significant hours 1's place
return (time[TIME_HOURS]%10)&(0x1<<3);
case 2: // most significant minutes 1's place
return (time[TIME_MINUTES]%10)&(0x1<<3);
case 4: // most significant seconds 1's place
return (time[TIME_SECONDS]%10)&(0x1<<3);
}
}
switch (col) {
case 4:
return (time[TIME_SECONDS]%10)&(0x1<<numrows-1-row);
case 3:
return (time[TIME_SECONDS]/10)&(0x1<<numrows-1-row);
case 2:
return (time[TIME_MINUTES]%10)&(0x1<<numrows-1-row);
case 1:
return (time[TIME_MINUTES]/10)&(0x1<<numrows-1-row);
case 0:
return (time[TIME_HOURS]%10)&(0x1<<numrows-1-row);
}
}
int led_display_binary(int row, int col) {
if (row == 0) {
// this row also contains seconds and minutes
switch (col) {
case 1:
// most significant minutes bit
return time[2]&(0x1<<5);
case 3:
// most significant seconds bit
return time[3]&(0x1<<5);
case 2:
return time[0]&(0x1<<1);
case 4:
return time[0]&(0x1);
case 0: // most significant days bit
return time[0]&(0x1<<2);
}
}
return time[row]&(0x1<<(numcols-1-col));
}
int led_display_gray(int row, int col) {
if (row == 0) {
// this row also contains seconds and minutes
switch (col) {
case 1:
// most significant minutes bit
return binaryToGray(time[2])&(0x1<<5);
case 3:
// most significant seconds bit
return binaryToGray(time[3])&(0x1<<5);
case 2:
return binaryToGray(time[0])&(0x1<<1);
case 4:
return binaryToGray(time[0])&(0x1);
case 0: // most significant days bit
return binaryToGray(time[0])&(0x1<<2);
}
}
return binaryToGray(time[row])&(0x1<<(numcols-1-col));
}
int led_display_symmetric_gray(int row, int col) {
if (row == 0) {
// this row also contains seconds and minutes
switch (col) {
case 1:
// most significant minutes bit
return binaryToSymmetricGray(time[2],30,6)&(0x1<<5);
case 3:
// most significant seconds bit
return binaryToSymmetricGray(time[3],30,6)&(0x1<<5);
case 2:
return binaryToGray(time[0])&(0x1<<1);
case 4:
return binaryToGray(time[0])&(0x1);
case 0: // most significant days bit
return binaryToGray(time[0])&(0x1<<2);
}
}
switch (row) {
case 0:
return binaryToGray(time[row])&(0x1<<(numcols-1-col));
case 1:
return binaryToSymmetricGray(time[row],12,5)&(0x1<<(numcols-1-col));
case 2:
return binaryToSymmetricGray(time[row],30,6)&(0x1<<(numcols-1-col));
case 3:
return binaryToSymmetricGray(time[row],30,6)&(0x1<<(numcols-1-col));
}
}
int led_display(int row, int col) {
switch (clock_mode) {
case CLOCK_MODE_BINARY:
return led_display_binary(row,col);
case CLOCK_MODE_BCD:
return led_display_bcd(row,col);
case CLOCK_MODE_GRAY:
return led_display_gray(row,col);
case CLOCK_MODE_RANDOM:
return led_display_random(row,col);
case CLOCK_MODE_SYMMETRIC_GRAY:
return led_display_symmetric_gray(row,col);
}
}
unsigned char clock_state_led_high(int row, int col)
// check to see if the settings wants to blank a row
{
if (clock_mode == CLOCK_MODE_BCD) return 1;
switch (clock_state) {
case CLOCK_STATE_RUNNING:
return 1;
case CLOCK_STATE_SET_MINUTES:
if (row==2) return 1; else
if (row==0 && col==1) return 1; else
return 0;
case CLOCK_STATE_SET_HOURS:
if (row==1) return 1; else return 0;
case CLOCK_STATE_SET_DAYS:
if (row==0){
if (col == 2 || col ==0 || col == 4)
return 1;
}
return 0;
default:
return 1;
}
}
void setup() {
int ii;
for (ii=2;ii<=11;ii++) {
pinMode(ii,OUTPUT);
}
Serial.begin(9600);
}
void loop() {
int ii,jj;
int input;
update_time();
for (ii=0;ii<numcols;ii++) {
digitalWrite(ii+col0,HIGH);
for (jj=0;jj<numrows;jj++) {
if (clock_state_led_high(jj,ii)) {
if (led_display(jj,ii))
{
digitalWrite(jj+row0,HIGH);
delayMicroseconds(brightness_delay[brightness_delay_index]);
digitalWrite(jj+row0,LOW);
}
}
}
digitalWrite(ii+col0,LOW);
}
input = !digitalRead(switch0); // inverse polarity
if (input && last_switch0 != 1) {
clock_state = (clock_state+1)%CLOCK_NUM_STATES;
}
last_switch0 = input;
input = !digitalRead(switch0+1); // inverse polarity
if (input && last_switch1 != 1) {
switch (clock_state) {
case CLOCK_STATE_RUNNING:
brightness_delay_index = (brightness_delay_index+1)%BRIGHTNESS_DELAY_NUM;
break;
case CLOCK_STATE_SET_MINUTES:
time[TIME_MINUTES] = (time[TIME_MINUTES] + 1)%NUM_MINUTES;
break;
case CLOCK_STATE_SET_HOURS:
time[TIME_HOURS] = (time[TIME_HOURS]+1)%NUM_HOURS;
break;
case CLOCK_STATE_SET_DAYS:
time[TIME_DAYS] = (time[TIME_DAYS]+1)%NUM_DAYS;
break;
case CLOCK_STATE_SET_MODE:
clock_mode = (clock_mode+1)%CLOCK_NUM_MODES;
break;
}
}
last_switch1 = input;
} // loop();
unsigned int binaryToSymmetricGray(unsigned int num, unsigned int num_max_div2,unsigned int num_bits){
if (num < (num_max_div2)) {
return (num >> 1) ^ num;
} else {
unsigned int output = ((((num_max_div2*2) - 1 - num) >> 1) ^ ((num_max_div2*2) - 1 - num));
output |= (0x1<<(num_bits-1));
return output;
}
}
unsigned int binaryToGray(unsigned int num){
return (num >> 1) ^ num;
}
unsigned int grayToBinary(unsigned int num){
unsigned int mask;
for (mask = num >> 1; mask != 0; mask = mask >> 1){
num = num ^ mask;
}
return num;
}