Introduction: Working RC Car Speedometer

This is a short project that I created as part of a larger RC build of a Lightweight Land Rover. I decided that I fancied having a working speedometer in the dashboard, but I knew that a servo wouldn't cut it. There was only one reasonable option: deploy the arduino!

A bit of background to begin with... I am not a coding or electronics person. I still think of electricity in terms of water flow and am somewhat mystified by resistors. That said, if even I was able to make this work, then you should be able to as well!


Microcontroller: I used an ATTiny85 chip, which cost about £1 each.

Microcontroller Programmer: In order to get the code onto the chip, you need a way to program it. With regular arduino this is just a USB cable, but for the ATTiny chip, you need something extra. You can use another arduino to do this or, like me, you can use a Tiny AVR programmer from Sparkfun.

I would recommend this, as I've tried programming them with various methods and this one is the easiest. The board is a bit expensive, but a good investment if you do a lot of ATTiny projects.

8 Pin Chip Socket: If you put the chip in a socket rather than soldering it directly, you can afford yourself some mistakes in assembly. Spoken from experience - nobody wants to desolder chips to reprogram them.

Capacitor: A decoupling capacitor of 100nF (code 104) is used. I don't understand quite why, but I read that decoupling capacitors are important on the internet, so it must be true...

Resistor: A 10kΩ resistor is used to pull down the line into the arduino. Again, yet another mystery of electronics.

Perfboard/Stripboard: Some baseboard upon which to assemble your circuit.

Winding Wire: Regular sheathed wire is too thick to solder onto the motor. Using fine enamelled wire will reduce the stress on the motor terminals and make your life a lot easier.

Servo Wire: A three wire ribbon terminating in a 3-pin JR female plug. I got mine from a burnt out servo that I was 'modifying'.

Stepper Motor: I used a 6mm bipolar Nidec stepper motor. Any small stepper should work, although keep them small, as the stepper is being driven directly from the Arduino.

Header Pins: Not essential, but if you wire your stepper to 4 header pins and put a socket on your circuit, you can easily unplug your dashboard for ease of installation.

Computer: To program your board, you'll need a computer. Possibly with the Arduino IDE. And maybe a USB cable. If it has a power cable as well, then even better.

Step 1: The System

The basic outline of the system I created was a method whereby the Pulse Width Modulation (PWM) signal coming from the RC receiver is converted into a stepper motor sweep via an ATTiny 85 microcontroller (uC).

Here is a resource on PWM signals and RC, but to replicate this you don't strictly need to understand it.

The ATTiny is my favorite flavour of Arduino because it's small with still enough I/O pins to do basic things, so fits perfectly into small models and RC projects. The main downside of the ATTiny is that it requires a bit more setup in order to program one, but once you've got it set up they are so cheap you can buy stacks of them for all sorts of projects.

The size of the speedometer dial is too small to have a geared motor with feedback, so in order to have a proportional response a stepper motor had to be used. A stepper motor is a motor that is moved in discrete amounts (or steps...!), which makes it ideal for a no-feedback system like this. The only caveat is that the 'steps' will cause the resultant movement to be jerky as opposed to smooth. If you get a stepper with enough steps per rotation, that isn't noticeable, but with the stepper that I used in this project having only 20 or so steps in a full rotation, the angle jump is quite bad.

The system, on power-up, will run the stepper backwards for two revolutions, so as to zero the needle. The speedometer needs a resting pin where you want the zero mark to be, or else it will just spin forever. Then it maps the forward and reverse PWM signals to a set number of steps of the motor. Easy, right...?

Step 2: The Software

Disclaimer: I'm not a programmer. For this project I am the digital equivalent of Dr. Frankenstein, assembling something working out of various found bits of code.

So, my heartiest thanks go to Duane B, who made the code for interpreting RC signals:

And to Ardunaut, who made the code for running a stepper as an analogue gauge:

And to both, my most sincere apologies for what I did to your code.

Now that's out of the way, here is what to upload to the ATTiny:

