Flickering when multiplexing LEDs

Hi all,

I’m making a wireless timing display for model car racing. It uses 4 7-segment digits, driven by 4 daisy-chained TPIC6B595 shift registers. I’ve used multiplexing (switching only one digit on in turn so that the duty cycle is 25%) which dims the display down quite nicely - before this the brightness was rather hard on the eyes!

Anyway, I’m getting a faint flickering when feeding it information over nRF24L01+. Note that the flickering is not there if I don’t multiplex.

Anyway, here’s the code:

Transmitter:

/*
RC Information Board Project

This project reads in RC car race information output from RC-Timing by Dale Burr
(race information such as heat number, round number, time elapsed etc) and also
gantry/start lights information.

Race information is read in via the normal Arduino serial port (USB on an Uno
or pins 0 and 1 on an Uno or Atmega 328P-Pu IC) whilst gantry/start lights
information is read in via 2 Arduino pins after having been converted from RS232
by a MAX232 or similar.

The Arduino then processes the information and transforms it into 7 seperate
integer variables which are then joined using the sprintf function and 
transmitted using the TMRh20 nRF24L01+ library from a 2.4GHz nRF24L01+ transceiver
connected to the Arduino

For more information on this project, visit:

https://rcinfoboard.wordpress.com/

Created by Joe Keaveney
April 2016
*/

#include <SoftwareSerial.h>
#include <SPI.h>   // Comes with Arduino IDE
#include <RF24.h>  // Download and Install (See above)
#include <nRF24L01.h>

RF24 myRadio (7, 8);

byte addresses[6] = {"1Node"}; 

SoftwareSerial mySerial(5, 6);

char tx_array[63]; // The array of variables that is transmitted

char *rnum; // round number character 
char *hnum; // heat number character
char *mnum; // etc
char *snum; // etc
int lstate = 0; // start/gantry lights state integer
int r_int; // round number integer
int h_int; // etc
int m_int; // etc
int s_int; // etc
int timeone; //integer that represents the first number of a 4-digit display
int timetwo; // second number
int timethree; // third number
int timefour; // fourth number

const int dtrPin = 2; // DTR input pin from MAX232/USB
const int rtsPin = 3; // RTS input pin from MAX232/USB
int dtrState = 0; // stores the state of DTR pin when read
int rtsState = 0; // stores the state of RTS pin when read

const byte numChars = 64;
char receivedChars[numChars];
char rideChars[numChars];
boolean stringComplete = false;  // whether the string is complete

void setup() { // setup Arduino in it's initial state & start services
  pinMode(dtrPin, INPUT); // set input pins for reading RS232 pin states
  pinMode(rtsPin, INPUT);
  Serial.begin(9600); // initialize serial so we can read in race information
  mySerial.begin(9600);
  myRadio.begin();
  myRadio.setChannel(108);
  myRadio.setPALevel(RF24_PA_MAX);
  myRadio.setDataRate(RF24_250KBPS);
  myRadio.openWritingPipe(addresses[0]);
  delay(50);
}

void loop() {
  
  dtrState = digitalRead(dtrPin); // read the state of the DTR and RTS inputs
  rtsState = digitalRead(rtsPin);
  
  if (dtrState == LOW && rtsState == LOW) {     
    // DTR HIGH and RTS HIGH mean Green Light On    
    lstate = 2; 
  } else if (dtrState == LOW && rtsState == HIGH) {     
    // DTR HIGH and RTS LOW means Red Light On   
    lstate = 1; 
  }  
  else {
    // Any other combination means both lights Off
    lstate = 0; 
  }
  
  if (stringComplete) {
    mySerial.println(rideChars);
    
    rnum = strtok_r(receivedChars,":",&i); // start breaking down the input info
    hnum = strtok_r(NULL,":",&i); // from RC-Timing into the data we need
    mnum = strtok_r(NULL,":",&i);
    snum = strtok_r(NULL,":",&i);
    r_int = atoi(rnum); // convert the info into integers
    h_int = atoi(hnum);
    m_int = atoi(mnum);
    s_int = atoi(snum);
    if (m_int <= 9) { // break the time info into two integers for use on a
        timeone = 0;  // 4-digit display
        timetwo = m_int;
    } 
    else {
        timeone = m_int / 10;
        timetwo = m_int % 10;
    }

    if (s_int <= 9) {
      timethree = 0;
      timefour = s_int;
    } 
    else {
      timethree = s_int / 10;
      timefour = s_int % 10;
    }
    stringComplete = false;
  }
  // the sprintf function consolidates all the integer variables together
  // and then myRadio.write sends out the tx_array variable via the 2.4Ghz transceiver
  sprintf(tx_array, "%d,%d,%d,%d,%d,%d,%d.",lstate,r_int,h_int,timeone,timetwo,timethree,timefour);
  myRadio.write(&tx_array, sizeof(tx_array));
}

