How I misunderstood the Arduino UNO Q Bridge three different ways before the architecture finally made sense.
Sometimes Advice Makes More Sense in Hindsight
When I published my first vision post for Project GEPARD, several members of the Element14 community shared suggestions about how I should approach the software architecture.
One comment suggested looking deeper into the UNO Q Bridge system and the documented examples rather than building a custom communication layer from scratch.
Another highlighted something I had underestimated at the time: the Arduino UNO Q is not simply another Arduino board. It combines a Linux computer and a microcontroller on the same platform, and understanding that distinction is important when designing larger applications.
At the time, those suggestions made sense intellectually, but I had not yet encountered the problems they were trying to help me avoid.
So I continued developing the architecture I had already planned.
For a while, everything seemed to be moving in the right direction. Components came online, code compiled, and individual subsystems appeared to work.
As the project grew, however, the cracks started to appear.
The telemetry architecture became increasingly difficult to reason about.
The control system became harder to debug.
Subsystems that worked independently did not always work together.
Fixing one problem often revealed another.
Eventually I found myself spending more time fighting the architecture than building the rover.
That was when I went back to the documentation, reread the community feedback, studied example projects, and looked more carefully at how the UNO Q was designed to operate.
What I discovered was not that my original ideas were completely wrong.
It was that I was trying to use the UNO Q like a traditional microcontroller platform when it was designed to operate as a dual-processor system.
Once I understood that distinction, several weeks of confusing software issues suddenly started making sense.
This post is about those lessons.
Bridge Issue #1: I Treated It Like a Serial Cable
My original mental model looked something like this:
That seems reasonable.
The microcontroller gathers data and pushes it upward whenever it wants.
That is how I had structured a significant portion of the project.
The UNO Q does not work that way.
The UNO Q Bridge is designed around a request-response model.
The Linux side initiates communication.
The STM32 responds.
Once I finally understood that, the architecture changed from:
to:
Bridge.provide("get_telemetry", get_telemetry);
and
telemetry = Bridge.call("get_telemetry")
The difference sounds tiny.
The impact was enormous.
Instead of trying to continuously push data upward, the Linux side now requests data when it actually needs it.
That immediately simplified the entire system.
Bridge Issue #2: The Motors Worked in the Test Sketch but Not in the Real Firmware
At one point I had a very confusing problem.
The motors worked perfectly.
Until they didn't.
My simple diagnostic sketch spun the wheels every time.
My "real" firmware did not.
From the outside that sounds like a wiring issue.
It wasn't.
The multimeter eventually gave me the answer:
The motor driver had power.
The standby pin was enabled.
The PWM signal simply never existed.
That pointed directly at the software stack.
The root cause turned out to be a combination of:
- Incorrect Bridge architecture
- Telemetry logic
- A safety override that was stopping forward motion
Ironically, my safety feature was preventing me from proving the motors worked.
Once I removed the unnecessary override and rebuilt the communication architecture around proper Bridge calls, the motors came back to life.
Bridge Issue #3: Not All Providers Are Equal
This one took me much longer to understand.
I originally treated these as interchangeable:
and
Bridge.provide_safe(...)
They are not.
The distinction matters.
Functions that touch hardware:
digitalWrite(...)
should use:
Bridge.provide_safe(...)
Examples:
set_drive()
set_estop()
test_module()
These affect the physical world.
Motors move.
Sensors trigger.
Pins change state.
Those belong in the safe execution context.
On the other hand:
get_telemetry()
should ideally be a read-only operation.
That means it can use:
Bridge.provide(...)
if it remains short and thread-safe.
That led directly into the next mistake.
Bridge Issue #4: My Telemetry Function Wasn't Actually Read-Only
I thought this function was simple:
get_telemetry()
It wasn't.
Inside it I was:
- Triggering ultrasonic pulses
- Reading I2C sensors
- Waiting on pulse timing
- Accessing hardware directly
Which meant the function wasn't actually just returning telemetry.
It was collecting telemetry on demand.
That sounds harmless until you realize the ultrasonic routine alone can block for milliseconds.
The fix was to separate two jobs.
Instead of:
Request → Read Sensors → Return
I moved to:
Loop()
↓
Now the microcontroller continuously updates a telemetry cache and the Bridge simply serves the latest values.
The function became short.
Predictable.
Thread-safe.
And much easier to reason about.
The Surprise Bug: The Sketch Wasn't Even the Main Problem
At one point I became convinced the firmware itself was broken.
But after rebuilding the project structure I discovered that many issues were actually App Lab configuration problems.
Things that changed included:
Old Architecture
Laptop application
UDP transport
Custom dashboard
Mixed project structure
New Architecture
UNO Q Linux application
I also discovered things like:
- Project structure matters more than I thought.
- UNO Q examples assume a specific layout.
- The WebUI Brick was solving problems I was manually creating.
- I was fighting the platform instead of using it.
That was a humbling realization.
The Comment I Should Have Listened To
Looking back, the most frustrating part is that several people had effectively pointed me toward the solution weeks earlier.
Not the exact solution.
But the correct direction.
I tend to learn in a very stubborn way:
- Build the thing.
- Assume the thing should work.
- Watch it fail.
- Spend hours troubleshooting.
- Finally accept that the original assumption was wrong.
- Open the documentation.
I joke about it, but Project GEPARD has forced me to become better at step 6.
The Architecture We Settled On
After all of the redesigns, experimentation, broken assumptions, and late-night troubleshooting sessions, the architecture finally became simple.
The responsibilities are now clear.
The Spine (STM32)
- Reads sensors
- Controls motors
- Handles hardware timing
- Provides telemetry
The Brain (Linux)
- Collects telemetry
- Hosts the WebUI dashboard
- Runs autonomy logic
- Interfaces with future AI systems
The Browser
- Displays the tactical HUD
- Shows telemetry
- Sends commands
- Works from a laptop or phone
The architecture finally matches the way the UNO Q was designed to work.
Current Status
Motors responding
Bridge architecture redesigned
WebUI architecture established
Ultrasonic testing functional
Individual subsystem testing implemented
Brain / spine architecture finalized
️ INA226 still being validated
️ MPU6050 still being validated
️ Encoder tuning still required
Final assembly underway
Final Thoughts
I think one of the most important lessons from this challenge is that being wrong is not the problem.
Staying wrong is.
The UNO Q Bridge was not one bug.
It was a chain of misunderstandings:
- Wrong communication model
- Wrong assumptions about providers
- Wrong telemetry strategy
- Wrong project architecture
Each fix revealed the next problem underneath it.
And honestly, that is probably the most realistic description of engineering I can give.
You don't usually solve the problem.
You solve enough problems that eventually only the real problem remains.
More soon.
The Bridge works. Mostly.