MSP430 VFD Clock

MSP430 VFD Clock

Overview

I wanted to do a follow-up to my last clock build, the MSP430 Analog Gauge Clock, reusing some of the code from that project, and I had an IV-18 vacuum florescent display (VFD) tube that I bought on Ebay. Also, I  wanted to finish the project before Christmas break was over. That didn’t happen. But I did manage to get the code written and most of the hardware built. I didn’t have the right parts on hand to build a boost converter to provide the 50V or so needed to drive the VFD, and school was about to start, so I decided to put that off until later, as adding an open-loop boost converter circuit using a PWM signal from my MSP430 would be pretty trivial. I’ve finally finished the project, and decided to do a write-up.

I know that and IV-18 based clock isn’t anything original. In fact, Adafruit used to sell a kit for this exact purpose. I don’t really care that it’s not original, because all vacuum tubes are cool, and doing all of the work yourself is really much cooler than using a kit. Also, I wanted to try my hand at Manhattan style construction. Manhattan style circuits are built on a copper ground plane, with little islands for every node of the circuit. Because of the ground plane, and because it has fewer parasitics than strip board or perfboard, Manhattan style construction is often used for radio frequency projects. My project isn’t a radio project, and the circuit probably would have worked just fine on a strip board, but Manhattan style construction gives a circuit a crude/retro look, which pairs nicely with a Russian vacuum tube.

If you wanna skip straight to the source code, the files are available here.

main.c
max6921.h
maz6921.c

Driving the VFD

Originally I wanted to avoid using a VDF display driver. I wanted to use shift registers and decoders driven with my trusty MSP430G2553. Then I started reading the datasheet of the IV-18. The tube needs +50V on each of the segment pins that need to be lit and +50V on the pin corresponding to the digit. This means that I would need high-voltage PNP transistors for each segment and each digit,for a total of 17 transistors. I could use an 8-bit shift register to drive the segments, but I would have needed 9 bits to control the characters (8 characters + the AM/PM indicator character). I could have used an 8 bit decoder and and extra pin on my microcontroller for the AM/PM. This would mean I’d need 7 total pins, three for the shift register, three for the decoder and one for the AM/PM segment. This just wasn’t practical since I’d need to but the high voltage transistors anyway. I decided to go with the Max6921 vacuum florescent display driver, which is basically just a 20-bit high voltage shift register. Here’s a link to the datasheet.

As I mentioned before the microcontroller I’m using is the TI MSP430G2553. I bought several for my embedded systems class I took last semester so I have a bunch laying around. The core code for the real time clock is the same as in my Analog Gauge Clock project. It uses 1 Hz interrupts generated from the Watchdog timer to keep time based off a 32 kHz watch crystal. Here are the important bits of that code.

1
2
3
4
5
6
// Configure clock
BCSCTL3 |= XCAP_1;            // enabling built in 6 pF capacitance for crystal
// Configure the Watch dog timer for the RTC
WDTCTL = WDT_ADLY_1000;       // watchdog interval timer mode ACLK
IE1 = WDTIE;                  // enable watchdog timer interrupt

If we look inside msp430g2553.h we can see what the WDT_ADLY_1000 is actually doing to the watchdog control register.

1
2
/* WDT is clocked by fACLK (assumed 32KHz) */
#define WDT_ADLY_1000       (WDTPW+WDTTMSEL+WDTCNTCL+WDTSSEL)                 /* 1000ms */

We can see that it is setting up the watchdog timer to trigger an interrupt every second, based on an input from ACLK, which is exactly what we want.

The interrupt service routine is shown below.

1
2
3
4
5
// watchdog timer interrupt
#pragma vector=WDT_VECTOR __interrupt
void watchdog_timer(void) {
addSec();
}

The addSec() function simply increments a global variable seconds, or, if seconds is rolling over, calls the addMin() function. There’s also an addHour() function which does the exact same thing.

There are two push button interrupts that are used to set the clock. The first interrupt resets the seconds variable and calls the addMin() function. The other interrupt does the same thing but calls the addHour() function. I used interrupts on two different ports with as little code in them as possible to minimize the time spent in the interrupt service routines. This is because the IV-18 tube is driven by the processor, and any time that the processor is not driving the display creates flicker.

The MAX6921 is driven with some shifting code that I wrote to display the time. I wrote the code myself, drawing from the shift register example by Andrew Morton on the MSP430 Launchpad wiki. The most important function is called print() and the implementation is shown below. The implementation is pretty specific to my application, and, though it could be re-written, isn’t intended to be used as a MAX6921 or general purpose shift register driver.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void print(unsigned int digit, unsigned int num, unsigned int pm){
    //Set latch to low (should be already)
      P1OUT &= ~LATCH;
      unsigned int i;
      unsigned int digitData;
      unsigned int numData;
      // If pm = 1; then we are drawing the PM dot
      if(pm == 1){
          digitData = 0;
          numData = 1;
          // Shift out the first 2 bits as zeros and the 3rd bit as a 1 (to indicate digit 9)
          P1OUT &= ~DATA;
          for(i = 0; i < 2; i++){
              pulseClock();
                }
          P1OUT |= DATA;
          pulseClock();
          }
      else{
          numData = getNum(num);
          digitData = getDigit(digit);
          // Shift out the first 3 bits as zeros (2 not connected to IV-18, and we're not drawing PM dot)
          P1OUT &= ~DATA;
          for(i = 0; i < 3; i++){
              pulseClock();
          }
      }
     // Shift out digit bits
     for(i=0; i<8; i++){
         pinWrite(DATA, (digitData & (1 << i)));
         pulseClock();
         }
     // Shift out the numner bits
     for(i=0; i<8; i++){
         pinWrite(DATA, (numData & (1 << i)));
         pulseClock();
          }
     // shift out one more NC bit
    P1OUT &= ~DATA;
    pulseClock();
    // Pulse the latch pin to write the values into the storage register
    P1OUT |= LATCH;
    P1OUT &= ~LATCH;
}

The getNum() and getDigit() functions are switch statements that return the binary representation for the number or digit I am trying to write to the VFD.

The main program continually loops the same function call, printTime() which relies on the print() function in the MAX6921.c file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void printTime(){
    // takes hours minutes and seconds (6-digits) and prints them on the VFD
    // HH-MM-SS
    unsigned int h1,h2,m1,m2,s1,s2;
    for(;;){
    h1 = hours/10;
    h2 = hours%10;
    m1 = minutes/10;
    m2 = minutes%10;
    s1 = seconds/10;
    s2 = seconds%10;
        print(8,h1,0);
        print(7,h2,0);
        print(6,10,0);
        print(5,m1,0);
        print(4,m2,0);
        print(3,10,0);
        print(2,s1,0);
        print(1,s2,0);
        if(pm==1) print(0,0,1);
    }
}

To get each digit from the hours, minutes, and seconds variables separate, the function first divides by 10 do get the ten’s place digit, and them modulos (is that the verb form?) by 10 to get the one’s place digit.

For More Details: MSP430 VFD Clock

Current Project / Post can also be found using:

  • Iv18 arduino
Scroll to Top