You are here : Main projects | IN-18 Nixie Tube Clock | 4. Source codes



IN-18 Nixie Tube Clock
February 2013

4.  Source codes

A step-by-step review of the programs running in the Arduinos. 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.

4.1   Primary Arduino

[Back to top]

Let's first have a look at the clock's functionalities, this will help us to go through the code. The clock has four 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 format, the leftmost tubes are off.
  • Sleep — all tubes turned off, unless activated with the remote (used to save nixie life).

Time count, multiplex and crossfade display were written by the ArduiNix team, the original code can be downloaded here. I added the Sleep, Date and Alarm modes, inputs, digit cycle and some other minor stuff. The complete source code for my own clock can be downloaded from the link on the right.

4.1.1   Setup, multiplex and display functions

/*
 * Program for a 6-digit, K155ID1-based clock.
 *
 * Time count, multiplex and crossfade display by ArduiNix : http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt
 * Sleep, Date and Alarm modes, inputs, digit cycle added by Chris Gerekos.
 */



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



void setup() 
{
  Serial.begin(9600);
  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);     
 
  // Commands : 
  pinMode(A0, INPUT_PULLUP);   //Mode (with pullup resistor)
  pinMode(A1, OUTPUT);         //Alarm LED indicator
  pinMode(A2, INPUT_PULLUP);   //Setting selection
  pinMode(A3, INPUT_PULLUP);   //Setting selection
  pinMode(A4, INPUT_PULLUP);   //Increase scroll
  pinMode(A5, INPUT_PULLUP);   //Decrease scroll
  pinMode(1, INPUT);           //Sleep lightning command will arrive here
  pinMode(13, OUTPUT);         //Alarm command will be sent here
}



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 = 6.0f;
float fadeStep = 0.3f; 
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_1, HIGH);   
  delay(NumberArrayFadeOutValue[2]);
  SetSN74141Chips(NumberArray[0],NumberArray[3]);   
  delay(NumberArrayFadeInValue[2]);
  digitalWrite(ledPin_a_1, LOW);
  
  // Anode channel 2 - numerals 1,4
  SetSN74141Chips(currNumberArray[1],currNumberArray[4]);   
  digitalWrite(ledPin_a_2, HIGH);   
  delay(NumberArrayFadeOutValue[2]);
  SetSN74141Chips(NumberArray[1],NumberArray[4]);   
  delay(NumberArrayFadeInValue[2]);
  digitalWrite(ledPin_a_2, LOW);
  
  // Anode channel 3 - numerals 2,5
  SetSN74141Chips(currNumberArray[2],currNumberArray[5]);   
  digitalWrite(ledPin_a_3, HIGH);   
  delay(NumberArrayFadeOutValue[2]);
  SetSN74141Chips(NumberArray[2],NumberArray[5]);   
  delay(NumberArrayFadeInValue[2]);
  digitalWrite(ledPin_a_3, 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];
      }
    }
  }  
}

In lines 23 to 68, we simply name some of the digital pins, assign them a mode in the setup function, where we also activate the serial communications for Xbee transmissions.

The SetSN74141Chips functions (lines 72 to 124) 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 128-133) 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. It lights up a certain anode pin, sends the corresponding numbers to SetSN74141Chips (see previous page for an explanation on multiplexing), shuts down the anode pin, then moves on to the next anode pin with a nice fading effect (performed by the last part).

4.1.2   Inputs

// Defines
unsigned long time = 0;       // Time from when we started.

// default time sets. Time will start at 12:24:50. Date will start at 01/01/13. Default Alarm is 08:00.
long ClockHourSet = 12;
long ClockMinSet  = 24;
long ClockSecSet  = 45;
long ClockDaySet  = 01;
long ClockMonthSet= 01;
long ClockYearSet = 13;
int AlmHours = 8;       //Those are set directly, no need for time count.
int AlmMins = 0;

