Nixie clock (no weird russian chips)

Nixie clocks are really beautiful pieces of equipment, but interfacing high voltage (250 vdc) obsolete components with 5 V microcontrollers is not an easy task. I came across many designs on the internet that use obsolete chips to solve this problem, but these chips are expensive and really hard to find.

1

How it works

Main board

3

The main board is based on the Atmel ATMEGA328P-PU microcontroller. The code was written in Arduino IDE and an Arduino UNO was used for programming the micro. The date and time are set using three buttons (Set, +, -). The clock also has a RTC ( DS3231 ) that keeps running when the clock loses power. It uses a supercapacitor that is charged while the clock is plugged in. (it works about a month without external power)

main_layout

Power supply

The power supply consists of a 9 VAC wall adapter, a small 12 V – 220 V transformer (used to step up), a rectifier bridge and filter capacitor for the anode voltage and another rectifier bridge, filter cap and 7805 regulator for the micro, RTC and the driver.

Nixie driver

The data from the microcontroller comes as BCD (parallel). There are 4 wires that select which digit is to be displayed that go into a BCD decoder, then to the cathodes of the tubes through the cathode output stages that use one NPN transistor each. Since the tubes are multiplexed, all the corresponding cathodes are wired in parallel. The tubes are powered one at a time but because the refresh rate is high enough they look like they are permanently turned on. In order to select which tube is on I used the same principle as for the cathodes, but the output stages are different because the PNP transistors are on the hot side (250 V to the emitters). I used optocouplers to drive the PNP transistors.

nixie_layout

 

The code

Note : The micro runs Arduino code although it does not have an Arduino bootloader on it. (the bootloader is not needed)
The code first checks if there is valid date and time data in the RTC. If data is found, the Time library gets the time from the RTC, then the clock starts running. If no data is found, the tubes display “00” and the user should set the time using the buttons. (time is set on both the micro and the RTC, of course) The micro also updates its time from the RTC once a day because it is less precise than the temperature compensated RTC.
Time is displayed by first turning on one of the NPN (cathode) transistor through the decoder. Then, the corresponding tube is powered for a short period of time by applying 250 V to its anode through the decoder and the anode output stage. Then the algorhytm moves to the next tube and so on and cycles through all tubes in order to make them seem turned on continuously. This is done because you cannot turn multiple tubes on while they’re multiplexed. (google multiplexing for more detail) Once every 20 seconds the time shifts left one digit at a time and then the date shifts in from the right and stays for a few second, then the clock displays time again.

//Nixie clock with RTC
//Written by Stelian Saracut for Arduino 1.0.4
#include <Wire.h>
#include "RTClib.h"
#include <Time.h> 



RTC_DS1307 rtc;

int k;
int del = 200; //wait between digits
int del2 = 400; //wait
boolean cs=HIGH,ps=HIGH,disp=LOW;

void setup() {
  //initializing
  DDRD = B1111; //port D outputs to cathode decoder
  DDRB = B1110; //port B outputs to anode decoder
  PORTC = B110111; //enable pullups for I2C (RTC) and buttons on port C
  Wire.begin();
  rtc.begin();
  if (! rtc.isrunning())
    adjTime(); //manually set time
    else
    getRTCtime(); //get time from RTC
  startup(400); //just because it looks nice
  
}


void loop() {
  if ((hour() == 0)&&(second()==0)) getRTCtime(); //update time once a day
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW)) 
      adjTime(); //manually adjust time
   ps = cs;
  if (second() % 20 == 0) disp = HIGH;
   if (disp == HIGH) {
     //time shifts out and date shifts in
      if (k<=100) nixDisp(hour() % 10,minute()/10,minute() % 10,second()/10,second() % 10,11);
      if ((k>100) && (k<=200)) nixDisp(minute()/10,minute() % 10,second()/10,second() % 10,11,11); 
      if ((k>200) && (k<=300)) nixDisp(minute() % 10,second()/10,second() % 10,11,11,11); 
      if ((k>300) && (k<=400)) nixDisp(second()/10,second() % 10,11,11,11,11);    
      if ((k>400) && (k<=500)) nixDisp(second() % 10,11,11,11,11,11);     
      if ((k>500) && (k<=600)) nixDisp(11,11,11,11,11,11); //blank display   
      if ((k>600) && (k<=700)) nixDisp(11,11,11,11,11,day()/10);     
      if ((k>700) && (k<=800)) nixDisp(11,11,11,11,day()/10,day() % 10); 
      if ((k>800) && (k<=900)) nixDisp(11,11,11,day()/10,day() % 10,month()/10); 
      if ((k>900) && (k<=1000)) nixDisp(11,11,day()/10,day() % 10,month()/10,month() % 10); 
      if ((k>1000) && (k<=1100)) nixDisp(11,day()/10,day() % 10,month()/10,month() % 10,year() % 100 /10); 
      if ((k>1100) && (k<=1500)) nixDisp(day()/10,day() % 10,month()/10,month() % 10,year() % 100 /10,year() % 10); 
      if (k>1500) {
       disp = LOW; //back to normal operation
       k = 0;
     } 
    k++; 
   }
   else
    
     
      
   nixDisp(hour()/10,hour() % 10,minute()/10,minute() % 10,second()/10,second() % 10); //display time under normal operation
       
}

