GPS VFD-tube clock guidance

Hi guys!

For the last couple of months I have been working on a clock made of four Russian IV-6 tubes, an Arduino Nano and a readily available Neo6m GPS module.

The concept is fairly simple: the GPS module receives an NMEA sentence, the Nano distills the time from the NMEA sentence, converts it in 4 separate digits, outputs it to a decoder (CD4511) which is then multiplexed to a set of VFD tubes. Each of the tubes is turned on or off by it's grid.

I have had a crude concept running for some time now, but so far I have only been able to get one digit working properly. With just a hand full of knowledge on coding and a lot of reading, working out the code led me to insanity. That is why I am reaching out to the community for some guidance.

What I have been trying to achieve with my code is that I would like the multiplexing to go on and on until the Nano receives a 'new' time from the GPS moldule and updates the time that is being outputted to the decoder (and the VFD tubes). The code as it is compiles, but behaves rather erratically when uploaded. The code is the best I have s far. It is a mix of parts of code that have worked in the past.

My question is if I am in the right direction with what I have written so far and if you would have suggestions for me on how to modify my code in order to get it working accordingly.

/*********************
  3 naar GPS Module TX   VOORHEEN 3, NU 0
  2 naar GPS Module RX   VOORHEEN 2, NU 1

  4511 constructor:
  cijfer 1 t/m 4 (7,8,11,12) is A, B, C en D
  cijfer 5 (1) is de hoeveelheid IC's die je gebruikt
  cijfer 6 (9,10) is latch pins maar die gebruiken we niet

  MICREL (2981) driver
  Digit 1 = pin D5 (T1)
  Digit 2 = pin D4 (T2)
  Digit 3 = pin D3 (T3)
  Digit 4 = pin D2 (T4)
*********************/

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SegmentDisplay_CD4511B.h>
#include <TimeLib.h>

// Choose two Arduino pins to use for software //serial
int RXPin = 1;
int TXPin = 0;

int GPSBaud = 9600;

int T1 = 5;
int T2 = 4;
int T3 = 3;
int T4 = 2;

// Create a TinyGPS++ object
TinyGPSPlus gps;

// Create a software serial port called "gpsserial"
SoftwareSerial gpsserial(RXPin, TXPin);

SegmentDisplay_CD4511B displayDriver(7, 8, 11, 12, 1);

//-----------------------------------------------

byte last_minute, Minute, Hour;

void setup() {
  // Start the software serial port at the GPS's default baud
  gpsserial.begin(GPSBaud);

  // MICREL 2981 grid driver als output
  pinMode(T1, OUTPUT);
  pinMode(T2, OUTPUT);
  pinMode(T3, OUTPUT);
  pinMode(T4, OUTPUT);

}

void loop() {

  while (gpsserial.available() > 0)
    if (gps.encode(gpsserial.read()))
    {
      // get time from GPS module
      if (gps.time.isValid())
      {
        Minute = gps.time.minute();
        Hour   = gps.time.hour();
      }
      if (last_minute != gps.time.minute())     // if time has changed
      {
        last_minute = gps.time.minute();

        int uurTientallen = ((Hour + 1) / 10 % 10);
        int uurEenheden = ((Hour + 1) % 10);
        int minuutTientallen = (Minute / 10 % 10);
        int minuutEenheden = (Minute % 10);

        digitalWrite(T1, HIGH);
        displayDriver.showNumber(uurTientallen);
        digitalWrite(T1, LOW);

        digitalWrite(T2, HIGH);
        displayDriver.showNumber(uurEenheden);
        digitalWrite(T2, LOW);

        digitalWrite(T3, HIGH);
        displayDriver.showNumber(minuutTientallen);
        digitalWrite(T3, LOW);

        digitalWrite(T4, HIGH);
        displayDriver.showNumber(minuutEenheden);
        digitalWrite(T4, LOW);

      }
    }
}

Desired cycle:

My schematic:

Finished clock. You've got to love the tubes!

And yes, for those wondering: I am running the filaments on 1v alternating current.

You only update the digits when there are data from the GPS. Why?
The on time of each digit gets very short. Is that good?

Cool looking display!

Without knowing the circuit details, it is not possible for us to help with code to write the correct digits to the display.

If you have figured out how to display an arbitrary digit, independently on each of the indicator tubes, post that code. If not, writing that code is what you should do first.

Thanks for your reply!
I think that might be a big part of the problem. Do you have a suggestion on how to change the order of the code?

Thank you!!

I fell short not to post the schematic, so I updated my post.

The sole digit I got working was on a breadboard and unfortunately wasn't multiplexed, nor did it work very well.
It noticably 'flickered' the moment the GPS received a new sentence. That's why I think there might be a problem in the order of my code, but I am out of options myself. Please feel free to make a suggestion.

Post a complete schematic, and a link to the library.

