You are here : Main projects | RLB Designs' Nixie Tube Clock | 3. Source code



RLB Designs' Nixie Tube Clock with Robin Birtles
April 2014

3.  Source code

A step-by-step review of the program running in the Arduino. In addition to in-code comments, some extra explanations are given below each piece to ease understanding. All the codes presented in this page are licensed under CC-BY-SA.

Let's first see what are the clock's functionalities. The clock has three display modes  :

  • Time — displays time in HH:MM:SS format.
  • Date — displays date in DD:MM:YY format.
  • Alarm — displays alarm setting in HH:MM:-x format ; the second rightmost tubes is off while the rightmost one indicates whether the alarm is armed or not (displaying respectively 1 or 0).

and is programmed displays a cathode-cleaning cycle every 10 minutes, as well as the date every 30 minutes in default mode. The code shares many similarities wit the one powering of my IN-18 clock, you might want to have a look at it as well. Apart from all that surrounds the RTC handling, an important difference lies with the way the values are set manually : this is done through a special mode, accessed by a long press on some button, in which the selected pair of tubes is made to blink.

The latest version of the clock is powered by a real-time clock (RTC) while the previous ones relied on the millis() function of the Arduino for timekeeping. Both the versions are available for download but only the first, which is more complicated, is explained thoroughly here.

Please note that the source codes shown here are "minimal versions" and will slightly differ from those available on RLB Designs' website.

3.1   Arduino oscillator version using millis()

[Back to top]

This older version was used before the RTC was installed, and uses the millis() function, which counts how many milliseconds were elapsed since the start of the program, as basis for timekeeping. The oscillator of the Arduino has a excellent accuracy for tasks like serial communication but shows significant drift for longer periods of times. On the Arduino Nano, this drift seems to be around 5 minutes a day, which is quite impractical for a desk clock. Hence the need to add a RTC.

I left this basic version available, for those interested only in the "core" code. It can be downloaded from the link on the right (Complete millis code). The RCT code is indeed a bit more complicated, as it involves I2C communication and EEPROM handling.

3.2   DS3231 RTC version

[Back to top]

The DS3231 is an extremely accurate RTC which will keep track of time for several years without significant deviation. Nevertheless, the possibility to manually modify the time has been implemented, at least for the fun of playing with the nixies' digits. In order to have the clock "remember" these modifications even when it's shut down, they have to be saved to the EEPROM, which is the Arduino's non-volatile memory. The RTC communicates with the microcontroller via the he I2C protocol.

In order to achieve a practical handling of the RTC as just described, The Arduino source code makes use of the "Wire" library, for I2C communication, the "RTClib" library, which handily gives all the time-related values we'll ever need, and finally the "EEPROM" library, which takes care of reading and writing onto the EEPROM (obviously). There's also a much simpler code which doesn't allow for manual modification. This may be useful if you mainly want to understand the RTC part, or simply don't want to bother. Both the complete (Complete RTC code) and the basic (Basic RTC code) versions be downloaded from the links on the right.

I will now describe the complete code in details.

3.2.1   Setup, multiplex and display functions

/*
 * Program for a 6-digit, K155ID1-based alarm clock;
 * coding by Robin Birtles and Chris Gerekos ( based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt );
 * for RLB Designs ( http://www.rlb-designs.com/ ).
 */


#include <EEPROM.h>
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 RTC;

// K155ID1 : Truth Table
//D C B A #
//L,L,L,L 0
//L,L,L,H 1
//L,L,H,L 2
//L,L,H,H 3
//L,H,L,L 4
//L,H,L,H 5
//L,H,H,L 6
//L,H,H,H 7
//H,L,L,L 8
//H,L,L,H 9

// K155ID1 (1)
int ledPin_0_a = 2;                
int ledPin_0_b = 3;
int ledPin_0_c = 4;
int ledPin_0_d = 5;

// K155ID1 (2)
int ledPin_1_a = 6;                
int ledPin_1_b = 7;
int ledPin_1_c = 8;
int ledPin_1_d = 9;

