Reading GPS Time Causing Multiplexing Flickering

Hi,
I am an avid Nixie clock collector and I am developing ideas how to build and program my own using an Atmel chip.

I am still learning coding and electronics so as a starting point I had an Arduino Uno outputting to individual 7 segment LED's using direct output and multiplexing (ie.e no shift registers in the mix). Initially I just programmed a simple counter to confirm the multiplexing routine and it worked great.

Then I added an Adafruit ultimate GPS breakout board and used their GPS library. For those not familiar with it, this board has a built-in RTC but it is not accessible to set or read from the Arduino, it is only to assist the GPS recover faster. To get time from this, I have to read the GPS data directly and parse it. The GPS board has to be set up as a serial device using gps.begin(9600) and the library is set to update at 1Hz.

I can get perfect operation and updating of GPS time, but when the serial connection with the GPS is active, I am seeing a flickering about once per second on the LED's. It's as if the reading and parsing of the GPS data is causing sligt delays in the multiplex routine. I can confirm this because if I just comment out the gps.begin(9600) line, then the flickering stops (but of course so does any updating time display updating)

The only time I was able to avoid this flicker while reading GPS time was by using another set od LED's mounted on the Adafruit LED backpack which contains its own driver / register chip. But of course this is irrelevant because my final aim is nixie display.

I tried the same setup outputting through an Arduinix board and nixies and got exactly the same result.

I have tried reprogramming to avoid using delay() in the multiplexing routine and I even tried direct port addressing instead of digital.Write to speed things up, but still get the same consistent flicker.

Should have mentioned, I also used a Skylab SKM53 GPS module using the TinyGPS library but got almost identical results.

So, my question is, how do I need to go forward and avoid this flickering while accessing the GPS?

Do I need to add a separate RTC board such as a DS1307 and only read from the RTC with occasional updates from the GPS?

This GPS board also has a PPS output, could that be helpful?

Any ideas gratefully received!!

Yawn - post your code and in this case the adafruit lib.

Mark

What he said^.
It's very hard to see what's wrong with your code if we can't actually see the code. :wink:

archiebald:
The GPS board has to be set up as a serial device using gps.begin(9600) and the library is set to update at 1Hz.

Can you change the baud of the GPS device? Set it to 115200 if you can.

If changing the baud isn't enough to completely solve the issue, you'll need to find a way to interweave the GPS parse code with the LED code.

you'll need to find a way to interweave the GPS parse code with the LED code.

If only there were some way to... oh, I don't know... parse each character during the RX interrupt? Instead of saving them all up in a buffer, and then parsing them all at once, which blocks for too long. Would that work?

Oh wait, it does. :slight_smile:

NeoGPS is faster, smaller, totally configurable and, if you also use NeoHWSerial, NeoSWSerial or NeoICSerial, it's also non-blocking.

Actually, after a little more thought, I bet you wouldn't have to use the ISR version. Try using a NeoGPS configuration with only GPS_FIX_TIME enabled (and DATE if you need that too). Parsing each character won't use much CPU time, because it will be skipping over most of them. You could strip a lot out of NMEA.ino, or add to NMEAblink.ino.

Cheers,
/dev

@archiebald, I have exactly the same problem - the clock part of my GPS pauses when displaying tenths of a second, and if I limit it to seconds, will skip a second every now and then. I too surmise that it is when the parsing happens in the Adafruit code.

@/dev, Thanks for your post, I will certainly try out your code!

Thanks for the replies and sorry for not posting the code. Please see below - some of this is re-hashed from other people but I have tidied it up and deleted anything not directly related to the issue.

Also, I am not actually using seconds in my display yet, this is just hours and minutes.

All of the statements relating to GPS are directly copied from an Adafruit example sketch named “clock_sevenseg_gps” included in their LED backpack library. I have attached both the Adafruit GPS and library and LED BackPack Library.

Please bear in mind what I said in my opening post, before I got the Adafruit parts and libraries, I had exactly the same result with flickering - virtually identical - with some much simpler code using the Skylab SKM53 GPS and the TinyGPS library.

The reason for using 9600 on the gps serial was as advised in the Adafruit comments.

Also, please be gentle with me, as I said, I am still learning to code.

/dev, thanks for the ideas I will look at your idea later when I get home (posting from my office!!)

#include <SoftwareSerial.h>
#include <Wire.h>
#include "Adafruit_GPS.h"

// GPS Setup
// Set false to display 12 hour format, or true to use 24 hour:
#define TIME_24_HOUR      true

