Moto Gymkhana Gate Timer




This is an Arduino-based timer that uses an infrared retro-reflector sensor. I've spent maybe 10-15 hours learning the basics of coding for Arduino, and I've put the code together with the help of ChatGPT (it took quite a few iterations to get it working). The project is inspired by this Instructables project, which is a fair bit more complicated than mine, and it does things I didn't need it to do.
I got it all working with an Arduino Uno R4 board, then jumped over to an Arduino Nano R4 (not straightforward - more on that later).
You can probably use any retro-reflective sensor with the code (it's just a HIGH/LOW signal), but you'll need the same LED display with the backpack for it to work.
You'll need to make yourself a case. I used a waterproof junction box, and quite a few bits I had lying around for the internal structure. I also machined some long brass stand-offs on my lathe for the display. I'm sure there are lots of different ways you could do this (cut a hole and mount it to the case, for example).
You'll also need to do a little bit of soldering (the backpack needs soldering to the display, and it also either needs pins soldering, or wires).
Supplies

At the heart of it, you just need:
- An Arduino board (I'd recommend a rev 4 as they have a wider voltage range which removes the need to step the voltage down). Linked to The Pi Hut, but also available on Amazon.
- The Adafruit 1.2" 4-Digit 7-Segment Display with an I2C Backpack.
- The Velleman PEM10D Retroreflective Photo Sensor 12-240V (comes with the reflector).
I also bought:
- A PD-capable USB-C battery pack.
- A right-angled USB-C adapter (the case wasn't wide enough to plug it in).
- A USB-C to barrel-jack cable, PD-capable.
- A Nano extension board for the Arduino Nano.
- A junction box as my case.
Things I had and didn't need to buy:
- Scraps of aluminium for the internal structure.
- Brass bar for the stand-offs.
- Small plastic stand-offs for the Arduino board.
- Bits of rubber to cushion the battery.
- A 1/4" cheese plate and 1/4" fasteners so I could attach a small tripod (this sort of thing).
- Wires from an Arduino starter kit.
- A soldering iron.
- A lathe!
- A couple of table top tripods.
Wiring






Let's start with power:
Cut the barrel jack off the USB-C cable. The positive goes to the VIN connector on the Nano extension board. The Velleman sensor also needs power, so it's easy to attach its brown wire to the same connector. The ground from the USB-C cable goes to the GND connector next to the VIN connector. Attached the Velleman's blue wire here also. That's power sorted.
Next the Velleman's signal wires:
The grey wire (5, NC) isn't needed at all. The black wire is signal ground. I attached that to the spare GND on the opposite side of the board to the VIN. The white COM wire goes to D2.
Finally the AdaFruit's backpack:
It's worth taking a look at AdaFruit's wiring tutorial.
The ground goes to the same signal ground connector as the Velleman (the Nano board has fewer GNDs than the Uno board). Vin goes to the Nano extension board's 5v connector. VIO is bridged and also goes to the extension board's 5v connector. SCL and SDA should have been straightforward, but weren't, so they get their own section.
The SCL and SDA pins:
On the Arduino Uno R4 board this was simple. Straight to the SCL and SDA pins on the board. But that didn't work on the R4 Nano board. They're there (as pads to solder to, and as a micro-connector thingy), they're labelled as SCL and SDA, but they don't work. After a fair bit of digging, I found this page. You have to use pin A4 for SDA, and pin A5 for SCL.
The Code
The code is below as an .ino file. Written (with a lot of help from ChatGPT) and uploaded with Arduino IDE.
There are some things you can change:
- The display's brightness can be altered (already set to max).
- There's a lock-out after the timer starts and stops (to prevent accidental triggering as the rest of your bike passes through the IR beam). It's set to four seconds (4000 milliseconds in the code).
There might be some bits in there that are unnecessary. ChatGPT suggested blinking the colon while the device is in lock-out. This seemed like a good idea, but it doesn't work. I think it's because the display is refreshed with every change of digit in a different loop of the code, so it's effectively ignored.
Here's the code if you want to study it:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include "Adafruit_LEDBackpack.h"
// CONFIG
const int sensorPin = 2; // Digital pin for sensor
const unsigned long lockoutMs = 4000; // Ignore further triggers for 4s
const unsigned long dotBlinkMs = 500; // decimal blink during lockout
Adafruit_7segment matrix = Adafruit_7segment();
bool timerRunning = false;
unsigned long startTime = 0;
unsigned long elapsedTime = 0;
unsigned long lastTriggerTime = 0;
void setup() {
pinMode(sensorPin, INPUT); // use INPUT_PULLUP if sensor is open-collector
matrix.begin(0x70); // I2C addr
matrix.setBrightness(15);
showTime(0, true); // start with 00.00
}
void loop() {
static int lastState = LOW;
static bool dotState = true;
static unsigned long lastDotToggle = 0;
int sensorState = digitalRead(sensorPin);
unsigned long now = millis();
// Rising edge detection
if (sensorState == HIGH && lastState == LOW) {
if (now - lastTriggerTime >= lockoutMs) {
lastTriggerTime = now;
if (!timerRunning) {
timerRunning = true;
startTime = millis();
} else {
timerRunning = false;
elapsedTime = millis() - startTime; // freeze
}
}
}
lastState = sensorState;
// Update elapsed time if running
if (timerRunning) {
elapsedTime = millis() - startTime;
}
// Blink decimal point if in lockout
bool dotOn = true;
if (now - lastTriggerTime < lockoutMs) {
if (now - lastDotToggle >= dotBlinkMs) {
dotState = !dotState;
lastDotToggle = now;
}
dotOn = dotState;
}
showTime(elapsedTime, dotOn);
}
// --- Show SS.hh on 4-digit display ---
void showTime(unsigned long ms, bool dotOn) {
int hundredths = (ms / 10) % 100; // 0-99
int seconds = (ms / 1000) % 100; // 0-99
int tensSec = seconds / 10;
int onesSec = seconds % 10;
int tenths = hundredths / 10;
int hundths = hundredths % 10;
matrix.clear();
matrix.writeDigitRaw(2, 0x02); // add colon RJ
matrix.writeDigitNum(0, tensSec);
matrix.writeDigitNum(1, onesSec, dotOn); // decimal point after seconds
matrix.writeDigitNum(3, tenths);
matrix.writeDigitNum(4, hundths);
matrix.writeDisplay();
}
Build Your Case



You're on your own here as you probably don't have access to exactly the same tools and materials I had. Use a 3D printer? LEGO? Meccano? Some twigs and some gaffer tape? Best of luck!
(Draw the rest of the fucking owl).