Manhattan style 7-segment Clock

Thread Starter

Robartes

Joined Oct 1, 2014
57
This is my version of a 7-segment clock, using big 7-segment displays:

AVR_clock_2.jpg

The back might be a bit more interesting:

AVR_clock_10.jpg

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:

AVR_clock_1.jpg

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:

AVR_clock_4.jpg

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:

schematic.png

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 */

}
 

Thread Starter

Robartes

Joined Oct 1, 2014
57
Any idea what the find build cost was?
Not really, as I mostly just grabbed stuff I had lying around. There's nothing really expensive in there - two voltage regulators, an ATmega8, jelly bean 4000 series CMOS and some passives and transistors. The most expensive parts are probably the MeSquares (which go for 10$ per set) and the segment displays themselves, which I got for £12.90 for 5.

A wild stab at parts cost for the whole would end up just north of 30$ or so?
 
Top