Not like the bird

Building an iRacing Telemetry Display Part 1 - Intro & A Hardware Shift Indicator

Overview

As somebody who doesn’t particularly love driving or care about cars in the real world I’ve found myself in the odd position lately of being very invested in both Formula 1, and driving simulation games.

I started watching Netflix’s Formula 1 show, Drive To Survive (ignore the dumb title, it’s very fun) around Thanksgiving of last year, just in time to miss all but the last race of the season. As I spent the off-season learning more about the sport and watching its forays into e-sports as they rushed to put together an actual calendar in the year of Covid-19, this interest gradually grew to encompass the virtual aspects of it as well.

Motorsport lends itself uniquely well to simulation, and soon I found myself down that rabbit hole as well. While the licensed F1 games are a lot of fun, the main game I found myself focusing on was iRacing.

iRacing is generally speaking the most popular of the heavily simulation-focused driving games. It’s online only (with an actually fairly interesting way of ranking players that incentivizes careful driving over pure performance), and heavily focused on simulating the actual experience of driving at top speed around a racetrack. This isn’t Mario Kart where if you take your finger off the accelerate button something’s gone wrong. In a race you’ll be managing your tyre temperatures vs. the temperature of the track, optimizing your shifts and braking points to shave tenths of seconds off your lap times, and at the higher levels adjusting performance aspects of your car like the balancing of braking force between your front and rear tyres on the fly.

If you’ve got a certain proclivity, this all starts to look like an opportunity to collect and display data. After all, you can’t improve what you don’t measure. Another factor here is that of your field of view (FoV).

In a driving simulator, it’s very important that your view through the monitor matches what you’d expect to see if you were sitting in the driver’s seat of that car. While you might think that having as much visibility as possible would give you an edge, really what you want is to trick your brain as much as possible into processing the depth cues of the moving scenery as if you were there in the world. In the process of doing so though, you’ll commonly find yourself in a situation where your racing wheel blocks the dashboard of the car you’re driving which is the source for any “diegetic” telemetry.

So taking all of the above into consideration, what I’m trying to accomplish is that I have some representation in the real world of all of the information I can pull from the virtual world about how to run a better race. This telemetry hardware will allow me to see, unimpeded, all of the relevant first-order data about the state of my car (e.g. when I need to shift, what gear I’m in), as well as potentially some computed data (e.g. did I start braking before/after I have on past optimal laps in any given turn).

Inspirations

The main thing that I’ve got stuck in my mind when I think about this project is a Formula 1 steering wheel.

Formula 1 steering wheel

If it wasn’t enough that F1 drivers are charging across the generally European countryside at 200+ km/h pulling 5Gs through turns, they’re also effectively operating a Bop It changing settings of their car on the fly based on telemetry streamed back to race engineers lining the pit lane (this information is unidirectional, unless there’s any rule breaking happening the race engineers aren’t allowed to remotely control any part of the car).

The caliber of racing I’m doing doesn’t give me nearly as many things to worry about, and frankly if I did I don’t think the lack of a capable wheel would be what was holding me back, but on the other hand I think it looks cool and I want one.

There are companies that make F1 style wheels for simulators, but they’re all much more expensive than I’m willing to shell out for so I haven’t done too much research into what the options are, preferring to come at it from first principles myself.

Building a Shift Indicator

The first thing that I wanted to tackle in this project was building a shift indicator. Aside from the usual gearing concerns of being in lower gears to get the car moving and higher gears to achieve higher speeds, when racing you’ll often be strategically downshifting and upshifting in quick succession to maximize maneuvering ability in a corner or to stabilize the car on an off-camber turn. It’s very important to be ambiently aware of when the motor has hit an optimal point for upshifting, and the shift indicator plays an important part in this. Unfortunately, it’s also one of the elements of the dashboard that usually winds up blocked as a result of the FoV concerns mentioned above.

So here was the first part of this project, using LEDs I wanted to bring the shift indicator (a row of lights ranging from green on the left to red to blue on right, which light up left to right, which is full illuminated when the engine hits its optimal shifting RPM) into the real world.

Tools

So now that LEDs are in the picture, I needed something to control them. Part of this whole project is that for as much of my time as I spend dealing with computers, I don’t really have much knowledge to speak of when it comes to electronics. I had a class on logic design in college, but apart from an ill-fated attempt to build a “party box” out of abandoned electrical detritus at the school’s circuitry lab I had never actually built anything akin to this.

My first step was purchasing an Arduino starter kit (which included the Arduino Uno), which actually turned out to have almost everything I needed for this. The other thing I purchased was a 74HC595 shift register, which I’ll get to in a bit.

Building the First Shift Indicator

My very first, “Hello, world!” version of this was to simply tie each positive LED leg to a digital output on the Arduino, with the negative leg being tied to ground through a resistor.

Once you have all of the LEDs wired up, it’s as simple to turn them on and off as

int LED_1 = <The pin you connected the first LED to, eg. 13>;
/* etc for each additional pin */

void setup() {
  pinMode(LED_1, OUTPUT);
}

void loop() {
  digitalWrite(LED_1, HIGH);
  delay(1000);
  digitalWrite(LED_1, LOW);
}

Sending Data to the Shift Indicator

Now that we have a very simple way to light up some LEDs, we need to figure out how many of them should be lit up at any point in time. I was expecting this to be a whole project in itself, but it turned out that somebody had already made a python library for accessing iRacing’s telemetry, which handily includes a ShiftIndicatorPct (which is marked as deprecated, but seems to work fine for the time being).