// anode pins
int ledPin_a_1 = 10;
int ledPin_a_2 = 11;
int ledPin_a_3 = 12;
int ledPin_a_4 = 13;

void setup() 
{
  Serial.begin(57600);
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()){
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled. 
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }

  pinMode(ledPin_0_a, OUTPUT);      
  pinMode(ledPin_0_b, OUTPUT);      
  pinMode(ledPin_0_c, OUTPUT);      
  pinMode(ledPin_0_d, OUTPUT);    
  
  pinMode(ledPin_1_a, OUTPUT);      
  pinMode(ledPin_1_b, OUTPUT);      
  pinMode(ledPin_1_c, OUTPUT);      
  pinMode(ledPin_1_d, OUTPUT);      
  
  pinMode(ledPin_a_1, OUTPUT);      
  pinMode(ledPin_a_2, OUTPUT);      
  pinMode(ledPin_a_3, OUTPUT);
  pinMode(ledPin_a_4, OUTPUT);  
 
  // NOTE: All analog pins as digital inputs with pull-up resistor activated.
  
  pinMode(A0, INPUT_PULLUP); // Set (Press for > 1sec)   
  pinMode(A1, INPUT_PULLUP); // Mode switch
  pinMode(A2, INPUT_PULLUP); // Pair selection
  pinMode(A3, INPUT_PULLUP); // Increase switch
  pinMode(A6, INPUT_PULLUP); // Decrease switch
  pinMode(A7, OUTPUT);        // Alarm tone will be sent there
  
}

void SetSN74141Chips( int num2, int num1 )
{
  int a,b,c,d;
  
  // set defaults.
  a=0;b=0;c=0;d=0; // will display a zero.
  
  // Load the a,b,c,d.. to send to the SN74141 IC (1)
  switch( num1 )
  {
    case 0: a=0;b=0;c=0;d=0;break;
    case 1: a=1;b=0;c=0;d=0;break;
    case 2: a=0;b=1;c=0;d=0;break;
    case 3: a=1;b=1;c=0;d=0;break;
    case 4: a=0;b=0;c=1;d=0;break;
    case 5: a=1;b=0;c=1;d=0;break;
    case 6: a=0;b=1;c=1;d=0;break;
    case 7: a=1;b=1;c=1;d=0;break;
    case 8: a=0;b=0;c=0;d=1;break;
    case 9: a=1;b=0;c=0;d=1;break;
    default: a=1;b=1;c=1;d=1;
    break;
  }  
  
  // Write to output pins.
  digitalWrite(ledPin_0_d, d);
  digitalWrite(ledPin_0_c, c);
  digitalWrite(ledPin_0_b, b);
  digitalWrite(ledPin_0_a, a);

  // Load the a,b,c,d.. to send to the SN74141 IC (2)
  switch( num2 )
  {
    case 0: a=0;b=0;c=0;d=0;break;
    case 1: a=1;b=0;c=0;d=0;break;
    case 2: a=0;b=1;c=0;d=0;break;
    case 3: a=1;b=1;c=0;d=0;break;
    case 4: a=0;b=0;c=1;d=0;break;
    case 5: a=1;b=0;c=1;d=0;break;
    case 6: a=0;b=1;c=1;d=0;break;
    case 7: a=1;b=1;c=1;d=0;break;
    case 8: a=0;b=0;c=0;d=1;break;
    case 9: a=1;b=0;c=0;d=1;break;
    default: a=1;b=1;c=1;d=1;
    break;
  }
  
  // Write to output pins
  digitalWrite(ledPin_1_d, d);
  digitalWrite(ledPin_1_c, c);
  digitalWrite(ledPin_1_b, b);
  digitalWrite(ledPin_1_a, a);
}


float fadeMax = 5.0f;
float fadeStep = 1.0f;
int NumberArray[6]={0,0,0,0,0,0};
int currNumberArray[6]={0,0,0,0,0,0};
float NumberArrayFadeInValue[6]={0.0f,0.0f,0.0f,0.0f,0.0f,0.0f};
float NumberArrayFadeOutValue[6]={8.0f,8.0f,8.0f,8.0f,8.0f,8.0f};