//Defining commands
byte Mode = 1;            //0=Sleep, 1=Time, 2=Date, 3=Alarm.
int ModeTimer = 0;
long AlmMillis = 0;
byte Set = 0;             //0=Left, 1=Middle, 2=Right.
byte ModeButtonPressed = false;
byte IncreaseButtonPressed = false;
byte DecreaseButtonPressed = false;
byte AlmButtonPressed = false;
byte LightCmdPin = false;
byte ALARM = false;
byte AlarmPlaying = false;



void loop()     
{
  digitalWrite(13,HIGH);  //default HIGH. Will be used for Alarm.

  // Get milliseconds.
  unsigned long runTime = millis();
  
//  MODE SELECTION ///////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
  //When button is pressed, the pin is grounded and reads 0, a timer counts how much time the button is pressed.
  int ModeInput = digitalRead(A0);  
  if(ModeInput==0)  ModeButtonPressed = true;
  while(ModeInput==0){
    ModeTimer++;
    delay(100);
    ModeInput = digitalRead(A0);
  }
  
  //If the button is pressed shortly, these actions will be taken just after the button is released.
  if (Mode==1 && ModeButtonPressed==true && ModeInput==1 && ModeTimer<5)
  {
    Mode = 2;    //Switching from Time to Date mode
    ModeButtonPressed = false;
    ModeTimer=0;
  }
  if (Mode==2 && ModeButtonPressed==true && ModeInput==1 && ModeTimer<5)
  {
    Mode = 3;    //Switching from Date to Alarm mode.
    ModeButtonPressed = false;
    ModeTimer=0;
  }
  if (Mode==3 && ModeButtonPressed==true && ModeInput==1 && ModeTimer<5)
  {
    Mode = 1;    //Switching from Alarm to Time mode
    ModeButtonPressed = false;
    ModeTimer=0;
  }
  
  //If the button is pressed longly, 
  if((Mode==1||Mode==2||Mode==3) && ModeButtonPressed==true && ModeInput==1 && ModeTimer>=5){
    Mode = 0;    //Switching from Time or Date to Sleep
    ModeButtonPressed = false;
    ModeTimer=0;
  }
  if(Mode==0 && ModeButtonPressed==true && ModeInput==1 && ModeTimer>=5){
    Mode = 1;    //Switching from Sleep to Time
    ModeButtonPressed = false;
    ModeTimer=0;
  }   
  
// SETTING SELECTION ////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
  int LeftInput = digitalRead(A2);
  int RightInput = digitalRead(A3);

  if(LeftInput==LOW && RightInput==HIGH)  Set = 0; //Left couple of tubes will be available for setting.
  if(RightInput==LOW && LeftInput==HIGH)  Set = 2; //Right pair of tubes will be available for setting.
  if(RightInput==HIGH && LeftInput==HIGH)  Set = 1; //Middle pair of tubes will be available for setting.
  
  if(Mode==3){
    if(LeftInput==LOW)  AlmButtonPressed = true;
    if(LeftInput==HIGH && AlmButtonPressed==true && ALARM==false){  //Kick on the "Set" switch upwards to set alarm on.
      ALARM = true;
      AlmButtonPressed = false;
    }
    if(LeftInput==HIGH && AlmButtonPressed==true && ALARM==true){ //turn alarm off.
      ALARM = false;
      AlmButtonPressed = false;
    }
  }
  
// SETTING THE SELECTED PAIR OF TUBES ////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
  int DecreaseInput = digitalRead(A4);  
  int IncreaseInput  = digitalRead(A5);

  if(DecreaseInput==0)    DecreaseButtonPressed = true;
  if(IncreaseInput==0)    IncreaseButtonPressed = true;
  
  if(DecreaseButtonPressed==true && DecreaseInput==1)
  {
    if(Set==0){
      if(Mode==1)  ClockHourSet--;
      if(Mode==2)  ClockDaySet--;
    }
    if(Set==1){
      if(Mode==1)  ClockMinSet--;
      if(Mode==2)  ClockMonthSet--;
      if(Mode==3)  AlmHours--;
    }
    if(Set==2){
      if(Mode==1)  ClockSecSet--;
      if(Mode==2)  ClockYearSet--;
      if(Mode==3)  AlmMins--;
    }
    DecreaseButtonPressed = false;
  }
  if(IncreaseButtonPressed==true && IncreaseInput==1)
  {
    if(Set==0){
      if(Mode==1)  ClockHourSet++;
      if(Mode==2)  ClockDaySet++;
    }
    if(Set==1){
      if(Mode==1)  ClockMinSet++;
      if(Mode==2)  ClockMonthSet++;
      if(Mode==3)  AlmHours++;
    }
    if(Set==2){
      if(Mode==1)  ClockSecSet++;
      if(Mode==2)  ClockYearSet++;
      if(Mode==3)  AlmMins++;
    }
    IncreaseButtonPressed = false;
  }

We initiate runtime in the loop (line 213), which counts the number of milliseconds elapsed since the Arduino began to run the program. We'll use it in the next part.

Lines 215 to 256 implement mode switching, which is operated via the push button on pin A0. The switching is cyclic : Time -> Date -> Alarm -> Time -> etc..., at each push. The "long press" switches from any of the previous modes to the Sleep mode ; while in Sleep mode, a long press will get the clock back to Time mode.

Next part : the selection switch. We have 6 tubes grouped by pairs ; the values displayed by these pairs in each mode was stated at the beginning of this section. The value (i.e. the pair of tubes) available for modification is selected with a 3-state switch and the code spanning lines 258 to 277 (pretty intuitive). In Alarm mode, in which the leftmost tubes are off, the last position of the switch is used to activate/deactivate the alarm (as in this video, around 00:13).

Finally, lines 279 to 322 will allow modification of the selected value. For each mode and each state of the selection switch, turning either the increase or the decrease scroll will affect the desired value. These scrolls act just like a push button : each click is closes or opens the circuit (you can see how the scrolls operate in this video, around 00:23).

4.1.3   Time and date calculation, Alarm set and trigger

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

// TIME CALCULATION /////////////////////////////////
/////////////////////////////////////////////////////
  // Get time in seconds.
  long time = runTime / 1000; 
  time += ClockSecSet + 60*ClockMinSet + 3600*ClockHourSet + 86400*ClockDaySet;    //time in seconds, based on offset

  // Convert time to days,hours,mins,seconds
  long days  = time / 86400;    time -= days  * 86400; 
  long hours = time / 3600;   time -= hours * 3600; 
  long minutes  = time / 60;    time -= minutes  * 60; 
  long seconds  = time; 

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

// DATE CALCULATION /////////////////////////////////
/////////////////////////////////////////////////////
  int Jour = days;
  int Mois= ClockMonthSet;
  int Annee = ClockYearSet;
  
    //Defining years incrementation.
    if(Mois>=13){
      Annee++;
      Mois=Mois-12;
    }
    if(Mois<=0){
      Annee--;
      Mois=Mois+12;
    }

	//Number of days in a month:
	byte February;
	if(Year%4==0) February=29;
	else February=28;
	byte MonthArray[13] = {0,31,February,31,30,31,30,31,31,30,31,30,31}; //We'll skip the first entry, so we can use the array's index directly.

    //Defining months incrementation.
	for(byte i=1;i<13;i++){ 
      if(Mois==i){
        if(Jour>MonthArray[i]){ //If "Day" exceeds the number of days in the present month, it is resetted to 1 and we step into the next month.
          Jour -= MonthArray[i];
          Mois++;
        }
        if(Jour<1){ //Similarly if "Day" is to become 0.
          if(i-1==0) i=13; //Before January comes December.
		  Jour += MonthArray[i-1];
		  Mois--;
		}
	  }
	}
    
  // Splitting.    
  byte lowerYears = Annee % 10;
  byte upperYears = Annee - lowerYears;
  byte lowerMonths = Mois % 10;
  byte upperMonths = Mois - lowerMonths;
  byte lowerDays = Jour % 10;
  byte upperDays = Jour - 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(ALARM==true){
    digitalWrite(A1,HIGH)		//Led is on.
    if(upperAlmHours==upperHours && lowerAlmHours==lowerHours && upperAlmMins==upperMins && lowerAlmMins==lowerMins && upperSeconds==0 && lowerSeconds==0){
      digitalWrite(13,LOW);		//Send a signal to the other Arduino.
      delay(2000);
      AlarmPlaying=true;
      AlmMillis=runTime;
      Mode=1;  					//Light the digits on.
    }
  }
  if(ALARM==false)  digitalWrite(A1,LOW); //Led is off.
    
  //How to shut down the alarm without "desarming" it for the next day.
  if(AlarmPlaying==true){
    if(ModeButtonPressed == true && ModeInput == 1 && ModeTimer<5){  //Manually : 
      digitalWrite(13,LOW);				//Send a signal to the other Arduino. After this, the next pulse will play the alarm again.
      delay(2000);
      AlarmPlaying=false;
    }
    if(runTime-AlmMillis>900000){	//Or automatically after 15' 
      digitalWrite(13,LOW);				//Send a signal to the other Arduino. After this, the next pulse will play the alarm again.
      delay(2000);
      AlarmPlaying=false;
      Mode=0;							//Shut the digits down.
    }        
  }   

Lines 323-333 spans the time calculation. The time integer represents the current time in seconds; it is defined from the number of seconds elapsed since the Arduino was turned on (through runtime, see above) and augmented from the Clock[...]Set numbers (see above), which allow us to manually change time. After extracting our values, we split them into their very digits for display (lines 335-344).

Same thing goes with the date : we implement the set of rules that will work out years, months and days as they should be (lines 346-381). They are then split into their digits (lines 383-392).

Finally, the alarm. After the setting rules and the splitting (lines 396-407), we implement the conditions for alarm playing : alarm activated AND correspondence between time and alarm. When the alarm is to be played, we ask that a pulse is send to the other Arduino (which will then play the alarm), plus some other things (lines 413-417). A second pulse will turn off the alarm. This one comes either manually (lines 424-428) after waking up or automatically after 15 minutes over (lines 429-434).

4.1.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==0){
    NumberArray[3] = upperHours;
    NumberArray[1] = lowerHours;
    NumberArray[5] = upperMins;
    NumberArray[0] = lowerMins;
    NumberArray[4] = upperSeconds;  
    NumberArray[2] = lowerSeconds;
    
    if(digitalRead(1)==HIGH){
      DisplayFadeNumberString();    //Light the digits only when told (see second Arduino code).
      LightCmdPin = true;
    }
    
    if(digitalRead(0)==LOW && LightCmdPin==true){  //After lightning time is over, "clean" the nixies with a digit cycle.
      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[3] = j; 
        NumberArray[1] = 9-j;
        NumberArray[5] = j;
        NumberArray[0] = 9-j;
        NumberArray[4] = j;  
        NumberArray[2] = 9-j;
        int k = 0;
        while(k<500){
          DisplayFadeNumberString();    // Display numbers for a while.
          k++;
          delay(1);
        }    
      }
      LightCmdPin=false;
    }   
  }
      
  if(Mode==1){
    NumberArray[3] = upperHours;
    NumberArray[1] = lowerHours;
    NumberArray[5] = upperMins;
    NumberArray[0] = lowerMins;
    NumberArray[4] = upperSeconds;  
    NumberArray[2] = lowerSeconds;
    DisplayFadeNumberString();	// Display time.
    
    if((lowerMins==0||lowerMins==5)&&upperSeconds==0&&lowerSeconds==1)	//lighting all the numbers every 5min to prevent cathode poisining.
    {
      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[3] = j; 
        NumberArray[1] = 9-j;
        NumberArray[5] = j;
        NumberArray[0] = 9-j;
        NumberArray[4] = j;  
        NumberArray[2] = 9-j;
        byte k = 0;
          while(k<20){
            DisplayFadeNumberString();    // Display numbers for a little while.
            k++;
            delay(1);
          }    
        }
    }
  }
  
  if(Mode==2){
    NumberArray[3] = upperDays;
    NumberArray[1] = lowerDays;
    NumberArray[5] = upperMonths;
    NumberArray[0] = lowerMonths;
    NumberArray[4] = upperYears;  
    NumberArray[2] = lowerYears;
    DisplayFadeNumberString();    //Display date.
  }
  
  if(Mode==3){
    NumberArray[3] = 11;  //Eleven will turn off the tube.
    NumberArray[1] = 11;
    NumberArray[5] = upperAlmHours;
    NumberArray[0] = lowerAlmHours;
    NumberArray[4] = upperAlmMins;  
    NumberArray[2] = lowerAlmMins;
    DisplayFadeNumberString();    //Display Alarm.
  }
  
}

