A Nixie Thermometer build

A Nixie Thermometer build

PXK59829The following is a detailed design/build of a nixie thermometer that I made last year. After building a nixie clock, I thought it would be cool to make something similar, but less involved. I choose a thermometer. After working on a few projects, I’ve quickly come to the realization that a project’s design is nearly 99% dictated by the case it is put into. I decided the first step would be finding a case. After much searching through my garage, I found a cutoff piece of aluminum from a panic closer body. Whether you know it or not, you’ve probably seen them. You’ll find them in many schools, government, and public commercial buildings. See the below image and then decide if you recognize the body.

61TUL1jEO0L._AA1500_

You’ll usually find these on doors to exit buildings. They come in certain lengths and rarely have to be cut to fit on a door. This was my inspiration for the thermometer case.

Since I knew my size, I was ready to start designing. But first I needed a schematic. Over the next couple days, I made the below schematic.

Nixie thermometer schematic

You can click the above schematic to see a larger version. I’ll quickly go through each section.

The high voltage required for the nixie tubes is copied nearly directly from the one I used on the nixie clock build, which sadly had a very messy schematic. I’ve tried to make this one neater for this very purpose.

The driver for the boost stage consists of a 555 timer. Notice the transistor that lowers the 2/3rds control voltage from the resistor divider. It receives feedback from the Boost Stage of the schematic. This feedback is controlled by a potentiometer inside of a voltage divider driven by the high voltage.

schematic

The Boost stage is your typical boost converter (coil, switch, diode, capacitor). A few notes on parts, make sure you use a fast or ultrafast diode with a reverse breakdown voltage higher than the voltage you’re looking to get. Make sure your capacitor is rated for 25% (at least) higher than the voltage you want. Also make sure your cap is not too large, as high capacitance values can put too much of a load on the system and cause it not to work. I used a .68uF that I found in my junk drawer.

Make sure the mosfet (or similar) that you use has a low internal “on resistance” (below 1 ohm – the lower the better) I find that the *30 series of the IRF (e.g. irf530, irf630, irf730, etc,…) have low resistances and work well. But make sure the one you are using has a higher reverse breakdown voltage than what you are trying to achieve.

I apologize for the jumpers on the board in the Boost Stage section. I couldn’t make a clean connection from the upper resistor to the potentiometer, so at the time I thought it would be a good idea to add 2 jumpers to the schematic, then just connect them on the pcb. The schematic is good though, and the jumpers are not necessary and can be omitted (unless you use my pcb design).

vreg

The next section, voltage regulator is your typical lm7805 setup for powering the logic level parts of the circuit (attiny85, shift registers, etc,..) The 555 section and boost all run from 9volts, the rest is 5v.

I used jumpers for the temperature sensor, LM335DZ, which reads in kelvin. Every 10 millivolts on the output = +1 degree kelvin; we’ll get to that later.

nixtub

I still have a box of IN-12b nixie tubes, so I used those for this project. HV in, approximately 160-180volts, to anode through a 33k current limiting resistor.

I used jumpers for the backlighting, which were going to be blue LEDs to contrast the reddish orange of the nixie tubes. I used 2 LEDs in series (as shown) on each side of the display. The code actually PWMs the blue LEDs, fading in and out very slow. The result looks very cool, as if it were alive and breathing.

I used the ATTiny85, since I knew I would be needing shift registers anyway and only need a couple pins from the microprocessor. It was just enough for the temperature sensor, backlighting, clock, latch, and data pins!

shift

Shown above are the shift registers and the darlington arrays. The devices used can be seen in the schematic, 74HC595N and SN75468N used to ground the cathodes on the nixie tubes.  Please note that the capacitor on the LATCH PIN to GROUND should not be used, and will only cause problems down the road. Also, I did the same silly jumper thing that I did on the Boost section of the circuit, so you can ignor the jumpers on the clock and latch, I only used them to make the connection on the board. (why didn’t I just use a via and run across top? dunno) The below is the finished design. Click to see a larger version.

