Nixie tubes not displaying correctly

Hello all,

I'm having trouble displaying numbers larger than 99 on a set of four in-12 nixie tubes. I'm using an Arduino Uno, two 595 shift registers, and four k155id Drivers. As far as I can tell, everything is connected correctly physically(per the official daisy chained circuit on 595 shift registers).

The code is set up to loop from 0 to 1000, and will display 0 to 99 correctly on the right two Nixie tubes, but the left two never change from zero. After x is higher than 99, the ten's place nixie tube turns off but will start counting again, from zero, at an incorrect number (nixie tubes say 26 but serial data says x is 180 or so). What could be the cause of this?

Also, if I swap the order of the highByte/lowbyte commands, the same thing happens but in the left two nixie tubes, while the right two remain at zero.

Any help is greatly appreciated.

int latchPin = 8;
int clockPin = 12;
int dataPin = 11;
int x;

void setup(){
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  delay(1000);
}

void loop(){
  for (x = 0; x<1000; x++){
  delay(50);

  writeNumber(x);
  Serial.print("x = ");
  Serial.println(x);
 }
}
void writeNumber(byte val) {
  
    digitalWrite(latchPin, LOW);
    byte value = decToBcd(val);
    shiftOut(dataPin, clockPin, MSBFIRST, highByte(value));    
    shiftOut(dataPin, clockPin, MSBFIRST, lowByte(value)); 
    digitalWrite(latchPin, HIGH);
}

byte decToBcd(byte val){
  
  return( (val/10*16) + (val%10) );
}

Earlier I had changed all of the bytes to int's because I feared I was truncating data, but that didn't change the output of the code. I forgot to change the decToBcd to an int. It still doesn't fully work, but now the hundredth place nixie tube is counting up.

Still, once the ten's place nixie tube reaches 9, it goes blank and then the counting is off.

Surely the easiest approach is to use regular maths, then create regular text in a buffer e.g. 1234567

Display the text string on Serial, LCD, ....
Or display each ascii character on the respectivevNixie tube.

Ok, not as efficient as performing BCD maths but all the decimal and ascii functions are already provided.

David.

Are you saying to do something similar to this example? (Found here)

char result[4] = {0};
int num = 345;

int digit = num % 10;  // get the 5 off the end
result[2] = digit + '0';  // convert to ascii and store
num /= 10;   // get rid of the 5
digit = num % 10;   // get the 4 off the end
result[1] = digit + '0';  // convert to ascii and store
num /= 10;   // get rid of the 4
digit = num % 10;   // get the 3 off the end
result[0] = digit + '0';  // convert to ascii and store

Serial.print(result);  // prints "345"

And then shift out the char array to the shift registers?

No, I am saying:

uint8_t ascii2nixie(char c)
{
    return (c - '0');
}
...
     uint32_t ulvalue = 12345678;
     char buf[12];
     ultoa(ulvalue, buf, 10);   //
 
     for (int i = 8; i-- > 0; ) {
         shiftOut(dataPin, clockPin, MSBFIRST, ascii2nixie(buf[i]));   
     }

How you operate the shift registers depends on your wiring. e.g. if they are all in series, you select CS, shift all 64 bits, deselect CS.

My point was that ascii is human-readable.
You only need a single "translate" function to operate on each char.

Similarly with 7-segment, matrix or any low level display

David.

I see what you mean now. I have never heard of ultoa before. The shift registers are in series, per the Arduino official tutorial.

Here's is how I tested using your example:

int latchPin = 8;
int clockPin = 12;
int dataPin = 11;

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT); 
  delay(1000);
}

void loop() {
  uint32_t ulvalue = 12345678;
  char buf[12];
  ultoa(ulvalue, buf, 10);
  digitalWrite(latchPin, LOW);
  for (int i = 8; i-- > 0; ) {shiftOut(dataPin, clockPin, MSBFIRST, ascii2nixie(buf[i]));}
  digitalWrite(latchPin, HIGH);
}

uint8_t ascii2nixie(char c)
{
    return (c - '0');
}

With this code, the nixie tubes read (from left to right) "0201". I tried changing the value of ulvalue to see what I would get and here are a few results:

ulvalue Nixies
1 001 (thousandth place digit has no numbers illuminated)
5 005 (thousandth place digit has no numbers illuminated)
10 0001
12 0201
17 0701
25 0502

So I still must not be understanding something that is going on to cause the first digit to not light up, and for it to have a 0 in between two digit numbers.

Your Nixies expect two BCD in an 8-bit byte.

You just change the ascii2nixie() function to take two chars. e.g.

uint8_t ascii2nixie(char hi, char lo)
{
    return ((hi - '0') << 4) | (lo - '0');
}
...
     uint32_t ulvalue = 12345678;
     char buf[12];
     ultoa(ulvalue, buf, 10);   //
 
     for (int i = 8; i > 0; i -= 2) {
         shiftOut(dataPin, clockPin, MSBFIRST, ascii2nixie(buf[i-2], buf[i-1]));  
     }

I bet the next problem ist that ultoa() does no left-padding with zeros.

Going back to the original code, decToBcd() works with values in the range 0-99. But modulo for the rescue.
The second problem is that writeNumber() was using byte. This limits the input values to 0-255.

int latchPin = 8;
int clockPin = 12;
int dataPin = 11;
int x;

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  delay(1000);
}

void loop() {
  for (x = 0; x < 1000; x++) {
    delay(50);

    writeNumber(x);
    Serial.print("x = ");
    Serial.println(x);
  }
}

void writeNumber(int val) {
  byte valueL = decToBcd(val % 100);
  byte valueH = decToBcd((val / 100) % 100);

  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, valueH);
  shiftOut(dataPin, clockPin, MSBFIRST, valueL);
  digitalWrite(latchPin, HIGH);
}

/*
   Converts val from binary to BCD
   Valid values for val: 0 - 99
*/
byte decToBcd(byte val) {
  return ( (val / 10 * 16) + (val % 10) );
}

The Arduino is a nightmare for formatting.

sprintf() is easy to use.
dstrtof() is excellent for formatting both f-p (and integers if you set prec = 0)

Both of these can pad with space or 0

Your method works with the shift register.
It does not work well with Serial.print()

David.

If the code in the OP worked for numbers 0 to 99, then I would suggest repairing that.
It has the obvious error that the function writeNumber() uses a single byte for numbers is the range 0 to 999 ( or maybe you even mean 9999 if you are testing a 4 digit display)

Edit

maybe something like this (untested):

int latchPin = 8;
int clockPin = 12;
int dataPin = 11;
int x;

void setup(){
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  delay(1000);
}

void loop(){
  for (x = 0; x<1000; x++){
  delay(50);

  writeNumber(x);
  Serial.print("x = ");
  Serial.println(x);
 }
}
void writeNumber(uint16_t val) {
 
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, decToBcd(val / 100 ));   
    shiftOut(dataPin, clockPin, MSBFIRST, decToBcd(val % 100));
    digitalWrite(latchPin, HIGH);
}

byte decToBcd(byte val){
 
  return( (val/10*16) + (val%10) );
}

It looks like two HC595 to provide 16-bit Shift Register.
And four Nixie Driver chips that each receive 4-bit BCD

So 4 Nixies can be driven from a uint16_t variable.

I misread the OP. Expecting 8 Nixie tubes requiring uint32_t variable.

It is a mystery why shiftOut() is used instead of HW SPI. But of course you would need to use the HW SPI pins in the correct order.

David.

I always fascinated to see Nixie projects. In fact, my last project was a Nixie clock and it's in the exhibition gallery, but a page or so back by now.

Since he is direct driving the Nixies, bus speed is not really an issue unless the brightness control is some sort of PWM and that should even be done with the anode power circuit instead of the bus and, anyway, to avoid visible flicker, a 5mS period is OK, so even that is not very demanding.

Hey everyone,
A big thanks for all of the info given.

Rintin, that was going to be my next question, but I was able to figure out how to pad with zeros before I came back to the forum :smiley: Although, your code does look cleaner. Here's what I did thanks to David's examples:

int latchPin = 8;
int clockPin = 12;
int dataPin = 11;
char OutTo[4];

void setup() {
pinMode(latchPin, OUTPUT);
}

