So, recently I have been watching some Ben Eater videos and have been inspired to try my hand at my own breadboard project. I decided that a digital clock should be simple enough. As you can probably tell I have very little previous experience with electrical stuff, so this is a bit of a learning experience for me. I won't be using a microprocessor for this project, and will be implementing most of the clock stuff in hardware, with an EEPROM being used for digit patterns.

So, this is the objectively terrible, mostly unlabelled schematic I came up with:

The rectangular things are 4-bit counters, and the long one in the bottom is an EEPROM.

I came up with the following parts list:
https://pastebin.com/raw/Hjz86fhp
There's not really a reason for any component on there over another, I was just looking for stuff that would fit on my breadboard. I'm somewhat regretting my choice of buttons. I probably should have consulted you guys before actually ordering anything, but whatever.

This project gave me an excuse to clean off my desk. By clean, I mean move everything on it to the shelves a few feet to the left:


Here's the giant mass of packaging materials I was presented with after opening the shipment (scissors not included):


These are the breadboards I'm using:

These GS-830's are apparently higher quality, which probably doesn't matter much for this project, but I might end up reusing them for something else later.


In this image we see, from top to bottom:
4 bit binary counters
Dual 2-4 demux
An EEPROM
Quad 2-input AND
Quad 2-input OR
Dual 4-input AND
Dual JK latch
Quad 2-1 multiplexer
7-seg display
Buttons

Finally, we have a 32,768 Hz oscillator, which is incredibly tiny:

That paper is college ruled, for a sense of scale. I have absolutely no idea how I'm going to avoid breaking or melting that.

Not pictured are some LEDs, resistors, and jumper cables, which I had lying around from previous projects. I also bought a SMD to DIM adapter, so that I can use the oscillator on the breadboard, and a 6-channel inverter, since it's probable that at least something is going to require it at some point.

I'll explain what each of these components do and how they are used in later updates when I actually start assembling the clock. In the meantime, if I'm making any obvious mistakes, please let me know before I fry something or am forced to place another order and wait a few days.

Also, does anyone know how to shrink images in posts? The size of the images makes it somewhat hard to read this post.