board

  And after using PCB2GCODE, I was able to etch a board on the CNC machine. Capture And after using liquid tin. PXK59777   Inside the case. PXK59776 PXK59780   I eventually got her all soldered up, and was ready for testing and coding. My standard hookup is to connect an arduino as if it were an ATtiny, using an IC socket. For instance Arduino pin 0 would connect to physical pin 5 on the IC socket (which is digital pin 0 on an attiny) and so on… As always, I will provide the code I used and briefly go over certain parts:

1
 const int latchPin = 0; //physical pin 5 //change to zero when programming 85 // change to 8 on uno const int clockPin = 1; //physical pin 6 //change to one when programming 85 // change to 12 on uno const int dataPin = 2; //physical pin 7 const int tempPin = A0; //physical pin 1 const int backlight = 4; //physical pin 3 //change to four when programming 85 //change to 5 on uno int registr[3] = {0,0,0};

const int numReadings = 30; int readings[numReadings]; // the readings from the analog input int index = 0; // the index of the current reading int total = 0; // the running total int average = 0; // the average int temperature = 0; int previoustemp = 1; boolean flipflop = 1; boolean flopflip = 1; int ledfader = 0; int fadestep = 1; int lowlightlevel = 0; //do not change int steppast = 100; //delay to pause LED on low light and max brightness before fading back to other value. play with the number to get right. int ten[10] = {2, 256 + 2, 256 + 4, 256 + 8, 256 + 16, 256 + 32, 256 + 64, 256 + 128, 8, 4}; int one[10] = {512 + 128,32,64,128, 512 + 2, 512 + 4, 512 + 8, 512 + 16, 512 + 32, 512 + 64}; void setup() { pinMode(latchPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(backlight, OUTPUT); //Serial.begin(9600); //Serial.println(“reset”); for (int thisReading = 0; thisReading < numReadings; thisReading++) readings[thisReading] = 0; initialreading(); } void loop(){ if(((millis() % 1000) > 500) && (flipflop)){ //Serial.println(“in”); total= total – readings[index]; readings[index] = analogRead(tempPin); total= total + readings[index]; index = index + 1; if (index >= numReadings) index = 0; average = total / numReadings; temperature = fahrenheit(average); if(previoustemp != temperature){ previoustemp = temperature; displayint(temperature); } flipflop = 0; } else if(((millis() % 1000) < 500) && (!flipflop)) { flipflop = 1; } if(((millis() % 20) > 10) && (flopflip)){ if(millis() < 2500) ledfader = 0; //Serial.println(millis() % 200); analogWrite(backlight, constrain(ledfader, lowlightlevel, 255)); ledfader = ledfader + fadestep; if((ledfader < (lowlightlevel – steppast)) || (ledfader > (255 + steppast))){ fadestep = -fadestep; lowlightlevel = 40; } flopflip = 0; } else if(((millis() % 20) < 10) && (!flopflip)) { flopflip = 1; } } int fahrenheit(int reading){ float Kelvin = ((reading*500.00)/1023.00); float Celsius = Kelvin – 273.15; float Fahrenheit = ((Celsius*9.00)/5.00)+32; Fahrenheit -=1.50; int Rounded = round(Fahrenheit); return constrain(Rounded, 0, 99); } void erasereg(){ for(int k = 0;k<3;k++){ registr[k] = 0; } } void displayint(int numbertodisplay){ erasereg(); int tens = numbertodisplay / 10; int ones = numbertodisplay % 10; //Serial.print(numbertodisplay); //Serial.print(“, tens register:”); //Serial.print((unsigned int)ten[tens] >> 8); //Serial.print(“, ones register:”); //Serial.println((unsigned int)one[ones] >> 8); registr[(unsigned int)ten[tens] >> 8] |= (ten[tens] -(((unsigned int)ten[tens] >> 8)*256)); registr[(unsigned int)one[ones] >> 8] |= (one[ones] -(((unsigned int)one[ones] >> 8)*256)); digitalWrite(latchPin, LOW); for(int j = 2; j>-1; j–){ shiftOut(dataPin, clockPin, MSBFIRST, registr[j]); /*if(j!=0){ Serial.print(registr[j], BIN); Serial.print(” : “); } else { Serial.println(registr[j], BIN); }*/ } //delay(5000); digitalWrite(latchPin, HIGH); } void initialreading(){ for(int i = 0; i < numReadings; i++){ total= total – readings[index]; readings[index] = analogRead(tempPin); total= total + readings[index]; index = index + 1; if (index >= numReadings) index = 0; average = total / numReadings; } } I’m going to try to explain the code a little in detail, but it’s not easy for me. The first section obviously defines the pins, and I use an array to keep track of the 3 shift registers. That can be seen in registr[3] = {0,0,0};