void getRTCtime() {
  DateTime now = rtc.now();
  setTime(now.hour(),now.minute(),now.second(),now.day(),now.month(),now.year());
  
} 

//function for adjusting time manually and button debouncing
void adjTime() {
  int H=0,M=0,D=1,Mo=1,Y=13;
  boolean cs1=HIGH,cs2=HIGH,ps1=HIGH,ps2=HIGH,cs=HIGH,ps=LOW;
  
 
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=H/10;
  PORTB = B1010;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=H%10;
  PORTB = B1000;
  
  
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         H--;
   if (H <0) H=23;    if ((cs2 != ps2)&&(cs2 == LOW))          H++;    if (H >23) H=0;
   ps1 = cs1;
   ps2 = cs2;
  
   delay(10);
  }
   
 
  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
 
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=M/10;
  PORTB = B110;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=M%10;
  PORTB = B100;

   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         M--;
   if (M < 0) M = 59;    if ((cs2 != ps2)&&(cs2 == LOW))          M++;    if (M > 59) M = 0;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }
  

  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=D/10;
  PORTB = B1010;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=D%10;
  PORTB = B1000;

   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         D--;
   if (D < 0) D = 31;    if ((cs2 != ps2)&&(cs2 == LOW))          D++;    if (D > 31) D = 0;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }
  
  
  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Mo/10;
  PORTB = B110;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Mo%10;
  PORTB = B100;
   
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         Mo--;
   if (Mo < 1) Mo = 12;    if ((cs2 != ps2)&&(cs2 == LOW))          Mo++;    if (Mo > 12) Mo = 1;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }


  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Y/10;
  PORTB = B10;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Y%10;
  PORTB = B0;
   
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         Y--;
   if (Y < 0) Y = 99;    if ((cs2 != ps2)&&(cs2 == LOW))          Y++;    ps1 = cs1;    ps2 = cs2;    delay(10);   }   rtc.adjust(DateTime(Y,Mo,D,H,M,0));   setTime(H,M,1,D,Mo,Y);             }  //button debouncing boolean debounce(boolean last, int pin) {  boolean current = digitalRead(pin);  if (last != current) {    delay(5);    current = digitalRead(pin);  }  return current; } void nixDisp(int a, int b, int c, int d, int e, int f) {   PORTB = B1110;    delayMicroseconds(del2);    PORTD=f;     if (f > 9) PORTB = B1110;
    else
    PORTB = B0; 
  delayMicroseconds(del); 
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=e;
  if (e > 9) PORTB = B1110;
    else
    PORTB = B10;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=d;
  if (d > 9) PORTB = B1110;
    else
    PORTB = B100;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=c;
  if (c > 9) PORTB = B1110;
    else
    PORTB = B110;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=b;
  if (b > 9) PORTB = B1110;
    else
    PORTB = B1000;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD =a;
  if (a > 9) PORTB = B1110;
    else
    PORTB = B1010;
  delayMicroseconds(del);
}


//some sort of counter that looks good
void startup(int t) {
  int a=0,b=1,c=2,d=3,e=4,f=5,i,j;
  for (j=1; j<=t; j++) {
    i++;
    if (i % 10 == 0) {
      a++; b++; c++;
      d++; e++; f++;
     }
    if (a == 10) a=0;
    if (b == 10) b=0;
    if (c == 10) c=0;
    if (d == 10) d=0;
    if (e == 10) e=0;
    if (f == 10) f=0;
  
    nixDisp(a,b,c,d,e,f); 
  } 
}

You can post my designs anywhere else as long as you provide a link to the original source or metion me as the author.

Click to download project files.

Contact me if you’re interested in buying fully assembled nixie clocks, driver boards or main boards.
Contact : Stelian Saracut – stelian@saracut.ro