#define THROTTLE_SIGNAL_IN 0 // INTERRUPT 0 = DIGITAL PIN 2 - use the interrupt number in attachInterrupt
#define THROTTLE_SIGNAL_IN_PIN 2 // INTERRUPT 0 = DIGITAL PIN 2 - use the PIN number in digitalRead #define NEUTRAL_THROTTLE 1500 // this is the duration in microseconds of neutral throttle on an electric RC Car #define UPPER_THROTTLE 2000 // this is the duration in microseconds of maximum throttle on an electric RC Car #define LOWER_THROTTLE 1000 // this is the duration in microseconds of nminimum throttle on an electric RC Car #define DEADZONE 50 // this is the throttle deadzone. The total deadzone is double this. #include #define STEPS 21 // steps per revolution (limited to 315°) Change this to adjust the maximum travel of the speedometer. #define COIL1 3 // Coil Pins. The ATTiny uses pins 0,1,3,4 for the stepper. Pin 2 is the only pin that can handle interrupts so it needs to be the input. #define COIL2 4 // Try changing these around if the stepper motor doesn't run properly. #define COIL3 0 #define COIL4 1 // create an instance of the stepper class: Stepper stepper(STEPS, COIL1, COIL2, COIL3, COIL4); int pos = 0; //Position in steps(0-630)= (0°-315°) int SPEED = 0; float ThrottleInAvg = 0; int MeasurementsToAverage = 60; float Resetcounter = 10; // time to reset while at idle throttle int Resetval = 0; volatile int ThrottleIn = LOWER_THROTTLE; volatile unsigned long StartPeriod = 0; // set in the interrupt // we could use nThrottleIn = 0 in loop instead of a separate variable, but using bNewThrottleSignal to indicate we have a new signal // is clearer for this first example void setup() { // tell the Arduino we want the function calcInput to be called whenever INT0 (digital pin 2) changes from HIGH to LOW or LOW to HIGH // catching these changes will allow us to calculate how long the input pulse is attachInterrupt(THROTTLE_SIGNAL_IN, calcInput, CHANGE); stepper.setSpeed(50); // set the motor speed to 30 RPM (360 PPS aprox.). stepper.step(STEPS * 2); //Reset Position(X steps counter-clockwise). } void loop() { Resetval = millis; for (int i = 0; i < MeasurementsToAverage; ++i) { ThrottleInAvg += ThrottleIn; delay(1); } ThrottleInAvg /= MeasurementsToAverage; // Forward mapping if (ThrottleInAvg > (NEUTRAL_THROTTLE + DEADZONE) && ThrottleInAvg < UPPER_THROTTLE) { SPEED = map(ThrottleInAvg, (NEUTRAL_THROTTLE + DEADZONE), UPPER_THROTTLE, 0, 255); Resetval = 0; } // Reverse mapping else if (ThrottleInAvg < (NEUTRAL_THROTTLE - DEADZONE) && ThrottleInAvg > LOWER_THROTTLE) { SPEED = map(ThrottleInAvg, LOWER_THROTTLE, (NEUTRAL_THROTTLE - DEADZONE), 255, 0); Resetval = 0; } // Out of range upper else if (ThrottleInAvg > UPPER_THROTTLE) { SPEED = 255; Resetval = 0; } // Out of range lower else if (ThrottleInAvg < LOWER_THROTTLE) { SPEED = 255; Resetval = 0; } // Deadzone else { SPEED = 0; int t2 = millis; if ((t2 - Resetval) > Resetcounter) { stepper.step(4); // I'm trying to tell the stepper to re-reset itself if the RC signal is in the deadzone for a long time. Not sure if this part of the code actually works. } } int val = SPEED ; //get the potentiometer value (range 0-1023) val = map(val, 0, 255, 0, STEPS * 0.75); // map pot range in the stepper range. if (abs(val - pos) > 2) { //if diference is greater than 2 steps. if ((val - pos) > 0) { stepper.step(-1); // move one step to the left. pos++; } if ((val - pos) < 0) { stepper.step(1); // move one step to the right. pos--; } } // delay(10); } void calcInput() { // if the pin is high, its the start of an interrupt if (digitalRead(THROTTLE_SIGNAL_IN_PIN) == HIGH) { // get the time using micros - when our code gets really busy this will become inaccurate, but for the current application its // easy to understand and works very well StartPeriod = micros(); } else { // if the pin is low, its the falling edge of the pulse so now we can calculate the pulse duration by subtracting the // start time ulStartPeriod from the current time returned by micros() if (StartPeriod) { ThrottleIn = (int)(micros() - StartPeriod); StartPeriod = 0; } } }

Refer to this for more information on programming an ATTiny85:

Step 3: The Hardware

Refer to the circuit diagram for building the circuit. How you assemble it is up to you, but I'd suggest using a bit of stripboard/perfboard used for circuit board prototyping, and mounting the chip in a socket.

C1 = 100nF

R1 = 10kΩ

The capacitor should be mounted as close to the chip as possible to be most effective.

When soldering the enamelled wires to the motor, be extremely careful, as the terminals on the motors like to snap off and sever the coil wire to the motor. To remedy this, a good solution is to solder the wires on, and then put a big blob of 2-part epoxy over the joint, , let it cure, then twist the wires together. This reduces stress on individual terminal joints and should stop them snapping off. If you don't do this, they will snap off at the least convenient time, guaranteed.

If you make the header pin connector, and set up the pins thus: [Ca1, Cb1, Ca2, Cb2] with Ca1 standing for Coil A, wire 1 etc. This allows you to change the rotational direction of the gauge by swapping the plug around.

The gauge will need an endstop to calibrate the zero position against. I'd recommend making the needle out of metal if possible. This stops it flexing when it hits the endstop. A way to get the needle in a good position is to temporarily glue the needle to the axle, power up the module, let it come to rest, and then remove and re-glue the needle on the axle, with the needle resting against the endstop. This aligns the needle with the magnetic cogging of the motor, and ensures that your needle should always come to rest against the endstop.

Step 4: Epilogue

Hopefully you've enjoyed this brief instructable, and found it useful. If you build one of these, do let me know!

Good luck!