void DisplayFadeNumberString()
{
 
  // Anode channel 1 - numerals 0,3
  SetSN74141Chips(currNumberArray[0],currNumberArray[3]);   
  digitalWrite(ledPin_a_2, HIGH);   
  delay(NumberArrayFadeOutValue[0]);
  SetSN74141Chips(NumberArray[0],NumberArray[3]);   
  delay(NumberArrayFadeInValue[0]);
  digitalWrite(ledPin_a_2, LOW);
  
  // Anode channel 2 - numerals 1,4
  SetSN74141Chips(currNumberArray[1],currNumberArray[4]);   
  digitalWrite(ledPin_a_3, HIGH);   
  delay(NumberArrayFadeOutValue[1]);
  SetSN74141Chips(NumberArray[1],NumberArray[4]);   
  delay(NumberArrayFadeInValue[1]);
  digitalWrite(ledPin_a_3, LOW);
  
   // Anode channel 3 - numerals 2,5
  SetSN74141Chips(currNumberArray[2],currNumberArray[5]);   
  digitalWrite(ledPin_a_4, HIGH);   
  delay(NumberArrayFadeOutValue[2]);
  SetSN74141Chips(NumberArray[2],NumberArray[5]);   
  delay(NumberArrayFadeInValue[2]);
  digitalWrite(ledPin_a_4, LOW);
  
  // Loop thru and update all the arrays, and fades.
  for( int i = 0 ; i < 6 ; i ++ )
  {
    if( NumberArray[i] != currNumberArray[i] )
    {
      NumberArrayFadeInValue[i] += fadeStep;
      NumberArrayFadeOutValue[i] -= fadeStep;
  
      if( NumberArrayFadeInValue[i] >= fadeMax )
      {
        NumberArrayFadeInValue[i] = 0.0f;
        NumberArrayFadeOutValue[i] = fadeMax;
        currNumberArray[i] = NumberArray[i];
      }
    }
  }  
}

The part of the code works exactly like my In-18's one, except for the pin affectation, the library calling, and some other minor details.

Lines 8-10 call the necessary libraries mentioned earlier while the line 11 defines the "RTC" object that the library will use. In lines 26 to 42, we simply name some of the digital pins, which will be assigned them a mode in the setup function (lines 55-68). Lines 46-53 establish the Serial communication needed to talk with the RTC.

The SetSN74141Chips functions (lines 81 to 133) implement the truth table of the chips (given at the very beginning) and lights up the Arduino digital pins corresponding to the numbers given to the function.

Just below (lines 136-141) are defined a few constants and variable that will be used on the main displaying function. The NumberArray[6] contains the six actual values that are to be displayed (like [H/10, H, M/10, M, S/10, S] to display time). We have arrived to DisplayFadeNumberString, the displaying function properly said (lines 143-186). It lights up a certain anode pin, sends the corresponding numbers to SetSN74141Chips, shuts down the anode pin, then moves on to the next anode pin with a nice fading effect (performed by the last part).

3.2.2   Inputs, EEPROM, and alarm arming

//Read the EEPROM just at switch-on.
int ClockHourSet = word(EEPROM.read(1), EEPROM.read(0));  //Reconstruction the integer form the two bytes saved on the EEPROM.
int ClockMinSet = word(EEPROM.read(3), EEPROM.read(2));   
int ClockSecSet = word(EEPROM.read(5), EEPROM.read(4));   
int ClockDaySet = word(EEPROM.read(7), EEPROM.read(6));  
int ClockMonthSet = word(EEPROM.read(9), EEPROM.read(8));  
int ClockYearSet = word(EEPROM.read(11), EEPROM.read(10));
int AlmHours = word(EEPROM.read(13), EEPROM.read(12));
int AlmMins = word(EEPROM.read(15), EEPROM.read(14));
byte AlmArmed= EEPROM.read(16);	// Used as flag as well as indicator on the tube themselves (0 or 1).

