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 did things a bit differently. Given my interest in racing, the Bristol Racing society was my top choice. There, I joined the electronics team to help primarily with the brain of the car -- a battle-worn but functional Arduino.
The team participated in the electric Greenpower Formula 24 class. These are single-motored, car battery-powered cars with custom chassis. It's a bit different when the cars aren't screaming with engine and turbo noise but racing is still racing. There's something special in the air that makes my heart race.
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 the pressure of an upcoming race was enough motivation. Later, we found that this was much easier to implement on a ESP32 when preparing for the next iteration of the race car.

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 the famed Goodwood Motor Circuit. 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. This was enough for peace of mind.
At some point, we started getting incredibly good signal from the race car and were wondering what happened. Turned out the chain fell off and the driver had to pull over at the end of pit exit. Oops. We ended the day with a DNF but at least the electronics held up the whole time!
