Nixie Clock 2: Difference between revisions

From Alnwlsn - Projects Repository
Jump to navigation Jump to search
No edit summary
 
(12 intermediate revisions by the same user not shown)
Line 1: Line 1:
After completing [[Nixie Clock 1]] over a year ago, I still had an extra set of IN-12B tubes that were not being put to good use. Time to build another one. Actually, I ordered yet more tubes and driver ICs so I could build 3 more.
After completing [[Nixie Clock 1]] over a year ago, I still had an extra set of IN-12B tubes that were not being put to good use. Time to build another one. Actually, I ordered yet more tubes and driver ICs so I could build 3 more.


The basic design is almost the same as Clock 1: Russian IN-12B with a K155D1 driver for each tube, shift registers to run the drivers, and [https://threeneurons.wordpress.com/nixie-power-supply/ this 180V power supply design]. A DS3232M is provided for the RTC. There are a few changes; remembering some lessons learned when I built clock #1, I made the following list of improvements:
The basic design is almost the same as Clock 1: Russian IN-12B with a K155D1 driver for each tube, shift registers to run the drivers, and [https://threeneurons.wordpress.com/nixie-power-supply/ this 180V power supply design]. A DS3232M is provided for the RTC, which is similar to the DS3231 modules commonly available from China (I used the same one in the [[Electronic Time Capsule]]). There are a few changes; remembering some lessons learned when I built clock #1, I made the following list of improvements:


* Simpler (or no) sockets. Most of the time towards building the first clock was wiring the 72 pins on the tube sockets. If I could somehow avoid doing that, it would save me lots of time. I ended up using pins from cheap DB25 connectors, which when taken apart get you 25 connectors which are the perfect size to accept the IN-12 pins. I'm not the first one to think of this, and I used an Eagle pattern that I found for the IN-12 Nixies, but the same idea might work for other tubes also. The female PCB mount version of the China DB-25 connectors are what I used, although the Eagle pattern I used accepts the pins a little farther up on the shoulders of the pin, rather than on the section designed to mount into the PCB, so that part is clipped off. Also of note is that the pins are made of stamped flat metal, not a solid chunk, so solder will flow into any holes and fill in the inside of the tube. Therefore, it is important not to use too much solder.  
* Simpler (or no) sockets. Most of the time towards building the first clock was wiring the 72 pins on the tube sockets. If I could somehow avoid doing that, it would save me lots of time. I ended up using pins from cheap DB25 connectors, which when taken apart get you 25 connectors which are the perfect size to accept the IN-12 pins. I'm not the first one to think of this, and I used an Eagle pattern that I found for the IN-12 Nixies, but the same idea might work for other tubes also. The female PCB mount version of the China DB-25 connectors are what I used, although the Eagle pattern I used accepts the pins a little farther up on the shoulders of the pin, rather than on the section designed to mount into the PCB, so that part is clipped off. Also of note is that the pins are made of stamped flat metal, not a solid chunk, so solder will flow into any holes and fill in the inside of the tube. Therefore, it is important not to use too much solder.  
Line 7: Line 7:
* PCB for components. After designing some PCBs for some of my other projects, I realized that it really isn't that hard to design a PCB. It makes everything smaller and neater, and cuts down on assembly time. To maintain a compact design, I designed 2 "stacked" PCBs: one has the power supply, microcontroller, RTC and drivers, and the other holds the tubes. I could also possibly change out the carrier board if I ever want to experiment with other tubes.
* PCB for components. After designing some PCBs for some of my other projects, I realized that it really isn't that hard to design a PCB. It makes everything smaller and neater, and cuts down on assembly time. To maintain a compact design, I designed 2 "stacked" PCBs: one has the power supply, microcontroller, RTC and drivers, and the other holds the tubes. I could also possibly change out the carrier board if I ever want to experiment with other tubes.
* Some kind of case (3D printed) which is easily replicated and doesn't require many hours of hand filing like Clock 1.
* Some kind of case (3D printed) which is easily replicated and doesn't require many hours of hand filing like Clock 1.
* ESP8266 powered. This would give Internet access, and I could also use UDP for some remote control features instead of an IR remote, which would allow greater flexibility when interfacing with other projects, as I have always intended for these displays.
* ESP8266 powered. The flexibility of a network interface provides so much more than an IR remote like I used on Clock 1.


All said and done, I came up with the solution you see here (or, at least you will once I add pictures).
All said and done, I came up with the solution you see here (or, at least you will once I add pictures).


I made a couple mistakes in PCB revision 1: a missing resistor on the I2C bus, and trying to use GPIO15 as the shift register latch pin, without realizing the logic level converter I made pulls it low on startup, not allowing the ESP8266 to boot. I fixed this by cutting the trace and attaching it to another pin.
I made a couple mistakes in PCB revision 1: a missing resistor on the I2C bus, and trying to use GPIO15 as the shift register latch pin, without realizing the logic level converter I made pulls it low on startup, not allowing the ESP8266 to boot. I fixed this by cutting the trace and attaching it to another pin.
==Software==
 
To make the PCB design easier, I drive the tubes "out of order", so I need to shift some bits around before the display comes out looking right. Here's the routine I came up with. It isn't very elegant but gets the job done.
==Control Overview==
Again, the base design is almost the same as in [[Nixie Clock 1]]. A small switch mode boost converter converts 9 VDC to about 185 VDC. Each tube has its own K155D1 driver IC that switches the digits on and off. These are BCD decoders; each one takes in 4 input lines, which covers the input range [0-9] in binary. A 74HC595 shift register has 8 output lines, so I connect 2 K155D1s to each shift register, and use 3 shift registers hooked in series. A 4th shift register is used to switch the 6 tubes' decimal points through dedicated transistors. I can therefore control all 6 tubes using only 3 pins over an SPI compatible interface, but I need to use a level shifter to change the 5V of the shift register / K155D1 voltage to the 3.3V of the ESP8266. 
 
The ESP8266 talks to the DS3232M RTC over I2C, connects to WiFi and listens to a UDP port for commands, and sets the tubes using the shift registers.
 
===UDP control===
In normal operation, the tubes display the hour, minutes and seconds from the RTC on the tubes. At night time, all the tubes cycle through their digits to preserve the life of the tubes. Commands can be sent to the clock, which can set the RTC time, but also display raw data on the tubes instead. When a display command is sent, it will immediately display on the tubes, and stay that way for about 10 seconds, before returning to display of the time. So, to display something using the commands, it's best to update the tubes every few seconds.
I use raw UDP packets for remote control. UDP is a good choice for something like this - for realtime display applications like this, we want fast response of the display, and don't care that much about lost packets because new data is being sent all the time.
 
====UDP packet format====
When a UDP packet is received by the clock, it looks at the first byte, and uses that to decide what to do with the rest of the bytes.
 
{| class="wikitable"
|-
! First Byte !! Function !! Interpretation of remaining bytes
|-
| '-' || Decrements the RTC time by 1 second ||
|-
| '+' || Increments the RTC time by 1 second ||
|-
| 0x01 || Set RTC time to received packet || {2 digit Year, Month, Day, Hour, Minute, Second}
|-
| 0x02 || Display number on the tubes. Data format matches each element to respective tube (place values). Last element is a bitmask for which decimal points to turn on. || {100K, 10K, 100, 10, 1, decimal point mask}
|-
| 0x03 || Display number on the tubes. Data format is an unsigned long (4 bytes, MSB first), followed by a bitmask for the decimal points.|| {four,bytes,unsigned,long, decimal mask}
|}
 
===Python control program (rev1)===
Of course, to make things a little more useful, I needed some way to generate the UDP packets other than by writing them by hand, and then send them. You can do this however you want; below are a couple Python functions to set the RTC time and display integers on the tubes.
<pre>
import msvcrt
import socket
import time
import sys
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
 
#example: settime("192.168.1.108",4201)
def settime(IP, PORT): #sets time on nixie2 clocks to computer's local time
    starttime=time.localtime()[5]
    while starttime==time.localtime()[5]: #wait for second to tick over
        pass #do nothing
    settime=bytes([1,time.localtime()[0]-2000,time.localtime()[1],time.localtime()[2],time.localtime()[3],time.localtime()[4],time.localtime()[5]])
    sock.sendto(settime, (IP, PORT))
   
#example: ndisp(123456,"192.168.1.108",4201)
def ndisp(nvar,IP,PORT): #displays an integer number on the nixie2 clock tubes
    if nvar>4294967295: #will be an error if long int > this value
        return
    psend = b'\x03'+nvar.to_bytes(4, byteorder='big')+b'\x00'
    sock.sendto(psend, (IP,PORT))
 
def fdisp(fvar,IP,PORT): #displays floating point numbers (with decimal point) in range (0.000001, 999999)
    fvar = abs(fvar)
    if fvar>=1000000 or fvar<0.000001:
        return #can't display numbers bigger or smaller than this
    ivar = int(fvar)
    p = 0
    while(100000 > ivar and 6 > p):
        p += 1
        ivar = int(fvar * 10**p)
    p = 2**(p-1)
    if(p<1):
        p=0
    psend = b'\x03'+ivar.to_bytes(4, byteorder='big')+bytes([p])
    sock.sendto(psend, (IP,PORT))
</pre>
 
===Notes on the firmware===
To make the PCB design easier, I drive the tubes "out of order"; the digits do not display in the same order the shift register sends out data. So, I need to shift some bits around before the display comes out looking right. Here's the routine I came up with. It isn't very elegant but gets the job done. This will display a long unsigned int on the tubes, with a decimal point mask.  
<pre>
<pre>
void nixie(long k, byte dp){
void nixie(long k, byte dp){ //displays data on tubes; k is the number, dp is a decimal point mask
   byte dig0=(k%10);
   byte dig0=(k%10);
   byte dig1=(k%100)/10;
   byte dig1=(k%100)/10;
Line 29: Line 97:
   digitalWrite(srLatchPin,LOW);
   digitalWrite(srLatchPin,LOW);
   digitalWrite(srLatchPin,HIGH);
   digitalWrite(srLatchPin,HIGH);
  yield();
}
}
</pre>
</pre>
Line 34: Line 103:
==Files==
==Files==
* [[File:Nixie2-hardware.zip]] - Hardware files - PCB and STL for case
* [[File:Nixie2-hardware.zip]] - Hardware files - PCB and STL for case
* [[File:Nixie2-software-rev1.zip]] - Arduino sketch and Python functions
===Revisions===
====Software====
* Revision 1 - first version, supports setting the RTC from the computer's time, and displaying integers on the tubes

Latest revision as of 22:50, 29 January 2020

After completing Nixie Clock 1 over a year ago, I still had an extra set of IN-12B tubes that were not being put to good use. Time to build another one. Actually, I ordered yet more tubes and driver ICs so I could build 3 more.

The basic design is almost the same as Clock 1: Russian IN-12B with a K155D1 driver for each tube, shift registers to run the drivers, and this 180V power supply design. A DS3232M is provided for the RTC, which is similar to the DS3231 modules commonly available from China (I used the same one in the Electronic Time Capsule). There are a few changes; remembering some lessons learned when I built clock #1, I made the following list of improvements:

  • Simpler (or no) sockets. Most of the time towards building the first clock was wiring the 72 pins on the tube sockets. If I could somehow avoid doing that, it would save me lots of time. I ended up using pins from cheap DB25 connectors, which when taken apart get you 25 connectors which are the perfect size to accept the IN-12 pins. I'm not the first one to think of this, and I used an Eagle pattern that I found for the IN-12 Nixies, but the same idea might work for other tubes also. The female PCB mount version of the China DB-25 connectors are what I used, although the Eagle pattern I used accepts the pins a little farther up on the shoulders of the pin, rather than on the section designed to mount into the PCB, so that part is clipped off. Also of note is that the pins are made of stamped flat metal, not a solid chunk, so solder will flow into any holes and fill in the inside of the tube. Therefore, it is important not to use too much solder.
  • PCB for components. After designing some PCBs for some of my other projects, I realized that it really isn't that hard to design a PCB. It makes everything smaller and neater, and cuts down on assembly time. To maintain a compact design, I designed 2 "stacked" PCBs: one has the power supply, microcontroller, RTC and drivers, and the other holds the tubes. I could also possibly change out the carrier board if I ever want to experiment with other tubes.
  • Some kind of case (3D printed) which is easily replicated and doesn't require many hours of hand filing like Clock 1.
  • ESP8266 powered. The flexibility of a network interface provides so much more than an IR remote like I used on Clock 1.

All said and done, I came up with the solution you see here (or, at least you will once I add pictures).

I made a couple mistakes in PCB revision 1: a missing resistor on the I2C bus, and trying to use GPIO15 as the shift register latch pin, without realizing the logic level converter I made pulls it low on startup, not allowing the ESP8266 to boot. I fixed this by cutting the trace and attaching it to another pin.

Control Overview

Again, the base design is almost the same as in Nixie Clock 1. A small switch mode boost converter converts 9 VDC to about 185 VDC. Each tube has its own K155D1 driver IC that switches the digits on and off. These are BCD decoders; each one takes in 4 input lines, which covers the input range [0-9] in binary. A 74HC595 shift register has 8 output lines, so I connect 2 K155D1s to each shift register, and use 3 shift registers hooked in series. A 4th shift register is used to switch the 6 tubes' decimal points through dedicated transistors. I can therefore control all 6 tubes using only 3 pins over an SPI compatible interface, but I need to use a level shifter to change the 5V of the shift register / K155D1 voltage to the 3.3V of the ESP8266.

The ESP8266 talks to the DS3232M RTC over I2C, connects to WiFi and listens to a UDP port for commands, and sets the tubes using the shift registers.

UDP control

In normal operation, the tubes display the hour, minutes and seconds from the RTC on the tubes. At night time, all the tubes cycle through their digits to preserve the life of the tubes. Commands can be sent to the clock, which can set the RTC time, but also display raw data on the tubes instead. When a display command is sent, it will immediately display on the tubes, and stay that way for about 10 seconds, before returning to display of the time. So, to display something using the commands, it's best to update the tubes every few seconds. I use raw UDP packets for remote control. UDP is a good choice for something like this - for realtime display applications like this, we want fast response of the display, and don't care that much about lost packets because new data is being sent all the time.

UDP packet format

When a UDP packet is received by the clock, it looks at the first byte, and uses that to decide what to do with the rest of the bytes.

First Byte Function Interpretation of remaining bytes
'-' Decrements the RTC time by 1 second
'+' Increments the RTC time by 1 second
0x01 Set RTC time to received packet {2 digit Year, Month, Day, Hour, Minute, Second}
0x02 Display number on the tubes. Data format matches each element to respective tube (place values). Last element is a bitmask for which decimal points to turn on. {100K, 10K, 100, 10, 1, decimal point mask}
0x03 Display number on the tubes. Data format is an unsigned long (4 bytes, MSB first), followed by a bitmask for the decimal points. {four,bytes,unsigned,long, decimal mask}

Python control program (rev1)

Of course, to make things a little more useful, I needed some way to generate the UDP packets other than by writing them by hand, and then send them. You can do this however you want; below are a couple Python functions to set the RTC time and display integers on the tubes.

import msvcrt
import socket
import time
import sys 
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)   

#example: settime("192.168.1.108",4201)
def settime(IP, PORT): #sets time on nixie2 clocks to computer's local time
    starttime=time.localtime()[5]
    while starttime==time.localtime()[5]: #wait for second to tick over
        pass #do nothing
    settime=bytes([1,time.localtime()[0]-2000,time.localtime()[1],time.localtime()[2],time.localtime()[3],time.localtime()[4],time.localtime()[5]])
    sock.sendto(settime, (IP, PORT))
    
#example: ndisp(123456,"192.168.1.108",4201)
def ndisp(nvar,IP,PORT): #displays an integer number on the nixie2 clock tubes
    if nvar>4294967295: #will be an error if long int > this value
        return
    psend = b'\x03'+nvar.to_bytes(4, byteorder='big')+b'\x00'
    sock.sendto(psend, (IP,PORT))

def fdisp(fvar,IP,PORT): #displays floating point numbers (with decimal point) in range (0.000001, 999999)
    fvar = abs(fvar)
    if fvar>=1000000 or fvar<0.000001:
        return #can't display numbers bigger or smaller than this
    ivar = int(fvar)
    p = 0
    while(100000 > ivar and 6 > p):
        p += 1
        ivar = int(fvar * 10**p)
    p = 2**(p-1)
    if(p<1):
        p=0
    psend = b'\x03'+ivar.to_bytes(4, byteorder='big')+bytes([p])
    sock.sendto(psend, (IP,PORT))

Notes on the firmware

To make the PCB design easier, I drive the tubes "out of order"; the digits do not display in the same order the shift register sends out data. So, I need to shift some bits around before the display comes out looking right. Here's the routine I came up with. It isn't very elegant but gets the job done. This will display a long unsigned int on the tubes, with a decimal point mask.

void nixie(long k, byte dp){ //displays data on tubes; k is the number, dp is a decimal point mask
  byte dig0=(k%10);
  byte dig1=(k%100)/10;
  byte dig2=(k%1000)/100;
  byte dig3=(k%10000)/1000;
  byte dig4=(k%100000)/10000;
  byte dig5=(k%1000000)/100000;
  byte dots=((dp&0b00100000)<<2)+((dp&0b00010000))+((dp&0b00001000)<<3)+((dp&0b00000100)<<1)+((dp&0b00000010)<<4)+((dp&0b00000001)<<2);
  shiftOut(srDataPin, srClockPin, MSBFIRST, dots);
  shiftOut(srDataPin, srClockPin, MSBFIRST, dig0*16+dig1);
  shiftOut(srDataPin, srClockPin, MSBFIRST, dig2*16+dig3);
  shiftOut(srDataPin, srClockPin, MSBFIRST, dig4*16+dig5);
  digitalWrite(srLatchPin,LOW);
  digitalWrite(srLatchPin,HIGH);
  yield();
}

Files

Revisions

Software

  • Revision 1 - first version, supports setting the RTC from the computer's time, and displaying integers on the tubes