How Can I Cross-Fade Two Elements of a Mutliplexed Display (sub-multiplex?)

Hello Guys (and Girls?),
I have searched the web for something like this but cannot seem to work out the correct search parameters.

Actually, this programming challenge is to cross-fade between incrementing digits of one nixie tube in a clock of 6 multiplexed tubes, but the code and hardware setup would be unnecessarily complicated to follow, so I made the attached simple code and a simulator. If I can achieve the result I need with this, then I should be able to easily drop the code into my clock routines.

The basic elements of anode and cathode multiplexing included here are already working perfectly in my clock – but with no cross-fade.

First some limitations,

  • Analog / PWM Output - I know there may be ways to achieve the result using analog and pwm outputs but those avenues will not be open to me in the final hardware setup. All ON/OFF switching must be by purely digital output.
  • Delay() - It is vital that delay() is not used anywhere as it will cause flickering to other routines that are not included in this simplified simulator. (I have come across a code method of achieving result with delay functions but it is very limited and causes flickering)
  • In the clock, the final output to the tube will be via K155ID or SN74141 high voltage nixie drivers so no additional hardware is practical – needs to be solved in the code.

I have attached my simulator code, plus a schematic and a Fritzing breadboard image. (Just like to add that it is thank to board member /dev that I managed to get this far, a large chunk of this code is his!!)

// Arduino Uno Mutliplex test with attempt to crossfade
// Cannot use PWM or analog output from Arduino
// Cannot use delay()

const int Anode1 = 2;             // Anode for LED pair 1
const int Anode2 = 3;             // Anode for LED pair 2
const int Cathode1a = 4;          // Fpr LED1a
const int Cathode1b = 5;          // For LED1b
const int Cathode2a = 6;          // For LED2a
const int Cathode2b = 7;          // For LED2b
const int Butt1 = 8;              // Button for LED pair 1
const int Butt2 = 9;              // Button for LED pair 2

const uint16_t tube_delay = 5000; // multiplexing delay in microseconds: adjust as required

long tube_start = 0;              // multiplex start time used in calculation
int display_status = 1;           // which anode to display now

int state1a = HIGH, state1b = LOW, state2a = HIGH, state2b = LOW; // Initial LED conditions
int reading1, reading2;               // For button presses
int previous1 = LOW, previous2 = LOW; // Default state of each button

// Button Variables
long prevtime1 = 0, prevtime2 = 0;  // debounce start time used in calculation
long debounce = 200;                // debounce value: adjust as required

void setup() {
  pinMode (Anode1, OUTPUT);
  pinMode (Anode2, OUTPUT);

  pinMode (Cathode1a, OUTPUT);
  pinMode (Cathode1b, OUTPUT);
  pinMode (Cathode2a, OUTPUT);
  pinMode (Cathode2b, OUTPUT);
  pinMode (Butt1, INPUT);
  pinMode (Butt2, INPUT);
} // Setup End

void loop() {
  // For button 1
  reading1 = digitalRead(Butt1);  // Butt2 state will be LOW unless pressed
  
  // Check for button press on button 1
  if (reading1 == HIGH && previous1 == LOW && millis() - prevtime1 > debounce) {
    if (state1a == HIGH){         // If Butt2 has been pressed
      state1a = LOW;              // LED1a off
      state1b = HIGH;             // LED1b on
    } else {                      // else
      state1a = HIGH;             // LED1a on
      state1b = LOW;              // LED1b off
    }
    prevtime1 = millis();         // Reset the debounce timer
  }
  previous1 = reading1;           // Resets Butt1 state to LOW when it is released

  // For button 2
  reading2 = digitalRead(Butt2);  // Butt2 state will be LOW unless pressed
  
  // Check for button press on button 2
  if (reading2 == HIGH && previous2 == LOW && millis() - prevtime2 > debounce) {
    if (state2a == HIGH){         // If Butt2 has been pressed
      state2a = LOW;              // LED2a off
      state2b = HIGH;             // LED2b on
    } else {                      // else
      state2a = HIGH;             // LED2a on
      state2b = LOW;              // LED2b off
    }
    prevtime2 = millis();         // Reset the debounce timer
  }
  previous2 = reading2;           // Resets Butt2 state to LOW when it is released

  DisplayOutput();                // Calls display routine
} // Loop End

void DisplayOutput(){                       // Actually gives outputs to pins
  if (micros()-tube_start >= tube_delay) {  // If time has passed for next display update to occur
    switch (display_status) {               // Increment through each case
        
      case 1:                               // Clears anode 2, sets anode 1
        digitalWrite(Anode2, LOW);          // Turn off previous anode    
        CathodeOutput1();                   // Get cathode state for LED 1
        digitalWrite(Anode1, HIGH);         // Turn on this anode
        tube_start += tube_delay;           // increment the delay value and
        display_status++;                   // increment to next anode
      break;                                // Ignore below cases, we are done here
      
      case 2:                               // Clears anode 1, set anode 2
        digitalWrite(Anode1, LOW);          // Turn off previous anode    
        CathodeOutput2();                   // Get cathode state for LED 2
        digitalWrite(Anode2, HIGH);         // Turn on this anode
        tube_start += tube_delay;           // increment the delay value
        display_status = 1;                 // All done, return to first case
      break;                                // Ignore below cases, we are done here
    }
  }
}  //DisplayOutput End

void CathodeOutput1(){                      // Set required cathode1 selection on or off
  digitalWrite(Cathode1a, state1a);
  digitalWrite(Cathode1b, state1b);
}
void CathodeOutput2(){                      // Set required cathode2 selection on or off
  digitalWrite(Cathode2a, state2a);
  digitalWrite(Cathode2b, state2b);
}

Overview

  • We have two pairs of LEDs labelled 1a, 1b, and 2a, 2b. In normal conditions 1a OR 1b can be on. Likewise, 2a OR 2b can be on. So, there will always be 2 LEDs lit, one from pair 1 and 1 from pair 2.
  • Pair 1 and Pair 2 are lit and controlled through a multiplexing routine called DisplayOutput.
  • When Butt1 or Butt2 is pressed, the associated LED pair will change (invert), i.e. 1a off, 1b on.
  • All that works fine, so the challenge is instead of, for example just turning off LED2a and instantly turning on LED2b, I want those 2 LEDs to cross fade over an easily adjustable overlap of up to 1 second. (Note – not 2a fade out followed by 2b fade in, fade out / in must overlap)

My difficulty is that (I think) I need to “sub-multiplex” these LEDs within the normal multiplex routine but I simply can’t visualize the solution. So, any pointers in the right direction or some sample code would be very gratefully received.

Caveat – The current code is not yet optimized in any way, I just made something that works and is simple to understand. For example, I know that I could speed it up more by direct port addressing, but at the moment, digitalWrite within this routine gives me sufficient speed. I’ll use port addressing later if required.