Driving 4 daisy chained 32 bit shift registers

Hello everyone,

I've been working on a project lately, which is to create a nixie tube display which is driven by four serial to parallel converter chips (Microchip HV5530's). These chips function like 32 bit shift registers, such as four 74HC595N's linked together each.

I know there are several ways of driving shift registers; the two I have been exploring for the past few nights (to no avail) are bit-banging and using SPI.

I have made a very short (34 second) unlisted YouTube video to demonstrate the furthest I managed to get by bit-banging the data lines: https://www.youtube.com/watch?v=468QV6E7rm4

In the description, there are links to the github where I posted the exact code used in the video, the PCB schematic, and the datasheet for the HV5530.

Fundamentally, the circuit is very simple; the latch, clock, data, and blank lines run from the shift register through a logic level converter and into the arduino. The first register receives data, then shoots it out to the input of the next. One change I had to make outside of the pcb is that the the POL line on the HV5530's is pulled up constantly (to VDD, +12 volts).

Table 3-2 in the datasheet demonstrates the truth function table for the HV chip. In my code, I am basically banging the "transparent latch modes" on and off in a for loop.

How can I go about controlling individual latches with this code, and eventually building a library where I enter an ASCII number through serial and display updates with the new number?

Also, I found a similar project that uses SPI. This is a little to far out of my understanding as of now, but if someone could dumb it down it might prove even better: https://hackaday.io/project/1940-modular-nixie-display/log/76998-test-code

Thanks!

Questions:

Are you using the chip by itself or is it on a breakout board? If it's a breakout board, datasheet? You can get 128 outputs by daisy-chaining, connecting pin 23 of one to pin 32 of the next, correct? How many pins do you need for each Nixie?

You can send data, clk and latch with three pins. Presumably POL will be a nailed connection, what about BL? If you want to be able to latch each separately, you'll need 3 extra pins for e additional latches, or do you have spare outputs of the 128 that can be used? If no spare outputs and you want to latch independently, you'll need DATA, CLK, LE1, LE2, LE3 and LE4, that's 6 pins. Any problem with theses coming out of a 595?

Coincidentally, I happen to be working on a similar project just now, not with Nixie tubes, but with extending PWM via three pins, serially to a bank of up to 32 - 595s for a total of 256 outputs. This could be a nice fit.

Below is the code from Github. You are sending 256 alternating LOW and HIGH bits?

I would start by sending 128 LOW bits to make sure the display blanks. If it does not, POL may be set wrong.

#define LATCH   10
#define CLOCK   13
#define DATA    11
#define PWM 9


void setup()
{
  pinMode(LATCH, OUTPUT); // Latch is pin 10
  pinMode(CLOCK, OUTPUT); // Clock is pin 13
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9


  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);


  for (uint8_t i = 0; i < 128; i++)
  {


    delay(100);


    digitalWrite(DATA, HIGH);     // Ensure the data pin is high
    digitalWrite  (DATA,  LOW);     // Set data low


    digitalWrite  (CLOCK, LOW);   // Set the clock low
    digitalWrite  (CLOCK, HIGH);    // Bring the clock high again


    digitalWrite  (LATCH, LOW);   // Bring the latch low so it can be brought high again.
    digitalWrite  (LATCH, HIGH);    // Bring the latch high to commit the bit to the shift register outputs




    delay(100);


    digitalWrite  (DATA,  LOW);    // Ensure the data pin is low
    digitalWrite  (DATA,  HIGH);     // Set data low


    digitalWrite  (CLOCK, LOW);    // Set the clock low
    digitalWrite  (CLOCK, HIGH);     // Bring the clock high again


    digitalWrite  (LATCH, LOW);   // Bring the latch low so it can be brought high again.
    digitalWrite  (LATCH, HIGH);    // Bring the latch high to commit the bit to the shift register outputs
  }
}


void loop()
{
}

This is how you would turn off all 128 segments:

#define LATCH   10
#define CLOCK   13
#define DATA    11
#define PWM 9


void setup()
{
  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);
  
  pinMode(LATCH, OUTPUT); // Latch is pin 10, FALLING edge copies shift register to latches
  pinMode(CLOCK, OUTPUT); // Clock is pin 13, FALLING edge clocks in a bit from DATA
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9


  BlankDisplay();
}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  // Set the data to LOW
  digitalWrite  (DATA,  LOW);
  
  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
  }
  
  // Copy registers into data latches
  digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches
}


