This is my version of a 7-segment clock, using big 7-segment displays:
The back might be a bit more interesting:
The circuit was built Manhattan style on two pieces of PCB material glued directly to the LED displays themselves. I had initially tried to build it on perfboard but had a hard time mating the LEDs to the board reliably. This is where I ended up before I chucked that version and decided to go Manhattan style:
The idea was to plug the LEDs into the stack of headers, but as I have cunningly displaced the two pairs of displays by the width of a standard rectangular LED, the pins on the LED displays did not match up perfectly with the headers, so it turned out to be a pain.
The Manhattan style version I eventually built uses some "MePads" and "MeSquares" from QRPme.com, so I could just bend back the pins of the LED displays onto some them and build from there:
The other details of the design are quite straight forward. The heart is an Atmega8 clocked by its internal oscillator and using a 32.768kHz watch crystal as its time base. It drives the 7-segment displays via a 4511 BCD to 7-segment driver for the segments and simple low side transistor switching for the digit multiplexing. Attached are the schematic and code for this project.
For those interested, the big 7-segment displays were bought off Ebay and turn out to be "Ningbo Foryard Opto Electronics" part number FYS-23011AS.
Thanks for looking!
Schematic:
Code:
The back might be a bit more interesting:
The circuit was built Manhattan style on two pieces of PCB material glued directly to the LED displays themselves. I had initially tried to build it on perfboard but had a hard time mating the LEDs to the board reliably. This is where I ended up before I chucked that version and decided to go Manhattan style:
The idea was to plug the LEDs into the stack of headers, but as I have cunningly displaced the two pairs of displays by the width of a standard rectangular LED, the pins on the LED displays did not match up perfectly with the headers, so it turned out to be a pain.
The Manhattan style version I eventually built uses some "MePads" and "MeSquares" from QRPme.com, so I could just bend back the pins of the LED displays onto some them and build from there:
The other details of the design are quite straight forward. The heart is an Atmega8 clocked by its internal oscillator and using a 32.768kHz watch crystal as its time base. It drives the 7-segment displays via a 4511 BCD to 7-segment driver for the segments and simple low side transistor switching for the digit multiplexing. Attached are the schematic and code for this project.
For those interested, the big 7-segment displays were bought off Ebay and turn out to be "Ningbo Foryard Opto Electronics" part number FYS-23011AS.
Thanks for looking!
Schematic:
Code:
Code:
/* avrclock.c
*
* 4 digit 7 segment LED clock with ATMega8
*
*/
/* Macros */
#define IS_BIT_SET(var, pos) ((var) & (1<<(pos)))
/* Libraries & helpers */
#define F_CPU 1000000 // needs to be set before includes
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
/* Constants */
// Pin settings
#define SECONDS_LED PB0
#define BCD_BIT_0 PB1
#define BCD_BIT_1 PB2
#define BCD_BIT_2 PB3
#define BCD_BIT_3 PB4
#define DIGIT_0 PD3
#define DIGIT_1 PD4
#define DIGIT_2 PD5
#define DIGIT_3 PD6
#define BUTTON_SET PD2
#define BUTTON_UP PD0
#define BUTTON_DOWN PD1
// Various
#define NUM_DIGITS 4
#define MAX_SECONDS 86400 // 60*60*24
#define DEBOUNCE_DELAY 100
/* Function declarations */
static void init_timers( void );
static void init_timer2(void);
static void init_timer0( void );
static void display_time( uint32_t);
/* Global variables */
volatile uint32_t seconds = 78120;
volatile uint8_t set_mode = 0;
volatile uint8_t current_digit = 0;
volatile uint8_t previous_digit = 0; // Defined here instead of in ISR to avoid more expensive stack frame setup in ISR
volatile uint8_t digit_current_value[4] = {0, 0, 0, 0};
volatile uint8_t button_press_counter[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
volatile uint8_t digits_blinking = 0;
static uint8_t digits[4] = { DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3 };
//
/* Interrupt routines */
/* 1Hz timebase interrupt */
ISR(TIMER2_COMP_vect) {
// Increase seconds if not in set mode
if (! set_mode ) {
if ( ++seconds == MAX_SECONDS ) { seconds = 0; }
}
// Toggle seconds LED
PORTB ^= ( 1 << SECONDS_LED );
}
/* Display interrupt: digit multiplexer */
ISR(TIMER0_OVF_vect) {
previous_digit = current_digit;
if ( ++current_digit == NUM_DIGITS ) {
current_digit = 0;
}
// Blank previous digit, light up next
PORTD &= ~(1 << digits[previous_digit] );
if ( ! set_mode ) {
PORTD |= (1 << digits[current_digit] );
} else {
// In set mode: check for blinking
// Blinking is synchronous with SECONDS_LED
if ( IS_BIT_SET(digits_blinking, current_digit) ) {
if ( IS_BIT_SET(PINB, SECONDS_LED )) {
PORTD |= (1 << digits[current_digit] );
}
} else {
PORTD |= (1 << digits[current_digit] );
}
}
// Load new digit value in BCD
PORTB &= 0b11100001;
PORTB |= (digit_current_value[current_digit] << 1);
}
/* Setup timers
*
* Timer0 setup as counter to cycle digits
* Timer2 as timebase for the clock with external 32 KHz crystal
*/
static void init_timers( void ) {
init_timer0(); // Used for LED refresh via interrupt
init_timer2(); // Used for color PWM
}
/* Setup Timer 0
*
* This timer drives the digit multiplexing
* Aim for 100Hz per digit, so 400Hz. Timer0 can only count to TOP, so:
* System clock = 1Mhz. Set prescaler to /8 -> 128KHz. Count to 256 -> 500Hz
*/
static void init_timer0( void ) {
// Clock select: /8 prescaler - CS01 set, CS02 & CS00 not set
TCCR0 |= ( 1 << CS01);
TCCR0 &= ~( 1 << CS02 | 1 << CS00);
// Set timer overflow interrupt
TIMSK |= ( 1 << TOIE0);
}
/* Setup Timer 2
*
* This is the 1Hz timebase. It is clocked by an external 32KHz crystal, with a /1024 prescaler.
* The timer counts at 32Hz, so generate compare match at 31 -> 1Hz (as interrupt happens at end
* of this cycle)
*/
void init_timer2( void ) {
// Clock source: external
ASSR |= 1 << AS2;
// OCR2 setup
OCR2 = 31;
// CTC mode: WGM20=0, WGM21=1
TCCR2 &= ~( 1 << WGM20);
TCCR2 |= 1 << WGM21;
// Normal port operation - no OC2 used
TCCR2 &= ~( 1 << COM21 | 1 << COM20);
// Prescaler /1024. This also starts the clock
TCCR2 |= ( 1 << CS22 | 1 << CS21 | 1 << CS20);
// Wait until TCR2UB and OCR2UB are low (datasheet p. 117)
while ( IS_BIT_SET(ASSR, TCR2UB) || IS_BIT_SET(ASSR, OCR2UB) ) {
// Do nothing
}
// Set interrupt
TIMSK |= 1 << OCIE2;
}
/* Check button press (includes debouncing) */
uint8_t check_button_pressed(uint8_t button) {
if ( bit_is_clear( PIND, button )) {
// Wait a bit before returning button status
_delay_ms(DEBOUNCE_DELAY);
return bit_is_clear( PIND, button);
} else {
return 0;
}
}
static uint8_t get_hour (uint32_t sec) {
return sec / 3600;
}
static uint8_t get_minute (uint32_t sec) {
return (sec % 3600) / 60 ;
}
/* Set mode */
static void handle_set_mode( uint32_t set_seconds ) {
uint8_t items[2] = { get_hour(set_seconds), get_minute(set_seconds) };
uint8_t item_max[2] = { 23, 59 };
uint8_t i = 0;
uint8_t i_max = 2;
// We're off
set_mode = 1;
// Set hours blinking
digits_blinking = 0b00000011;
while ( i < i_max ) {
// Up button
if ( check_button_pressed(BUTTON_UP) ) {
if (++items[i] > item_max[i] ) { items[i] = 0; }
}
// Down button
if ( check_button_pressed(BUTTON_DOWN) ) {
if ((int8_t) --items[i] < 0 ) { items[i] = item_max[i]; }
}
// Set button
if (check_button_pressed(BUTTON_SET)) {
i++;
digits_blinking <<= 2;
}
// Set (possibly) new time
set_seconds = (uint32_t) items[0]*3600+items[1]*60;
display_time(set_seconds);
}
// We're done.
seconds = set_seconds;
set_mode = 0;
digits_blinking = 0;
// Wait for button to be released
_delay_ms(1000);
}
static void display_time (uint32_t seconds) {
uint8_t hours = get_hour(seconds);
uint8_t minutes = get_minute(seconds);
// Set digit values for display interrupt to pick up
digit_current_value[0] = hours / 10; // uint8_t takes care of 'round down'
digit_current_value[1] = hours % 10;
digit_current_value[2] = minutes / 10; // uint8_t takes care of 'round down'
digit_current_value[3] = minutes % 10;
}
/* Main loop */
int main( void ) {
uint8_t current_minute = 0;
// Data direction
DDRB = 0b00011111;
DDRD = 0b01111000;
// Set unused pins to internall pullup (datasheet best practice)
// This also takes care of the pull ups for the buttons
PORTB = 0b11100000; // Also sets BCD_BLANK high
PORTC = 0xFF;
PORTD = 0b10000111;
init_timers();
// Start interrupts
sei();
/* Event loop */
for(;;) {
// Clock update
if ( current_minute != get_minute(seconds) ) {
current_minute = get_minute(seconds);
display_time( seconds );
}
// Set mode
if ( check_button_pressed(BUTTON_SET) ) { handle_set_mode( seconds ); };
}
return 0; /* never reached */
}