Using this library goes a bit against the first principles approach I was trying to take, but the library itself pretty straightforwardly reads from a memory mapped file and just handles knowing which values are at which locations. That’s not to discount it, it’s a super useful piece of code but it didn’t seem like there was much to learn from reimplementing it.

Given the above, we now need a way of transmitting that indicator percentage to the Arduino, which we can do by writing it over the serial port the Arduino establishes with the computer over USB.

From Python, we can establish a connection and write data in the form of a binary string into it, e.g.

import serial

ser = serial.Serial('COM5', 115200)
# The first argument to Serial is the name of the port
# your Arudino has open to your computer. You can
# find it in the Arduino IDE. The second argument
# is the baud rate which is the rate of data transfer
# across the port.
ser.write(b'1')

Combinding this with the Python SDK, it’ll look something like the following. Note that the shift percentage reported doesn’t map linearly to to the number of LEDs illuminated. I tried to match this up with the Mazda MX5 in iRacing, but it’s likely that each car has a slightly different mapping here. One future enhancement I’d like to make is to allow a curve to be defined for each car based on RPM.

import irsdk
import serial
import time

ir = irsdk.IRSDK()
ir.startup()

ser = serial.Serial('COM5', 115200)

while True:
  data = b''

# I'm using letters to encode this just so that it's easier
# to express two digit numbers

  pct = ir['ShiftIndicatorPct']
  if (pct == 0.0): data += b'a'
  if (pct > 0.0 and pct < 0.05):  data += b'b'
  if (pct >= 0.05 and pct < 0.15 ):  data += b'c'
  if (pct >= 0.15 and pct < 0.238):  data += b'd'
  if (pct >= 0.238 and pct < 0.33):  data += b'e'
  if (pct >= 0.33 and pct < 0.43):  data += b'f'
  if (pct >= 0.43 and pct < 0.53):  data += b'g'
  if (pct >= 0.53 and pct <  0.62):  data += b'h'
  if (pct >= 0.62 and pct < 0.715):  data += b'i'
  if (pct >= 0.715 and pct < 0.76):  data += b'j'
  if (pct >= 0.76): data += b'k'

  ser.write(data)

  time.sleep(0.01)

On the Arduino side, you can read by polling against that port and pulling out any new data one byte at a time (this approach is clunky, but we’ll be moving away from it soon).

char shiftLeds = '';
boolean newData = false;

void setup() {
  /* pin setup from above */
  Serial.begin(9600);
}

void loop() {
  getData();
  if (newData) {
    updateRevMeter();
  }
}

void getData() {
  if (Serial.available() > 0) {
    shiftLeds = Serial.read();
    newData = true;
  }
}

void updateRevMeter() {
  if (shiftLeds == 'a') {
    digitalWrite(LED_1, LOW);
    digitalWrite(LED_2, LOW);
    ...
  }
  if (shiftLeds == 'b') {
    digitalWrite(LED_1, HIGH);
    digitalWrite(LED_2, LOW);
    ...
  }
}

Disadvantages of This Approach

At this point we have a fairly functional shift indicator, but there’s definitely room to improve.

It’s annoying having to individually address each LED to go into each state (you could loop through the pins to save having to address each manually or could potentially do some logic based on representing the light states as binary strings and XORing the old state with the new state to determine which LEDs need to change state but either way it’s still janky), and by tying each LED to its own digital pin on the Arduino we’ve now used up almost all of our outputs (the Uno has 14).

Using a Shift Register

I mentioned above having purchased a shift register alongside the Arduino, and here’s its time to shine. What a shift register allows you to do is multiplex a pin by sending a bunch of bits in (using a clock signal to distinguish the bits from eachother), then invoke a latch to have it hold that state.

Effectively, you can write a number 0-255 (e.g. 00000000 to 11111111) to the shift register with the latch pin low, then take the latch pin high and that value will result in the corresponding outputs on the shift register going low for a 0 or high for a 1. This means that instead of needing one pin for each LED, we can drive 8 of them with 3 pins (the latch, the clock, and the data pins). There are other shift registers that can support more outputs, but I wanted to do a proof of concept before moving on.

With this new approach, our python script can stay the same but on the Arduino side our code will look something like

int latchPin = 10; /* connected to ST_CP pin of register */
int clockPin = 11; /* connected to SH_CP pin */
int dataPin = 12; /* connected to DS pin */

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop() {
  /* same as above */
}

void updateRevMeter() {
  if (shiftLeds == 'a') { shiftOut(dataPin, clockPin, MSBFIRST, 0); }
  if (shiftLeds == 'b') { shiftOut(dataPin, clockPin, MSBFIRST, 1); }
}

There’s still a bit we could do to tidy up having to have a conditional case for each character we received, but you can already see this making life a bit easier.

Conclusion

So we’re not quite at an F1 wheel yet, but starting to see some of this digital information break out to the real world is weirdly satisfying.

As much fun as building this in hardware was, as soon as my fascination with seeing the LEDs light up wore off, I realized I was going to need to take a different approach to get the level of information density I was looking for. Even with the shift register reducing the number of pins I needed, to get a display running as well would take up a bunch of these pins and that’s even before wiring up any buttons or other controls. I might come back around to this hardware approach for displaying the shift indicator as I develop this project further, but my next step was to try to replicate it on an LCD, which I’ll be discussing in Part 2 of this series.


Some thoughts and words by Dan Segal. No analytics here, so say hi!