void loop() {}

johnwasser:
This is how you would turn off all 128 segments:

#define LATCH   10

#define CLOCK  13
#define DATA    11
#define PWM 9

void setup()
{
  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);
 
  pinMode(LATCH, OUTPUT); // Latch is pin 10, FALLING edge copies shift register to latches
  pinMode(CLOCK, OUTPUT); // Clock is pin 13, FALLING edge clocks in a bit from DATA
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9

BlankDisplay();
}

void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);

// Set the data to LOW
  digitalWrite  (DATA,  LOW);
 
  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);  // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
  }
 
  // Copy registers into data latches
  digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches
}

void loop() {}

High John,

Yes, I modified your code a little bit to slow things down; it turns the every latch off in order until all displays are blank. I suppose the next challenge is writing code that can control the tubes in a numerical sequence. Here is what I’ve got, which turns everything off individually:

#define LATCH   10
#define CLOCK   13
#define DATA    11
#define PWM 9


void setup()
{
  
  pinMode(LATCH, OUTPUT); // Latch is pin 10, FALLING edge copies shift register to latches
  pinMode(CLOCK, OUTPUT); // Clock is pin 13, FALLING edge clocks in a bit from DATA
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9

  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);

delay(100);
  BlankDisplay();
}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  // Set the data to LOW
  digitalWrite  (DATA,  LOW);
  
  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
    
    digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches

    delay(100);
  }

}


void loop() {}

From the board layout (I don’t see a schematic anywhere) it looks like you have 9 decimal points followed by 11 digits with 10 bits each (1 through 0). So if we start at the first bit and set every 11th bit we should have 1 decimal point and the digits “234567890 1”. If we start at the next bit we’d get the next decimal point and get “34567890 12”. Let’s try that:

#define LATCH   10
#define CLOCK   13
#define DATA    11
#define PWM 9




void setup()
{
  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);


  pinMode(LATCH, OUTPUT); // Latch is pin 10, FALLING edge copies shift register to latches
  pinMode(CLOCK, OUTPUT); // Clock is pin 13, FALLING edge clocks in a bit from DATA
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9


  BlankDisplay();
}




void TestDisplay(byte start)
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  //  Clock in 128 bits
  for (uint8_t i = 0; i < 128; i++)
  {
    // Turn on every 11th segment
    digitalWrite(DATA, (i + start) % 11 == 0);


    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
  }


  // Copy registers into data latches
  digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches
}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  // Set the data to LOW
  digitalWrite  (DATA,  LOW);


  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
  }


  // Copy registers into data latches
  digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches
}


void loop()
{
  for (byte start = 0; start < 11; start++)
  {
    TestDisplay(start);
    delay(700);  // Show each pattern for 0.7 seconds.
    BlankDisplay();
    delay (300);  // Blank for 0.3 seconds.
  }
  delay(500);  // Extra 500 milliseconds between sequences
}

Hi John and all;

I've attempted the code you posted previously John, but unfortunately it did not work as intended. I've since been trying a different approach to "organize" the bitbanging approach into a code that can easily address each High Voltage output and turn it on and off accordingly. I found another code which was used to bitbang a 6 digit clock, and modified it to this point.

The advantage I see here is that each tube or digit is addressed in a table. Then, in the loop function, you can load changing inputs and turn on the desired outputs through a single function, without having to change the number of bits being "banged" manually in the loop function.

Please see below:

#define PIN_LE    10 //Shift Register Latch Enable
#define PIN_CLK   13  //Shift Register Clock
#define PIN_DATA  11  //Shift Register Data
#define PIN_BL    9  //Shift Register Blank (0=display off     1=display on)


// lookup tables for the high and low value of mins/sec to determine
// which number needs to be displayed on each of the minutes and seconds tubes
// e.g. if the clock is to diplay 26 minutes then it 
// will look up the values at the 26th position and display a 2 on the high mins tube and a 6 on the low mins tube
const byte minsec_high[]= {0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,11};
const byte minsec_low[] = {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,11};

// shift register positions
// --------------------------HV5530 #1--------------------------
// Controls 9 Dots (One cathode each), Plus/Minus tube (2 cathodes), One 10 digit tube (10 cathodes)