/*
  SerialEvent runs between each iteration of the loop() function and 
  checks for data on the serial buffer
 */
void serialEvent(){
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '[';
    char endMarker = ']';
    char rc;
 
    while (Serial.available() > 0 && stringComplete == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                rideChars[(ndx + 1)] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                rideChars[(ndx + 1)] = rc;
                rideChars[(ndx + 2)] = '\0';
                recvInProgress = false;
                ndx = 0;
                stringComplete = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
            rideChars[ndx] = rc;
        }
    }
}

Receiver (Display) code in next post as there’s a character limit in the forum.

Hoping you guys may have a few pointers.

Receiver (Display) code:

#include <SPI.h>   // Comes with Arduino IDE
#include <RF24.h>  // Download and install the TMRh20 nRF24L01+ library, see documentation
#include <nRF24L01.h>

RF24 myRadio (9, 10); // create object for interacting with the nRF24L01+ transceivers

byte addresses[6] = { // give the radio link a unique name to help the devices pick out the right signal
  "1Node"}; 

int i = 1; 
char tx_array[63];
int lstate;
int r_int;
int h_int;
int timeone;
int timetwo;
int timethree;
int timefour;

unsigned long time;
unsigned long oldtime;
unsigned long r_interval = 3000;
unsigned long h_interval = 6000;
unsigned long t_interval = 12000;

int radav = 0;

// TPIC6B595 pin declarations
////Pin connected to DS of 74HC595
int dataPin = 2;
//Pin connected to ST_CP of 74HC595
int latchPin = 3;
//Pin connected to SH_CP of 74HC595
int clockPin = 4;

// digit declaration digits 0-9
byte lights[] = {0b01110111,0b00100100,0b01011101,0b01101101,0b00101110,0b01101011,0b01111011,0b00100101,0b01111111,0b01101111};
byte others[] = {0b00000000,0b00011000,0b00111010};// blank, r, h

void setup()
{
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT); 
  myRadio.begin();
  myRadio.setChannel(108);
  myRadio.setPALevel(RF24_PA_MAX);
  myRadio.setDataRate(RF24_250KBPS);
  myRadio.openReadingPipe(1, addresses[0]);
  myRadio.startListening();
  delay(50);
}
void loop()
{
  recvlstate();
  if (radav == 1) // Non-blocking
  {
    lstate = atoi(strtok((char*)tx_array, ","));
    r_int = atoi(strtok(NULL, ","));
    h_int = atoi(strtok(NULL, ","));
    timeone = atoi(strtok(NULL, ","));
    timetwo = atoi(strtok(NULL, ","));
    timethree = atoi(strtok(NULL, ","));
    timefour = atoi(strtok(NULL, "."));
    radav = 0;
  }
  time = millis();
  if ((time - oldtime) <= r_interval){
    firstfunction();
  }
  if (((time - oldtime) > r_interval) && ((time - oldtime) <= h_interval)){
    secondfunction();
  }
  if (((time - oldtime) > h_interval) && ((time - oldtime) <= t_interval)){
    thirdfunction();
  }
  if ((time - oldtime) > t_interval){
    oldtime = time;
  }
}

void recvlstate(){
  if (myRadio.available())  // While there is data ready
  {
    myRadio.read( &tx_array, sizeof(tx_array) ); // Get the data payload (You must have defined that already!)
    radav = 1;
  }
}

void firstfunction(){// round number
  digitalWrite(latchPin, LOW);
  switch (i) {
    case 1:
      shiftOut(dataPin, clockPin, MSBFIRST, lights[(r_int)]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
    break;
    case 2:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);  
    break;
    case 3:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
    break;
    case 4:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[1]);
    break;
  } 
  digitalWrite(latchPin, HIGH);                           // take the latch pin high so the LEDs will light up:
  i++;
  if (i > 4){
    i = 1;
  }
}

