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.