A PIC16F84A single-digit nixie clock with DCF-77 atomic clock support and a high voltage power supply built with only 4 components.

Overview

This project features a PIC16F84A driving a single-digit nixie tube as a clock. It supports DCF-77 atomic clock synchronization for automatic time setting. The high voltage power supply uses only 4 components. 24-hour programmable extinction saves tube life. No MikroC licence is needed as the firmware fits within the demo limit.

Powering the Nixie

Nixie tubes require approximately 170V at about 1.5mA. A DC/DC converter generates this from the +5V supply using software PWM driving a MOSFET. The components are: an IRF830 MOSFET, a 330uH coil, a fast recovery diode, and a 2.2uF 250V capacitor.

WARNING: High Voltage

The DC/DC converter produces dangerous voltage (170V). Exercise extreme caution when building and testing this circuit.

High voltage power supply
High voltage power supply

Driving the Nixie

The nixie tube is driven through a 74141 BCD-to-decimal decoder, which contains high-voltage open-collector transistors suitable for nixie driving. The IN-14 Russian nixie tube fits on a standard 14-pin DIP socket.

IN-14 on DIP socket
IN-14 on DIP socket

Circuit Schematic

Circuit schematic
Circuit schematic

R3 and D2 protect the PIC input from the high voltage feedback.

Operation

Mode selection at power-up: 0 = positive logic DCF, 1 = negative logic DCF, 2 = manual time setting. The single digit sequentially displays hours, minutes, and seconds with pauses between each pair of digits.

C Source Code

nixieclock.c
/*
 * DCF-77 SINGLE NIXIE CLOCK
 * January, 2006
 *
 * 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 HV power supply
 *
 *******************************************************************************
 */

#define BLANK   0x0f            // BCD code for nixie blanking
#define MAXCOUNT        15625   // TMR0 overflows in 1 second
#define ADJUST  0

#define timer_d_min 28000       // TMR0 overflows in ~2 seconds
#define timer_h_0 1400          // TMR0 overflows in ~0.1 second
#define timer_h_1 2800          // TMR0 overflows in ~0.2 second

#define putNibble(b)            PORTB = b

unsigned int    tmrh ;
unsigned int    tmrd ;
unsigned char   bitnum ;
char            last_bit ;
unsigned char   parity ;
unsigned char   full ;
unsigned char   locked ;
unsigned char   mode ;          // 0:pos, 1:neg, 2:no DCF
unsigned char   mn ;
unsigned char   hh ;
unsigned int    scaler ;
unsigned char   ohh, omn, oss ; // RTC
unsigned char   dhh, dmn, dss ; // display values
unsigned char   pwm ;           // PWM output enable
unsigned char   vmax, vcurr ;
unsigned char   curr ;

void    delay_25ms(unsigned char n)
        {
        while(n)
                { Delay_ms(25) ; n-- ; }
        }

/* display a value 00-99 on nixie */
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, flashing vcurr */
void    selectDigit(void)
        {
        unsigned char i, j, k ;

        pwm = 1 ;
        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 ; }
                        }
                }
        }

/*
 * ISR
 */
void    interrupt(void)
        {
        if(INTCON.T0IF)
                {
                PORTB.F7 = !PORTB.F7 & !PORTB.F6 & pwm ;

                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 ;
                        }

                /* RTC */
                scaler++ ;
                if(scaler > MAXCOUNT)
                        {
                        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
 */
main()
        {
        TRISA = 0b00001001 ;
        TRISB = 0b01000000 ;

        INTCON = 0b10100000 ;
        OPTION_REG = 0b11011000 ;

        locked = 0 ;
        vcurr = EEPROM_Read(0) ;        // get last mode

        /* select mode 0-2 */
        vmax = 2 ;
        selectDigit() ;
        mode = curr ;

        /* if mode 2 (no DCF), set time manually */
        vcurr = 10 ;
        if(mode == 2)
                {
                vmax = 2 ; selectDigit() ; ohh = curr * 10 ;   // hours tens
                vmax = 9 ; selectDigit() ; ohh += curr ;       // hours units
                vmax = 5 ; selectDigit() ; omn = curr * 10 ;   // minutes tens
                vmax = 9 ; selectDigit() ; omn += curr ;       // minutes units
                oss = 3 ;
                locked = 1 ;
                }

        EEPROM_Write(0, mode) ;         // store selected mode

        /* main loop */
        for(;;)
                {
                if(locked > 0)
                        {
                        if(PORTA.F3)
                                {
                                /* edit sleep mode schedule */
                                for(dhh = 0 ; dhh < 24 ; dhh++)
                                        {
                                        pwm = 1 ;
                                        mkDigit(dhh) ;
                                        vcurr = EEPROM_Read(0x10 + dhh) ;
                                        vmax = 1 ;
                                        selectDigit() ;
                                        EEPROM_Write(0x10 + dhh, curr) ;
                                        }
                                }
                        else
                                {
                                /* display cycle */
                                PORTB = BLANK ;
                                pwm = 1 ;
                                delay_25ms(1) ;

                                dss = oss ; dmn = omn ; dhh = ohh ;

                                mkDigit(dhh) ;
                                mkDigit(dmn) ;
                                mkDigit(dss) ;

                                pwm = 0 ;               // nixie off

                                if(EEPROM_Read(0x10 + dhh))
                                        {
                                        delay_25ms(60) ;        // short pause
                                        }
                                else
                                        {
                                        if(mode == 2)
                                                { delay_25ms(255) ; }   // long pause
                                        else
                                                { locked = 0 ; }        // unlock for re-sync
                                        }
                                }
                        }
                }
        }

Download

Single-Tube Nixie Clock files

Pre-compiled HEX file ready to program into your PIC16F84A (16 MHz crystal):

nixieclock.hex