// Offset the hours from UTC (universal time) to your local time:
#define HOUR_OFFSET       9

// Use Analog pins A0, A1 connected to GPS pins Tx, Rx respectively
SoftwareSerial gpsSerial (A0, A1);
Adafruit_GPS gps(&gpsSerial);

// Output Setup
// Cathode Pins SN74141 (1)
const int outPin_c0_a = 2;               
const int outPin_c0_b = 3;
const int outPin_c0_c = 4;
const int outPin_c0_d = 5;
// Cathode SN74141 (2)
const int outPin_c1_a = 6;               
const int outPin_c1_b = 7;
const int outPin_c1_c = 8;
const int outPin_c1_d = 9;
// anode pins
const int outPin_a_1 = 10;
const int outPin_a_2 = 11;
const int outPin_a_3 = 12;
const int outPin_a_4 = 13;
 
// Display Setup
byte NumberArray[6]={0,0,0,0,0,0}; // current display
int display_status = 0; // which tube anode to display now
long tubes_busy_until = 0; // multiplex delay holder
const int tube_delay = 3; // multiplexing delay; adjust for tube type

// Countdown Timer Setup
const int timer_1s_delay = 1000; // countdown 1s
long timer_delay = 0;

// Counter variable
int i;

void setup() {    // OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
  // initialize ArduiNix pins
  pinMode(outPin_c0_a, OUTPUT);
  pinMode(outPin_c0_b, OUTPUT);
  pinMode(outPin_c0_c, OUTPUT);
  pinMode(outPin_c0_d, OUTPUT);

  pinMode(outPin_c1_a, OUTPUT);
  pinMode(outPin_c1_b, OUTPUT);
  pinMode(outPin_c1_c, OUTPUT);
  pinMode(outPin_c1_d, OUTPUT);

  pinMode(outPin_a_1, OUTPUT);
  pinMode(outPin_a_2, OUTPUT);
  pinMode(outPin_a_3, OUTPUT);
  pinMode(outPin_a_4, OUTPUT);

  // Setup the GPS using a 9600 baud connection (the default for most
  // GPS modules).
  gps.begin(9600);

  // Configure GPS to only output minimum data (location, time, fix).
  gps.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);

  // Use a 1 Hz, once a second, update rate.
  gps.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);

  // Enable the interrupt to parse GPS data.
  enableGPSInterrupt();

  // Start-up display test (counts up 0 to 9 on all tubes at boot-up)
  timer_delay = 0;
  while (millis() < timer_1s_delay * 1.1) {
    if (millis() > timer_delay) {
      timer_delay = millis() + (timer_1s_delay / 10);
      int i = millis() / (timer_1s_delay / 10);
      NumberArray[0] = i;   // upperHour
      NumberArray[1] = i;   // lowerHour
      NumberArray[2] = i;   // upperMin
      NumberArray[3] = i;   // lowerMin
      NumberArray[4] = i;   // upperColon
      NumberArray[5] = i;   // lowerColon
    }
    WriteDisplay( NumberArray );
  }
}   // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

void loop() {     // 000000000000000000000000000000000000000000000000
  // Check if GPS has new data and parse it.
  if (gps.newNMEAreceived()) {
    gps.parse(gps.lastNMEA());
  }
  // Grab the current hours, minutes, seconds from the GPS.
  // This will only be set once the GPS has a fix!  Make sure to add
  // a coin cell battery so the GPS will save the time between power-up/down.
  int hours = gps.hour + HOUR_OFFSET;  // Add hour offset to convert from UTC
                                       // to local time.
  // Handle when UTC + offset wraps around to a negative or > 23 value.
  if (hours < 0) {
    hours = hours - 24;
  }
  if (hours > 23) {
    hours = hours - 24;
  }

  int minutes = gps.minute;
  int seconds = gps.seconds;
  
 // Get the high and low order values for hours,min,seconds.
  int upperHours = hours/10;
  int lowerHours = hours % 10;
  int upperMins = minutes/10;
  int lowerMins = minutes % 10;
  int upperSecs = seconds/10;
  int lowerSecs = seconds % 10;

  WriteDisplay( NumberArray );
}   // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

// Functions below 