void secondfunction(){// heat number
  digitalWrite(latchPin, LOW);
    switch (i) {
    case 1:
      shiftOut(dataPin, clockPin, MSBFIRST, lights[(h_int)]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
    break;
    case 2:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);  
    break;
    case 3:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
    break;
    case 4:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[2]);
    break;
  } 
  digitalWrite(latchPin, HIGH);                           // take the latch pin high so the LEDs will light up:
  i++;
  if (i > 4){
    i = 1;
  }
}

void thirdfunction(){
  digitalWrite(latchPin, LOW);
      switch (i) {
    case 1:
      shiftOut(dataPin, clockPin, MSBFIRST, lights[timefour]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
    break;
    case 2:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, lights[timethree]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);  
    break;
    case 3:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, lights[timetwo]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
    break;
    case 4:
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]);
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, others[0]); 
      shiftOut(dataPin, clockPin, MSBFIRST, lights[timeone]);
    break;
  } 
  digitalWrite(latchPin, HIGH);                           // take the latch pin high so the LEDs will light up:
  i++;
  if (i > 4){
    i = 1;
  }
}

Hello,

You are not really multiplexing at all. You are pwm dimming in software. That's why it flickers when the Arduino is doing other tasks like communicating via rf.

If you connect a pwm pin to the output enable pins on the shift registers, that should fix the flickering. And reduce the size of your code considerably.

Paul

You do a pretty amount of work when you receive data. Why do you convert all the data to ascii on the transmitter to send it when the first thing the receiver does is making values again? Why not send the values? Takes away all the complex conversions…

JoeK1973:
before this the brightness was rather hard on the eyes!

I hope you do use resistors in series with the segments?

Btw, you’re not multiplexing. You’re just turning on one segment at each time. If you multiplexed all the segments would connect together. Then you would only need 2 shift registers to do the same. One for all segments, 1 for all digits.

Btw, some code changes:

The whole interval part can be made a whole lot faster/simpler by using the else if statement as wel

  if ((time - oldtime) <= r_interval){
    firstfunction();
  }
  else if ((time - oldtime) <= h_interval){
    secondfunction();
  }
  else if ((time - oldtime) <= t_interval){
    thirdfunction();
  }
  else{
    oldtime = time;
  }

And when you have 4 functions with almost the same name and code there should ring a bell and think, I might can do this in an easier way…

byte displayBuffer[4];

void updateDisplay(){
  static byte i = 0;
  
  digitalWrite(latchPin, LOW);
  for(byte j = 0; j < 4; j++){
    if(j == i){
      shiftOut(dataPin, clockPin, MSBFIRST, lights[displayBuffer[i]);
    }
    else{
      shiftOut(dataPin, clockPin, MSBFIRST, 0);
    }
  }
  digitalWrite(latchPin, HIGH);   
  
  i++;
  if (i >= 4){
    i = 0;
  }
}

Can replace all the long functions with bad names.

To use it:

  time = millis();
  if ((time - oldtime) <= r_interval){
    displayClear();
    displayBuffer[0] = r_int;
    displayBuffer[3] = Rchar;
  }
  else if ((time - oldtime) <= h_interval){
    displayClear();
    displayBuffer[0] = h_int;
    displayBuffer[3] = Hchar;
  }
  else if ((time - oldtime) <= t_interval){
    displayClear();
    displayNumber(time);
  }
  else{
    oldtime = time;
  }
  
  updateDisplay();
}

Alright, I made small helper functions:

const byte Rchar = 0b00011000;
const byte Hchar = 0b00111010;


void clearDisplay(){
  for(byte i = 0; i < sizeof(displayBuffer); i++){
    displayBuffer[i] = 0;
  }
}

void displayNumber(unsigned int number){
  for(byte i = 0; i < sizeof(displayBuffer) && number; i++){
    displayBuffer[sizeof(displayBuffer) - i] = number % 10;
    
    number /= 10;
  }
}

Wow, thanks for the pointers guys, I really appreciate it. I've already made (some time ago) some PCBs without access to OE on the shift registers but I'll put it into the new design.

Septillion - thank you very much for the code! I'll incorporate it into my programs (I have two different receiver implementations - one with the 7 segment PCBs and one with a HT1632 8 x 32 LED array, the intervals and function calls are the same so I'll use this in both programs)

Joe