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. <me@joaoubaldo.com>
Author: D. Nicklin <danicklin.co.uk>

This example uses PySerial (http://pyserial.sourceforge.net) 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):
        pass

    # game shutdown
    def Shutdown(self):
        pass

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

# exiting realtime
    def ExitRealtime(self):
        self.ser.close()

    # session started
    def StartSession(self):
        pass

    # session ended
    def EndSession(self):
        pass

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

    # 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

        self.ser.write(chr(int(mr/1000)))
        self.ser.write(chr(int(g)))
        self.ser.write(chr(int(ot)))
        self.ser.write(chr(int(wt)))
        self.ser.write(val)
        self.ser.write('\n')

    # 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

/*
UWE RACING
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
  //Serial
  Serial.begin(9600);
  }

  void loop() {
    while (Serial.available()) {
   char c = (char)Serial.read();
    //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;
      pos++;
       //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.

Requirements:

Personal Portfolio