Last but not least, sending our digits into NumberArray and calling DisplayFadeNumberString(); to display them on tubes.

In Sleep mode, this function is called only if the secondary Arduino lights the digital pin 1 (line 447), and this is done only when told by the remote (see next section). The digits will remain lit for maximum 5 minutes (see secondary Arduino code). After pin 1 reads LOW again, a digit cycle is performed (see this video) : each digit of each tube is lit for a certain time so help prevent cathode poisoning (see page 2).

In Time, Date or Alarm mode, the function is always called and the digits are lit. A digit cycle also takes place every five minutes in Time mode, also to avoid cathode poisoning. Note that this cycle is much shorter than in Sleep mode (10 seconds vs 2 minutes) to not impede time reading too long. Line 519 closes the void.loop() function and terminates the program altogether.

4.2   Secondary Arduino

[Back to top]

This Arduino plays the alarm when told by the other one, reads the orders of the remote and tells the primary Arduino to light the tubes accordingly when in Sleep mode (see above). The file reading and song playing parts were written by Adafruit, so were the waveHC libraries they rely upon. The original codes can be downloaded here. Unlike previously, the entire code is posted below in one block.

#include <FatReader.h>
#include <SdReader.h>
#include <avr/pgmspace.h>
#include "WaveUtil.h"
#include "WaveHC.h"

SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the filesystem on the card
FatReader f;      // This holds the information for the file we're play
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 5  // button debouncer

long lightCounter = 0;
byte snooze;
byte AlmCmdSent;
byte AlmSwitch=0;
volatile byte pressed, justpressed, justreleased;

// this handy function will return the number of bytes currently free in RAM, great for debugging!   
int freeRam(void)
{
  extern int  __bss_end; 
  extern int  *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
} 

void sdErrorCheck(void)
{
  if (!card.errorCode()) return;
  putstring("\n\rSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  putstring(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}



void setup() {
  
  // set up serial port
  Serial.begin(9600);

  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
  
  // Set the output pins for the DAC control. This pins are defined in the library
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  pinMode(A0,OUTPUT); //Light command will be sent from here.
  pinMode(A1, INPUT_PULLUP); //Alarm command will arrive here./*
  
  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!)  
    putstring_nl("Card init. failed!");  // Something went wrong, lets print out why
    sdErrorCheck();
    while(1);	// then 'halt' - do nothing!
  }
  
  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);
 
// Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {     // we have up to 5 slots to look in
    if (vol.init(card, part)) 
      break;	// we found one, lets bail
  }
  if (part == 5) {	// if we ended up not finding one  :(
    putstring_nl("No valid FAT partition!");
    sdErrorCheck();	// Something went wrong, lets print out why
    while(1);	// then 'halt' - do nothing!
  }
  
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?
  
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    putstring_nl("Can't open root dir!"); // Something went wrong,
    while(1);                             // then 'halt' - do nothing!
  }
  
  // Whew! We got past the tough parts.
  putstring_nl("Ready!");
  
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;
  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= 1<<TOIE2;
}

