Trueline: a BMX race timing system built on the SparkFun M7E Hecto + UHF antenna (working prototype)

Hi everyone,

I’d like to formally introduce a project I’ve been building for the past months: Trueline, a timing system for BMX racing in Chile. Its core is the SparkFun Simultaneous RFID Reader (M7E Hecto) and a SparkFun UHF RFID antenna.

Why

BMX timing in Chile, as in much of the world, is dominated by MyLaps. It’s the industry standard, but here the experience has been rough. Clubs and riders regularly deal with wrong times, mixed-up positions and incorrect rider data. Clubs are fed up with it, and the federation has no real alternative to turn to.

So yes, this is very much a David vs. Goliath story: one independent developer with an Orange Pi and a SparkFun reader going up against the company that defines race timing. I know how that sounds, and I’m determined to win it anyway. I don’t plan to win by copying their system. I want to build something clubs can afford, run themselves and trust. No BMX track here has its own internet connection, so it also has to be portable and offline-first.

What’s in the case

  • Orange Pi 5 Pro (RK3588S) running Armbian, with a Python/FastAPI backend and a local SQLite database.
  • SparkFun M7E Hecto, driven over USB with python-mercuryapi. The RF chain is u.FL → RP-SMA → a 4 m RG-58 cable → the SparkFun antenna at the finish line.
  • Reed switch on the start gate. It marks the exact moment the gate drops, and that’s t0 for every rider in the heat.
  • A potentiometer read through an ADS1115, for adjusting read power in the field.
  • A 7" touchscreen kiosk on the case, so the coach can pick riders, set up a heat and watch arrivals live without a phone or laptop.
  • Offline-first sync: times are stored locally and pushed to the cloud whenever the case finds a connection.

How the reader is used

  • Tags go on the bike’s fork, not on the helmet or number plate, so we’re working with on-metal UHF tags close to the ground.
  • Dynamic power management: the reader sits in standby at 0 dBm and only goes to full power about 25 s after the gate drops, shortly before riders actually reach the finish. It drops back to standby when the heat closes. Power settings are never written to NVM.
  • An EPC de-duplication buffer, so one tag crossing the line is registered as exactly one arrival.
  • Module temperature is polled in the background and logged.
  • A manual fallback always exists: the coach can mark the start and arrivals by hand if any sensor fails.

Current status

This is still a working prototype. The software (rider management, training sessions, the race-day heat sheet and season ranking) is already in use, and the case hardware is assembled and running on the bench. Photo-finish is not built yet. I still need to buy a high-speed camera for it, because RFID alone won’t settle close finishes in a sport where riders cross the line inches apart.

Next steps

In the coming weeks I expect to start testing on a real track with real riders. I’ll share the results here, including read rates per tag with a full gate of 8 riders, read-zone shape and how arrival order compares to what the judges see, along with any problems that come up. Technical feedback from people who have used the M7E in fast-moving, multi-tag scenarios would be really valuable, especially about antenna placement and reading tags on metal.

Thanks to SparkFun for making this kind of hardware accessible. It’s what made a project like this possible for a small, independent developer.

Felipe, Trueline (Chile)

One thing I’m curious about is how you’re recording the arrival times when several riders cross almost together.

Are you using the timestamps from the M7E itself, or the time when Python receives each tag read?

I wonder if USB buffering or the order in which reads are processed could make two riders appear in the wrong order, even when both tags are detected correctly.

Since you already have EPC deduplication, it might be useful to keep the raw read timestamps and RSSI values during testing before collapsing everything into a single arrival per rider.

That could also help you understand how much the antenna’s detection zone affects the recorded times.

Have you tested that part with two tags passing through the field close together yet?

Nice project, and good reason to start it, when clubs are angry with the standard you have market. Question from production side: do you plan to keep SparkFun boards inside for club units, or make own PCB around the M7E? For first 5-10 units modules are fine, but then module price and stock become main cost and risk. Worth to know second source early.

How many units clubs ask for first season?

Thanks, this is a really useful question, and it made me go back and check the code.

Short answer: no, I haven’t tested it yet. The M7E hasn’t arrived, so the driver has never run against real hardware, and that includes two tags passing together.

Your concern is valid. Right now the daemon ignores the timestamp from the read and uses the host time when it reports the arrival, and that time only has one-second resolution. It also keeps only the latest tag between 150 ms polls, so two riders crossing together could overwrite each other. On top of that, deduplication keeps the first read and throws away the rest, including the RSSI.

So I’m going to change it before testing. Every read will go into a queue and be logged raw (EPC, RSSI, reader timestamp, host monotonic time), and deduplication will happen afterwards, as a separate step. That will also let me compare the reader timestamp with the host time and see how much USB buffering and processing order actually matter.

For the crossing time, I plan to use the RSSI peak rather than the first read, because the first read mostly tells you where the read zone starts. The first track test will be two tags passing close together, then up to 8, measuring reads per second per tag and comparing the peak time against video.