// GPS
SIGNAL(TIMER0_COMPA_vect) {
  // Use a timer interrupt once a millisecond to check for new GPS data.
  // This piggybacks on Arduino's internal clock timer for the millis()
  // function.
  gps.read();
}
void enableGPSInterrupt() {
  // Function to enable the timer interrupt that will parse GPS data.
  // Timer0 is already used for millis() - we'll just interrupt somewhere
  // in the middle and call the "Compare A" function above
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

// Write Display for each tube
// sets tube display (without using "delay")
void WriteDisplay( byte* array )  {
  if (millis() >= tubes_busy_until) {
    switch (display_status) {
      case 0: // set anode 1
        digitalWrite(outPin_a_3, LOW);               
        DisplayBin(array[0],array[3]);  
        digitalWrite(outPin_a_1, HIGH);
        tubes_busy_until = millis() + tube_delay;
        display_status++;
        break;
      case 1: // clear anode 1, set anode 2
        digitalWrite(outPin_a_1, LOW);
        DisplayBin(array[1],array[2]);   
        digitalWrite(outPin_a_2, HIGH);
        tubes_busy_until = millis() + tube_delay;
        display_status++;
        break;
      case 2: // clear anode 2, set anode 3
        digitalWrite(outPin_a_2, LOW);
        DisplayBin(array[4],array[5]);   
        digitalWrite(outPin_a_3, HIGH);
        tubes_busy_until = millis() + tube_delay;
        display_status = 0;
        break;
    }
  }
}

// DisplayBin
// Sets cathodes. Expects data in Bin/BCD format (native format of 74141)
void DisplayBin(byte num1, byte num2) {
  // Write to output pins.
  digitalWrite(outPin_c0_a, bitRead(num1, 0));
  digitalWrite(outPin_c0_b, bitRead(num1, 1));
  digitalWrite(outPin_c0_c, bitRead(num1, 2));
  digitalWrite(outPin_c0_d, bitRead(num1, 3));   

  digitalWrite(outPin_c1_a, bitRead(num2, 0));
  digitalWrite(outPin_c1_b, bitRead(num2, 1));
  digitalWrite(outPin_c1_c, bitRead(num2, 2));
  digitalWrite(outPin_c1_d, bitRead(num2, 3));
}

Adafruit-GPS-Library-master.zip (46.8 KB)

Adafruit-LED-Backpack-Library-master.zip (69.8 KB)

    // get time
    p = strchr(p, ',')+1;
    float timef = atof(p);
    uint32_t time = timef;
    hour = time / 10000;
    minute = (time % 10000) / 100;
    seconds = (time % 100);

    milliseconds = fmod(timef, 1.0) * 1000;

Can you believe that Limor herself wrote this?!

You have a lot of problems with your use of mills

First - you should not calculate the time at which you want to update the tubes in the way you do for the correct method take a look at blink without delay.

Second - mills returns an unsigned long not a long

In addition never use ints when a byte will do it not only use more SRAM t also increases execution time

Mark

holmes4:
In addition never use ints when a byte will do it not only use more SRAM t also increases execution time

I've read elsewhere that using an int instead of a byte will decrease execution time.
Besides, ints are good for "catching" negative values (example: a backward-counting loop index reaching -1 ).

If you are really concerned about "efficiency", then rewrite the GPS parsing library to avoid floating-point and minimize use of division.

When I checked memory use of byte and int sized constants I found the type didn't effect the memory usage. When a small value is defined as a long, the compiler doesn't actually use 32-bits to store the constant. If a constant isn't used within the code, I'm pretty sure the compiler doesn't store it at all.

I've read elsewhere that using an int instead of a byte will decrease execution time.

Um, no. On 8-bit AVRs, using a 8-bit type is almost always faster because the registers are only 8 bits wide. Most 16-bit "operations" require two 8-bit primitive operations.

Besides, ints are good for "catching" negative values (example: a backward-counting loop index reaching -1 ).

Well, testing for 0 can be faster, so using a loop variable that decrements until 0 might be "better". However, millis() is unsigned long value, and when it rolls over in the highest bit, you aren't suddenly in a negative time.

So to avoid the rollover problem, you must do unsigned math. Timed interval tests must take the form:

  unsigned foo startTime = millis();
    ...
  unsigned foo interval = 100;
  if (millis() - startTime > interval) {
     // Expired!
  }

foo can be an 8-bit, 16-bit or 32-bit integer type, depending on the interval you want to measure and how frequently you check millis(). (For smaller types, you need to cast millis to avoid promoting the other two values to 32-bit values.) Just search for "millis rollover".

When I checked memory use of byte and int sized constants I found the type didn't effect the memory usage. When a small value is defined as a long, the compiler doesn't actually use 32-bits to store the constant. If a constant isn't used within the code, I'm pretty sure the compiler doesn't store it at all.

Yes, mostly. const built-in types do not normally occupy RAM, with the exception of string literals like

  const char *foo = "a string literal";

What can affect the program space is whether you have an 8-bit, 16-bit or 32-bit constant. Then the compiler generates more code for the larger types, as described above for int vs. byte.

If you are really concerned about "efficiency", then rewrite the GPS parsing library to avoid floating-point and minimize use of division.

Ok, done. :slight_smile:

Cheers,
/dev

holmes4:
You have a lot of problems with your use of mills

First - you should not calculate the time at which you want to update the tubes in the way you do for the correct method take a look at blink without delay.

Second - mills returns an unsigned long not a long

In addition never use ints when a byte will do it not only use more SRAM t also increases execution time

Mark

I'n sorry if I'm being dumb (said I was a beginner) but I cannot clearly understand your comment. Can you be more specific as to how it is wrong after reviewing what I have done below?

In the Arduino blink without delay description, they use a variable called "interval" as a long so as per your advice I have changed my variable "tube delay" to long. No effect on the problem.

Other than that I am simply using millis() directly without involving a variable so can't see how that is wrong.

Then I have rewritten my multiplexing function as follows making it effectively the same as the blink without delay method.

void WriteDisplay( byte* array )  {
  if (millis() - tubes_busy_until >= tube_delay) {
    switch (display_status) {
      case 0: // set anode 1
        digitalWrite(outPin_a_4, LOW);               
        DisplayBin(array[0],array[3]);  
        digitalWrite(outPin_a_1, HIGH);
        tubes_busy_until = millis();
        display_status++;
        break;
      case 1: // clear anode 1, set anode 2
        digitalWrite(outPin_a_1, LOW);
        DisplayBin(array[1],array[2]);   
        digitalWrite(outPin_a_2, HIGH);
        tubes_busy_until = millis();
        display_status++;
        break;
      case 2: // clear anode 2, set anode 3
        digitalWrite(outPin_a_2, LOW);
        DisplayBin(array[4],array[5]);   
        digitalWrite(outPin_a_3, HIGH);
        tubes_busy_until = millis();
        display_status++;
        break;
      case 3: // clear anode 3, set anode 4
        digitalWrite(outPin_a_3, LOW);
        DisplayBin(array[4],array[5]);   
        digitalWrite(outPin_a_4, HIGH);
        tubes_busy_until = millis();
        display_status = 0;
        break;
    }
  }
}

This also made no difference whatsoever. There was never a problem with the basic multiplexing routine, I can control it easily so that it doesn't flash using the variable tube_delay. With my set-up, anything under about 6-7 milliseconds causes no flash.

My problem remains the slight flicker occurring at regular 1 second intervals, something like a heartbeat and only when the gps serial is active. Without gps, this multiplex routine works like a charm both before and after the modifications.

I'm also not sure abut your "int versus byte" comment, can you specify exactly where it would be a benefit in relation to my code?

odometer:

    // get time

p = strchr(p, ',')+1;
   float timef = atof(p);
   uint32_t time = timef;
   hour = time / 10000;
   minute = (time % 10000) / 100;
   seconds = (time % 100);

milliseconds = fmod(timef, 1.0) * 1000;



Can you believe that *Limor herself* wrote this?!

As I said I am learning code so I am not sure whether your comment is praising or criticizing that library code. Can you be a little less cryptic please?

Guys,
Please don't take this the wrong way - I really do appreciate the enthusiasm but to my lowly coding ability, the detailed discussion about ints, bytes and efficiency for multiplex timing is going over my head and seems to be going off topic.

I already have a multiplexing routine that works well enough for my needs but for now I think I need more advice on how to resolve the regular hiccup that is apparently being caused by the GPS parsing.

I haven't had a chance to look at NeoGPS yet and will probably have more questions when I do, but for now this comment from odometer seems to be the most relevant - is it possible to delve into it with a bit more specific detail?

odometer:
If you are really concerned about "efficiency", then rewrite the GPS parsing library to avoid floating-point and minimize use of division.

Again, a reminder - I need my hand being held through this.

For everyone's better understanding, I have uploaded a short video clip to YouTube. Viewing the tubes in the flesh , I can set tube_delay as long as 6 or 7 milliseconds but this causes frame-rate flicker on video. So to avoid that, for this clip I set tube_delay to 1 millisecond to which made multiplexing silky smooth even on video capture, but the regular "heartbeat" can still be seen very easily (although the camera doesn't capture every flicker). Here is the link

