// timer_demo2.c
// -------------
//
// Example file for ATmega88 (and similar AVR chips) demonstrating how to use
// timer interrupt to realize Clocks with LED display.
//
// The program uses TIMER1 to generate interrupts and the interrupt routine counts
// fragments of second, seconds, tens of seconds etc. In the same interrupt routine
// the display multiplex is done. We use 4 digit 7 segment display.
// Common anodas are connected to LED_DIGITS_PORT and segments are connected
// to LED_SEGMENTS_PORT (use serial rezistors here, cca 100 OHM).
//
// (c) miho 2014 http://www.mlab.cz
//
// History
//   2014 01 31 - First demo


// System Configuration
#define F_CPU 4000000                                   // Do not forget to set FUSEs to external XTAL osc with DIV/8 off and connect xtal

// LED Display - Configuration
#define LED_DIGITS_PORT         C                       // Digits Port (common anodas)
#define LED_DIGITS                      4                       // Number of display digits (1..8)
#define LED_SEGMENTS_PORT       D                       // Segments Port (catodas)
#define LED_SEGMENTS            8                       // Usualy 7 or 8 (bit 0 is A)
#define TICKS_PER_SEC           500                     // Number of interrupts per second ( >250 to look steady)


// -------------- DO NOT EDIT BELOW THIS LINE --------------


// LED Display - Internal Defs
#define LED_DIGITS_MASK         ((1<<LED_DIGITS)-1)             // For 4 digits   0x0F=0b00001111
#define LED_SEGMENTS_MASK       ((1<<LED_SEGMENTS)-1)   // For 7 segments 0x7F=0b01111111


// LED Display - Verify Configuration
#if (F_CPU / 64 % TICKS_PER_SEC)
#error "TICKS_PER_SEC" should be chosen so that "F_CPU / 64 / TICKS_PER_SEC" is a whole number!
#endif
#if ( (F_CPU / 64 / TICKS_PER_SEC) > 65536)
#error "TICKS_PER_SEC" should be chosen bigger (timer is long 16bit only)!
#endif


// Library Headers
#include <avr/interrupt.h>


// Compatibility ATmega8 / ATmega88
#ifndef TIMSK
#define TIMSK TIMSK1
#endif


// Macro for Port (enables to easily define IO signals)
#define GLUE(A,B) A##B
#define DDR(PORT_LETTER)  GLUE(DDR, PORT_LETTER)                // Makes DDRC  from DDR(C) etc.
#define PORT(PORT_LETTER) GLUE(PORT,PORT_LETTER)                // Makes PORTC from PORT(C)
#define PIN(PORT_LETTER)  GLUE(PIN, PORT_LETTER)                // Makes PINC  from PIN(C)


// 7 Segment Decoder
unsigned char Convert(unsigned char Number)
{
        // 7 Segment Decoder Table
        const unsigned char Decoder[] = 
        {
        //        HGFEDCBA
                0b00111111,             // 0
                0b00000011,             // 1
                0b01101101,             // 2
                0b01100111,             // 3
                0b01010011,             // 4
                0b01110110,             // 5
                0b01111110,             // 6
                0b00100011,             // 7
                0b01111111,             // 8
                0b01110111              // 9
        };

        // Decoding Function
        if(Number<sizeof(Decoder)) return Decoder[Number]; else return 0;
}


// Global Variable - Time Counters
volatile unsigned int Fractions;                        // 1/100s
volatile unsigned char Seconds, Seconds10;      // Seconds and tens of seconds
volatile unsigned char Minutes, Minutes10;      // Minutes and tens of minutes
volatile unsigned char Hours, Hours10;          // Hours and tens of hours


// Global Variable - Display Multiplex
volatile unsigned char DisplayDigit;            // Multiplex state 0 1 2 3 (binary counter)
volatile unsigned char DisplayDigitMask;        // Multiplex state 1 2 4 8 (mask 1 from N)


// Interrupt Routine for TIMER1 Output Compare A
// Activated 500 per second
ISR(TIMER1_COMPA_vect)
{
        // Every 1/500s
        Fractions++;
        if (Fractions>(TICKS_PER_SEC-1))
        {
                // Every 1s
                Fractions=0;
                Seconds++;
                if (Seconds>9)
                {
                        // Every 10s
                        Seconds = 0;
                        Seconds10++;
                        if (Seconds10>5)
                        {
                                // Every 1m
                                Seconds10=0;
                                Minutes++;
                                if (Minutes>9)
                                {
                                        // Every 10m
                                        Minutes=0;
                                        Minutes10++;
                                        if (Minutes10>5)
                                        {
                                                // Every 1h
                                                Minutes10=0;
                                                Hours++;
                                                if (Hours10==2 && Hours>3)
                                                {
                                                        // Every Day
                                                        Hours=0;
                                                        Hours10=0;
                                                }
                                                if (Hours>9)
                                                {
                                                        // At 10 and 20 hours
                                                        Hours=0;
                                                        Hours10++;
                                                }
                                        }
                                }
                        }
                }
        }

        // LED display time multiplex - next digit
        // 500x per second
        DisplayDigit++;
        DisplayDigitMask<<=1;
        if (DisplayDigit == LED_DIGITS)
        {
                DisplayDigit = 0;
                DisplayDigitMask = 1;
        }

        // Get Segment Combination for Current Digit
        unsigned char Segments=0;
        switch(DisplayDigit)
        {
        case 0: Segments = Convert(Seconds);
                        break;
        case 1: Segments = Convert(Seconds10);
                        break;
        case 2: Segments = Convert(Minutes);
                        break;
        case 3: Segments = Convert(Minutes10);
        }

        // LED display update - All digits off
        PORT(LED_DIGITS_PORT) &= ~LED_DIGITS_MASK;      // common anoda 0=off
        // LED display update - New segments combination
        PORT(LED_SEGMENTS_PORT) = (PORT(LED_SEGMENTS_PORT) & ~LED_SEGMENTS_MASK) | (~Segments & LED_SEGMENTS_MASK);
        // LED display update - One digit on
        PORT(LED_DIGITS_PORT) |= (LED_DIGITS_MASK & DisplayDigitMask); // (common anoda 1=on)
}


// Main
int main()
{
        // Enable LED Display Output
        DDR(LED_SEGMENTS_PORT)  |= LED_SEGMENTS_MASK;   // 8 segments 0b11111111
        DDR(LED_DIGITS_PORT)    |= LED_DIGITS_MASK;             // 4 digits   0b00001111

        // Set MAX value for Timer1
        OCR1A = (F_CPU+64/2)/64/TICKS_PER_SEC-1;        // 1/500s

        // Set Timer1 to CTC with presacaller 1/64
        // CTC  Mmode counts from 0 to value stored in OCR1A
        // and generates interrupt every time it goes back to 0
        TCCR1B |= (1<<CS11) | (1<<CS10) | (1<<WGM12);

        // Enable Interrupt for Timer1 OCRA
        TIMSK |= (1<<OCIE1A);

        // Enable Global (CPU) Interrupt
        sei();

        // Main Loop
        for(;;)
        {
                // Do any job here
        }

        return 0;
}