1
 const int numReadings = 30; int readings[numReadings]; // the readings from the analog input int index = 0; // the index of the current reading int total = 0; // the running total int average = 0; // the average

The next section (above) just defines some variables for averaging the reading from the temperature sensor. It’s tightly based off the arduino examples for smoothing.

1
 int temperature = 0; int previoustemp = 1;

boolean flipflop = 1; boolean flopflip = 1; int ledfader = 0; int fadestep = 1; int lowlightlevel = 0; //do not change int steppast = 100; //delay to pause LED on low light and max brightness before fading back to other value. play with the number to get right. int ten[10] = {2, 256 + 2, 256 + 4, 256 + 8, 256 + 16, 256 + 32, 256 + 64, 256 + 128, 8, 4}; int one[10] = {512 + 128,32,64,128, 512 + 2, 512 + 4, 512 + 8, 512 + 16, 512 + 32, 512 + 64}; Then the variable to keep track of temp readings current and previous, my stupid flip flop variables, variables related to led fading. Finally the arrays ten[] and one[] which i’ll explain. I have a function in the code, displayint, which basically is fed an integer, and is responsible for displaying that number on the nixie tubes. Since I’m using 3 shift registers to light up the tubes, using the arduino ide built in shiftout statement, that line looks like this:

1
 digitalWrite(latchPin, LOW); for(int j = 2; j&gt;-1; j--){ shiftOut(dataPin, clockPin, MSBFIRST, registr[j]); } //delay(5000); digitalWrite(latchPin, HIGH);

Notice the line iterates 3 times, starting with 2 and counting down to zero. This feeds the data in my registr[] array backwards to the shift registers. So the first byte output ends up in the last register in the chain, and the last byte output ends in the first register. There are many good tutorials about shift registers on the web, so I won’t go into too much detail. Just know that the data for all 3 registers are kept in the registr[] array. I’ve compiled an image (below) that may help. register Notice in the above that if the value of registr[2] were say 4, then (in binary 00000100) pin #3 would be high on the right shift register. Another example, if the value of registr[1] were 128, then pin #9 on the middle shift register would be high and the rest would be low. Another example, lets say registr[0] = 5, then both pin #1 and pin#3 would be high since 5 in binary is 00000101. Last example, if registr[2] = 255, then all pins on the right shift register would be HIGH since 255 is 11111111 in binary. Doing things this way, although complicated, actually simplifies things when variable declarations like below are involved:

1
 int ten[10] = {2, 256 + 2, 256 + 4, 256 + 8, 256 + 16, 256 + 32, 256 + 64, 256 + 128, 8, 4}; int one[10] = {512 + 128,32,64,128, 512 + 2, 512 + 4, 512 + 8, 512 + 16, 512 + 32, 512 + 64};

Each integer, in the above, is the data that is used by the displayint function to easily display any integer using math, rather than a ton of if/else or switch statements. When I program, I try to make everything make sense using math rather than “string logic,” as I call it. I will now explain how this works, while going through the displayint fucntion: When my displayint function runs, it first clears out the data that is to be sent to the register, using the function erasereg shown below:

1
 void erasereg(){ for(int k = 0;k&lt;3;k++){ registr[k] = 0; } }