void loop() {
   for (int j = 0; j < 1500; j++) {
    sendOut(j);
     delay(100);
  }
}

void sendOut(int a) {
     
     char buf[5] = {0};
     itoa(a, buf, 10);
     
     if(a < 10)                  {OutTo[0] = buf[0]; OutTo[1] = '0';    OutTo[2] = '0';    OutTo[3] = '0';}
     if(a >= 10 && a < 100)      {OutTo[0] = buf[1]; OutTo[1] = buf[0]; OutTo[2] = '0';    OutTo[3] = '0';}
     if(a >= 100 && a < 1000)    {OutTo[0] = buf[2]; OutTo[1] = buf[1]; OutTo[2] = buf[0]; OutTo[3] = '0';}
     if(a > 1000)                {OutTo[0] = buf[3]; OutTo[1] = buf[2]; OutTo[2] = buf[1]; OutTo[3] = buf[0];}
     
     digitalWrite(latchPin, 0);
     for (int i = 4; i > 0; i -= 2) {shiftOut(dataPin, clockPin, ascii2nixie(OutTo[i-1], OutTo[i-2]));}
     digitalWrite(latchPin, 1);
}

uint8_t ascii2nixie(char hi, char lo) {
    return ((hi - '0') << 4) | (lo - '0');
}

void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
  int i=0;
  int pinState;
  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, OUTPUT);

  digitalWrite(myDataPin, 0);
  digitalWrite(myClockPin, 0);

  for (i=7; i>=0; i--)  {
    digitalWrite(myClockPin, 0);

    if ( myDataOut & (1<<i) ) {
      pinState= 1;
    }
    else { 
      pinState= 0;
    }
    digitalWrite(myDataPin, pinState);
    digitalWrite(myClockPin, 1);
    digitalWrite(myDataPin, 0);
  }
  digitalWrite(myClockPin, 0);
}

It works as it should!
I did forget to change writeNumber() to an int, instead of a byte, earlier.
I'll try the code that Rintin revised as well, just to see another way of going about this task. I was wondering why David was using uint32_t earlier, but knowing now that his logic can display up to eight characters make my project far easier and use less pins/ hardware.

6v6gt, my plan is to replace the stock instrument cluster in my 2000 Mazda Miata with nixie tubes. I wanted to use four tubes and two 595's for RPM and then three more tubes for MPH and one for fuel level(would display numerator value in tenth's of a tank). The MPH and fuel level would use another two 595's and three more pins to go along with it.

Given that I can convert an integer to individual chars in an array, what I may do is this:

  1. get fuel level(represented by a single digit) multiply this by 1000, put in temp location
  2. get MPH(other hardware/calculations done to get this number)
  3. Add MPH to fuel level temp location to get a four digit number
  4. itoa the temp number that has MPH and fuel level
  5. put into array with RPM and then shift all data out to 8 tubes(with 4 595's daisy chained)

This way I'm only using three pins to display all of my numerical data.

David, how would I go about using SPI, and what would be the advantages? I only used the pins I did because that was what the Arduino example used.

I do need to work on a dimming circuit, which will probably be as 6v6gt described, unless there is a better way.
Again, thanks for all of the help!

It sounds like an interesting project. Do post some pictures when it is complete.
To blank the display, I think you simply send those bcd driver chips anything which does not translate to a number from 0 to 9. To dim it, alternate between displaying a blank and the desired digits. The timings here control the (perceived) brightness. Some experimentation is necessary to get a frequency high enough to stop visible flicker, while not so high that switching losses limit the maximum brightness. Maybe in the range 200 to 500 Hz.

Now its getting clearer what you want.

I would probably write a library to control the nixie tubes.

The interface could be like this:

Nixie n(pin_data, pin_clock, pin_latch);

n.begin(number_of_digits); // creates array displaydata, initialize pins
n.setDigit(index_of_digit, value); // value in the range of 0-9, write to displaydata
n.update(); // shifts out displaydata
n.printValue(index_first_digit, number_of_digits, value); // does a lot of calls to setDigit()

Outputing all values would be like this:

n.printValue(0,1, fuel_level);
n.printValue(1,3, mph);
n.printValue(4,4, rpm);
n.update();