archiebald:
Viewing the tubes in the flesh , I can set tube_delay as long as 6 or 7 microseconds but this causes frame-rate flicker on video.

How can such a short interval make a difference when you are testing in milliseconds with millis()? Did you mean 6 or 7 milliseconds?

aarg:
How can such a short interval make a difference when you are testing in milliseconds with millis()? Did you mean 6 or 7 milliseconds?

Yes, of course that is a typo, should have been milliseconds, I will go back and correct it, thanks for highlighting that.

archiebald:
Viewing the tubes in the flesh , I can set tube_delay as long as 6 or 7 milliseconds but this causes frame-rate flicker on video.

If you are trying to time intervals of only a few milliseconds, maybe better to use micros() than millis(). The reason is that sometimes millis() will skip a number (for reasons that I won't get into here). Yes, micros() skips numbers too, but it's better to be off by several microseconds than by a whole millisecond.

We need to be sure that the problem is with the GPS, so lets try and change the flicker rate, change (in setup())

gps.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);

to

gps.sendCommand(PMTK_SET_NMEA_UPDATE_100_MILLIHERTZ);

If the problem really is with the GPS then you should now flicker once every 10 seconds.

Data types and stuff

All ways use the smallest data type you can on Arduinos and such. In the case of the Uno you have only 2k of data space so don't piss it away.

