
 * DCF-77 SINGLE NIXIE CLOCK
 * Bruno Gavand
 * January, 2006
 * www.micro-examples.com
 *
 * This code is given 'as this', without any garantee
 * Use it at your own risks !
 *
 * PIC16F84A
 * 16 Mhz crystal, HS clock
 *
 * PORTA.0, in  : DCF pulse input
 * PORTA.1, out : LED pulse repeater
 * PORTA.2, out : LED pulse synch
 * PORTA.3, in  : SWITCH, pull-down
 *
 * PORTB.0->3, out      : BCD to 74141 decoder
 * PORTB.4->5, undef    : not connected
 * PORTB.6, in          : voltage control
 * PORTB.7, out         : 40 Khz pulse for nixie high voltage power supply
 *
 */

/*
 * constant definitions
 */
#define BLANK   0x0f            // BCD code for nixie blanking
#define MAXCOUNT        15625    // number of tmr0 overflows in 1 second
#define ADJUST  0             // number of extra cycles in one second

/*
 * following values are reduced
 * to give some flexibility to DCF-77 pulse reception
 */
#define timer_d_min 28000       // number of tmr0 overflows in 2 seconds
#define timer_h_0 1400          // number of tmr0 overflows in 0.1 second
#define timer_h_1 2800          // number of tmr0 overflows in 0.2 second

/*
 * macro definitions
 */
#define putNibble(b)            PORTB = b       // write a BCD value for 74141

/*
 * RAM variables
 */
unsigned int    tmrh ;          // count of tmr0 overflows since pulse is high
unsigned int    tmrd ;          // count of tmr0 overflows since pulse is down
unsigned char   bitnum ;        // number of last valid bit received
char            last_bit ;      // value of last valid bit received
unsigned char   parity ;        // count of positive bits
unsigned char   full ;          // set to 1 when sequence is complete
unsigned char   locked ;        // set to 1 when clock has been adjusted
unsigned char   mode ;          // 0:positive logic receiver, 1:negative logic receiver, 2:no receiver
unsigned char   mn ;            // minutes in sequence progress
unsigned char   hh ;            // hours in sequence progress
unsigned int    scaler ;        // count of tmr0 overflows for RTC
unsigned char   ohh, omn, oss ; // RTC clock : hours, minutes, seconds
unsigned char   dhh, dmn, dss ; // display : hours, minutes, seconds
unsigned char   pwm ;           // PWM output enable flag
unsigned char   vmax, vcurr ;   // max and current value for selector
unsigned char   curr ;          // selector choice

/*
 * wait n times 25 ms
 */
void    delay_25ms(unsigned char n)
        {
        while(n)
                {
                Delay_ms(25) ;
                n-- ;
                }
        }

/*
 * display a value from 00 to 99 on nixie, tenths first and then units
 */
void    mkDigit(unsigned char d)
        {
        putNibble(BLANK) ;
        delay_25ms(10) ;

        putNibble(d / 10) ;
        delay_25ms(16) ;
        
        putNibble(BLANK) ;
        delay_25ms(2) ;
        
        putNibble(d % 10) ;
        delay_25ms(12) ;
        }

/*
 * select a digit from 0 to vmax
 * make the digit vcurr flash to show the current selected value
 * curr contains the digit displayed when the key is pressed
 */
void    selectDigit(void)
        {
        unsigned char i, j, k ;

        pwm = 1 ;       // turn nixie on

        i = 0 ;
        for(;;)
                {
                for(j = 0 ; j <= vmax ; j++)
                        {
                        for(k = 0 ; k < 10 ; k++)
                                {
                                putNibble(j) ;
                                delay_25ms(1) ;
                                putNibble(vcurr == j ? BLANK : j) ;
                                delay_25ms(1) ;
                                if(PORTA.F3)
                                        {
                                        putNibble(BLANK) ;
                                        delay_25ms(8) ;
                                        putNibble(j) ;
                                        pwm = 0 ;
                                        curr = j ;
                                        return ;
                                        }
                                }
                        }

                if(vcurr < 10)
                        {
                        i++ ;
                        if(i == 3)
                                {
                                pwm = 0 ;
                                curr = vcurr ;
                                return ;
                                }
                        }
                }
        }

/*
 * interrupt routine called 2500000/256 times by seconds
 */
