Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Doug Domke
Published © GPL3+

Multiplexed Nixie Tube Clock

Less expensive and easier to build than some other Nixie clocks. Displays seconds, blinking colons, and it sets itself using WiFi!

IntermediateFull instructions provided8 hours5,225

Things used in this project

Hardware components

Arduino MKR WiFi 1010
Arduino MKR WiFi 1010
$32
×1
IN-1 Nixie Tube
$18 for all 6
×6
K1551D1 BCD to Decimal IC (or SN74141)
$9 for 6
×1
A92 300 volt PNP Transistor
$9 for all 7 PNP and all 7 NPN
×7
A42 300 volt NPN Transistor
No Link and $0, as these are included in item above
×7
Neon Bulbs with resistor
$3 for all
×4
1500 ohm 1/4 watt resistor
$1
×7
1200 ohm 1/4 watt resistor
$1
×7
120 K ohm 1/4 watt resistor
$1
×7
DC-DC StepDown Converter
$5
×1
High voltage DC-DC boost converter
$9
×1
15 volt DC wall adapter
$10
×1
Small Solderable Breadboard
$12 for 5
×2

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)
3D Printer (generic)
3D Printer (generic)
Optional

Story

Read more

Custom parts and enclosures

3d Print of Clock Face

STL file

3D Print of Clock Base

STL file

3D Print of Clock Cover

STL file

Schematics

Schematic for Nixie Tube Clock

Code

Nixie_Clock

Arduino
#include <WiFiNINA.h>
#include <RTCZero.h>

// USER SETTINGS
char ssid[] = "Network";  // Your WiFi network
char pass[] = "Password";  // Your WiFi password
const int GMT = -7; //change this to adapt it to your time zone
const int myClock = 12;  // can be 24 or 12 hour clock
// END USER SETTINGS

RTCZero rtc; // create instance of real time clock
int status = WL_IDLE_STATUS;
int myhours, mins, secs, myday, mymonth, myyear;
int mydigit [6] = {1, 2, 3, 4, 5, 6};  //the actual digits to be stored in the Nixie tubes

void setup() {
  while ( status != WL_CONNECTED) {
    status = WiFi.begin(ssid, pass);
  }
  delay(6000); // setRTC hangs without this delay - you may be able to shorten it.
  rtc.begin();
  setRTC();  // get Epoch time from Internet Time Service
  fixTimeZone();
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  timerSetup(); // set up the ISR timer for refreshing Nixies
}

void loop() {
  secs = rtc.getSeconds();
  // when secs is 0, update everything and correct for time zone
  if (secs == 0) fixTimeZone(); // otherwise everything else stays the same.
  mydigit[0]= myhours/10;
  mydigit[1]= myhours%10;
  mydigit[2]= mins/10;
  mydigit[3]= mins%10;
  mydigit[4]= secs/10;
  mydigit[5]= secs%10;
  analogWrite(19, 16); // turn on colons
  delay(60);  // due to refreshes, this delay is approx. 500 ms
  analogWrite(19, 0); // turn off colons
  while (secs == rtc.getSeconds())delay(10); // wait until seconds change
  if (mins == 59 && secs == 0) setRTC(); // get NTP time every hour at minute 59
}


void setRTC() { // get the time from Internet Time Service
  unsigned long epoch = 0;
  int numberOfTries = 0, maxTries = 10;
  do {
    epoch = WiFi.getTime();
    numberOfTries++;
  }
  while ((epoch == 0) && (numberOfTries < maxTries));

  if (numberOfTries == maxTries) {
    while (1);  // hang
  }
  else {
    rtc.setEpoch(epoch); // The RTC is set to GMT or 0 Time Zone and stays at GMT.
  }
}


/* (Correction here for day, month and year could be removed for the Nixie clock)
   There is more to adjusting for time zone that just changing the hour.
   Sometimes it changes the day, which sometimes chnages the month, which
   requires knowing how many days are in each month, which is different
   in leap years, and on Near Year's Eve, it can even change the year! 
   Some of this could be deleted for Nixies where we are only showing hours, minutes, seconds */