byte Mode = 1;  //1: time, 2: date, 3: alarm
byte ModeButtonPressed = false;
byte Set = 1;   //1: left, 2: middle, 3: right
long SetTimer = 0;
byte SETMODE = false;
byte SETMODEentered = false;
byte SetButtonPressed = false;
byte IncreaseButtonPressed = false;
byte DecreaseButtonPressed = false;
byte AlmButtonPressed = false;
int nixState = HIGH;
int toneState = LOW;
long previousTimeBlink = 0;
long previousTimeTone= 0;
byte AlmPlaying=false;

void loop(){
  // Get milliseconds.
  unsigned long runTime = millis();  

// MODE SELECTION ///////////////////////////////////
/////////////////////////////////////////////////////  
  byte ModeInput = digitalRead(A1);
  if(ModeInput==0)  ModeButtonPressed = true;
  
  if (Mode==1 && ModeButtonPressed==true && ModeInput==1)
  {
    Mode = 2;    //Switching from Time to Date mode
    ModeButtonPressed = false;
  }
  if (Mode==2 && ModeButtonPressed==true && ModeInput==1)
  {
    Mode = 3;    //Switching from Date to Alarm mode
    ModeButtonPressed = false;
  }
  if (Mode==3 && ModeButtonPressed==true && ModeInput==1)
  {
    Mode = 1;    //Switching from Alarm to Time mode
    ModeButtonPressed = false;
  } 

// ALARM ARMING /////////////////////////////////////
/////////////////////////////////////////////////////
  if(Mode==3 && AlmPlaying==false){
    int AlmInput = analogRead(A6);
    if(AlmInput==0)  AlmButtonPressed = true;
    
    if(AlmButtonPressed==true && AlmInput>250){
      if(AlmArmed==true)  AlmArmed=false;
      else  AlmArmed=true;
      AlmButtonPressed = false;
    }
  }
  
// SETTING //////////////////////////////////////////
///////////////////////////////////////////////////// 
  byte SetInput = digitalRead(A0);
  if(SetInput==0)  SetButtonPressed = true;
  while(SetInput==0){
    SetTimer++;
    delay(10);
    SetInput = digitalRead(A0);
  } 
  
  if(SetTimer>=70){
    if(SETMODE==false)  SETMODE=true;  //A long press will either enter or exit the setting mode option.
    else SETMODE=false;
  }
  SetTimer=0;  //Reinitializing.
  
  if(SETMODE==true){
    SETMODEentered=true;
    if (Set==1 && SetButtonPressed==true && SetInput==1)
    {
      Set = 2;    //Switching from left to middle pair available for setting.
      SetButtonPressed = false;
    }
    if (Set==2 && SetButtonPressed==true && SetInput==1)
    {
      Set = 3;    //Switching from middle to right pair available for setting.
      SetButtonPressed = false;
    }
    if (Set==3 && SetButtonPressed==true && SetInput==1)
    {
      Set = 1;    //Switching from right to left pair available for setting.
      SetButtonPressed = false;
    }
 
    //Now we set the selected the pair of tube, let's modify its value:
    int DecreaseInput = digitalRead(A3);  
    int IncreaseInput  = digitalRead(A2);
  
    if(DecreaseInput==0)    DecreaseButtonPressed = true;
    if(IncreaseInput==0)    IncreaseButtonPressed = true;
    
    if(DecreaseButtonPressed==true && DecreaseInput==1)
    {
      if(Set==1){
        if(Mode==1)  ClockHourSet--;
        if(Mode==2)  ClockDaySet--;
        if(Mode==3)  AlmHours--;
      }
      if(Set==2){
        if(Mode==1)  ClockMinSet--;
        if(Mode==2)  ClockMonthSet--;
        if(Mode==3)  AlmMins--;
      }
      if(Set==3){
        if(Mode==1)  ClockSecSet--;
        if(Mode==2)  ClockYearSet--;
      }
      DecreaseButtonPressed = false;
    }
    if(IncreaseButtonPressed==true && IncreaseInput==1)
    {
      if(Set==1){
        if(Mode==1)  ClockHourSet++;
        if(Mode==2)  ClockDaySet++;
        if(Mode==3)  AlmHours++;
      }
      if(Set==2){
        if(Mode==1)  ClockMinSet++;
        if(Mode==2)  ClockMonthSet++;
        if(Mode==3)  AlmMins++;
      }
      if(Set==3){
        if(Mode==1)  ClockSecSet++;
        if(Mode==2)  ClockYearSet++;
      }
      IncreaseButtonPressed = false;
    }
  }
  
  long TimeShift = ClockSecSet + 60*ClockMinSet + 3600*ClockHourSet + 86400*ClockDaySet + 2678400*ClockMonthSet + 31536000*ClockYearSet;
  
  //Saving to EEPROM after exiting the SETMODE
  if(SETMODE==false && SETMODEentered==true){
    SETMODEentered=false;

    byte lowCHS = lowByte(ClockHourSet);    //Split the 2-bytes integer into the respecting "high" and "low" parts in order to save them on the EEPROM.
    byte highCHS = highByte(ClockHourSet);
    byte lowCMS = lowByte(ClockMinSet);
    byte highCMS = highByte(ClockMinSet);
    byte lowCSS = lowByte(ClockSecSet);
    byte highCSS = highByte(ClockSecSet);
    byte lowCDS = lowByte(ClockDaySet);
    byte highCDS = highByte(ClockDaySet);
    byte lowCOS = lowByte(ClockMonthSet);
    byte highCOS = highByte(ClockMonthSet);
    byte lowCYS = lowByte(ClockYearSet);
    byte highCYS = highByte(ClockYearSet);
    byte lowAH = lowByte(AlmHours);
    byte highAH = highByte(AlmHours);
    byte lowAM = lowByte(AlmMins);
    byte highAM = highByte(AlmMins);
    
    EEPROM.write(0, lowCHS);  EEPROM.write(1, highCHS); //Saving
    EEPROM.write(2, lowCMS);  EEPROM.write(3, highCMS);
    EEPROM.write(4, lowCSS);  EEPROM.write(5, highCSS);
    EEPROM.write(6, lowCDS);  EEPROM.write(7, highCDS);
    EEPROM.write(8, lowCOS);  EEPROM.write(9, highCOS);
    EEPROM.write(10, lowCYS);  EEPROM.write(11, highCYS);
    EEPROM.write(12, lowAH);  EEPROM.write(13, highAH);
    EEPROM.write(14, lowAM);  EEPROM.write(15, highAM);
    EEPROM.write(16, AlmArmed);
  }

This part starts by constructing the values of the Clock...Set integers from the EEPROM (lines 188-193). These numbers are used to set the time and date manually, as it will be explained later. To better understand this section, look first at the lines 354 to 362, where these Clock...Set integers are saved. The EEPROM can only save bytes, and as an "int" is a 2-bytes long number, we have to decompose it into its constitutive bytes, i.e. "lowbyte" and "highbyte". This is the job of lines 337-352. We then save all these bytes into specified emplacements of the EEPROM (0 to 16) through the EEPROM.write() function. Now let's get back to our first lines. What we do is reconstructing the integers from its constitutive bytes, though the word() function, which are read from the aforementioned emplacements of the EEPROM. When the Arduino will be powered for the first time, the EEPROM will be blank and all these numbers will read as zero, but each time a Clock...Set changes, it is saved for later retrieval.

After defining a few variables that will come in handy later (lines 198-212), we finally enter the void.loop(). The mode selection (lines 218-237) is very simple. When the A1 button is pressed then released, the clock goes from one mode to another in a cyclic way.

Lines 239-250 implement the alarm arming, which is simply switching between the values 0 or 1 for the AlmArmed variable through a press on A6. This is done in such a way the user doesn't have to re-arm the alarm every day.

Back to settings. In order to manually modify the values displayed by the clock, we must enter the "SETMODE", which is done by pressing and holding the A0 button for more than one second (lines 254-266). Once in SETMODE, the pair of tube that will have its value available for modification is accessed in a cyclic way, similarly to the mode selection (lines 268-284).

Once the desired pair of tubes is chosen, its value can be increased or decreased by one at each press of two distinct buttons, namely A3 and A2 (lines 286-329). The TimeShift variable will memorise total change in seconds that our modifications represent.

Note also that the EEPROM only has a limited number of write-erase cycle. In order to not overuse it and extend its lifetime, the overwriting is only done when the values may have changed, i.e. just after exiting the Setmode (condition on line 334).

3.2.3   Time and date calculation, alarm trigger

NB : We're still in void.loop().

// TIME RETRIEVING //////////////////////////////////
/////////////////////////////////////////////////////

  DateTime now = RTC.now();
  DateTime future = (now.unixtime() + TimeShift);

  // Convert time to days,hours,mins,seconds
  long Year = future.year()%100;
  long Month = future.month();
  long Day = future.day();
  long hours = future.hour();
  long minutes = future.minute();
  long seconds = future.second(); 

  // Get the high and low order values for hours,min,seconds. 
  byte lowerHours = hours % 10;
  byte upperHours = hours - lowerHours;
  byte lowerMins = minutes % 10;
  byte upperMins = minutes - lowerMins;
  byte lowerSeconds = seconds % 10;
  byte upperSeconds = seconds - lowerSeconds;
  if( upperSeconds >= 10 )   upperSeconds = upperSeconds / 10;
  if( upperMins >= 10 )      upperMins = upperMins / 10;
  if( upperHours >= 10 )     upperHours = upperHours / 10;

  // Same for date. 
  byte lowerYears = Year % 10;
  byte upperYears = Year - lowerYears;
  byte lowerMonths = Month % 10;
  byte upperMonths = Month - lowerMonths;
  byte lowerDays = Day % 10;
  byte upperDays = Day - lowerDays;
  if( upperDays >= 10 ) upperDays = upperDays / 10;
  if( upperMonths >= 10 ) upperMonths = upperMonths / 10; 
  if( upperYears >= 10 ) upperYears = upperYears / 10;

// ALM RULES /////////////////////////////////
////////////////////////////////////////////// 
  if(AlmHours>23)  AlmHours=AlmHours-24;  //After 23:00 comes 00:00
  if(AlmHours<0)  AlmHours=AlmHours+23;   //vice-versa
  if(AlmMins>59)  AlmMins=AlmMins-60;     //same thing
  if(AlmMins<0)  AlmMins=AlmMins+59;
  
   // Splitting.    
  byte lowerAlmHours = AlmHours % 10;
  byte upperAlmHours = AlmHours - lowerAlmHours;
  byte lowerAlmMins = AlmMins % 10;
  byte upperAlmMins = AlmMins - lowerAlmMins;
  if( upperAlmMins >= 10 )      upperAlmMins = upperAlmMins / 10;
  if( upperAlmHours >= 10 )     upperAlmHours = upperAlmHours / 10;
  
  //Alm actions:
  if(AlmArmed==true){
    if(upperAlmHours==upperHours && lowerAlmHours==lowerHours && upperAlmMins==upperMins && lowerAlmMins==lowerMins && upperSeconds==0 && lowerSeconds==0){
      AlmPlaying = true;  //Alarm rings when the time matches the pre-entered alarm values.
    }
  }
  
  if(AlmPlaying==true){
    if(runTime-previousTimeTone > 500){    //Play an intermittent (1s period) tune.
      previousTimeTone = runTime;
      if(toneState == HIGH){
        tone(10, 200);                      //Tune is 200Hz.
        toneState=LOW;
      }
      else{
        noTone(10);
        toneState=HIGH;
      }
    }
    if(digitalRead(A0)==LOW || digitalRead(A1)==LOW || digitalRead(A2)==LOW || digitalRead(A3)==LOW)  AlmPlaying=false;  //Press anything to stop it.
  }
  else  noTone(10);

Unlike my previous IN-18 clock, there's no need for explicit time and date calculations. This is all done by the RTC code. The Unix time that the RTC code is using to compute the time and date represents the number of seconds that were elapsed since 01/01/1970 at midnight.

Lines 367-376 spans the time and date retrieving procedure. What we do is simply asking the RTC code to calculate the time and date at some point in the future/past based on the TimeShift variable we defined earlier. Once we have the year, month, day, hour, minute and second we want the clock to display, we split these number into their respective digits in order to display them on the tubes. This is done on lines 348-387 for time, and lines 389-398 for the date.

As for the alarm part (lines 400-436), we first implement the basic rules for display (lines 402-413). The code dealing with the actions to be taken with these values follows. When the alarm is armed and when the time matches the pre-entered values, the AlmPlaying flag goes true. The an intermittent 200Hz tune is sent to pin A7, following the famous "Blink without delay" example. The tune will stop only when any of the five button is pressed.

3.2.4   Display

NB : We're still in void.loop().

// SHOWTIME /////////////////////////////////////////
///////////////////////////////////////////////////// 
// Fill in the Number array used to display on the tubes following the Mode.
  if(Mode==1){
    if(SETMODE==true){
      if(Set==1){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[5] = 11;  //Turning this pair off.
            NumberArray[1] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[5] = upperHours;  //Turning this pair on again.
            NumberArray[1] = lowerHours;
            nixState = LOW;
          }
        }
        NumberArray[3] = upperMins;    //Makes sure no pair stays off even if left in that state in the previous set mode.
        NumberArray[2] = lowerMins;
        NumberArray[4] = upperSeconds;
        NumberArray[0] = lowerSeconds;
      }
      if(Set==2){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[3] = 11;
            NumberArray[2] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[3] = upperMins;
            NumberArray[2] = lowerMins;
            nixState = LOW;
          }
        }
        NumberArray[5] = upperHours;
        NumberArray[1] = lowerHours;
        NumberArray[4] = upperSeconds;
        NumberArray[0] = lowerSeconds;
      }
      if(Set==3){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[4] = 11;
            NumberArray[0] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[4] = upperSeconds;
            NumberArray[0] = lowerSeconds;
            nixState = LOW;
          }
        }
        NumberArray[5] = upperHours;
        NumberArray[1] = lowerHours;
        NumberArray[3] = upperMins;
        NumberArray[2] = lowerMins;
      }
    }
    else{  //Fill the digits normally.
      NumberArray[5] = upperHours;
      NumberArray[1] = lowerHours;
      NumberArray[3] = upperMins;
      NumberArray[2] = lowerMins;
      NumberArray[4] = upperSeconds;
      NumberArray[0] = lowerSeconds;
    }

     DisplayFadeNumberString();// Display time.

      if((lowerMins==5)&&upperSeconds==0&&lowerSeconds==1)//lighting all the numbers every 10min to prevent cathode poisoning.
      {
        for(int j=-1; j<11; j++){//alternating to make it more beautiful. the -1 and 11 value will turn the digits off to mark a short pause.
         NumberArray[5] = j; 
         NumberArray[1] = 9-j;
         NumberArray[3] = j;
         NumberArray[2] = 9-j;
         NumberArray[4] = j;
         NumberArray[0] = 9-j;
         int k = 0;
         while(k<10){
           DisplayFadeNumberString();// Clean each nixie digits for 2 seconds.
           k++;
           delay(1);
         }
        }
      }
      if(lowerMins==3 && upperSeconds==0 && lowerSeconds==1){ //Display date only on every (03,13,23,33) mins.
      	NumberArray[5] = upperDays;
      	NumberArray[1] = lowerDays;
      	NumberArray[3] = upperMonths;
      	NumberArray[2] = lowerMonths;
      	NumberArray[4] = upperYears;
      	NumberArray[0] = lowerYears;
      	int k = 0;
      	while(k<550){				   // Display date for 10 seconds (50 = 1sec).
      		DisplayFadeNumberString(); // Display date.
      		k++;
      		delay(1);
        }
      }
  }
  
  if(Mode==2){
    if(SETMODE==true){
      if(Set==1){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[5] = 11;  //Turning this pair off.
            NumberArray[1] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[5] = upperDays;
            NumberArray[1] = lowerDays;
            nixState = LOW;
          }
        }
        NumberArray[3] = upperMonths;
        NumberArray[2] = lowerMonths;
        NumberArray[4] = upperYears;
        NumberArray[0] = lowerYears;
      }
      if(Set==2){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[3] = 11;
            NumberArray[2] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[3] = upperMonths;
            NumberArray[2] = lowerMonths;
            nixState = LOW;
          }
        }
        NumberArray[5] = upperDays;
        NumberArray[1] = lowerDays;
        NumberArray[4] = upperYears;
        NumberArray[0] = lowerYears;
      }
      if(Set==3){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[4] = 11;
            NumberArray[0] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[4] = upperYears;
            NumberArray[0] = lowerYears;
            nixState = LOW;
          }
        }
        NumberArray[5] = upperDays;
        NumberArray[1] = lowerDays;
        NumberArray[3] = upperMonths;
        NumberArray[2] = lowerMonths;
      }
    }
    else{  //Fill the digits normally.
     NumberArray[5] = upperDays;
     NumberArray[1] = lowerDays;
     NumberArray[3] = upperMonths;
     NumberArray[2] = lowerMonths;
     NumberArray[4] = upperYears;
     NumberArray[0] = lowerYears;
    }

     DisplayFadeNumberString();  // Display date.
  }
  
  if(Mode==3){
    if(SETMODE==true){
      if(Set==1){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[5] = 11;  //Turning this pair off.
            NumberArray[1] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[5] = upperAlmHours;
            NumberArray[1] = lowerAlmHours;
            nixState = LOW;
          }
        }
        NumberArray[3] = upperAlmMins;
        NumberArray[2] = lowerAlmMins;
        NumberArray[0] = AlmArmed;	//1: armed, 0: not armed
      }
      if(Set==2){
        if(runTime-previousTimeBlink > 500){  //Blinking these digits while in setmode.
          previousTimeBlink = runTime;
          if(nixState == LOW){
            NumberArray[3] = 11;
            NumberArray[2] = 11;
            nixState = HIGH;
          }
          else{
            NumberArray[3] = upperAlmMins;
            NumberArray[2] = lowerAlmMins;
            nixState = LOW;
          }
        }
        NumberArray[5] = upperAlmHours;
        NumberArray[1] = lowerAlmHours;
        NumberArray[0] = AlmArmed;
      }
    if(Set==3)  Set=1;	//Dirty fix to convert a 3-cycle into a 2-cycle.
    }
    else{	//Fill the digits normally.
     NumberArray[5] = upperAlmHours;
     NumberArray[1] = lowerAlmHours;
     NumberArray[3] = upperAlmMins;
     NumberArray[2] = lowerAlmMins;
     NumberArray[0] = AlmArmed;
    }
     NumberArray[4] = 11;			//Eleven will turn off the tube
     DisplayFadeNumberString();		// Display alarm.
  }
 
}