SIGNAL(TIMER2_OVF_vect) {
  check_switches();
}

void check_switches()
{
  static byte previousstate;
  static byte currentstate;
  
    currentstate = digitalRead(A1);   // read the button
    
    if (currentstate == previousstate) {
      if ((pressed == LOW) && (currentstate == LOW)) {
          // just pressed
          justpressed = 1;
      }
      else if ((pressed == HIGH) && (currentstate == HIGH)) {
          // just released
          justreleased = 1;
      }
      pressed = !currentstate;  // remember, digital HIGH means NOT pressed
    }
    previousstate = currentstate;   // keep a running tally of the buttons
}



void loop() {
  
  /////////// Light part //////////////
  
  while( Serial.available()>0)      //Do this only when receiving data.
  {
    while(Serial.findUntil("clock", "."))    //"clock1." or "clock0." will be sent.
    {
      int light = Serial.parseInt();
        while(lightCounter<30000& & light==1){    //Will run for 5 minutes if no counterorder is given.
          digitalWrite(A0,HIGH);;
          lightCounter++;
          if(Serial.available()>0) if(Serial.findUntil("clock", "."))  light=Serial.parseInt();    //Checking for counterorder from the remote.
          delay(10);
        }
      lightCounter=0;          //Reinitialising counter.
      digitalWrite(A0,LOW);    //Shutting tubes down (see clock code).
    }
  }
  
  /////////// Alarm part //////////////
  
  byte AlmCmd = digitalRead(A1);        //When alarm is to be activated/deactivated, A1 will read LOW.
  if(AlmCmd==LOW)  AlmCmdSent=true;
  if(AlmCmd==HIGH && AlmCmdSent==true && AlmSwitch==0){    //AlmSwitch will tell if the alarm is to be played or stopped.
    AlmSwitch=1;
    AlmCmdSent=false;
  }
  if(AlmCmd==HIGH&&AlmCmdSent==true&&AlmSwitch==1){
    AlmSwitch=0;
    AlmCmdSent=false;
  }
  
  if (justpressed) {  //play or stop alarm when A1 is "triggered".
    justpressed=0;
    if(AlmSwitch==1)  playfile("SONG.WAV");    
    if(AlmSwitch==0)  wave.stop();
  }
}