The rules of math are strange around computers and change with the type of the variables used.

Mark

My problem remains the slight flicker occurring at regular 1 second intervals, something like a heartbeat and only when the gps serial is active.

Yes, as you can see here, other libraries will take about 1.4ms to parse the sentence, and it happens all in one burst. NeoGPS spreads about 0.8ms of processing out across the receipt of ~80 characters, blocking loop() an average of only 10us. I’m a little skeptical that you could see this difference, though.

Hmm… Here’s your sketch modified to use NeoGPS and NeoSWSerial, plus a few debug prints you may need to remove:

#include <NeoSWSerial.h>
#include <Wire.h>
#include "NMEAGPS.h"

NMEAGPS gps;
uint32_t changes; // debug, shows how many times the multiplexing switches

// GPS Setup
// Set false to display 12 hour format, or true to use 24 hour:
#define TIME_24_HOUR      true

// Offset the hours from UTC (universal time) to your local time:
#define HOUR_OFFSET       9

// Use Analog pins A0, A1 connected to GPS pins Tx, Rx respectively
NeoSWSerial gpsSerial( A0, A1 );

// Output Setup
// Cathode Pins SN74141 (1)
const int outPin_c0_a = 2;               
const int outPin_c0_b = 3;
const int outPin_c0_c = 4;
const int outPin_c0_d = 5;
// Cathode SN74141 (2)
const int outPin_c1_a = 6;               
const int outPin_c1_b = 7;
const int outPin_c1_c = 8;
const int outPin_c1_d = 9;
// anode pins
const int outPin_a_1 = 10;
const int outPin_a_2 = 11;
const int outPin_a_3 = 12;
const int outPin_a_4 = 13;
 
// Display Setup
byte NumberArray[6]={0,0,0,0,0,0}; // current display
int display_status = 0; // which tube anode to display now
long tube_start = 0; // multiplex start time
const uint16_t tube_delay = 3000; // multiplexing delay in microseconds; adjust for tube type