Please find the schematic added to my first post.

SegmentDisplay (CD4511) library:

TinyGPS++ library:

TimeLib library:

Can You provide a link to the datasheet of the digits?
I would like to know the time needed to turn on the digit, how many mS. Updating the digits some 50 times per second would give an on time 5 ms per digit.
I suggest a switch case construction having 4 cases, one per digit.
Then enter the switch code once every 5 mS.
Let loop() service the GPS. Next part of the code services the updating and multiplexing of the display.

Looks like you have all the circuitry required to multiplex the digits, so you just need to learn how to do that. There are plenty of tutorials on line, mostly for seven-segment LED displays, but the principles are identical.

The basic idea is to break down the number to be displayed into individual digits, then:

(1) output the code for the correct segments of the appropriate indicator tube
(2) enable the display of that indicator tube
(3) wait for a short period of time
(4) disable the selected indicator tube
(5) step to the next digit in the series and loop back to (1)

References to the tubes I have found and used:
Unfortunately the datasheets are in Russian... I put the text in Google translate, but can't seem to find a switching time.

Thanks for the suggestion of the loop() servicing the gps and using switch case statements to multiplex the display. I will be looking in to that!

Datasheet 1:

Datasheet 2:


Translation:

Thanks, this is what I have been trying to do with my code and the multiplexing itself seems to work fine.
It is just the combination and timing in my code that seems to be off. As illustrated in the 'circular diagram' in my first post, the ideal situation would be that the Nano would continuously read out the GPS parallel to multiplexing the digits until the time variable changes.
When the time changes, the digits should update.

I am currently looking into Railroader's idea of using the loop() solely to read out the GPS and using switch case statements for multiplexing. Any further suggestions would be very welcome!

I just wrote another iteration based on the switch case suggestion. It compiles, but I haven't been able to upload and test it yet.

What I aimed to do is to first add an if-statement to check if the time has changed. If the time has changed, it will change the newTime integer from 0 to 1. The loop then reaches the switch case part which checks if 'newTime' is unchanged (default) or 1. In each of the two cases it calls multiplex() to show either changed or unchanged digits.

I hope my elaboration makes some sence. Could you please provide some feedback on this code?

*Update: I've just uploaded the code. It doesn't do what I hoped it would. The first tube shows an undifinable digit, then after a few milliseconds all digits simultaneously ligt up a few times (they flash). Only the second digit is readable...
I notice that as the code runs it seems to 'wait' until it receives a new gps sentence.

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SegmentDisplay_CD4511B.h>
#include <TimeLib.h>

// Choose two Arduino pins to use for software //serial
int RXPin = 1;
int TXPin = 0;

int GPSBaud = 9600;

int T1 = 5;
int T2 = 4;
int T3 = 3;
int T4 = 2;

int oldTime;
int currentTime;
int newTime = 0;

// Create a TinyGPS++ object
TinyGPSPlus gps;

// Create a software serial port called "gpsserial"
SoftwareSerial gpsserial(RXPin, TXPin);

SegmentDisplay_CD4511B displayDriver(7, 8, 11, 12, 1);

//-----------------------------------------------

byte last_minute, Minute, Hour;

void setup() {
  // Start the software serial port at the GPS's default baud
  gpsserial.begin(GPSBaud);

  // Micrel driver als output
  pinMode(T1, OUTPUT);
  pinMode(T2, OUTPUT);
  pinMode(T3, OUTPUT);
  pinMode(T4, OUTPUT);

}

void loop() 
{
  while (gpsserial.available() > 0)
    if (gps.encode(gpsserial.read()))
    {
      // get time from GPS module
      if (gps.time.isValid())
      {
        Minute = gps.time.minute();
        Hour   = gps.time.hour();
      }

      oldTime = currentTime;
      currentTime = gps.time.minute();

      if (oldTime != currentTime) // if time has changed
      {
        newTime = 1;
      }

      switch (newTime)
      {
        case 1:
          multiplex();
          break;
        default:
          multiplex();
          break;
      }
    }
}


void multiplex()
{
int uurTientallen = ((Hour + 1) / 10 % 10);
int uurEenheden = ((Hour + 1) % 10);
int minuutTientallen = (Minute / 10 % 10);
int minuutEenheden = (Minute % 10);

digitalWrite(T1, HIGH);
displayDriver.showNumber(uurTientallen);
digitalWrite(T1, LOW);

digitalWrite(T2, HIGH);
displayDriver.showNumber(uurEenheden);
digitalWrite(T2, LOW);

digitalWrite(T3, HIGH);
displayDriver.showNumber(minuutTientallen);
digitalWrite(T3, LOW);

digitalWrite(T4, HIGH);
displayDriver.showNumber(minuutEenheden);
digitalWrite(T4, LOW);
}

