Novice coder - advice on my approach to this project: Nixietube Dashboard

Hello all

firstly, thank you to all the contributors on this forum, I've found some fantastic advice already. for those with a short attention span, or who don't like preambles, skip to post number two which will explain what I am trying to achieve... for those with some time to kill, read on!

I am not "new" to arduino (and raspberry pi etc) but most of what I have done before is lifting a code, tweaking it, maybe bashing two scripts together, compiling and off I go. Either that, or using useful code builder tools for the likes of Libelium motes and such. I've "programmed" 3D printers but that was largely "cheating" using tools like the Repetier configuration tool.

Sadly, this has meant that I have been left with a broad understanding of what a code "does" but not "how", much in the same way someone can install a washing machine in a house or an engine in a car using a manual or instruction book, and not have the foggiest idea how it actually works.

This is my first time "properly" trying to code from scratch.

So, the project... I suppose I had better start with an introduction of what it is. I am building a hotrod, which has so far had absolutely enourmous amounts of work go into the body, chassis, engine... 7 years so far into the build. I won't put it all here, but there is a thread (which I no longer update for reasons best left) just here that you can flick through if welding, turbochargers, crazy customisations, obscene amounts of hard work and old beaten up Volkwagens are your thing...

The project had a stric rule, that I was only allowed to use period technology available at the time of manufacture. So far, this has been a steep learning curve (with problems such as oil flowing into the intake when the throttle shut off owed to a change in turbocharger manufacturing techniques), but a well worth-it exercise to re-learn some lost knowledge and techniques.

So, why am I looking at arduinos? Well... I want to use nixie tubes for the dash.

4 tubes for rev counter
2 tubes for speedo
and a whole bunch of bars for various other parameters.

