Build · Test · Model · Evaluate · Connect 12 essays · 6 projects
Building & modeling systems

Wireless Race Car

Getting Involved

One of the things I regret about my university days is spending too much time playing video games and not getting involved in enough student societies. So when I started my masters program last year, I crammed in as many societies as I could. There were weekly social sessions at the chess society and twice-weekly sessions for autonmous driving. Although I had spent a lot of time driving in wheel-to-wheel racing, I hadn't done much of the building. I joined the Bristol Racing society's electronics team to help with the brain of the car -- a battle-worn but functional Arduino serving as the ECU.

The team participated in the electric Greenpower Formula 24 class. These are single-motored, battery-powered cars with custom chassis. No screaming V12 engine -- just the whine of the motor. The eerie silence reminds me of the time I first sat in a Tesla.

Our next race was at the famed Goodwood Motor Circuit. The electronics team's task was to implement a communication module to stream the car's telemetry data. This would allow us to make better decisions on track. The clock was ticking.

How It Works

The vehicle was powered by an Arduino Mega which controlled the motor and tracked sensor data. As such, we used an Arduino UNO as a receiver and used a LoRa RFM95 module. It was a bit finnicky to get everything working but there's nothing like a hard deadline of a race to push you along.

LoRa wiring diagram

Image courtesy of https://randomnerdtutorials.com/esp32-lora-rfm95-transceiver-arduino-ide/

LoRa Receiver Code

// Arduino9x_RX
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messaging client (receiver)
// with the RH_RF95 class. RH_RF95 class does not provide for addressing or
// reliability, so you should only use RH_RF95 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example Arduino9x_TX

#include <SPI.h>
#include <RH_RF95.h>

#define RFM95_CS 10
#define RFM95_RST 9
#define RFM95_INT 2

// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 434.0

// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);

// Blinky on receipt
#define LED 13

void setup() 
{
  pinMode(LED, OUTPUT);     
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);

  while (!Serial);
  Serial.begin(9600);
  delay(100);

  Serial.println("Arduino LoRa RX Test!");
  
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    while (1);
  }
  Serial.println("LoRa radio init OK!");

  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  if (!rf95.setFrequency(RF95_FREQ)) {
    Serial.println("setFrequency failed");
    while (1);
  }
  Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);

  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on

  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
  rf95.setTxPower(23, false);
}

void loop()
{
  if (rf95.available())
  {
    // Should be a message for us now   
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    
    if (rf95.recv(buf, &len))
    {
      digitalWrite(LED, HIGH);
      RH_RF95::printBuffer("Received: ", buf, len);
      Serial.print("Got: ");
      Serial.println((char*)buf);
       Serial.print("RSSI: ");
      Serial.println(rf95.lastRssi(), DEC);
      
      // Send a reply
      uint8_t data[] = "And hello back to you";
      rf95.send(data, sizeof(data));
      rf95.waitPacketSent();
      Serial.println("Sent a reply");
      digitalWrite(LED, LOW);
    }
    else
    {
      Serial.println("Receive failed");
    }
  }
}

LoRa Sender Code

The sender code is integrated with our main codebase. It combines the various sensor outputs into a comma-delimited string and broadcasts the string via LoRa. It tracked metrics like temperature, voltage, and current. The GitHub commit can be found here.

Field Testing

After trialing the kit at the lab, we eagerly brought out the prototype to Goodwood. Although it's a lot quieter than what I'm used to, there's still something special in the air. As cars rolled up to the start/finish line, messages from our race car started trickling in. Once the cars got going, we quickly found that the signal dropped once the car was not in direct line of sight. However, we still got a few pings as the car passed pit lane. As I relayed the data to our team, I made a mental note to look for ways to extend the range.

At some point, we started getting incredibly good signal from the race car and were wondering what happened. It turns out the chain fell off and the driver had to pull over at pit out. Oops. We ended the day with a DNF but at least the electronics held up the whole time!

← Back to Writing & Projects