Steering Wheel – Electronic Design and Assembly

The task of designing the PCB, to be mounted to the steering wheel assembly, proved to be tough. With only some experience from GCSE electronics, this part of the project was accompanied with a steep learning curve.

Through recommendation on a number of Arduino forums, the software of choice was Eagle v6 from CadSoft. This is a powerful tool with an extensive library of electronic components. The user interface wasn’t the most user friendly, instead relying on keyboard shortcuts. Although it was easy to learn by following a selection of YouTube tutorials by Jeremy Blum.

The size of the board (51mm x 110mm) was dictated by the space available between the top of the steering wheel and the steering boss, leaving adequate clearance for covers. Placement of the display components upon the board were already decided. This left only the other electrical components and tracks to be placed and routed.

The board was fabricated by Spirit Circuits and their free 48-hour prototyping service “Go Naked”. They provided us with a two sided aluminium coated board with just the essentials, tracks and holes. Their customer service was exceptional in helping to meet our needs. We promptly received a professional high quality board that only required cutting to size prior to use.

Initially the assembly and soldering of the electronic dash went to plan, with the majority of the components fitting without a problem. A couple of overlooks included; attachment of the switch and warning LED, placement of the ribbon cable and selection of connectors.

When linked to the Arduino and powered for the first time, all of the display lit up and ran through the start-up test routine a few times. But before too long, it was noticeable that the display was not functioning entirely correctly. The central RPM range LED failed to blink and the LCD display would occasionally flicker with random characters. This suggested that either a faulty component had been used or the board was shorting. After inspecting the soldering and replacing components, it was found that a grounding fault was the cause. Although it’s exact location was unknown.

While it was disappointing that the display did not work correctly. It proved to be a great prototype and provided us with a number of improvements for the next revision. The main points being; bigger tracks, pads and clearances along with smaller components.

Next… Steering Wheel – Final Assembly

Steering Wheel – rFactor

Testing the electronic dash using rFactor required a method of transfering the game data to the Arduino. Fortunately creating custom displays for racing simulators has become a popular DIY hobby over the last few years. Therefore there are plenty of examples to be found online of various approaches. A great project by João Ubaldo provided detailed information and instructions for creating the display.

The Arduino is connected to the computer using a USB connection. Data is taken from the game using a plugin; converted to serial and then transfered to the Arduino where it is re-assembled. Initially I used the plugin rfactor2python which provided a great basis to improve on. This plugin is easy to install and modify as it is written using a language called python.

Breadboard assembled electronics being tested using rFactor for input data
Reposted image because there’s a metric **** tonne of code to follow…

As I was using an LCD in addition to the 7 segment display and range of LEDs, a larger amount of data needed to be converted and transfered. This resulted in the following python plugin which breaks everything into byte sized chucks.

Python Serial

rfactor2python - UWE Racing Electronic Dash Test
Credit: Joao C. <>
Author: D. Nicklin <>

This example uses PySerial ( module.

#  Configuration
# Look inside RF2PyPlugin.__init__
#  /

import serial
import struct

class RF2PyPlugin(object):
    def __init__(self):
        self.PORT = "COM4"
        self.BPS = 9600
        self.RPM_LED_COUNT = 7  # number of LEDs to display RPMs

        self.ser = None

    # game startup
    def Startup(self):

    # game shutdown
    def Shutdown(self):

    # entering realtime (where the vehicle can be driven)
    def EnterRealtime(self):
        self.ser = serial.Serial(self.PORT,self.BPS)

# exiting realtime
    def ExitRealtime(self):

    # session started
    def StartSession(self):

    # session ended
    def EndSession(self):

    # update plugin with scoring info (approximately once per second)
    #   'info' is a dictionary with scoring data
    def UpdateScoring(self, info):

    # update plugin with telemetry info
    #   'info' is a dictionary with telemetry data
    def UpdateTelemetry(self, info):
        g = info["mGear"]
        r = info["mEngineRPM"]
        mr = info["mEngineMaxRPM"]
        wt = info["mEngineWaterTemp"]
        ot = info["mEngineOilTemp"]

        val = struct.pack("I", r)
        g = g & 0xFF


    # See if the plugin wants to take over a hardware control.  If the plugin takes over the
    # control, this method returns true and sets the value of the float pointed to by the
    # second arg.  Otherwise, it returns false and leaves the float unmodified.
    # Important: fRetVal is a list with only one value.
    # In order to modify this value you should do something like:
    # fRetVal[0] = newValue
    def CheckHWControl(self, controlName, fRetVal):
        return False

The Arduino code reads the incoming serial data, byte by byte, these are placed into a buffer. The gear value is too large for a single byte and instead is split into four. The displays are refreshed every time it recieves a newline character. A little maths is performed to ensure that the full LED RPM range is utilised.

Arduino Sketch

Daniel Nicklin
Formula Student Steering Wheel Display

Reads data from rfactor using a python plugin
Dsplays important variables on LCD, 7 segment and range of leds
Created 26/03/2013
Last Modified 26/03/2013
  void setup() {
  // Setup - run once

  void loop() {
    while (Serial.available()) {
   char c = (char);
    //Check for end of carriage
    if (c == '\n') {
  //asign values to variables
   rpmMaxValue =(buffer[0]*1000);
   newGear = constrain(buffer[1], -1, 7); // gear value between -1 (R) and 7
   oilTemp = byte(buffer[2]);
   waterTemp = byte(buffer[3]);

    //Convert bytes in buffer to long integer for currrent rpm value
    union u_tag {
    byte b[4];
    unsigned long ulval;
    } u;
    u.b[0] = buffer[4];
    u.b[1] = buffer[5];
    u.b[2] = buffer[6];
    u.b[3] = buffer[7];
    newRpmValue = u.ulval;

   //rpmLedLevel = (newRpmValue/(rpmMaxValue/7));

   // Sets rpm value range for which leds are lit
   rpmLedStart = rpmMaxValue*0.8; //Sets minimum

   rpmMaxValue = rpmMaxValue * 1.05; //Game max value is set too low

   rpmLedLevel = map(constrain(newRpmValue,rpmLedStart,rpmMaxValue), rpmLedStart, rpmMaxValue, 0, 7);

      //Update shift registers if current gear or rpm has changed
   if (newGear != gear || newRpmValue != rpmValue){
       digitalWrite(latchPin, LOW);             //Pull latch LOW to start sending data
       shiftOut(dataPin, clockPin, MSBFIRST,gearArray[newGear]);          //Send the data
      shiftOut(dataPin, clockPin, MSBFIRST,rpmArray[rpmLedLevel]);          //Send the data
      digitalWrite(latchPin, HIGH);            //Pull latch HIGH to stop sending data
      gear = newGear;
      rpmValue = newRpmValue;
      pos = 0; //Reset position in buffer to start
    else {
      //Add serial read data to buffer and increment position
      buffer[pos] = c;
       //check buffer size has not been exceeded
       if (pos >= sizeof(buffer))
         pos = 0;

I attempted to display only the important sections of code in this post as there is rather a lot of it! I have attached the full code included start-up tests as a ZIP file.  I’d be happy to know if you use this project and it’s code for Formula Student or a racing simulator of your own.


Personal Portfolio