void fixTimeZone() {
  int daysMon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  if (myyear % 4 == 0) daysMon[2] = 29; // fix for leap year
  myhours = rtc.getHours();
  mins = rtc.getMinutes();
  myday = rtc.getDay();
  mymonth = rtc.getMonth();
  myyear = rtc.getYear();
  myhours +=  GMT; // initial time zone change is here
  if (myhours < 0) {  // if hours rolls negative
    myhours += 24; // keep in range of 0-23
    myday--;  // fix the day
    if (myday < 1) {  // fix the month if necessary
      mymonth--;
      if (mymonth == 0) mymonth = 12;
      myday = daysMon[mymonth];
      if (mymonth == 12) myyear--; // fix the year if necessary
    }
  }
  if (myhours > 23) {  // if hours rolls over 23
    myhours -= 24; // keep in range of 0-23
    myday++; // fix the day
    if (myday > daysMon[mymonth]) {  // fix the month if necessary
      mymonth++;
      if (mymonth > 12) mymonth = 1;
      myday = 1;
      if (mymonth == 1)myyear++; // fix the year if necessary
    }
  }
  if (myClock == 12) {  // this is for 12 hour clock
    myhours = myhours % 12; // convert to 12 hour clock
    if (myhours == 0) myhours = 12;  // show noon or midnight as 12
  }
}

void BCDout(int mynumber) {  // this routine take a number and outputs it to the 74141 as BCD (Binary Coded Decimal)
  for (int pin = 6; pin < 10; pin++) {  // set all output to 0
    digitalWrite(pin, LOW);
  }
  if (mynumber > -1 && mynumber < 11) {  // make sure number is in range
    if (mynumber & 1)digitalWrite(6, HIGH);
    if (mynumber & 2)digitalWrite(7, HIGH);
    if (mynumber & 4)digitalWrite(8, HIGH);
    if (mynumber & 8)digitalWrite(9, HIGH);
  }
}

void timerSetup() { // sets up a 16 millisecond timer for ISR (interrupt service routine)
  // Set up the generic clock (GCLK4) used by ISR
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_TC4_COUNT16_CC0 = 0xB71A;                   // Set the TC4 CC0 register as the TOP value in match frequency mode
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_INTFLAG |= TC_INTFLAG_OVF;              // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_OVF;             // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_OVF;          // Disable TC4 interrupts

  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV16 |   // Set prescaler to 8, 48MHz/8 = 46.875kHz   *** DIV8 = 8ms Timer  / Div16 = 16ms Timer
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
}

void TC4_Handler()  // Interrupt Service Routine (ISR) to refresh Nixie tube display
{ // Each Nixie is turned on with its digit of the time for 2.5 msec.
  for (int digit = 0; digit < 6; digit++) {
    BCDout(mydigit[digit]);
    digitalWrite(digit, HIGH);
    delayMicroseconds(2500);
    digitalWrite(digit, LOW);
  }
  TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;             // Clear the OVF interrupt flag
}

Nixie_Test_Routine

Arduino
int count = 0;

void setup() {
  Serial.begin(9600);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(19, OUTPUT);
}

void loop() {
  for (int nixie = 0; nixie < 6; nixie++) {
    digitalWrite(nixie, HIGH); 
    for (count = 0; count < 10; count++) {
      BCDout(count);
      if (count%2)analogWrite(19, 128); 
      delay(300);
      analogWrite(19, 0); 
    }
    digitalWrite(nixie, LOW);
  }  
}

void BCDout(int mynumber) {
  for (int pin = 6; pin < 10; pin++) {
    digitalWrite(pin, LOW);
  }
  if (mynumber > -1 && mynumber < 11) {
    if (mynumber & 1)digitalWrite(6, HIGH);
    if (mynumber & 2)digitalWrite(7, HIGH);
    if (mynumber & 4)digitalWrite(8, HIGH);
    if (mynumber & 8)digitalWrite(9, HIGH);
  }
}

Credits

Doug Domke

Doug Domke

39 projects • 105 followers

Comments