Edit by admin: you can append a "s", "m" or "l" at the end of your image name (i.e. https://i.imgur.com/4wrPRHA.jpg -> https://i.imgur.com/4wrPRHAm.jpg) to display smaller images.
use bbcode to embed your images

Code:

[img width="100" height="50" alt="Lubeck city gate" title="This is one of the medieval city gates of Lubeck"]https://www.bbcode.org/images/lubeck_small.jpg[/img]


I think its a great start to hardware development! if i were to do something Ben eater (who is an amazing person btw I totally recommend you to watch (https://www.youtube.com/beneater)! I would probably choose the DIY graphics card he did



or the complete 8 bit computer that he did, however I would probably start with the 8-bit computer that he did because I already know the fundamentals of computer hardware design and the graphics card was a little too basic for me lol!
Great project, commandblockguy! I'm glad you're using no microprocessor for this; your next challenge is to draw out the truth table for the segments for each digit and figure out how to generate the patterns without the EEPROM (or just use a 74LS48 or equivalent BCD-to-7 Segment decoder).

Oooh, Ben Eater is the one who was making the DIY video card. That was a fascinating project to learn about.
An update, because it's been over a month since the last one:
I ended up ordering a new crystal because the old one was so tiny. However, I ended up getting a 32KHz instead of a 32768Hz by accident, so I had to place yet another order. That one took four weeks to ship for whatever reason and only arrived in the mail today. I hope to resume work on this now that it's arrived and post a status update in the near future.
I've started working on this project for real now. I still haven't been able to get the clock generator working so I'm using an Arduino instead for now.

I've gone ahead and added all of the ICs to their positions on the boards and connected both power and ground. This was super boring so I didn't take any pictures.

Here's a picture of me programming the AT28C64B:


I'm using the EEPROM to decode characters for the seven-segment displays. I haven't connected any of the inputs up for real yet, but the address space I have set up uses two bits to select which of the four displays is displaying a character, the two upper bits of the minutes, and the four lower bits of either the minutes or the hours, depending on which is currently being displayed.

The displays themselves use persistence-of-vision - I've connected their common cathodes to the outputs of a 74LS156N, so that only one is active at a time. I will also connect a step in my clock divider to the 74LS156N, the EEPROM, and a 74HC157N which determines whether the minutes or hours are sent to the EEPROM.

Here's the actual code involved:
This is what I used to generate the ROM itself. This was originally running on the Arduino itself, but that was a pain to debug. I accidentally made a C++ program for it when porting it and was too lazy to make another project in my IDE, so I just used C libraries. This trend continues if you read the actual code - since this is running only once the quality is best described as "extremely lazy."

Code:
#include <cstdint>
#include <cstdio>

enum segments {
    BOTTOM_LEFT = (1 << 0),
    BOTTOM = (1 << 1),
    BOTTOM_RIGHT = (1 << 2),
    TOP_RIGHT = (1 << 3),
    TOP = (1 << 4),
    TOP_LEFT = (1 << 5),
    MIDDLE = (1 << 6)
};

const uint8_t chars[10] = {
        TOP | TOP_RIGHT | BOTTOM_RIGHT | BOTTOM | BOTTOM_LEFT | TOP_LEFT, //0
        TOP_RIGHT | BOTTOM_RIGHT, //1
        TOP | TOP_RIGHT | MIDDLE | BOTTOM_LEFT | BOTTOM, //2
        TOP | TOP_RIGHT | MIDDLE | BOTTOM_RIGHT | BOTTOM, //3
        TOP_LEFT | MIDDLE | TOP_RIGHT | BOTTOM_RIGHT, //4
        TOP | TOP_LEFT | MIDDLE | BOTTOM_RIGHT | BOTTOM, //5
        TOP | TOP_LEFT | BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT | MIDDLE, //6
        TOP | TOP_RIGHT | BOTTOM_RIGHT, //7
        TOP | TOP_RIGHT | MIDDLE | TOP_LEFT | BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT, //8
        TOP | TOP_RIGHT | MIDDLE | TOP_LEFT | BOTTOM | BOTTOM_RIGHT, //9
//  TOP | TOP_RIGHT | MIDDLE | TOP_LEFT | BOTTOM_LEFT | BOTTOM_RIGHT, //A
//  TOP_LEFT | BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT | MIDDLE, //B
//  TOP | TOP_LEFT | BOTTOM_LEFT | BOTTOM, //C
//  TOP_RIGHT | BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT | MIDDLE, //D
//  TOP | MIDDLE | TOP_LEFT | BOTTOM_LEFT | BOTTOM, //E
//  TOP | MIDDLE | TOP_LEFT | BOTTOM_LEFT, //F
};

uint8_t get_raw_value(uint8_t address) {
    uint8_t display = 0b11 - (address >> 6);
    uint8_t lower = address & 0b1111;
    uint8_t minutes = (address & 0b110000) | lower;
    switch(display) {
        case 0:
            if(lower == 0) return 1;
            if(lower < 10) return 0;
            return 1;
        case 1:
            if(lower == 0) return 2;
            return lower % 10;
        case 2: return minutes / 10;
        case 3: return minutes % 10;
    }
}

uint8_t get_value(uint8_t address) {
    return chars[get_raw_value(address)];
}

int main() {
    for(int i = 0; i < 10; i++) {
        printf("%03u ", chars[i]);
    }
    printf("\n");
    for(int i = 0; i < 10; i++) {
        if(chars[i] & TOP) printf("--- ");
        else printf("    ");
    }
    printf("\n");
    for(int i = 0; i < 10; i++) {
        if(chars[i] & TOP_LEFT) printf("| ");
        else printf("  ");
        if(chars[i] & TOP_RIGHT) printf("| ");
        else printf("  ");
    }
    printf("\n");
    for(int i = 0; i < 10; i++) {
        if(chars[i] & MIDDLE) printf("--- ");
        else printf("    ");
    }
    printf("\n");
    for(int i = 0; i < 10; i++) {
        if((uint8_t)chars[i] & (uint8_t)BOTTOM_LEFT) printf("| ");
        else printf("  ");
        if(chars[i] & BOTTOM_RIGHT) printf("| ");
        else printf("  ");
    }
    printf("\n");
    for(int i = 0; i < 10; i++) {
        if(chars[i] & BOTTOM) printf("---,");
        else printf("   ,");
    }
    printf("\n");

    for(int i = 0; i < 256; i++) {
        printf("0x%02X, ", get_value(i));
    }
    printf("\n\n");


    for(int i = 0; i < 64; i++) {
        printf("%u, ", get_raw_value(i));
    }
    printf("\n");
    for(int i = 64; i < 128; i++) {
        printf("%u, ", get_raw_value(i));
    }
    printf("\n");
    for(int i = 128; i < 192; i++) {
        printf("%u, ", get_raw_value(i));
    }
    printf("\n");
    for(int i = 192; i < 256; i++) {
        printf("%u, ", get_raw_value(i));
    }
    printf("\n");
    return 0;
}

I realized while typing this post that one of the changes that I made while debugging that caused a minor issue in the ROM output - for hours less than 10, they will be displayed with a zero in front because I'm using the character for 0 rather than outputting 0 (everything off) to the ROM.
And here's the code actually running on the Arduino, which I stole from https://github.com/crmaykish/AT28C-EEPROM-Programmer-Arduino and then absolutely butchered:

Code:
// AT28C EEPROM Programmer for Arduino Mega
// Colin Maykish (cmaykish.com)
// 04/17/2016

//#define WRITE

// Width in bits of address bus (64K has 13, 256K has 15)
const int ADDR_BUS_WIDTH = 8;

const byte data[256] = {0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, 0x77, 0x5B, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x5B, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x5B, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x5B, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x77, 0x1C, 0x7F, 0x7E, 0x3F, 0x0C, 0x5B, 0x5E, 0x6C, 0x76, 0x0C, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, };

// Chip Enable
const int CE = A1;
#ifdef WRITE
const int WE = A0;
#else
const int OE = A0;
#endif

// Data bus pins
const int I[] = {5, 4, 3, A5, A4, A3, A2};
// Address bus pins
const int A[] = {6, 7, 8, 9, 10, 11, 12, 13};

void setup() {
   Serial.begin(9600);

   // Define control pins and turn them all off
  pinMode(CE, OUTPUT);
  digitalWrite(CE, HIGH);
#ifdef WRITE
   pinMode(WE, OUTPUT);
   digitalWrite(WE, HIGH);
#else
  pinMode(OE, OUTPUT);
  digitalWrite(OE, HIGH);
#endif

   // Set address bus to outputs
   for (int i = 0; i < ADDR_BUS_WIDTH; i++) {
      pinMode(A[i], OUTPUT);
   }

#ifdef WRITE
   // Write hex file to EEPROM
   for (int i = 0; i < (1 << 8); i++) {
      writeByte(i, data[i]);
   }

  Serial.print("Data written.\n");
#else
 int i;
 for(i = 0; i < (1 << 8); i++) {
  Serial.print(readByte(i));
  Serial.print(", ");
 }

 Serial.print("\n");
#endif
}

void loop() {
#ifndef WRITE
  while (Serial.available() > 1) {
    int b = Serial.parseInt();
    Serial.print(b);
    Serial.print(": ");
    Serial.println(readByte(b));
  }
#endif
}

// Set data bus to INPUT or OUTPUT
void setDataBusMode(int mode) {
   if (mode == INPUT || mode == OUTPUT) {
      for (int i = 0; i < 7; i++) {
         pinMode(I[i], mode);
      }
   }
}

// Write an address to the address bus
void setAddress(int addr) {
   for (int i = 0; i < ADDR_BUS_WIDTH; i++) {
      int a = (addr & (1 << i)) > 0;
      digitalWrite(A[i], a);
   }
}

#ifdef WRITE
void writeByte(int addr, byte val) {
   setDataBusMode(OUTPUT);
   setAddress(addr);

   // Send data value to data bus
   for (int i = 0; i < 7; i++) {
      int a = (val & (1 << i)) > 0;
      digitalWrite(I[i], a);
   }

   // Commit data write
   digitalWrite(CE, LOW);
   delay(10);
   digitalWrite(WE, LOW);
   delay(10);
   digitalWrite(WE, HIGH);
   delay(10);
   digitalWrite(CE, HIGH);
   delay(10);
}
#endif

#ifndef WRITE
byte readByte(int addr) {
  byte data = 0;

  setDataBusMode(INPUT);

  // Write the addr
  for (int i = 0; i < ADDR_BUS_WIDTH; i++) {
    int a = (addr & (1 << i)) > 0;
    digitalWrite(A[i], a);
  }

  digitalWrite(CE, LOW);
  digitalWrite(OE, LOW);
  delay(10);

  // Read data bus
  for (int i = 0; i < 7; i++) {
    int d = digitalRead(I[i]);
    data += (d << i);
  }

  //digitalWrite(OE, HIGH);
  //digitalWrite(CE, HIGH);
  delay(10);

  return data;
}
#endif


As for why I'm using an EEPROM as opposed to a BCD converter - I felt that programming an EEPROM would be more interesting than buying a part that does most of the work for me. Also, it would require more dividers, which means more wiring for me.
As for why I'm only using 5% of the EEPROM's actual memory - I originally planned to buy one with only 256 bytes, though I couldn't really find any that met my other requirements. Also, my Arduino has exactly as many unused ports as necessary for 8 address lines and 7 data lines (though that's mostly me attempting to justify this after the fact). Finally, the thing cost me like $2 so it's no issue whatsoever.

Here's me firing up the display after programming the EEPROM:

As you can see the EEPROM is working fine, but because I'm manually setting all of its inputs besides the ones associated with the display number, it currently can't display any time such that (hours & 0b1111 != minutes & 0b1111). I'm representing 12 as 0 internally and then using the EEPROM to output 1 2 instead, so 12:00 meets this requirement. This will be fixed after I connect the 74HC157N, which properly selects between minutes and hours.

I also added a nice blinking effect where the decimal points of the two middle digits toggle once every second.

It's not too clear from the image, but all of the pins for the displays are connected under the plastic casings. That way, I only have to connect the data pins of the EEPROM to one display and then use the multiplexer to turn the rest of the displays off.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement