Single-Tube Nixie Clock

A PIC16F84A DCF-77 nixie tube clock

IN-14 Nixie tube glowing

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

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

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):