This part of the code is notably more complex than the one of its IN-18 counterpart because of all the blinking thing. Indeed, in order to distinguish the simple display state from the state in which the values displayed can be modified, a slow blinking of the pair of tubes selected for modification has been implemented. This feature was heavily inspired form, once again, the "Blink without delay" example.

To display a digit, the required entry of NumberArray must be assigned with this digit and the DisplayFadeNumberString() (described above) must be called.

In time mode (Mode=1), NumberArray is normally filled with hours, minutes and seconds (lines 500-507). In SETMODE, the blinking of the selected pair of tubes is implemented as following. For each value of Set, the corresponding tubes will oscillate between their actual value and eleven, as eleven turns the tube off, with a half-period of 500 milliseconds (lines 443-455 for Set=1) while the other tubes are displayed normally (lines 456-459 for Set=1). Also in time mode, we ask that a cathode-cleaning digit cycle is performed and that date is displayed periodically (request from the boss!), both every 10 minutes (resp. lines 511-527 and 528-540).

The blinking feature is implemented the same way for the other modes. A small subtlety to note on alarm mode (Mode=3) is that the AlmArmed flag is used as is to indicate whether or not the alarm is indeed armed. The rightmost tube will then display, respectively, 1 or 0.

Line 667 closes the void.loop() function and terminates the program altogether.