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:

Leave a Reply

Your email address will not be published. Required fields are marked *

Personal Portfolio