void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  //look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
  
  // ok time to play! start playback
  wave.play();
}

The light part spans lines 137 to 153. It is very simple, in-code comments should suffice. The alarm part is just below, lines 155-173. The pulses the primary Arduino will send will either turn on or off the alarm, following the value of AlmSwitch. Along with this, there many are other functions in this code, mainly for error-checking.

4.3   Remote control

[Back to top]

Last and least, the overly simple code within the remote control Arduino. A button switches the value of some integer between 1 and 0, then sends it over the serial pin to the Xbee. As shown in the codes above, 1 will turn the nixies ON and 0 will turn them OFF (all of this when the clock is in Sleep mode only).

  int light = 0;
  int lightPressed = false;
  
void setup()
{
  Serial.begin(9600);
  pinMode(A0,INPUT_PULLUP);
  pinMode(13,OUTPUT);
  digitalWrite(13,LOW);
  
  for(int i=0; i<51; i++){    //Sending a short "light" command at startup.
    Serial.print("clock1.");
    i++;
    delay(50);
  }
}

void loop()
{
  int lightCmd = digitalRead(A0);
  if(lightCmd==LOW)    lightPressed = true;

  //switching between light modes.
  if(light==0 && lightPressed==true && lightCmd==HIGH){
    light=1;
    lightPressed=false;
  }
  if(light==1 && lightPressed==true && lightCmd==HIGH){
    light=0;
    lightPressed=false;
  }

  //sending data.
  if(lightCmd==LOW){  
    Serial.print("clock");                
    Serial.print(light);
    Serial.print(".");
    delay(50);
  }
}