// Dots are first
//      Dot   1  2  3  4  5  6  7  8  9
int dots[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

//HV5530 #1 Pin 10 (HVOUT20) is not used

// Plus/minus tube  +   -
int plusminus[] = {11, 12};

// Rightmost numeric tube (Tube 1)
//        Digit  0   1   2   3   4   5   6   7   8   9
//  NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube1[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
             
//Pins 35-44 (HVOUT1 - HVOUT10) are not used

// --------------------------HV5530 #2,3,4--------------------------
//HV5530#2,3,4 each control three (3) numeric tubes each (10 cathodes each, 30 cathodes total)
//Connections are similarly numbered between each set of tubes and HV chip for chips 2,3,4
//Pins 21 and 22 (HVOUT31 and HVOUT32) are not used

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube3[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube4[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube5[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube6[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube7[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube8[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube9[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube10[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}; 



void setup() {

  pinMode(PIN_LE,  OUTPUT);
  pinMode(PIN_BL,  OUTPUT);
  pinMode(PIN_DATA,OUTPUT);
  pinMode(PIN_CLK, OUTPUT);

  digitalWrite(PIN_BL, LOW);

  Serial.begin(115200);

}


unsigned long previousSRMillis = 0;    // keeping track last time shift register values were clocked in

void loop() {

delay(100);
digitalWrite(PIN_BL, HIGH);

  unsigned long currentMillis = millis();
  
  if (currentMillis - previousSRMillis >= 250) {  // clocking in 4 times a second
    
    previousSRMillis = currentMillis;

    boolean srBuffer[128] = {0};

//    time_t now;
//    struct tm * timeinfo;
//    time(&now);
//    timeinfo = localtime(&now);  

    
    int seconds1 = 1;
    int seconds2 = 2;
    int seconds3 = 3;
    int seconds4 = 4;
    int seconds5 = 5;
    int seconds6 = 6;
    int seconds7 = 7;
    int seconds8 = 8;
    int seconds9 = 9;
    int seconds10 = 10;
    

    // doing the lookups for what number to display then
    // looking up which shift register position
    // for each tube
    // "10" from the lookup table indicates to blank the tube since it is out of range.
    
    srBuffer[dots[minsec_low[1]] - 1] = 1;
    srBuffer[plusminus[minsec_low[1]] - 1] = 1;
    srBuffer[tube1[minsec_low[seconds1]] - 1] = 1;
    srBuffer[tube2[minsec_low[seconds2]] - 1] = 1;
    srBuffer[tube3[minsec_low[seconds3]] - 1] = 1;
    srBuffer[tube4[minsec_low[seconds4]] - 1] = 1;
    srBuffer[tube5[minsec_low[seconds5]] - 1] = 1;
    srBuffer[tube6[minsec_low[seconds6]] - 1] = 1;
    srBuffer[tube7[minsec_low[seconds7]] - 1] = 1;
    srBuffer[tube8[minsec_low[seconds8]] - 1] = 1;
    srBuffer[tube9[minsec_low[seconds9]] - 1] = 1;
    srBuffer[tube10[minsec_low[seconds10]] - 1] = 1;

    
//
//    srBuffer[10 - 1] = 1;    // tube 2 decimal point
//    srBuffer[52 - 1] = 1;   // tube 5 decimal point

    digitalWrite(PIN_LE, LOW);

    for(int i = 127;i >= 0;i--){ 
      
      digitalWrite(PIN_DATA,srBuffer[i]);
      digitalWrite(PIN_CLK,HIGH);
      delayMicroseconds(5);
      digitalWrite(PIN_CLK,LOW);
      delayMicroseconds(5);

    }

    digitalWrite(PIN_LE, HIGH);
    
  }

}

Now this code has been turning on different digits, but not in a controlled manner. The results are still very much scattered on the display:

Here is the original project from which I have tried adapting the code; this one is complete with schematics and a much better explanation than I could hope to provide with my limited experience: http://makerangst.com/nixie-clock-3

One problem:

int tube4[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36};

How do you get bit numbers higher than 31 in a 32-bit shift register?

Another problem:

    srBuffer[tube4[minsec_low[seconds4]] - 1] = 1;
    srBuffer[tube5[minsec_low[seconds5]] - 1] = 1;

This doesn't take into account that tube4 and tube5 are on different shift registers. Your bit numbers go up to 44 but no higher. Shouldn't they go up close to 127 like the array you are indexing?

Ah okay, I may have misinterpreted how this code works; so what you're saying is that the numbers in these arrays:

// --------------------------HV5530 #2,3,4--------------------------
//HV5530#2,3,4 each control three (3) numeric tubes each (10 cathodes each, 30 cathodes total)
//Connections are similarly numbered between each set of tubes and HV chip for chips 2,3,4
//Pins 21 and 22 (HVOUT31 and HVOUT32) are not used

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube3[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube4[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube5[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube6[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube7[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube8[] =  {16, 17, 19, 22, 18, 14, 20, 13, 15, 21}; 
int tube9[] =  {4, 5, 7, 10, 6, 2, 8, 1, 3, 9}; 
int tube10[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36};

... should be addressed as bits; not the physical pin # between the HV chip and the corresponding nixie cathode.

For example int tube10[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}

This array should be an index of 10 bits somewhere between 1-128, depending on the position of the tube in the shift register arrangement. In that case, I will rewrite the tube definitions above from 1-128 (if that's correct?) and see what happens.

kevina96: Ah okay, I may have misinterpreted how this code works; so what you're saying is that the numbers in these arrays: ... should be addressed as bits; not the physical pin # between the HV chip and the corresponding nixie cathode.

Yes. Certainly not pin numbers on your chips because all you can control are the 32 bits in the shift register. You load them in bit order. The pin numbers are only useful in the schematic, to determine which digit in the tube is connected to which bit in the register.

kevina96: For example

int tube10[] =  {41, 40, 38, 35, 39, 43, 37, 44, 42, 36}

This array should be an index of 10 bits somewhere between 1-128, depending on the position of the tube in the shift register arrangement. In that case, I will rewrite the tube definitions above from 1-128 (if that's correct?) and see what happens.

Well, somewhere between 0 and 127 because those are the indexes in your srBuffer array. Bit numbers and array indexes generally start at 0 (even though the shift register outputs are labeled 1 to 32). You'll be well served to get used to bit numbers 0 through 31.

I notice that you are loading the shift registers in reverse order, bits 127 through 0. If you are going to calculate bit numbers for each of the digits you can simplify things by numbering the bits in the order they get loaded into the registers (most significant bit of the furthest register first).

Hi John,

So I’ve tried addressing all of the outputs to the display devices (Tubes and dots) by writing the code as such:

#define PIN_LE    10 //Shift Register Latch Enable
#define PIN_CLK   13  //Shift Register Clock
#define PIN_DATA  11  //Shift Register Data
#define PIN_BL    9  //Shift Register Blank (0=display off     1=display on)


// shift register positions
// --------------------------HV5530 #1--------------------------
// Controls 9 Dots (One cathode each), Plus/Minus tube (2 cathodes), One 10 digit tube (10 cathodes)

// Dots are first
//      Dot   1  2  3  4  5  6  7  8  9
int dots[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

//HV5530 #1 Pin 10 (HVOUT20) is not used

// Plus/minus tube  +   -
int plusminus[] = {10, 11};

// Rightmost numeric tube (Tube 1)
//        Digit  0   1   2   3   4   5   6   7   8   9
//  NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube1[] =  {15, 16, 18, 21, 17, 13, 19, 12, 14, 20};          
//Pins 35-44 (HVOUT1 - HVOUT10) are not used

// --------------------------HV5530 #2,3,4--------------------------
//HV5530#2,3,4 each control three (3) numeric tubes each (10 cathodes each, 30 cathodes total)
//Connections are similarly numbered between each set of tubes and HV chip for chips 2,3,4
//Pins 21 and 22 (HVOUT31 and HVOUT32) are not used

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {35, 36, 38, 41, 37, 33, 39, 32, 34, 40};
int tube3[] =  {45,  46, 48, 51, 47, 43, 49, 42, 44, 50}; 
int tube4[] =  {57,  58, 60, 63, 59, 55, 61, 54, 56, 62};  

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube5[] =  {67, 68, 70, 73, 69, 65, 71, 64, 66, 72};
int tube6[] =  {77,  78, 80, 83, 79, 75, 81, 74, 76, 82};
int tube7[] =  {89,  90, 92, 95, 91, 87, 93, 86, 88, 94}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube8[] =  {99,100,102,105,101,97,103,96,98,104};
int tube9[] =  {109,110,112,115,111,107,113,106,108,114};  
int tube10[] =  {121,122,124,127,123,119,125,118,120,126}; 


void setup() {

  pinMode(PIN_LE,  OUTPUT);
  pinMode(PIN_BL,  OUTPUT);
  pinMode(PIN_DATA,OUTPUT);
  pinMode(PIN_CLK, OUTPUT);

  digitalWrite(PIN_BL, LOW);

  Serial.begin(115200);

}


unsigned long previousSRMillis = 0;    // keeping track last time shift register values were clocked in

void loop() {

digitalWrite(PIN_BL, HIGH);

  unsigned long currentMillis = millis();
  
  if (currentMillis - previousSRMillis >= 2000) {  // clocking once every 2 seconds
    
    previousSRMillis = currentMillis;

    boolean srBuffer[128] = {0};

    // doing the lookups for what number to display then
    // looking up which shift register position
    // for each tube
    // "10" from the lookup table indicates to blank the tube since it is out of range.
    
    srBuffer[dots[1]] = 1;
    srBuffer[plusminus[1]] = 1;
    srBuffer[tube1[0]] = 1;
    srBuffer[tube2[1]] = 1;
    srBuffer[tube3[1]] = 1;
    srBuffer[tube4[1]] = 1;
    srBuffer[tube5[2]] = 1;
    srBuffer[tube6[2]] = 1;
    srBuffer[tube7[2]] = 1;
    srBuffer[tube8[3]] = 1;
    srBuffer[tube9[3]] = 1;
    srBuffer[tube10[3]] = 1;


    digitalWrite(PIN_LE, LOW);

    for(int i = 0; i < 128; i++){ 
      
      digitalWrite(PIN_DATA,srBuffer[i]);
      digitalWrite(PIN_CLK,HIGH);
      delayMicroseconds(5);
      digitalWrite(PIN_CLK,LOW);
      delayMicroseconds(5);

    }

    digitalWrite(PIN_LE, HIGH);
  }

}

From the way I laid the board out, several High voltage outputs are not used, (Pins 35-44 on the first HV chip, and pins 21-22 on shift registers 2,3,and 4), as shown:

I still get mixed results with this code. Am I not accounting for the unused outputs correctly? For example, I go from Tube one on the first HV5530:

int tube1[] =  {15, 16, 18, 21, 17, 13, 19, 12, 14, 20};          
//Pins 35-44 (HVOUT1 - HVOUT10) are not used

to tube 2 on the second shift register:

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {35, 36, 38, 41, 37, 33, 39, 32, 34, 40};

I think this should be correct but I keep getting random outputs on the display

I’ve cleaned up the code some but it still doesn’t perform the way I’m trying to make it: controlling one digit per tube. I tried accounting for the “empty” bits which correspond to unused outputs on the HV5530’s as well:

#define PIN_LE    10 //Shift Register Latch Enable
#define PIN_CLK   13  //Shift Register Clock
#define PIN_DATA  11  //Shift Register Data
#define PIN_BL    9  //Shift Register Blank (0=display off     1=display on)


// shift register positions
// --------------------------HV5530 #1--------------------------
// Controls 9 Dots (One cathode each), Plus/Minus tube (2 cathodes), One 10 digit tube (10 cathodes)

// Dots are first
//      Dot   1  2  3  4  5  6  7  8  9
int dots[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

//HV5530 #1 Pin 10 (HVOUT20) is not used
//Bit 9 not used
int blank1[] = {9};
// Plus/minus tube  +   -
int plusminus[] = {10, 11};

// Rightmost numeric tube (Tube 1)
//        Digit  0   1   2   3   4   5   6   7   8   9
//  NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube1[] =  {15, 16, 18, 21, 17, 13, 19, 12, 14, 20};          
//Pins 35-44 (HVOUT1 - HVOUT10) are not used
int blank2[] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
// --------------------------HV5530 #2,3,4--------------------------
//HV5530#2,3,4 each control three (3) numeric tubes each (10 cathodes each, 30 cathodes total)
//Connections are similarly numbered between each set of tubes and HV chip for chips 2,3,4
//Pins 21 and 22 (HVOUT31 and HVOUT32) are not used

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {35, 36, 38, 41, 37, 33, 39, 32, 34, 40};
int tube3[] =  {45, 46, 48, 51, 47, 43, 49, 42, 44, 50}; 
int blank3[] = {52 , 53};
int tube4[] =  {57,  58, 60, 63, 59, 55, 61, 54, 56, 62};  

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube5[] =  {67, 68, 70, 73, 69, 65, 71, 64, 66, 72};
int tube6[] =  {77,  78, 80, 83, 79, 75, 81, 74, 76, 82};
int blank4[] = {84 , 85};
int tube7[] =  {89,  90, 92, 95, 91, 87, 93, 86, 88, 94}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube8[] =  {99,100,102,105,101,97,103,96,98,104};
int tube9[] =  {109,110,112,115,111,107,113,106,108,114};  
int blank5[] = {116 , 117};
int tube10[] =  {121,122,124,127,123,119,125,118,120,126}; 


void setup() {
  pinMode(PIN_LE,  OUTPUT);
  pinMode(PIN_BL,  OUTPUT);
  pinMode(PIN_DATA,OUTPUT);
  pinMode(PIN_CLK, OUTPUT);
  digitalWrite(PIN_BL, HIGH);
  BlankDisplay();
}


unsigned long previousSRMillis = 0;    // keeping track last time shift register values were clocked in

void loop() {
  boolean srBuffer[128] = {0};
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousSRMillis >= 250) {  // clocking in 4 times a second
    
    previousSRMillis = currentMillis;

    boolean srBuffer[128] = {0};

    // doing the lookups for what number to display then
    // looking up which shift register position
    // for each tube
    // "10" from the lookup table indicates to blank the tube since it is out of range.
    
    srBuffer[dots[1]] = 1; //light up dot 1
    srBuffer[blank1[1]] = 0; //unused output on HV5530 #1 (pin 10)
    srBuffer[plusminus[1]] = 1; //light up plus
    srBuffer[tube1[1]] = 1; //light up number 1 on tube 1
    srBuffer[blank2[1,2,3,4,5,6,7,8,9,10]] = 0; //unused outputs on HV5530 #1 (pins 35-44)
    srBuffer[tube2[2]] = 1; //light up number 2 on tube 2
    srBuffer[tube3[1]] = 1; //light up number 1 on tube 3
    srBuffer[blank3[1,2]] = 0; //unused outputs on HV5530 #2 (pins 21-22)
    srBuffer[tube4[2]] = 1; //light up number 2 on tube 4
    srBuffer[tube5[1]] = 1; //light up number 1 on tube 5
    srBuffer[tube6[2]] = 1; //light up number 2 on tube 6
    srBuffer[blank4[1,2]] = 0; //unused outputs on HV5530 #3 (pins 21-22)
    srBuffer[tube7[1]] = 1; //light up number 1 on tube 7
    srBuffer[tube8[2]] = 1; //light up number 2 on tube 8
    srBuffer[tube9[1]] = 1; //light up number 1 on tube 9
    srBuffer[blank5[1,2]] = 0; //unused outputs on HV5530 #4 (pins 21-22)
    srBuffer[tube10[2]] = 1; //light up number 2 on tube 10


    digitalWrite(PIN_LE, LOW);

    for(int i = 0; i < 128; i++){ 
      
      digitalWrite(PIN_DATA,srBuffer[i]);
      digitalWrite(PIN_CLK,HIGH);
      delayMicroseconds(5);
      digitalWrite(PIN_CLK,LOW);
      delayMicroseconds(5);
    }

    digitalWrite(PIN_LE, HIGH);
  }

}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(PIN_LE, LOW);


  // Set the data to LOW
  digitalWrite  (PIN_DATA,  LOW);


  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(PIN_CLK, LOW);   // Clock in one data bit
    digitalWrite(PIN_CLK, HIGH);
  }
}

Did you try just making a more basic sketch that as you press a button it turns off the current nixie and increments to the next.

Outputting value to serial monitor so you can actually map out what does what and when your values fail?

    srBuffer[blank2[1,2,3,4,5,6,7,8,9,10]] = 0; //unused outputs on HV5530 #1 (pins 35-44)
    srBuffer[blank5[1,2]] = 0; //unused outputs on HV5530 #4 (pins 21-22)

You can't use multiple elements of an array like that. It doesn't matter since those elements of the array are already zero. Just remove those lines.

    srBuffer[dots[1]] = 1; //light up dot 1
    srBuffer[plusminus[1]] = 1; //light up plus
    srBuffer[tube1[1]] = 1; //light up number 1 on tube 1
    srBuffer[tube2[2]] = 1; //light up number 2 on tube 2
    srBuffer[tube3[1]] = 1; //light up number 1 on tube 3
    srBuffer[tube4[2]] = 1; //light up number 2 on tube 4
    srBuffer[tube5[1]] = 1; //light up number 1 on tube 5
    srBuffer[tube6[2]] = 1; //light up number 2 on tube 6
    srBuffer[tube7[1]] = 1; //light up number 1 on tube 7
    srBuffer[tube8[2]] = 1; //light up number 2 on tube 8
    srBuffer[tube9[1]] = 1; //light up number 1 on tube 9
    srBuffer[tube10[2]] = 1; //light up number 2 on tube 10

You are trying to display "+.1212121212". What result are you getting on the display?

Slumpert:
Did you try just making a more basic sketch that as you press a button it turns off the current nixie and increments to the next.

Outputting value to serial monitor so you can actually map out what does what and when your values fail?

I have a simple code which first blanks all the outputs, then turns each high voltage output on one by one with a slight delay, so you can see them turning on individually. Here is the code:

#define LATCH   10
#define CLOCK   13
#define DATA    11
#define PWM 9


void setup()
{
  
  pinMode(LATCH, OUTPUT); // Latch is pin 10, FALLING edge copies shift register to latches
  pinMode(CLOCK, OUTPUT); // Clock is pin 13, FALLING edge clocks in a bit from DATA
  pinMode(DATA, OUTPUT); // Data is pin 11
  pinMode(PWM, OUTPUT); // PWM (Blank) is pin 9

  digitalWrite(LATCH, HIGH);
  digitalWrite(CLOCK, HIGH);
  digitalWrite(DATA, HIGH);
  digitalWrite(PWM, HIGH);

delay(10);
  BlankDisplay();
  LightDisplay();
}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  // Set the data to LOW
  digitalWrite  (DATA,  LOW);
  
  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
    
    digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches

}
}

void LightDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(LATCH, LOW);


  // Set the data to LOW
  digitalWrite  (DATA,  HIGH);
  
  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(CLOCK, LOW);   // Clock in one data bit
    digitalWrite(CLOCK, HIGH);
    
    digitalWrite(LATCH, HIGH);    // Bring the latch high to copy the shift registers to output latches

delay(100); // Slow this step down by 20ms so we can see each output turning on 

}
}


void loop() {
//nothing
}

And here is what it does on the display:

This is turning on each high voltage output using the second “transparent latch mode” on the bottom of the HV5530 truth table, found on page 10 of the chip datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20005851A.pdf

johnwasser:
You are trying to display “+.1212121212”. What result are you getting on the display?

So when I run the following code:

#define PIN_LE    10 //Shift Register Latch Enable
#define PIN_CLK   13  //Shift Register Clock
#define PIN_DATA  11  //Shift Register Data
#define PIN_BL    9  //Shift Register Blank (0=display off     1=display on)


// shift register positions
// --------------------------HV5530 #1--------------------------
// Controls 9 Dots (One cathode each), Plus/Minus tube (2 cathodes), One 10 digit tube (10 cathodes)

// Dots are first
//      Dot   1  2  3  4  5  6  7  8  9
int dots[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

//HV5530 #1 Pin 10 (HVOUT20) is not used
//Bit 9 not used
int blank1[] = {9};
// Plus/minus tube  +   -
int plusminus[] = {10, 11};

// Rightmost numeric tube (Tube 1)
//        Digit  0   1   2   3   4   5   6   7   8   9
//  NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube1[] =  {15, 16, 18, 21, 17, 13, 19, 12, 14, 20};          
//Pins 35-44 (HVOUT1 - HVOUT10) are not used

// --------------------------HV5530 #2,3,4--------------------------
//HV5530#2,3,4 each control three (3) numeric tubes each (10 cathodes each, 30 cathodes total)
//Connections are similarly numbered between each set of tubes and HV chip for chips 2,3,4
//Pins 21 and 22 (HVOUT31 and HVOUT32) are not used

// Next 3 tubes from right to left (Tubes 2,3,4)
//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube2[] =  {35, 36, 38, 41, 37, 33, 39, 32, 34, 40};
int tube3[] =  {45, 46, 48, 51, 47, 43, 49, 42, 44, 50}; 
int tube4[] =  {57,  58, 60, 63, 59, 55, 61, 54, 56, 62};  

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube5[] =  {67, 68, 70, 73, 69, 65, 71, 64, 66, 72};
int tube6[] =  {77,  78, 80, 83, 79, 75, 81, 74, 76, 82};
int tube7[] =  {89,  90, 92, 95, 91, 87, 93, 86, 88, 94}; 

//       Digit  0   1   2   3   4   5   6   7   8   9
// NL840 Pin #  13  5   7   10  6   2   8   1   3   9 
int tube8[] =  {99,100,102,105,101,97,103,96,98,104};
int tube9[] =  {109,110,112,115,111,107,113,106,108,114};  
int tube10[] =  {121,122,124,127,123,119,125,118,120,126}; 


void setup() {
  pinMode(PIN_LE,  OUTPUT);
  pinMode(PIN_BL,  OUTPUT);
  pinMode(PIN_DATA,OUTPUT);
  pinMode(PIN_CLK, OUTPUT);
  digitalWrite(PIN_BL, HIGH);
  BlankDisplay();
}


unsigned long previousSRMillis = 0;    // keeping track last time shift register values were clocked in

void loop() {
  boolean srBuffer[128] = {0};
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousSRMillis >= 250) {  // clocking in 4 times a second
    
    previousSRMillis = currentMillis;

    boolean srBuffer[128] = {0};

    // doing the lookups for what number to display then
    // looking up which shift register position
    // for each tube
    // "10" from the lookup table indicates to blank the tube since it is out of range.
    
    srBuffer[dots[1]] = 1; //light up dot 1
    srBuffer[plusminus[1]] = 1; //light up plus
    srBuffer[tube1[1]] = 1; //light up number 1 on tube 1
    srBuffer[tube2[2]] = 1; //light up number 2 on tube 2
    srBuffer[tube3[1]] = 1; //light up number 1 on tube 3
    srBuffer[tube4[2]] = 1; //light up number 2 on tube 4
    srBuffer[tube5[1]] = 1; //light up number 1 on tube 5
    srBuffer[tube6[2]] = 1; //light up number 2 on tube 6
    srBuffer[tube7[1]] = 1; //light up number 1 on tube 7
    srBuffer[tube8[2]] = 1; //light up number 2 on tube 8
    srBuffer[tube9[1]] = 1; //light up number 1 on tube 9
    srBuffer[tube10[2]] = 1; //light up number 2 on tube 10


    digitalWrite(PIN_LE, LOW);

    for(int i = 0; i < 128; i++){ 
      
      digitalWrite(PIN_DATA,srBuffer[i]);
      digitalWrite(PIN_CLK,HIGH);
      delayMicroseconds(5);
      digitalWrite(PIN_CLK,LOW);
      delayMicroseconds(5);
    }

    digitalWrite(PIN_LE, HIGH);
  }

}


void BlankDisplay()
{
  // Disconnect shift register from data latches:
  digitalWrite(PIN_LE, LOW);


  // Set the data to LOW
  digitalWrite  (PIN_DATA,  LOW);


  //  Clock in the LOW 128 times
  for (uint8_t i = 0; i < 128; i++)
  {
    digitalWrite(PIN_CLK, LOW);   // Clock in one data bit
    digitalWrite(PIN_CLK, HIGH);
  }
}

I get this result:

I have no idea why this does not work. From what I can tell, I should be shifting in 128 bits, each assigned to the proper register and pin from my physical board layout. There has to be some sort of fundamental mistake I’m not seeing.

The BlankDisplay() seems to work and the clocking there is different in two ways:

  • Put the "digitalWrite(PIN_CLK, LOW);" BEFORE the "digitalWrite(PIN_CLK, HIGH);"
  • I would take out the "delayMicroseconds(5);" calls.

You have two variables named srBuffer[] and the first one (line 67) is not used. Take out that line.

The "digitalWrite(PIN_LE, HIGH);" at the end of BlankDisplay() is missing. Please put it back in.

Hi guys. Finally, after a lot of sweat and frustration, I've developed a robust code to drive my display. It deals with decimal numbers as well. Essentially, it takes a char array input from the serial port and creates the appropriate 128 bit boolean array which is then clocked out to the 4 HV chips. It was a bit challenging (no pun intended) to deal with decimal numbers, but nevertheless it works! The code is quite long so I'll upload it in a separate message. Lots of it is just bit mapping, but I tried to make it as clear as possible for the sake of my own understanding. Hopefully this will also be useful to someone else one day.

See the attached .ino if you are interested; code exceeded post character length (again, not pretty but it works well).

finally.ino (11.3 KB)

I'm a firm believer in "seeing is believing": https://youtu.be/Pbq0HJ7vn24