inGrid IV-18 VFD clock

or, do I really have to make a new clock every time I meet a girl?

Apparently so. This is my fifth retro display clock (others: 1, 2, 3, 4), but the first based on a vacuum fluorescent display. Let's get right down to it.

Driving a VFD

The most important part of making a clock based on some strange display is understanding how to drive the damn thing. VFDs are somewhat more complex than NIXIEs, since they generally sport directly heated cathodes (whereas NIXIEs are cold cathode displays, i.e., no heater drive is required). In short, to drive a VFD, you have to put some current through the filament (which also acts as the cathode, hence directly heated, as opposed to an indirectly heated tube where the heater and cathode are electrically isolated). Then you raise the grid and desired segments (which are actually the anodes) to some voltage (depending on the VFD; newer ones generally require lower voltage, whereas IV-18s take about 50V) and they glow.

The most complete guide I've found to driving VFDs is the Noritake Guide to VFD Operation. Note that the Noritake guide talks about the need for AC filament drive in order to prevent brightness gradients. Why does this happen? The filament acts as the cathode for the display, so if there is a voltage gradient across the filament, there is a gradient in anode-cathode voltage as well. For older VFDs which require higher anode voltages, this is less of a problem, since the gradient across the filament is much lower than the anode-cathode voltage. The IV-18, which needs 50V on the anode but only about 3.5V across the filament, has only a barely noticeable gradient with DC filament drive. Basically, don't worry about AC drive for an IV-18; I sure didn't.

The IV-18

I found it hard to find anything but a Russian IV-18 datasheet, so I've done a brief job of what may be the worst datasheet translation of all time (I took three years of Russian in high school, which means many of you who've never even seen Cyrillic before are probably better than me). Apologies for inaccuracy.

filament current
segment current, total
grid current
maximum negative voltage, cathode to grid
maximum anode (and grid) voltage

