Manhattan style 7-segment Clock

Discussion in 'The Completed Projects Collection' started by Robartes, Mar 3, 2015.

  1. Robartes

    Thread Starter Member

    Oct 1, 2014
    57
    13
    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 (Text):
    1.  
    2. /* avrclock.c
    3. *
    4. * 4 digit 7 segment LED clock with ATMega8
    5. *
    6. */
    7.  
    8. /* Macros */
    9. #define IS_BIT_SET(var, pos) ((var) & (1<<(pos)))
    10.  
    11. /* Libraries & helpers */
    12. #define F_CPU 1000000  // needs to be set before includes
    13. #include <avr/io.h>
    14. #include <avr/interrupt.h>
    15. #include <util/delay.h>
    16. #include <stdint.h>
    17.  
    18. /* Constants */
    19.  
    20. // Pin settings
    21. #define SECONDS_LED     PB0
    22. #define BCD_BIT_0       PB1
    23. #define BCD_BIT_1       PB2
    24. #define BCD_BIT_2       PB3
    25. #define BCD_BIT_3       PB4
    26.  
    27. #define DIGIT_0         PD3
    28. #define DIGIT_1         PD4
    29. #define DIGIT_2         PD5
    30. #define DIGIT_3         PD6
    31.  
    32. #define BUTTON_SET      PD2
    33. #define BUTTON_UP       PD0
    34. #define BUTTON_DOWN     PD1
    35.  
    36. // Various
    37. #define NUM_DIGITS           4
    38. #define MAX_SECONDS          86400   // 60*60*24
    39. #define DEBOUNCE_DELAY       100
    40.  
    41. /* Function declarations */
    42. static void init_timers( void );
    43. static void init_timer2(void);
    44. static void init_timer0( void );
    45. static void display_time( uint32_t);
    46.  
    47. /* Global variables */
    48. volatile uint32_t seconds = 78120;
    49. volatile uint8_t set_mode = 0;
    50. volatile uint8_t current_digit = 0;
    51. volatile uint8_t previous_digit = 0; // Defined here instead of in ISR to avoid more expensive stack frame setup in ISR
    52. volatile uint8_t digit_current_value[4] = {0, 0, 0, 0};
    53. volatile uint8_t button_press_counter[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    54. volatile uint8_t digits_blinking = 0;
    55.  
    56. static uint8_t digits[4] = { DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3 };
    57. //
    58. /* Interrupt routines */
    59.  
    60. /* 1Hz timebase interrupt */
    61. ISR(TIMER2_COMP_vect) {
    62.  
    63.   // Increase seconds if not in set mode
    64.   if (! set_mode ) {
    65.     if ( ++seconds == MAX_SECONDS ) { seconds = 0; }
    66.   }
    67.  
    68.   // Toggle seconds LED
    69.   PORTB ^= ( 1 << SECONDS_LED );
    70. }
    71.  
    72.  
    73. /* Display interrupt: digit multiplexer */
    74. ISR(TIMER0_OVF_vect) {
    75.  
    76.   previous_digit = current_digit;
    77.  
    78.   if ( ++current_digit == NUM_DIGITS ) {
    79.     current_digit = 0;
    80.   }
    81.   // Blank previous digit, light up next
    82.   PORTD &= ~(1 << digits[previous_digit] );
    83.   if ( ! set_mode ) {
    84.     PORTD |= (1 << digits[current_digit] );
    85.   } else {
    86.     // In set mode: check for blinking
    87.     // Blinking is synchronous with SECONDS_LED
    88.     if ( IS_BIT_SET(digits_blinking, current_digit) ) {
    89.       if ( IS_BIT_SET(PINB, SECONDS_LED )) {
    90.         PORTD |= (1 << digits[current_digit] );
    91.       }
    92.     } else {
    93.       PORTD |= (1 << digits[current_digit] );
    94.     }
    95.  
    96.   }
    97.  
    98.   // Load new digit value in BCD
    99.   PORTB &= 0b11100001;
    100.   PORTB |= (digit_current_value[current_digit] << 1);
    101.  
    102.  
    103. }
    104.  
    105. /* Setup timers
    106. *
    107. * Timer0 setup as counter to cycle digits
    108. * Timer2 as timebase for the clock with external 32 KHz crystal
    109. */
    110.  
    111. static void init_timers( void ) {
    112.  
    113.   init_timer0();  // Used for LED refresh via interrupt
    114.   init_timer2();  // Used for color PWM
    115.  
    116. }
    117.  
    118. /* Setup Timer 0
    119. *
    120. * This timer drives the digit multiplexing
    121. * Aim for 100Hz per digit, so 400Hz. Timer0 can only count to TOP, so:
    122. *   System clock = 1Mhz. Set prescaler to /8 -> 128KHz. Count to 256 -> 500Hz
    123. */
    124. static void init_timer0( void ) {
    125.  
    126.   // Clock select: /8 prescaler - CS01 set, CS02 & CS00 not set
    127.   TCCR0 |= ( 1 << CS01);
    128.   TCCR0 &= ~( 1 << CS02 | 1 << CS00);
    129.  
    130.   // Set timer overflow interrupt
    131.   TIMSK |= ( 1 << TOIE0);
    132.  
    133.  
    134. }
    135.  
    136. /* Setup Timer 2
    137. *
    138. * This is the 1Hz timebase. It is clocked by an external 32KHz crystal, with a /1024 prescaler.
    139. * The timer counts at 32Hz, so generate compare match at 31 -> 1Hz (as interrupt happens at end
    140. * of this cycle)
    141. */
    142.  
    143. void init_timer2( void ) {
    144.  
    145.   // Clock source: external
    146.   ASSR |= 1 << AS2;
    147.  
    148.   // OCR2 setup
    149.   OCR2 = 31;
    150.  
    151.   // CTC mode: WGM20=0, WGM21=1
    152.   TCCR2 &= ~( 1 << WGM20);
    153.   TCCR2 |= 1 << WGM21;
    154.  
    155.   // Normal port operation - no OC2 used
    156.   TCCR2 &= ~( 1 << COM21 | 1 << COM20);
    157.  
    158.   // Prescaler /1024. This also starts the clock
    159.   TCCR2 |= ( 1 << CS22 | 1 << CS21 | 1 << CS20);
    160.  
    161.   // Wait until TCR2UB and OCR2UB are low (datasheet p. 117)
    162.   while ( IS_BIT_SET(ASSR, TCR2UB) || IS_BIT_SET(ASSR, OCR2UB) ) {
    163.     // Do nothing
    164.   }
    165.   // Set interrupt
    166.   TIMSK |= 1 << OCIE2;
    167.  
    168.  
    169. }
    170.  
    171.  
    172. /* Check button press (includes debouncing) */
    173. uint8_t check_button_pressed(uint8_t button) {
    174.  
    175.   if ( bit_is_clear( PIND, button )) {
    176.     // Wait a bit before returning button status
    177.     _delay_ms(DEBOUNCE_DELAY);
    178.     return bit_is_clear( PIND, button);
    179.   } else {
    180.     return 0;
    181.   }
    182. }
    183.  
    184. static uint8_t get_hour (uint32_t sec) {
    185.  
    186.   return sec / 3600;
    187.  
    188. }
    189.  
    190. static uint8_t get_minute (uint32_t sec) {
    191.  
    192.   return (sec % 3600) / 60 ;
    193.  
    194. }
    195.  
    196.  
    197. /* Set mode */
    198. static void handle_set_mode( uint32_t set_seconds ) {
    199.  
    200.   uint8_t items[2] = { get_hour(set_seconds), get_minute(set_seconds) };
    201.   uint8_t item_max[2] = { 23, 59 };
    202.   uint8_t i = 0;
    203.   uint8_t i_max = 2;
    204.  
    205.   // We're off
    206.   set_mode = 1;
    207.  
    208.   // Set hours blinking
    209.   digits_blinking = 0b00000011;
    210.  
    211.   while ( i < i_max ) {
    212.  
    213.     // Up button
    214.     if ( check_button_pressed(BUTTON_UP) ) {
    215.       if (++items[i] > item_max[i] ) { items[i] = 0; }
    216.     }
    217.     // Down button
    218.     if ( check_button_pressed(BUTTON_DOWN) ) {
    219.       if ((int8_t) --items[i] < 0 ) { items[i] = item_max[i]; }
    220.     }
    221.  
    222.     // Set button
    223.     if (check_button_pressed(BUTTON_SET)) {
    224.       i++;
    225.       digits_blinking <<= 2;
    226.     }
    227.  
    228.     // Set (possibly) new time
    229.     set_seconds = (uint32_t) items[0]*3600+items[1]*60;
    230.     display_time(set_seconds);
    231.  
    232.  
    233.   }
    234.  
    235.   // We're done.
    236.   seconds = set_seconds;
    237.   set_mode = 0;
    238.   digits_blinking = 0;
    239.   // Wait for button to be released
    240.   _delay_ms(1000);
    241.  
    242.  
    243. }
    244.  
    245.  
    246.  
    247. static void display_time (uint32_t seconds) {
    248.  
    249.   uint8_t hours = get_hour(seconds);
    250.   uint8_t minutes = get_minute(seconds);
    251.  
    252.   // Set digit values for display interrupt to pick up
    253.   digit_current_value[0] = hours / 10; // uint8_t takes care of 'round down'
    254.   digit_current_value[1] = hours % 10;
    255.   digit_current_value[2] = minutes / 10; // uint8_t takes care of 'round down'
    256.   digit_current_value[3] = minutes % 10;
    257.  
    258. }
    259.  
    260. /* Main loop */
    261. int main( void ) {
    262.  
    263.   uint8_t current_minute = 0;
    264.  
    265.   // Data direction
    266.   DDRB = 0b00011111;
    267.   DDRD = 0b01111000;
    268.  
    269.   // Set unused pins to internall pullup (datasheet best practice)
    270.   // This also takes care of the pull ups for the buttons
    271.   PORTB = 0b11100000;  // Also sets BCD_BLANK high
    272.   PORTC = 0xFF;
    273.   PORTD = 0b10000111;
    274.  
    275.  
    276.   init_timers();
    277.  
    278.   // Start interrupts
    279.   sei();
    280.  
    281.   /* Event loop */
    282.   for(;;) {
    283.  
    284.     // Clock update
    285.     if ( current_minute != get_minute(seconds) ) {
    286.  
    287.       current_minute = get_minute(seconds);
    288.       display_time( seconds );
    289.  
    290.     }
    291.  
    292.     // Set mode
    293.     if ( check_button_pressed(BUTTON_SET) ) { handle_set_mode( seconds ); };
    294.  
    295.   }
    296.  
    297.   return 0;   /* never reached */
    298.  
    299. }
    300.  
     
    Last edited: Mar 3, 2015
    ricky diaz, darrough and absf like this.
  2. tshuck

    Well-Known Member

    Oct 18, 2012
    3,531
    675
    Nice project!

    Thanks for sharing.

    Any idea what the find build cost was?
     
  3. Robartes

    Thread Starter Member

    Oct 1, 2014
    57
    13
    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?
     
    absf and tshuck like this.
Loading...