Don't bother about time being changed or not. You still need to refresh the digits all the time.
Read the topic Blink witout delay.
Every timeToUpdate, lets start with 5 mS, You activate one digit with the data from the last GPS reading. Next 5 mS You power the next digit and so on.

Thanks again for you reply.
Do you suggest changing the multiplex() part by using Millis?
Would you like elaborate some more on your point, please?

It looks like it does wait for a sentence because display is dependent on encode returning true.

Move the display to the very bottom of loop, outside the if and follow @Railroader's advice to update it frequently using millis.

Ah yes, of course it is. Thank you!

Will do!

Take the math out of the function multiplex and do it when the GPS has given new data. Let multiplex just pick ready patterns and apply them to the display. That will fasten up the time to do the actual switching.

Good point, thanks! I'll update the code based on your advise and recommendations.

I've been very busy rewriting the code and testing it, but I'm still stuck in the process. So I need some more advice/feedback.

  • I scrapped the switch case in the loop() part, as Railroader said I shouldn't worry about the time being changed or not. Was scrapping it the best option, or should I have incorporated a switch case in the multiplex() part of the sketch turning the digits on and off?

  • I'm not sure whether I should use Millis to time the switching of the tubes or to update the GPS reading.

According to Arduino: " A while loop will loop continuously, and infinitely, until the expression inside the parenthesis, () becomes false"

  • I might be wrong here, but when my code enters the while loop, it won't exit anymore. This is what confuses me most. Does it mean I should call multiplex() in the while loop?
    As Wildbill suggested to move the display to the very bottom of the loop: do you mean the bottom of the while loop or the loop()?

Sketch so far:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SegmentDisplay_CD4511B.h>
#include <TimeLib.h>

// Choose two Arduino pins to use for software //serial
int RXPin = 1;
int TXPin = 0;

int GPSBaud = 9600;

int T1 = 5;
int T2 = 4;
int T3 = 3;
int T4 = 2;

int uurTientallen;
int uurEenheden;
int minuutTientallen;
int minuutEenheden;

const long interval = 5;
unsigned long previousMillis = 0;
byte last_minute, Minute, Hour;

// Create a TinyGPS++ object
TinyGPSPlus gps;

// Create a software serial port called "gpsserial"
SoftwareSerial gpsserial(RXPin, TXPin);

SegmentDisplay_CD4511B displayDriver(7, 8, 11, 12, 1);


void setup() {
  // Start the software serial port at the GPS's default baud
  gpsserial.begin(GPSBaud);

  // Micrel driver als output
  pinMode(T1, OUTPUT);
  pinMode(T2, OUTPUT);
  pinMode(T3, OUTPUT);
  pinMode(T4, OUTPUT);

}

void loop()
{
  unsigned long currentMillis = millis();

  while (gpsserial.available() > 0)
    if (gps.encode(gpsserial.read()))
    {

      // get time from GPS module
      if (gps.time.isValid())
      {
        Minute = gps.time.minute();
        Hour   = gps.time.hour();

        uurTientallen = ((Hour + 1) / 10 % 10);
        uurEenheden = ((Hour + 1) % 10);
        minuutTientallen = (Minute / 10 % 10);
        minuutEenheden = (Minute % 10);
      }
    }
  if (currentMillis - previousMillis >= interval)
  {
    multiplex();
    previousMillis = currentMillis;
  }
}

void multiplex()
{
  digitalWrite(T1, HIGH);
  displayDriver.showNumber(uurTientallen);
  digitalWrite(T1, LOW);

  digitalWrite(T2, HIGH);
  displayDriver.showNumber(uurEenheden);
  digitalWrite(T2, LOW);

  digitalWrite(T3, HIGH);
  displayDriver.showNumber(minuutTientallen);
  digitalWrite(T3, LOW);

  digitalWrite(T4, HIGH);
  displayDriver.showNumber(minuutEenheden);
  digitalWrite(T4, LOW);
}

Separating the collection of the time from the GPS module and the management of the display is a good idea, as already mentioned.

I would do it like this:

Have a global variable which represents the display, say byte display[4].
You set up a timer, say using the TimerOne.h library, which every couple of milliseconds calls an interrupt service routine. In that interrupt service routine, you extinguish the current displayed digit and load the next, from the display[] array, into the next tube. That means that a specific digit is displayed until the next call of the ISR. This implements the cycle you have illustrated.

At the moment, you seem to rely on the execution time of digitalWrite() to keep the tubes illuminated long enough for a good visual effect. This will not work nicely.

The other advantage of managing the display in an ISR is that there will not be any noticeable flicker if anything happens in the loop such as reading the GPS device.

Periodically, you read the GPS device in the loop and update the global array display[].

It will mean a light restructure of what you have already done.

Your project is very nicely presented.

Incidentally, I've just got notice that the post will be bringing me today six IV-4 16 segment display tubes which I ordered from a Ukraine based Ebay retailer.