The pinout for each group of anodes is depicted to the left. Pins 1 and 13 are the filament (if you're applying DC, it would appear that positive voltage should be applied from pin 13 to pin 1, i.e., pin 1 should be ground). The grids are, from left to right, pins 14, 18, 19, 20, 17, 21, 16, 22, and 15. Note that grid 9 only has a dot and a bar, which are connected to pins 2 and 9 respectively.

Jkx designed his own IV-18 clock, and his site has more info on how he drove it. Multiple independent sources of information are always good.

High Voltage Level Shifter

In order to drive the IV-18 grids and filaments from logic-level outputs, we have to use some sort of high-voltage level shifter. Maxim makes the MAX6921, which is a 20-bit shift register with outputs capable of withstanding up to 76V. I chose not to use this IC, though, since they're not stocked by Digi-Key, and because when multiplexing it's annoying to have to update the whole shift register every time you want to switch to the next grid.

Instead, I went with a BCD-to-decimal decoder driving discrete level shifters for the grids (since I only want one grid driven at a time); to drive the anodes, I use 8 pins on the microcontroller to drive another set of level shifters, allowing me to address in parallel all of the elements associated with each grid.

The circuit to the right is the level shifter I'm using. Here, we drive 5V onto the base of an NPN transistor, which impresses across the emitter resistor about 4.3V; this causes the NPN to pull 1mA through the 10kΩ PNP base resistor, impressing 9.4V across the 750Ω emitter resistor. From a 60V supply, this pulls the output up to about 50V, which means that the 47kΩ collector resistor steals 1mA and the remaining 11.5mA is available to drive the grid/anode. This 11.5mA current corresponds to the maximum grid current above; if less is necessary, the PNP transistor saturates, which slows down the switching, but not unduly considering we only need to multiplex at 4kHz or so.

The BC846 and BC856 are exceedingly cheap in SOT-23, and 0805 resistors are so close to free it doesn't matter, so while these level shifters take up lots of board area compared to the MAX6921, they are comparable in cost. Moreover, using discrete drivers lets me use the same boards later for other projects which might require higher voltage level shifters, whereas with the MAX6921 I'm limited to 76V, period. Finally, since the IV-18 is itself rather large, saving 6 square inches of board area won't actually shrink the clock all that much.

Boost Converter

In order to generate the 60V supply for the VFD, I'm using a boost converter driven from the PWM output of my microcontroller of choice, the PIC16F628A. Since the load is relatively constant, and because I don't really need the output voltage to be a very specific value, I'm running the boost converter open loop—just set the duty cycle value in the code and let 'er rip. I agree it isn't pretty, but it gets the job done while using only one pin on the PIC, input and output capacitors, a switching MOSFET, and the boost inductor.

The dc/dc converter is somewhat overdesigned, but it's still cheap enough not to matter. The assumption is that we need 60V at 50mA, i.e., 3 Watts. The input-output transfer for a boost converter is 1/(1-D), so we nominally need an 80% duty cycle to get 60V. To choose the inductor, we just want to pick up a value large enough to keep the peak-to-peak current small. In particular, to stay in continuous conduction mode, we need to satisfy the condition D*T*Vin/(2*L) < iout/(1-D). At 64kHz, the minimum inductor for 60V at 50mA from 12V is about 250uH; because it's got plenty of margin, and because they're plenty cheap, we choose a 1mH inductor. The output capacitor doesn't matter much as long as it's big; looking on Digi-Key, we can find a 100V 33uF cap that's small and cheap, so we'll pick that value; 50mA/(64kHz*33uF) = 23mV ripple. Simple enough—of course it's simple when you run it open loop!

The Code

The PIC16F628A has just enough i/o for my purposes: 4 for the BCD-Decimal converter to drive the grids, 8 for the anodes, one each for the hours and minutes buttons, one for the 60Hz input, and one for the PWM output driving the boost converter. In other words, where there is a will, there is a way. To wit:
B<3>dc/dc PWM out
A<6>,B<2:0>grid (BCD)
A<7,5>switches: hours, minutes
A<4>60 Hertz input

Clock Multiplicity

The 60Hz counting part of this is the same as it's always been, except that I'm not interrupting on the 60Hz input; instead, I interrupt at a subharmonic (4kHz) of the PWM frequency (64kHz), trapping rising edges of the 60Hz clock while performing the grid muxing.

Why do it this way? Trying to be synchronous with two different clock sources whose frequency and phase relationship is not well known (and can change, e.g., with temperature, since we're using the onboard RC oscillator in the PIC) is a nasty problem. In digital design you call this clock domain spanning problems or the like, whereas in soft/firmware it generally shows its ugly face in the form of asynchronous interrupt contention. Instead, it's much better to synchronize the slower clock to the faster, which works well as long as you can take delays in the slow clock on the order of one cycle of the faster clock. Since we're just counting the 60Hz cycles, and not really synchronizing anything to them, we can certainly get away with it here.

To trap the 60Hz cycles, I'm still using timer 0 in external clock mode, but keeping the timer 0 interrupt disabled. Every 4kHz muxing cycle I poll the timer 0 flag (INTCON,T0IF), which is set even if the interrupt is disabled, and manually clear it and reset the prescaler when it flips over. This lets me take advantage of the hardware at my disposal to get rid of an appreciable chunk of code.

Seeing Ghosts

Another pitfall in the code is minimizing ghosting—when going from one grid to the next, if care isn't taken to turn off the segments before activating the next grid, the segments that should be off will still show faint traces of glow. This is made especially difficult by the fact that the grid control is split between ports A and B (in retrospect, I should have used B<7:5>,A<6,3:0> for the segments so that I could use B<4,2:0> for the grid, but as it stands the code eliminates all ghosting with only minor discomfort). To ensure that we don't see ghosts, at each multiplexing transition we first change the grid control to 0x0C, ensuring that the 4028 BCD decoder blanks all its outputs; after this, the segment values are retrieved from flash, and finally the grid is un-blanked. The corresponding code:

 movlw 0x40			; gridmsb
 movwf PORTA			; in porta
 movlw 0x04			; gridm2sb
 movwf PORTB			; in portb
; ... (decide what to display based on the grid)
; ...
; ... (skipping ahead)
 bsf EECON1,RD			; read data from EEPROM
 movf EEDATA,w			; load EEPROM data
 bcf STATUS,RP0			; bank 0
 movwf segsave			; save EEDATA to segsave
 andlw 0x0F			; mask off top nibble
 movwf seg2save			; save until later
 xorwf segsave,w		; XOR remaining bits with original data to get top nibble
 iorwf grid,w			; and then OR in the grid number
 movwf PORTB			; write PORTB
 bcf gridmsb			; turn off the grid MSB
 movf seg2save,w		; load PORTA value
 movwf PORTA			; write PORTA
; ... (and we're done)

Note that even though we would be clearing the grid MSB with the final movwf PORTA, the extra microsecond delay causes a very slight ghosting on the grid 8 dash when the grid 0 center segment is set.

Runtime Soft Configuration

Another goal on this project was to create a piece of software that didn't have to be configured at assemble time for things like mains frequency, brightness, and display mode. There are a couple ways to achieve this end—with a little additional specialized hardware, or with slightly more complex software and reusing the switches we already have.

From a code standpoint, the simpler way is to use port B (all outputs while running) as inputs at boot to read a few bits worth of settings. We can do this without interfering with the normal (output) operation by attaching weak pull-ups or pull-downs to the port B pins which can easily be driven in parallel with the normal load. Now at boot time, port B is set up as an input, its value read, and then it's switched to output mode and used as normal. From these configuration bits, we can set things like the PWM duty cycle (display brightness), display mode (see below), or region (50 versus 60 Hertz mains frequency). In practice, we can save a bit of hardware by enabling the internal pull-ups on port B when we read the inputs; that way, we just have to jumper the bits we want to be zero to ground through a resistor and we're set.

More complex, slightly more difficult to set up, but requiring absolutely no additional hardware, we can instead store the configuration word in the PIC's NVRAM and use the state of the minutes and hours buttons at boot to cycle through various options. In the simplest implementation, we can just read the state of the switches once, make one change to the configuration bits, and then write back the new configuration. Multiple changes require power cycling several times, but since these changes shouldn't be made very often, even this very simple implementation is quite handy.
0b00No state change
0b01toggle 50/60Hz mode
0b10cycle through display modes
0b11cycle through brightness levels

The latter implementation is the one I chose at first (mostly because I didn't decide to implement the runtime configuration software until after I'd built the first board revision). The code is pretty heavily commented, so please have a look. Basically, the steps are

  1. read in the old configuration
  2. read in the buttons
  3. if a change is necessary, update the config and write back the new one
  4. set up the appropriate registers based on the configuration
  5. go into normal operation

In the second revision of the board, I added pulldown resistor pads for all of the port B outputs (except B3, which I didn't want to load with additional parasitics). Now the software first looks for the presence of resistors (signalled by the presence of a resistor on B<6>); if it finds them, it uses port B for its configuration word. Otherwise, if B<6> does not have a resistor, the resistor pulldowns are ignored and the internal setup is used instead. So, if you decide you want to use resistor configuration, the following table should be your guide. Note that 0b1 means the resistor is installed, 0b0 means the resistor is not installed.
B<7>Mains frequency0b1: 50Hz; 0b0: 60Hz
B<6>Config type0b1: resistor config; 0b0: software-only (resistors ignored)
B<5:4>Display mode0b00: blank; 0b01: complex; 0b10: simple, dash; 0b11: simple, box
B<2:0>Display brightnessVdcdc = 30 + 4 * B<2:0>; 0b011 is a good place to start


I use gpasm for the code and a perl script to generate the flash RAM data (i.e., the segment patterns and config word). Everything is held together with a Makefile, as all software should be. Grab the tarball or, if you prefer, the assembled hex file ready to program.


As always, my layouts are done using Eagle. Note in the schematic that each level shifter is just a box with four terminals; this is so that I could lay out the level shifter once, then stamp down that layout many times, making it very compact and making wiring substantially easier. Each level shifter is exactly as depicted above.

The PCB is intended to be cut apart between the sets of headers; the part to the right attaches via right angle header to the solder side of the PCB and the IV-18 is thus suspended above the circuit board. The orientation is such that the bottom left pin on the daughterboard mates to the top left pin on the mainboard. The IV-18 connects to the 24 pins in the square on the daughterboard; pin 1 corresponds to the pin just below the nine o'clock position in the square.

If you plan on having the board fabricated, you'll want the Gerber files, ready to upload to, e.g., Advanced Circuits. If instead you'd like to try your hand at toner transfer, you'll probably prefer the EPS files, but note that to make the layout as compact as possible I ended up using a via straight into an SMD pad, which will make home production somewhat difficult (unless you have a home-based process for through-plated vias, which by the way I'd very much like to see!).


470uF, 25V		3		565-1678-ND
33uF, 100V		1		565-1150-ND
.1uF, 0805		4		PCC1853CT-ND
.1uF, 1206, 100V	1		399-1805-1-ND

20 Ohm, 1206		1		541-20.0FCT-ND
47k, 0805		17		541-47.0KCCT-ND
10k, 0805		20		541-10.0KCCT-ND
750 Ohm, 0805		17		311-750CRCT-ND
4.3k, 0805		17		311-4.30kCRCT-ND
1k, 0805		7		311-1.00kCRCT-ND

1N4148, SOD123		2		1N4148WTPMSCT-ND
SS110 Schottky diode	1		SS110-TPCT-ND
PIC16F628A		1		PIC16F628A-I/P-ND
DF04S			1		DF04SCT-ND
4028D			1		497-1122-1-ND
7805DT			1		497-1203-1-ND
BC846			17		568-1632-1-ND
BC856			17		568-1637-1-ND

SPST momentary switch	2		401-1011-ND
1mH 300mA inductor	1		553-1073-1-ND
r/a pin header, 2x12	1		A26533-16-ND
12VAC wall wart		1		T601-ND (international: you'll need a trafo appropriate for your locale)
IV-18			1		(certainly not carried by Digi-Key!  try eBay)


These reflect the two states in "complex" mode, which alternate every second. Also have a look at the video.

"Simple" mode just has dashes; the other "simple" mode (not pictured) just turns on the lower four segments (think of them as large periods).

"Blank" mode uses spaces instead.

Questions? Comments? <[email protected]>
Copyright © 2007 by Riad S. Wahby. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
$Id: index.html 333 2007-04-20 13:05:34Z rsw $