This turns all 3 values of the array to zero. I then separate out the integer fed to the function by using:

1
 int tens = numbertodisplay / 10; int ones = numbertodisplay % 10;

For instance, if the number 67 were given to the function, then: tens = 6 and ones = 7 by using the above code (the modulo % returns the remainder) Now comes the 2 power lines. These do all the heavy lifting in the code.

1
 registr[(unsigned int)ten[tens] &gt;&gt; 8] |= (ten[tens] -(((unsigned int)ten[tens] &gt;&gt; 8)*256)); registr[(unsigned int)one[ones] &gt;&gt; 8] |= (one[ones] -(((unsigned int)one[ones] &gt;&gt; 8)*256));

Yikes! Don’t be scared. Let’s look at what we’re doing here. First we’re selecting which register we want to change the value of:

1
 registr[(unsigned int)ten[tens] &gt;&gt; 8]

Simply put, this is taking a value and dividing by 256. So a value of 0-255 would return a “0”, a value of 256-511 would return a “1”, and a value of 512-767 would return a 2. These correspond to our 3 shift registers (0, 1, and 2). The next part of the statment:

1
 |= (ten[tens] -(((unsigned int)ten[tens] &gt;&gt; 8)*256));

This says to “or equals” the value of the register selected by a number. For instance when the decimal 4 (00000100) |= (or equals) decimal 1 (00000001), then the output is decimal 5 (00000101). Each bit is set by combining all the 1’s in the two binary numbers, so it adds, or appends to the original. So in the above example with the 1 and 4, if this were registr[2] then both pins #1 and #3 would be on. (00000101) Lets look at one value and run it through our function. The number to display is 67, so tens=6 and ones=7; Using our array:

1
 int ten[10] = {2, 256 + 2, 256 + 4, 256 + 8, 256 + 16, 256 + 32, 256 + 64, 256 + 128, 8, 4}; int one[10] = {512 + 128,32,64,128, 512 + 2, 512 + 4, 512 + 8, 512 + 16, 512 + 32, 512 + 64};

You can see that ten[6] = 256 + 64 and one[7] = 512 + 16 so…


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

actually looks like:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

and that translates to:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Which further translates to:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Which again is:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

which outputs to our register: Untitled-1 So when I’m declaring my arrays:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

The first part is selecting the appropriate register (0 = 0, 256 = 1, 512 = 2), and the second part is selecting the appropriate pin (using binary) to light up the appropriate number on the nixie that corresponds to the number that is fed to the array! I really hope all that makes sense, cause I can write the code better than I can explain it. so ten[8] would be equal to 8, or 0 + 8, or register 0 pin 4 (8 in binary = 1000 = pin 4 on) and one[4] would be equal to 512 + 2, or register 2 pin 2 (2 in binary = 10 = pin 2 on) And the reason I append (or equals) each number is becacuse 2 nixie tubes using 3 shift registers actually presents a case where the number to be displayed, both the tens and ones, will share the same shift register. Using the |= allows me to turn 2 pins on, on one register without overwriting the other. If you can understand any of that, then I don’t really need to explain anything more. I just want to point out that all the statements like the one shown below allow me to do other things while the program is running. For instance:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

only runs once every second. If you want to understand that better you could read my post, Theres also a bunch of jargon to make the led do what I want it to do, which can be confusing. The operation of the LED, though, is as follows: The LED starts at 0 pwm, ramps up to 255 (max pwm), waits for steppast then proceeds to ramp down to lowlightlevel before delaying again for steppast, then ramping up to max and continues… The LED never gets back to zero after fading up initially. All the above logic is achieved using the following code:


GeSHi Error: GeSHi could not find the language quotphp (using path /home/secretsather/public_html/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

After doing a little woodworking and putting everything together in the case, a little shrink tubing on the LM335DZ, and the final result: PXK59814 PXK59842 PXK59841 Check out the gallery below and feel free to comment and rip it apart, telling me that I need to write code that is more readable and user friendly. Well, I understand it. That’s all that really matters.