I drew up a board to run the dashboard (that's a lie, I was drawing up a board and then stumbled upon someone who had the same idea as me, and had already finished a board to run a nixie tacho, and had made it open source and published the design so I lifted that, tweaked it, altered some parts and added to it the rest of the circuits to run the rest of the dash). Then I got some quotes to have the board made... Over £500. So then I started to re-design to only use through-hole components rather than their more modern surface-mount counterparts. Then I realised that the effort, and cost, would not be worthwhile. But, becuase i have "proved" I can do it with circuitry, and had that learning experience and done it in principle, I am going to "allow" a shortcut here (and a saving of several hundred pounds!) by using an Arduino to do the job in "one hit"... Which then puts me into a new learning experience, coding from scratch!

And, so, on to post #2 where I will lay out what I am trying to achieve!

For those of you with an attention span similar to mine (that is, very short!) and therefore skipped the yawnfest preamble, welcome to the problem I am trying to solve! You didn't miss much...

For those who read the preamble, well done, and thank you!

So. A nixie dashboard is the goal, featuring:

4 digit nixie tachometer
2 digit nixie speedometer
nixie bar tachometer (to compliment the digital tachometer)
nixie bar oil temperature
nixie bar oil pressure
nixie bar turbo boost pressure
nixie bar fuel gauge
nixie bar fuel pressure
nixie bar battery / vehicle voltage
nixie bar lambda
4 or 6 digit nixie clock

Other functions:

Each bar will flash at whatever value it is currently reading when parameters fall below or above preset levels to alert of potential issues (such as boost creep, temperatures getting a shade high etc)
Each bar will flash at full scale when there is no input signal at all (that is, a zero input)
The tachometer bar will be set so that full scale is the optimum gear shift point, with some overhead to send it bright (since bars just get brighter when you try to drive them further than full scale) so it would act as a handy shift light (possibility to set the shift point manually - but I'll stick that in "pipe dreams" for now!)
The battery level bar will flash at full scale when the charge warning light from the alternator is on
The oil pressure bar will flash at full scale when the oil pressure warning is on.

(There's also a nixie gearshifter with the nixie tube embedded in the handle in the mix, but that needs no coding or programming since it operates on a set of microswitches)

So, I know what I want the code to do, so my "thinking" is this:

Analogue pin 0 = PWM Anaogue rev output
Analogue pin 1 = Oil temp input
Analogue pin 2= oil temp output
Analogue pin 3= oil pressure input
Analogue pin 4 = oil pressure output
Analogue pin 5 = boost pressure input
Analogue pin 6 = boost pressure output
Analogue pin 7 = Fuel level input
Analogue pin 8 = fuel level output
Analogue pin 9 = fuel pressure input
Analogue pin 10 = fuel pressure output
Analogue pin 11 = lambda input (from pin 9 on SLC)
Analogue pin 12 = lambda output
Analogue pin 13 = battery voltage input
Analogue pin 14 = battery voltage output

14 analogue pins used

Digital pin 0 = RPM interrupt input
Digital pin 1 = Speend interrupt input
Digital pin 2 = nixie 4 (rpm) least significant bit
Digital pin 3 = nixie 4 (rpm) data bit
Digital pin 4 = nixie 4 (rpm) data bit
Digital pin 5 = nixie 4 (rpm) most significant bit
Digital pin 6 = nixie 3 (rpm) least significant bit
Digital pin 7 = nixie 3 (rpm) data bit
Digital pin 8 = nixie 3 (rpm) data bit
Digital pin 9 = nixie 3 (rpm) most significant bit
Digital pin 10 = nixie 2 (rpm) least significant bit
Digital pin 11 = nixie 2 (rpm) data bit
Digital pin 12 = nixie 2 (rpm) data bit
Digital pin 13 = nixie 2 (rpm) most significant bit
Digital pin 14 = nixie 1 (rpm) least significant bit
Digital pin 15 = nixie 1 (rpm) data bit
Digital pin 16 = nixie 1 (rpm) data bit
Digital pin 17 = nixie 1 (rpm) most significant bit
Digital pin 18 = nixie 6 (speed) least significant bit
Digital pin 19 = nixie 6 (speed) data bit
Digital pin 20 = nixie 6 (speed) data bit
Digital pin 21 = nixie 6 (speed) most significant bit
Digital pin 22 = nixie 5 (speed)least significant bit
Digital pin 23 = nixie 5 (speed)data bit
Digital pin 24 = nixie 5 (speed) data bit
Digital pin 25 = nixie 5 (speed) most significant bit
Digital pin 26 = oil pressure warning
Digital pin 27 = charge warning

28 pins used

I could run the nixie clock on the Arduino, or on a seperate board, I've not decided yet.

// Startup

Cycle all nixies 0-9
Cycle all nixie bars 0-max (0-255 on analogue outputs)

// Tachometer

read interrupts on digital pin 0 (RPM) for 20mS
Count pulses per 20mS
store reading
take 5 readings
Detach interrupt
calculate average of the five readings
Divide by 2 // there are 2 pulses per crank revolution on a 4 cylinder engine
Write result to pins as 4 binary words (0000/0000/0000/0000):
digital 2, 3, 4, 5 (4th digit nixie - least significant digit)
digital 6, 7, 8, 9 (3rd digit nixie)
digital 10, 11, 12, 13 (2nd digit nixie)
digital 14, 15, 16, 17 (1st digit nixie - most significant digit)
Convert to PPM (0-254)
Output PPM signal (revs) on analogue 0
Flash analogue 0 when PPM = 255
write to SD card & bluetooth

// Speedometer

read interrupts on digital pin 1 (speed)
count pulses per 50mS
record value
Take 5 readings
Detach interrupt
calculate average of the five readings
Multiply by 2 to give revolutions per second // there are two pulses per speedo cable rotation - so multiply by 2 here rather than 4 to give RPS
Multiply by 3600 to give revolutions per hour // there are 3600 seconds in an hour
Divide by 894 to give miles per hour //894 is revolutions per mile for my tyres
Write result to digital pins as 2 binary words (0000/0000)
digital 18, 19, 20,21 (6th nixie)
digital 22, 23, 24, 25 (5th nixie)
write to SD card & bluetooth

// Oil Temperature

Read analogue 1 (oil temp)
Calculate to output 0-254 using voltage range from sender input
Output PPM signal (oil temp) on analogue 2
Flash analogue 2 when PPM = >200
Flash analogue 2 at 255 when PPM <10
write to SD card & bluetooth

// Oil pressure

read analogue 3 (oil pressure)
calculate to output 1-255 using voltage range from sender input
output PPM signal (oil pressure) on analogue 4
flash analogue 3 when PPM <50
flash analogue 3 at 255ppm when PPM <10
write to SD card & bluetooth

//Boost pressure

read ananolgue 5 (boost pressure)
calculate to output 0-255 using voltage range from sender input
output PPM signal (boost pressure) on analogue 6
flash analogue 6 when PPM >130
flash analogue 6 at 255 when PPM <10
write to SD card & bluetooth

//Fuel level

read analogue 7 (fuel level)
calculate to output 0-255 using voltage range from sender input
output PPM signal (fuel level) on analogue 8
flash analogue 8 when PPM <50
flash analogue 8 at 255 when PPM <10
write to SD card & bluetooth

// Fuel pressure

read analogue 9 (fuel pressure)
calculate to output 0-255 using voltage range from sender input
output PPM signal (fuel pressure) on analogue 10
flash analogue 10 when PPM >200 or <100
flash analogue 10 at 255 when PPM <10
write to SD card & bluetooth

// Lambda

read analogue 11 (lambda)
calculate to output 0-255 using voltage range from sender input
output PPM signal (lambda) on analogue 12
flash analogue 12 when PPM >140 or <100
flash analogue 12 at 255 when PPM <10
write to SD card & bluetooth

// Battery level

read analogue 13
calculate to output 0-255 using voltage divider input (10v - 15v
output PPM signal (battery) on analogue 14
flash analogue 14 when PPM >230 or <100

// Oil pressure warning

read digital 26 (oil pressure warning)
if high, flash analogue 3
if low, ignore

// charge warning

read digital 27 (charge warning)
if high, flash analogue 14 at 255
if low, ignore

Repeat!

The code I wrote to follow...

So... the best approach for translating this into code would be presumably to tackle each function in turn, and then bash them all together into one script?

The read analogue pin, output PWM, is easy, and I think I've already got something for that. For example, boost pressure:

int boostsensePin =5
int boostoutPin = 6

void setup ()_{
 

 pinMode(boostoutPin, OUTPUT); // setting pin 6 as an output for PWM

}

void loop() {
 int val = analogRead(sensePin); // read the value from the boost pressure sender

 val = constrain(val, 500, 900); // reset the value to within these parameters so I don't end up with values <0 or >255

 int boostoutLevel = map (val, 500, 900, 0, 255) // I've just chosen an arbitrary value for the input here, I don't know exactly at the moment what range the boost pressure sender will give, as I don't have one fitted yet, it is all done with analogue gauges at the moment

 analogWrite(boostoutPin, boostoutLevel); // write whatever boostsense translates to, to the boostout

 if (boostoutLevel > 130) || (boostoutLevel < 90) analogWrite(boostoutPin, 0); // If boost is less than 90 or more than 130, switch the bar off for 500mS
 delay(500);
 analogWrite(boostoutPin, boostoutLevel); // ... and then switch it back on, making the boost nixie bar flash becuase the boost is too high or low
 
 if (boostoutLevel ==0) analogWrite(boostoutPin, 255); //if boost pin is showing zero, switch the bar on to full
 delay(500); //wait 500mS
 analogueWrite(boostoutPin, 0); //... and switch it off again, making it flash on and off to warn that there's a fault
 
}

Now, would this bit of code seem reasnoable? I have no way of testing right now as I am working away from home and don't have my box of toys with me. Feel free to rip that to pieces, but please be constructive rather than pedantic, sarcastic, or otherwise uncool.

The RPM / speedo has me a little vexed, as there seems to be lots of ways of doing it!

The other "vex" is programming to output binary words... I can easily make the nixie display numbers by manually setting pins high and low. But what would be the best approach to translate a 4 digit number to four binary words of 4 bits each to feed into the decoder chips? I'm trying to get my head around the examples out there but I must confess my understanding is lacking... But I will talk more about that once I am confident that I have a good code for the bars.

For what it is worth, I am actually willing to pay someone for their time if they want to write code for me, if it comes it it. But that would come with the proviso that they really talk me through exactly how the code works so that I can learn from them! that s the real goal here, knowledge!

fall-apart-dave:
For those of you with an attention span similar to mine (that is, very short!) and therefore skipped the yawnfest preamble,

I guess my attention span is much shorter.

Can you reduce your question to a few lines? Maybe focus on getting one little piece working to start with?

Have a look at Planning and Implementing a Program

...R

Robin2:
I guess my attention span is much shorter.

Can you reduce your question to a few lines? Maybe focus on getting one little piece working to start with?

Have a look at Planning and Implementing a Program

...R

I thought about that. But the project is large, and my question is "is my approach sensible, given the scale and my current lack of ability?" - and I did focus on one part, and posted the code I had written, and asked something along the lines of "am I on the right track here?" - but without outlining the goal of the whole project, it felt a bit, well, random. :slight_smile:

But thanks for the response :slight_smile:

EDIT

Reading your guide now - thank you for writing it

EDIT AGAIN

Ok so I can see my first mistake from reading your tutorial... I've got a delay in my loop.

So, a direct question, what would be the best way of flashing the bar tubes on and off in the event of an "out of range" or "falut" condition?

"out of range" would want the bar to simply flash on and off with whatever value it is displaying, "fault" would have the bar flash on and off at full brightness.

In my code, I used a delay and an "If" statement with instructions to turn the pin on and off within the loop, which seems to be incorrect. It makes sense now I think about it, a delay in the loop will mean that it takes ages to get back to the start, and of course that would likely cause everything to flash on and off. See - learning! But, time for a cup of tea now...

Would the correct approach be to write each funciton as, well, a funciton, before the loop, and then call each function as required as I need them?

Kind of like this:

void Function1() 
       {
        measure oil temp
        }

void Function2() 
        {
         measure oil pressure
         }

 void Function3()  
        {
flash like buggery when something is wrong with the oil pressure
         }

void loop()
       {  
        Function1();
        if funciton 1 isn't right, function 3
        else
        Function 2();
        
       } // end of loop

Would that work? You know what, I'll ask no more questions until I have read through more tutorial...

fall-apart-dave:
I thought about that. But the project is large, and my question is "is my approach sensible, given the scale and my current lack of ability?" - and I did focus on one part, and posted the code I had written, and asked something along the lines of "am I on the right track here?" - but without outlining the goal of the whole project, it felt a bit, well, random. :slight_smile:

First of all, slow down. Allow time for us to read and respond to your Reply before making another one. Or, wait until you have gathered all your thoughts before making a Reply.

I have not seen where you posted code.

Without seeing the code you are using for flashing the "bar tubes" I cannot comment. A link to the datasheet for the "bar tube" would also be a help.

Would the correct approach be to write each funciton as, well, a funciton, before the loop, and then call each function as required as I need them?

Yes. Short single-purpose functions with meaningful names (NOT Function1) make debugging much easier and allow the different pieces to be tested and debugged on their own.

Always design and write code with debugging in mind. You could count on the fingers of 1 hand the number of people in the world who can write bug free code.

...R

Robin2:
First of all, slow down. Allow time for us to read and respond to your Reply before making another one. Or, wait until you have gathered all your thoughts before making a Reply.

I have not seen where you posted code.

Without seeing the code you are using for flashing the "bar tubes" I cannot comment. A link to the datasheet for the "bar tube" would also be a help.
Yes. Short single-purpose functions with meaningful names (NOT Function1) make debugging much easier and allow the different pieces to be tested and debugged on their own.

Always design and write code with debugging in mind. You could count on the fingers of 1 hand the number of people in the world who can write bug free code.

...R

Apologies. I type as I think. I'm working on a ship so internet comes and goes, so it becoems habit to jsut type and submit as I go, in case the internet goes off again (which it does regularly!).

So, the code I posted up in post 2 is this:

int boostsensePin =5
int boostoutPin = 6

void setup ()_{
 

 pinMode(boostoutPin, OUTPUT); // setting pin 6 as an output for PWM

}

void loop() {
 int val = analogRead(sensePin); // read the value from the boost pressure sender

 val = constrain(val, 500, 900); // reset the value to within these parameters so I don't end up with values <0 or >255

 int boostoutLevel = map (val, 500, 900, 0, 255) // I've just chosen an arbitrary value for the input here, I don't know exactly at the moment what range the boost pressure sender will give, as I don't have one fitted yet, it is all done with analogue gauges at the moment

 analogWrite(boostoutPin, boostoutLevel); // write whatever boostsense translates to, to the boostout

 if (boostoutLevel > 130) || (boostoutLevel < 90) analogWrite(boostoutPin, 0); // If boost is less than 90 or more than 130, switch the bar off for 500mS
 delay(500);
 analogWrite(boostoutPin, boostoutLevel); // ... and then switch it back on, making the boost nixie bar flash becuase the boost is too high or low
 
 if (boostoutLevel ==0) analogWrite(boostoutPin, 255); //if boost pin is showing zero, switch the bar on to full
 delay(500); //wait 500mS
 analogueWrite(boostoutPin, 0); //... and switch it off again, making it flash on and off to warn that there's a fault
 
}

I'm afraid I don't have a link to the bar tubes, but being nixie tubes they are not being driven directly by the arduino. The tubes are just cold cathode devices - little more than glorified light bulbs with a light-up bar inside which lights up futher along the higher the input voltage (in the 180v range). They will be split with a potentiometer and a fixed resistor for fine tuning, with the arduino PWM pins driving the transistor which controls the supply voltage. Longer PWN signal, more open transistor, bar lights up further along. Drop the PWM, bar light shrinks. Nothing complicated nor difficult in there. For our purposes, we can consider the outputs to be LEDs to simplify things.

I didn't intend on calling them "function 1" etc, I just typed that shorthand to convey my meaning.

Already picked up some more tips from your tutorial, but I did some of the basic things in there in post 1 and 2 where I put down in writing what I wanted the code to do. I know it's a long read, but the answer to the questions you asked me are there, and it does clearly outline what I am trying to achieve.

In the meantime, I'll take out the delay from the loop, and stack voids up as funcitons to call upon (I did not realise you could do that), and then see what I can bash together.

thanks for your patneince. I will refrain from posting on this thread until I have something more coherant to post. :slight_smile:

Interesting project. How robust are Nixie tubes? I've no experience with them but I'd be concerned that vibration in the vehicle might shorten their life - are they as fragile as they look?

I guess I missed your code in all the other text :slight_smile: Sorry.

I suspect it was not there when i wrote Reply #3. For the future please don't make retrospective changes to Posts other than to fix typos.

I am not good at reading code and identifying whether it will work or not. Testing is the key.

I suggest your code will be easier to read if you always use this style, even for short IF statements

if (boostoutLevel > 130) || (boostoutLevel < 90) {
    analogWrite(boostoutPin, 0);
}

And if you plan to have a complex program you should not use delay() anywhere as it blocks the Arduino from doing other things. Use millis() to manage timing as illustrated in Planning and Implementing a Program and in Several Things at a Time

...R

I can assure you it was there, the edit was because I missed out and opening bracket after void. I suspect you skimmed past it (as I often do when reading things) but it is a moot point - you've seen it now :slight_smile:

The program won't be complex, it will just have several near-identical simple functions fot call in sequence. The "complicated" but I guess is the calculations for speed and RPM.

wildbill - yes they are fragile, but anyhting is when treated incorrectly. I'm not at all worried about vehicle vibrations, a simple soft rubber gasket will be enough to stop them being vibrated to death. My bigger worry is having 180v inside my dashboard when I have to reach up inside to find a cable or something hahaha! Besides, driving the car is like being trapped in a phone box with a panicking gorilla, it won't see many road miles!

Hi,
The first question to ask is;
What year was the car manufactured?

Tom..... :slight_smile:

Haha - the chassis is part '72, part '69, the body is 4 different cars, late 50's, '72 and '74, the engine is '72 block with very new internals...

But registered as a '72. Have a look at the build thread on retro rides on the link in post 1.

fall-apart-dave:
I can assure you it was there, the edit was because I missed out and opening bracket after void. I suspect you skimmed past it (as I often do when reading things) but it is a moot point - you've seen it now :slight_smile:

In that case I apologize for my slackness.

The program won't be complex, it will just have several near-identical simple functions fot call in sequence. The "complicated" but I guess is the calculations for speed and RPM.

A series of functions each of which has some delay() in it can quickly lead to a program that is too slow to be useful.

...R

Robin2:
In that case I apologize for my slackness.
A series of functions each of which has some delay() in it can quickly lead to a program that is too slow to be useful.

...R

Yep I understand that. I have already dropped delay() from any voids apart from setup.

Two observations.

  1. Nixie tubes will last about a week in a car environment unless you have really good vibration damping.

  2. You’ve made a lot of progress defining your I/O pin assignments.
    Can you sketch out your wiring/block diagram, as I suspect there will be many ways to simplify your overall design, and make it more reliable - as well as easier to program.

After reviewing your plans, The next step is a detailed schematic, so you have something to ‘hang’ the code on.

I already have a full schematic. The arduino is a last minute substitute. I will upload it when I am back home and have it.

My background is electronics engineer, air force, offshore geophysics, university observatory instruments tech, now back offshore again.

Contrary to most fears nixies actually do ok in automotive applications provided they are damped and sensibly mounted. Their longevity is not a real concern for me.

The pin definitions are the least I can get away with I think without using shift registers. But I am very open to suggestions once the schematic is uploaded.

The assigned pins are just arbitrary at the moment, I will adjust the actual numbers when I get around to it, I know that some need to be A pins,some PWM etc. I will use an arduino mega, might as well for the price.

fall-apart-dave:
from any voids apart from setup.

The correct name for them is "functions"

The word void just tells the compiler that the function will not return any value. You will also see functions defined like

int myFunction() {

so that compiler knows that it will return an int value

...R

With your schematic, a block diagram may also help us visualise how the major components are supposed to relate to each other.

Schematic detail is fine, but it may be premature if simplification of the block can be achieved. Save yourself some extra work!

TBH, it seems like you’re implementing a LOT of wiring that may not be needed. Power supplies are another factor in the block.

Sorry, yes, functions. Coding does seem to be 75% terminology!

I will translate the full schematic into a block diagram, as I said the schematic is already there.

Most of the wiring is taken up by the numerical nixies which require a 4 bit word each to feed the decoder chips. My original play had 10 pins per nixie plus a PWM for each cluster to control brightness but I ditched that in favour of decoders and a potentiometer on the dash.

Diagram to follow, I can draw that off the top if my head since I built the car from the ground up and know every but bolt and weld seam...