void    interrupt(void)
        {
        if(INTCON.T0IF)
                {
                PORTB.F7 = !PORTB.F7 & !PORTB.F6 & pwm ;
//                PORTB.F7 = !PORTB.F7 ;


                if(PORTA.F0 ^ mode)
                        {
                        tmrh++ ;
                        if(tmrd > timer_d_min)
                                {
                                bitnum = 0 ;
                                if(full)
                                        {
                                        ohh = hh ;
                                        omn = mn ;
                                        oss = 3 ;
                                        scaler = 0 ;
                                        locked = 1 ;
                                        }
                                mn = hh = 0 ;
                                parity = 0 ;
                                full = 0 ;
                                PORTA.F2 = 1 ;
                                }
                        tmrd = 0 ;
                        }
                else
                        {
                        tmrd++ ;
                        if(tmrh > 0)
                                {
                                if(tmrh > timer_h_1)
                                        {
                                        last_bit = 1 ;

                                        switch(bitnum)
                                                {
                                                case 21: mn++ ; break ;
                                                case 22: mn += 2 ; break ;
                                                case 23: mn += 4 ; break ;
                                                case 24: mn += 8 ; break ;
                                                case 25: mn += 10 ; break ;
                                                case 26: mn += 20 ; break ;
                                                case 27: mn += 40 ; break ;

                                                case 29: hh++ ; break ;
                                                case 30: hh += 2 ; break ;
                                                case 31: hh += 4 ; break ;
                                                case 32: hh += 8 ; break ;
                                                case 33: hh += 10 ; break ;
                                                case 34: hh += 20 ; break ;
                                                }

                                        if((bitnum != 28) && (bitnum != 35) && (bitnum != 58))
                                                {
                                                parity++ ;
                                                }
                                        bitnum++ ;
                                        }
                                else if(tmrh > timer_h_0)
                                        {
                                        if(bitnum == 20)
                                                {
                                                last_bit = -1 ;

                                                bitnum = 0 ;
                                                PORTA.F2 = 0 ;
                                                }
                                        else
                                                {
                                                last_bit = 0 ;

                                                bitnum++ ;
                                                }
                                        }
                                else
                                        {
                                        last_bit = -1 ;

                                        bitnum = 0 ;
                                        PORTA.F2 = 0 ;
                                        }

                                if(bitnum == 21)
                                        {
                                        parity = 0 ;
                                        }
                                if((bitnum == 29) || (bitnum == 36) || (bitnum == 59))
                                        {
                                        if((parity & 1) != last_bit)
                                                {
                                                bitnum = 0 ;
                                                PORTA.F2 = 0 ;
                                                }
                                        parity = 0 ;
                                        }

                                if(bitnum == 59)
                                        {
                                        full++ ;
                                        }
                                }
                        tmrh = 0 ;
                        }

                scaler++ ;
                if(scaler > MAXCOUNT)
                        {
//                        TMR0 += ADJUST ;
                        scaler = 0 ;
                        
                        oss++ ;
                        if(oss == 60)
                                {
                                oss = 0 ;
                                omn++ ;
                                if(omn == 60)
                                        {
                                        omn = 0 ;
                                        ohh++ ;
                                        if(ohh == 24)
                                                {
                                                ohh = 0 ;
                                                }
                                        }
                                }
                        }

                PORTA.F1 = PORTA.F0 ^ mode ;

                INTCON.T0IF = 0 ;
                }
        }

/*
 * program entry point
 */
main()
        {
        TRISA = 0b00001001 ;            // see header
        TRISB = 0b01000000 ;                  // see header
	
        INTCON = 0b10100000 ;           // T0IF and GIE enabled

        OPTION_REG = 0b11011000 ;       // no prescaler
        
        locked = 0 ;

        vcurr = EEPROM_Read(0) ;        // get last mode

        /*
         * select mode from 0 to 2
         * mode 0 : positive DFC pulse
         * mode 1 : negative DCF pulse
         * mode 3 : no DCF, manual adjustment
         */
        vmax = 2 ;
        selectDigit() ;
        mode = curr ;           // new mode

        /*
         * if mode 2 (no DCF), proceed to hour and minute settings
         */
        vcurr = 10 ;
        if(mode == 2)
                {
                /*
                 * tenth of hours
                 */
                vmax = 2 ;
                selectDigit() ;
                ohh = curr * 10 ;
                /*
                 * units of hours
                 */
                vmax = 9 ;
                selectDigit() ;
                ohh += curr ;

                /*
                 * tenth of minutes
                 */
                vmax = 5 ;
                selectDigit() ;
                omn = curr * 10 ;
                /*
                 * units of minutes
                 */
                vmax = 9 ;
                selectDigit() ;
                omn += curr ;

                /*
                 * seconds must be 3 in advance, due to display system
                 */
                oss = 3 ;

                /*
                 * clock is adjusted, lock it
                 */
                locked = 1 ;
                }

        EEPROM_Write(0, mode) ;         // store selected mode
        
        /*
         * main loop
         */
        for(;;)
                {
                /*
                 * if not locked, no display to prevent
                 * radio emission from high voltage supply
                 * that could perturb DCF receiver
                 */
                if(locked > 0)
                        {
                        if(PORTA.F3)
                                {
                                /*
                                 * enter edit sleep mode
                                 */
                                for(dhh = 0 ; dhh < 24 ; dhh++)
                                        {
                                        pwm = 1 ;       // turn nixie on
                                        mkDigit(dhh) ;  // display hours
                                        vcurr = EEPROM_Read(0x10 + dhh) ;       // get current sleep mode for hour
                                        vmax = 1 ;                              // range from 0 to 1
                                        selectDigit() ;                         // select new value
                                        EEPROM_Write(0x10 + dhh, curr) ;        // store choice in EEPROM
                                        }
                                }
                        else
                                {
                                /*
                                 * otherwise, start display cycle
                                 */
                                PORTB = BLANK ;
                                pwm = 1 ;                       // turn nixie on
                                delay_25ms(1) ;

                                /*
                                 * time values have to be stored in other variables
                                 * in order to prevent changes during display
                                 */
                                dss = oss ;
                                dmn = omn ;
                                dhh = ohh ;

                                mkDigit(dhh) ;
                                mkDigit(dmn) ;
                                mkDigit(dss) ;

                                pwm = 0 ;               // make nixie extinguish
                        
                                if(EEPROM_Read(0x10 + dhh))
                                        {
                                        /*
                                         * display time, just make a short pause
                                         */
                                        delay_25ms(60) ;
                                        }
                                else
                                        {
                                        /*
                                         * sleep time
                                         */
                                        if(mode == 2)
                                                {
                                                /*
                                                 * long pause when no dcf receiver
                                                 */
                                                delay_25ms(255) ;
                                                }
                                        else
                                                {
                                                /*
                                                 * dcf receiver is there,
                                                 * unlock to allow new sync
                                                 * meanwhile there will be no PWM activity
                                                 */
                                                locked = 0 ;
                                                }
                                        }
                                }
                        }
                }
        }