I don’t expect passive UHF to separate riders by a few centimeters: at 60 km/h, 1 ms is about 1.7 cm. For very tight finishes, the finish judge (and later a photo-finish camera) will decide the order, and RFID will provide the times and rider identity.

One question, if you’ve worked with these modules: in continuous reading, have you found the per-read timestamps reliable, or do reads arrive grouped per cycle?

Thanks! That’s a fair question, and the honest answer is that I’m not at that stage yet.

For now I’m staying with the SparkFun board. There’s one prototype unit, and the M7E hasn’t even arrived, so I don’t want to design hardware around a reader I haven’t validated at a real finish line. A custom PCB is on the list, but the first version would replace the loose boards in the case (power, optocouplers for the start gate input, ADC), not the reader. Whether to go with the bare module on my own board is a separate decision, and it will depend on how the reader performs on the track.

I agree about the second source, and I started on it early. I’ve been comparing fixed readers and modules based on the Impinj E710 from other manufacturers, and the software talks to the reader through an interface, so changing readers should mean writing one new driver, not rewriting the system. None of those alternatives has been tested yet, though.

On units: no club has placed an order so far. Clubs want an alternative, but I think real demand will only show once they see it working on a track, so I’d rather not guess a number for the first season. Validation comes first, then a small batch, and I expect your 5-10 unit threshold is about where the sourcing question gets serious.

Out of curiosity, at what volume have you seen a custom carrier board start to make sense compared with buying finished modules?

Makes sense to validate the reader on track before you touch the RF side. Carrier for power/optocouplers/ADC first is the right order.

On volume: for a board that only holds power and IO, own PCB already makes sense around 10-20 units. The SparkFun board + loose modules take space, need wires, and one bad cable kills a race day. A small carrier is cheap and one evening with JLCPCB. For the reader itself I would wait longer. Bare Impinj module only pays when you need different antenna connectors, smaller case, or the SparkFun board price starts to hurt, usually past a few dozen units, and only after the module is proven on track.

One thing that helped us: design the carrier so the reader sits on a connector or daughter board. Then you can swap E710 modules later without respin of the whole case board.

When you do the first track test with 2 tags, will you log RSSI vs time to a file, or only watch it live?

That’s very helpful, thanks. The 10-20 unit figure for a power and IO board is a useful reference, and one bad cable ruining a race day is exactly what I want to avoid. I hadn’t thought about putting the reader on a connector or daughter board, but it fits well with the software, which already keeps the reader behind an interface. I’ll design the carrier that way.

For the first track test I’ll log to a file. Watching it live wouldn’t tell me much, since a tag only spends a fraction of a second in the read zone at race speed. I want every read saved (EPC, RSSI, reader timestamp and host time), so I can plot RSSI against time for each pass afterwards and compare the peak with the crossing on video. A live view would only be there to confirm the reader is working.

The raw logging is already in the code, but it hasn’t run on real hardware yet. I’ll post the results and plots here once the M7E arrives and I’ve run the two-tag test.

Good plan on the logging. RSSI vs time plus video is the only way to see if the peak is the real crossing or a side lobe.

One thing that helped us on similar pass tests: sync the host clock to the video timeline at the start of each run (clap next to the antenna, or a short beep on the audio track). Later you dont argue about offset when you overlay plots.

Also keep antenna height and tilt fixed for the first two-tag day. Change only one thing per session, or the plots lie.

When M7E arrives, first pass at race speed or walk the bike through once to check the log format?

Thanks, the clap/beep sync is a great tip. The Pi often has no internet at the
track, so I can’t rely on NTP anyway. I’ll log a manual marker on the host at
the same moment so both timelines share a reference.

Agreed on fixing antenna height and tilt. I’ll settle the read zone first, then
keep height, tilt and power fixed for the two-tag day.

To answer your question: walk the bike through first. That checks the log
format and confirms raw reads with RSSI and reader timestamps are being saved
before any dedup. Then I’ll move on to race-speed passes.

The manual marker plus the clap gives you two independent references, which is more robust than NTP at a track anyway. One suggestion for the walk-through: run it twice and diff the two files. If field order or timestamp formatting shifts between runs, you’ll catch it before the race-speed passes generate a pile of logs you can’t compare. Keeping height, tilt and power fixed for the two-tag day is the right call; RSSI plots are only as trustworthy as the setup you held constant.

Good call, thanks. I checked the logger and field order and timestamp precision are fixed in code (fixed column tuple, 6 decimals on all three time columns), so those shouldn’t drift between runs. The walk-through twice is still worth it for a different reason: rssi and read_count are written raw from the library, and if the mercuryapi build on the Pi is missing an attribute like phase or frequency, the logger closes the CSV on the first read and you get a header-only file. A second run would catch that before race day.

I won’t diff the raw files since every value changes between runs. I’ll compare structure instead: same header, same column count on every row, same type and precision per column. The manual marker isn’t in the CSV yet, so for now I’ll write down the clap time by hand. I’m thinking about logging it as its own event.

And agreed on keeping height, tilt and power fixed for the two-tag day.