void setup() {    // OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO

Serial.begin( 9600 );
Serial.println( F("archibald started") );

  // initialize ArduiNix pins
  pinMode(outPin_c0_a, OUTPUT);
  pinMode(outPin_c0_b, OUTPUT);
  pinMode(outPin_c0_c, OUTPUT);
  pinMode(outPin_c0_d, OUTPUT);

  pinMode(outPin_c1_a, OUTPUT);
  pinMode(outPin_c1_b, OUTPUT);
  pinMode(outPin_c1_c, OUTPUT);
  pinMode(outPin_c1_d, OUTPUT);

  pinMode(outPin_a_1, OUTPUT);
  pinMode(outPin_a_2, OUTPUT);
  pinMode(outPin_a_3, OUTPUT);
  pinMode(outPin_a_4, OUTPUT);

  // Setup the GPS using a 9600 baud connection (the default for most
  // GPS modules).
  gpsSerial.begin(9600);

  // Configure GPS to only output minimum data (location, time, fix).
  gps.send_P( &gpsSerial, (str_P) F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") );

  // Use a 1 Hz, once a second, update rate.
  gps.send_P( &gpsSerial, (str_P) F("PMTK220,1000") );

  // Start-up display test (counts up 0 to 9 on all tubes at boot-up)
  const uint16_t digit_interval = 250;
  uint16_t start = millis()-digit_interval;
  uint8_t i=0;
  while (i <= 9) {
    if ((uint16_t)millis()-start >= digit_interval) {
      NumberArray[0] = i;   // upperHour
      NumberArray[1] = i;   // lowerHour
      NumberArray[2] = i;   // upperMin
      NumberArray[3] = i;   // lowerMin
      NumberArray[4] = i;   // upperColon
      NumberArray[5] = i;   // lowerColon
Serial.println( i );
      i++;
      start += digit_interval;
    }
    WriteDisplay( NumberArray );
  }
}   // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

void loop() {     // 000000000000000000000000000000000000000000000000

  while (gpsSerial.available()) {

    if ((gps.decode( gpsSerial.read() ) == NMEAGPS::DECODE_COMPLETED) &&
        (gps.nmeaMessage == NMEAGPS::NMEA_RMC) &&
         gps.fix().valid.time) {

Serial << gps.fix().dateTime;
Serial.print( ' ' );
Serial.print( changes );
Serial.println();
changes = 0;

      // Grab the current hours, minutes, seconds from the GPS.
      // This will only be set once the GPS has a fix!  Make sure to add
      // a coin cell battery so the GPS will save the time between power-up/down.

      //  Offset to the local time zone
      NeoGPS::clock_t localSeconds = gps.fix().dateTime; // UTC to seconds
      localSeconds += HOUR_OFFSET * NeoGPS::SECONDS_PER_HOUR;

      NeoGPS::time_t localTime( localSeconds );

      //  Adjust for 12 or 24-hour format
      uint8_t hours = localTime.hours;
      if (!TIME_24_HOUR && hours > 12)
        hours -= 12;
      
      // Get the high and low order values for hours,min,seconds.
      NumberArray[0] = hours/10;
      NumberArray[1] = hours % 10;
      NumberArray[2] = localTime.minutes/10;
      NumberArray[3] = localTime.minutes % 10;
      NumberArray[4] = localTime.seconds/10;
      NumberArray[5] = localTime.seconds % 10;
    }
  }

  WriteDisplay( NumberArray );
}   // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

// Functions below 

// Write Display for each tube
// sets tube display (without using "delay")
void WriteDisplay( byte* array )  {
  if (micros()-tube_start >= tube_delay) {
changes++; // <-- debug statement, remove eventually!
    switch (display_status) {
      case 0: // set anode 1
        digitalWrite(outPin_a_3, LOW);               
        DisplayBin(array[0],array[3]);  
        digitalWrite(outPin_a_1, HIGH);
        tube_start += tube_delay;
        display_status++;
        break;
      case 1: // clear anode 1, set anode 2
        digitalWrite(outPin_a_1, LOW);
        DisplayBin(array[1],array[2]);   
        digitalWrite(outPin_a_2, HIGH);
        tube_start += tube_delay;
        display_status++;
        break;
      case 2: // clear anode 2, set anode 3
        digitalWrite(outPin_a_2, LOW);
        DisplayBin(array[4],array[5]);   
        digitalWrite(outPin_a_3, HIGH);
        tube_start += tube_delay;
        display_status = 0;
        break;
    }
  }
}

// DisplayBin
// Sets cathodes. Expects data in Bin/BCD format (native format of 74141)
void DisplayBin(byte num1, byte num2) {
  // Write to output pins.
  digitalWrite(outPin_c0_a, bitRead(num1, 0));
  digitalWrite(outPin_c0_b, bitRead(num1, 1));
  digitalWrite(outPin_c0_c, bitRead(num1, 2));
  digitalWrite(outPin_c0_d, bitRead(num1, 3));   

  digitalWrite(outPin_c1_a, bitRead(num2, 0));
  digitalWrite(outPin_c1_b, bitRead(num2, 1));
  digitalWrite(outPin_c1_c, bitRead(num2, 2));
  digitalWrite(outPin_c1_d, bitRead(num2, 3));
}

It uses micros instead, too. With a tube_delay of 3000, it says the multiplexing happens about 333 times per second, more or less.

Cheers,
/dev