Nixie Tube Thermostat
A blend of technologies spanning fourty years, this project was developed as an example project for my Embedded Control Systems class during the mid-2000's.
This project consists of a thermostat for an office heating/cooling system which employs a PIC18F252 microcontroller and three nixie cold-cathode display tubes. Nixie tubes, dating back to the 1960's, operate using high voltages to generate a discharge in neon gas - cathodes in the shape of numerals glow when grounded. This blend of technologies spans from the 60's (nixie tubes) to the 2000's (the PIC controller). The PIC has an internal ten-bit ADC which converts voltages from a resistive voltage divider (of which one arm consists of a thermistor) into a binary number. The ADC reading is then converted into a temperature via a large 16-bit look-up table and displayed on the nixie tubes. User-configurable limits control the operation of two relays to operate heating and cooling systems.
Nixie Display Design
Nixie tubes require high voltages (>170 volts) to illuminate. A single anode is connected to a high voltage source via a dropping resistor and cathodes shaped like numbers are grounded to make them illuminate. Drivers, then, must be capable of withstanding the driving voltage. Two common choices are to use a high-voltage (Vce) transistor like the MPSA42 or to use a nixie driver chip like the 7441 or 74141 TTL chips. Each of these chips is a decoder featuring a four-line binary (TTL-level) input and ten outputs especially designed to directly drive nixie tube cathodes. Unfortunately supply of these chips, like supply of the nixie tubes they were designed to drive, has dropped drastically. Luckily, I had several available in a junkbox.
Had 7441's nor 74141's not been available, discrete transistors could have been employed. Other alternatives considered include a vacuum-fluorescent display (VFD) driver chip which can drive cathodes through a total range of 96 volts - this is below the rated minimum drive voltage for most nixies tubes (approx 125 volts) but may be a viable alternative.
Prototyping the nixie drivers
In order to test the nixie tubes a high voltage source was required. A compact inverter module was found in a Burrough's self-scan bargraph display removed from an old PID controller. This module runs with a 12 volt input, giving 200 volts output. Each nixie requires only 2mA of drive current so the module was tested to ensure it can deliver a minimum of 6mA of output current.
A basic test circuit was then wired using a 74141 to drive all ten cathodes of a nixie with binary input provided by four DIP switches. Operation of the nixie tubes and the driver chips were hence verified - required since both the tubes and the chips have been in storage for decades.
Since three driver chips were available it was not necessary to use a multiplexing scheme to drive the nixie tubes however twelve I/O lines are required from the PIC (three four-binary-digit numbers). As well, three neon indicators are used to indicate the parameter the user is setting (heat, cool or span/hysteresis) as well as the status of the heat/cool outputs. A fourth 74141 driver is used to drive these discrete neon lamps requiring only two I/O lines on the PIC.
The 28-pin PIC18F252 chip has a total of 22 I/O lines - 14 are allocated to the nixie tube driver binary inputs, one input is used for the analog input from the thermistor, three inputs are connected to user pushbuttons (SET, DOWN, and UP), and two outputs to drive heat and cool relays. This leaves two I/O lines (RB6 and RB7) allocated for use with the ICD-2 debugger: for simplified debugging an RJ-12 connector is permanently connected to the chip.
View the Complete Hardware Schematic (version 1.0) for the project here. Be advised changes were made in the final version as discussed in the text below.
After assembly of the prototype (details below), the displays were tested by applying a 12 volt source to the board and grounding bit D3 of each 74141 driver. Leaving bits D0, D1, and D2 of each chip unconnected allow these to float high as per TTL standards so that a "7" is displayed on each nixie. The PIC microcontroller was installed and the ICD-2 connector verified - a wiring error on the prototype resulted in mirror-image connection of the RJ-12 pins (i.e. pins 1 and 6 were interchanged, 2 and 5, etc). Once operation of the microcontroller was verified (using the ICD-2 debugger itself) the system was tested by running a small program which cycled each display from "0" through "9" and continuing through 0x0A through 0x0F (which should blank the display, according to the datasheet for the 74141 driver).
The first problem noted when the program was run was that the processor ran normally when stepped but reset when ran at full speed. The problem was eventually traced to poor decoupling - an incredible amount of AC 'hash' was noted on the +5V power line, undoubtedly generated from the high-frequency inverter on the board. The addition of several tantalum capacitors across the 5V power line solved the problem. A 68mF tantalum was placed across the power rails very close to the MCU chip, and three other tantalum caps (10mF, 22mF, and 1mF) were placed between the +5V line and ground at various points. These caps provided more than adequate decoupling to ensure stable operation and the test program ran reliably.
The second problem was noted during execution of the test program: numerals '0' through '9' displayed correctly but the display glows in a diffused 'haze' when it should have been blank (i.e. with a code > '1001'), and the three neon lamps glowed dimly when they, too, should have been extinguished!
The Nixie display shown correctly displaying "55.5" (left photo) and a diffused haze when it should have been blank (right photo)
Investigation revealed that tube anodes are at essentially ground potential (0.6 V) when ON, but those displaying a diffused glow show 76.5 V when they should essentially be 'floating' to the high voltage supply (+200V). It might be reasoned that small leakage currents through the meter itself (with an impedance of 10 MW) is responsible for these readings but in the absence of any metering at the cathodes a tube glowing diffusedly shows an anode current of 424mA. Normal current for an operating tube (with one lit cathode) was measured at 1.19mA - slightly higher for the middle tube which has a lit decimal point (which consumes a measured current of 196mA itself). And so, the problem appears to be the fault of the 74141 driver itself which does not function according to the datasheet - selecting a blanking function (binary input 0x0A through 0x0F) allows small leakage currents (about 42mA) through each output of the chip which is enough to produce a dim, diffused glow i the tube.
Similar problems were noted (actually, first) with the discrete neon lamps which would all glow dimly when they should have been off. When any one of the three lamps was selected to be ON, that one lamp glowed brightly and the others went out completely. One might draw the inference that a 'dummy load' lamp (one not visible through the window) could be connected to an unused output on the chip so that it glows when the other three are not, however the entire approach of using discrete neon lamps was scrapped in favour of using two 12V incandescent lamps instead. The lamps then indicate 'Heat' and 'Cool', and when both are simultaneously lit, 'Span'. MCU I/O lines RA1 and RA2 are connected to two NPN transistors to drive the lamps (lamps are a considerably better choice than LEDs for this project ... LEDs are far too 'new' to fit with the nixie theme).
A view of both sides of the board. The inverter module has several components protruding from it since at this stage of development modifications were being made to increase the frequency of operation to beyond the audible range (> 20KHz). Changing several resistors and capacitors solved that problem. The right photo shows point-to-point wiring on the rear of the board which resembles the rhetorical "rats nest".
The PIC would normally be installed in the socket to then left however in this case wires are used to ground the D3 line driving each 74141 ... doing so causes unconnected bits D0, D1, and D2 to float high (TTL standard) and so each nixie will display "7" as seen in the first photo. The final hardware revision of the board. Discrete neon lamps were replaced with incandescent lamps (another old technology) along with 2N4401 driver transistors allowing the 12V lamps to be controlled with the low output current of the micro. Of course there are ramifications to software design, namely that the display can no longer be blanked ... another indication will be required for under- and over-range indications such as alternating digits.
The board is shown here after redesign during final checkout before mounting with the PIC18F252 installed and the system connected to the ICD-2 via the cable to the left of the board coming from the RJ-12 jack.
In the final version, it was also found unnecessary to modify the inverter module - the module shown here is stock.
During hardware testing the usual bugs associated with prototyping were discovered including the lack of adequate decoupling capacitors (noted above under Design) and a missing wiring including grounding the resistor pack pulling RA3-RA5 low - the symptoms of that error being the effect that pushing any one button caused all three lines to go high simultaneously.
The board as mounted in a Hammond plastic case. The case was designed for battery-powered apparatus and so features a removable battery door. The door (seen here removed, just below the two relays) is used in the prototype to gain access to a barrier-strip with terminals for power as well as connections for the heating and cooling relays. A hole was cut into the front of the case and covered with a contrast-enhancing tinted window. Three holes in the cover are used for user pushbuttons and the pushbuttons are connected to the main board via four wires and a SIP plug allowing easy disconnect. Another small hole was cut to allow the thermstor to protrude from the unit (on the right side of the case) ensuring it is not overtly affected by heat from the unit itself.
The assembled thermostat. Access to the terminal strip is provided via the battery cover. No access hole was cut for the RJ-12 jack - to access it during programming the cover is simply removed and the ICD-2 plugged directly into the board. The silver flat cable seen above is simply removed after the chip is programmed (for the final time) and the case is assembled. The three pushbuttons (from the left) are assigned the functions:
- SET - to enter the SET LIMITS mode
- DOWN - to decrease the value
- UP - to increase the value
During hardware development it was necessary to write a simple test program which exercises the display drivers. This simple program generates binary codes '0000' through '1111' which cycles each nixie display from "0" to "9" and then _should_ blank the display. This program revealed limitations in the 74141 driver chips employed which necessitated a hardware revision. Download the Test Program Assembly Code here.
From a software perspective, most of the code is straightforward. The main loop samples a ten-bit value from the ADC, converts the value into a temperature using a look-up table, and displays the temperature on the nixie display. Additionally, the temperature is compared to two target values for heating and cooling and relays are operated as required. Each element is discussed below in detail ... consider the main loop of the program:
MainLoop call ReadADC ;Read ADC and start the next conversion call ConvertToTemp ;Convert ADC reading into Temperature (binary #) call CheckCoolRelay ;Activate cool relay if required call DisplayMode ;Display cool/heat indication, operate relays call DisplayTemp ;Display binary # on nixies, blink if overrange call CheckPBs ;Check for PBs, set parameters, update eeprom call LongDelay ;Loop timing goto MainLoop
Functions were written for all major functions allowing re-use of code (e.g. DisplayTemp was used in the function where limits are set by the user as well as in the main program loop as seen here.
The ten-bit binary number from the ADC must now be converted into a temperature (in degrees C) using a table. Temperatures are returned as a sixteen-bit binary number with a single implied decimal so that ten degrees (10.0) is encoded as 0x0063 and negative 25.9 is encoded as 0xFEFE. The eight-bit table, then, has 1024 entries (corresponding to an ADC value of 0x000 to 0x3FF) with each entry being two bytes (-25.9C being encoded as '-259' with the implied decimal place and so in the table as db 0FE, 0FE ;Temp for ADC = 0x45). The table is fixed to begin at location 0x2000 using an ORG statement as per:
org 2000 db 0FC,019 ;Temp for ADC = 0x0 db 0FC,0BB ;Temp for ADC = 0x1 db 0FD,009 ;Temp for ADC = 0x2
The first entry, corresponding to '-99.99' is encoded in hexadecimal as 0xFC19.
The ADC value is first multiplied by two (by rotating left) to generate a bytewise address and the table read by loading TBLPTRH:TBLPTRL with 0x2000 and adding the ADC value (multiplied by two) to this pointer (as demonstrated in the code below). In this manner, an ADC reading of 0x0 generates an address of 0x2000, an ADC reading of 0x1 generates an address of 0x2002, etc. Again, the table is arranged as HighByte:LowByte using 'db' directives so two consecutive table reads are required to obtain a sixteen-bit temperature value.
bcf STATUS,C ;Multiply by two rlcf AdcLoByte,f rlcf AdcHiByte,f movf AdcHiByte,w ;Add 0x2000 to ADC value addlw 20 movwf TBLPTRH,A movff AdcLoByte,TBLPTRL tblrd * movff TABLAT,TempHigh ;First byte is MSB incf TBLPTRL tblrd * movff TABLAT,TempLow ;Second byte is LSB
The nature of a thermistor is exponential and so the table is generated using a spreadsheet program. 1024 entries define each ADC reading which is then related to the Voltage at the ADC input by dividing by 1023 and multiplying by 5 volts (full scale). The resistance of the thermistor is then computed using a standard voltage-divider formula and the temperature computed using a formula found in the datasheet for the thermistor, a Philips 2322-640-63103 NTC:
Degrees C = -273.15+(3.35*10^-3+2.57*10^-4*ln(Rth/10000)+2.63*10^-6*ln(Rth/10000)^2+6.75*10^-8*ln(Rth/10000)^3)^-1
A binary number is computed, then, representing the temperature to one-decimal place precision. The number is converted to hexadecimal and split into MSB and LSB bytes which are both used to generate a line of assembly code defining these two as table entries.
Download the Thermistor Calculator here.
The temerature returned from the table is in the form of a sixteen-bit binary number which must be converted into three BCD digits for display on the nixie tubes. The range of the display is limited to '00.0' to '99.9' so the number is first checked to ensure it is positive by testing the high bit - if it is set the number is negative and the underrange condition (a blinking '00.0') is displayed. If the number is positive it is converted using the binary-to-bcd routine found in the Microchip 'Demo18' sample routine into five BCD digits - this routine accepts an argument in NumH:NumL and returns BCD digits in registers TenK:Thou:Hund:Tens:Ones. The top two digits (thousands and ten-thousands) are checked to ensure they are zero - if they are greater than zero the temperature is over 99.9 degrees C so the overrange condition (a blinking '99.9') is displayed. Assuming the temperature is in the valid range for the display, the nixie display is updated by simply writing the BCD digits to the appropriate port.
It might also be noted that a SLOW update rate is desirable here. Even a more modern LED display will show a confusing display when alternating between two values if updated too quickly (i.e. with two consecutive numbers such as '1' and '2'). In the case of the nixie display, an orange 'haze' is displayed.
The heat and cool relays are operated on an algorithm which features a hysteresis (span). If the current temperature exceeds the user-programmed limit plus the span, the cool relay is actuated. Only when the temperature falls below the user-programmed limit will the relay be deactivated. If, for example, the user-programmed limit temperature was 25.0 C and the user-programmed span was 0.6 C, the temperature must rise above 25.6 C in order to activate the relay and fall again below 25.0 C in order to release it.In this manner the relay is prevented from rapidly cycling.
Of course, sixteen-bit mathematics, including comparisons, is required since the temperature as well as the user-programmable limits are sixteen-bit values occupying two eight-bit registers each. The following example computes the value of the user-programmable cool limit added to the value of the span:
movff CoolLimitLow,ScratchLow movff CoolLimitHigh,ScratchHigh bcf STATUS,C movf Span,w addwf ScratchLow,f btfsc STATUS,C incf ScratchHigh,f
After this operation, ScratchHigh:ScratchLow contains the desired value.
Aside from the aforementioned functions, the software also allows the user to set the cooling and heating limits, and the span using three pushbuttons, SET, UP, and DOWN. In version 1.2, only user setting of the cooling limit is supported since the unit is intended to be used as a cooling thermostat only. Pushbuttons are interrogated inside the main loop of the program. When the SET pushbutton is detected, the COOL lamp is lit and the display is set to show the CoolLimit temperature (by simply copying CoolLimitHigh:CoolLimitLow to TempHigh:TempLow and calling the same display function used in the main loop of the program - it _does_ pay to write functions in programs). When in this mode (i.e. by holding the SET key depressed), pressing the UP or DOWN buttons will increment or decrement the CoolLimit value:
btfss PORTA,PB_UP ;Check for UP pushbutton bra NoUpPb incfsz CoolLimitLow,f ;Increment limit temperature bra LoopCheckPBs incf CoolLimitHigh,f ;If low byte = 0 increment high byte bra LoopCheckPBs
When the SET key is released, the current CoolLimit value is written into EEPROM allowing the reloading of updated limits each time the processor is powered on. A total of five EEPROM locations are allocated to store the Cool Limit (16-bit), Heat Limit (16-bit), and the Span (8-bit).
- Nixie 1.0 assembly code. A basic version which functions as a thermometer. For MPLAB 7.2x.
- Nixie 1.1 assembly code. Implements thermostat functions, lacking only SET mode (to set temperature limits). Upgraded for MPLAB 7.5x (new CONFIG directives).
- Nixie 1.2 assembly code. With implementation of a cooling function and storage of limits in EEPROM memory. For MPLAB 7.5x.