Nixie Clock 2: Difference between revisions
No edit summary |
|||
(11 intermediate revisions by the same user not shown) | |||
Line 13: | Line 13: | ||
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. | ||
== | ==Control Overview== | ||
Each | 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. | ||
===Notes on the firmware=== | 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. | ||
To make the PCB design easier, I drive the tubes "out of order", | |||
===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 33: | Line 97: | ||
digitalWrite(srLatchPin,LOW); | digitalWrite(srLatchPin,LOW); | ||
digitalWrite(srLatchPin,HIGH); | digitalWrite(srLatchPin,HIGH); | ||
yield(); | |||
} | } | ||
</pre> | </pre> | ||
Line 38: | 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
- 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