Newsletter
Subscribe to receive our blog updates
Hi Everyone!
I want to show off something we've been building that isn't a camera. It's the rack we use to develop the cameras.
I built a hardware in the loop test bench. Every OpenMV Cam we make can be mounted in it, powered by it, flashed from it, and tested from it, with nobody sitting at the bench.
The first job we gave it was to get Wi-Fi HaLow, a brand new radio system, brought up on the N6, the AE3, and the RT1062 at the same time. Three MCU architectures, three flashing rituals, one driver, every line of it written by AI and soak tested on real hardware. That's months of work, and I never sat at the bench for any of it!
Firmware development has a bottleneck that regular software development doesn't have. Every time you want to test a change, somebody has to put the board into DFU, run dfu-util, and then check that it came back on USB afterwards. When it doesn't come back, somebody has to reach over and hit reset. Multiply that by three boards across three silicon families, each with its own bootloader ritual, and an afternoon of driver work turns into a week.
That loop is also what stops an AI model from being useful on embedded work. A model can write a driver, but it can't reach over and hit reset. So it writes the driver, hands you a patch, and then you're the one running it.
So we closed the loop. If building, flashing, resetting, recovering, and measuring are all reachable over the network, the model can run the whole cycle itself. Build, flash, run, read the result, fix it, build again, with nobody in the middle.
The rack holds up to 27 OpenMV Cams and 27 NUCs at once, plus three boxes at the top that actuate everything else.
From the bottom up:
Every shelf is wired the same way. Using an N6 as the example:
So that board has three separate ways to get online, plus a UART for debug output that works even if USB fails.
Claude reaches into any box on the rack over the network and takes it from there. It compiles the firmware on the NUC, connects to the camera, pushes the image in over J-Link, resets the board, reads the output, and works through the bugs. Then it does it again.
And it isn't one Claude on one board. There's room for 27 cameras in the rack, so we can have a pile of agents working different problems on different hardware all at the same time.
Every box is also a self-hosted GitHub runner, so once a change is ready and the PR goes up, the same machines that developed it pick up the CI jobs and run them.
The part I like the most is that Claude itself lives in one of the three boxes on the top that drive the rack. You RDP in to tell it what to do, and then you can close your laptop and walk away and the work keeps going. We can drop our own custom sensor modules onto a shelf and have drivers worked on overnight.
Wi-Fi HaLow is 802.11ah. It's Wi-Fi, but down in the sub-GHz band, trading throughput for range and power. Think a camera at the far end of a field, running off a battery, punching through walls and trees that 2.4GHz gives up on. We're bringing it up on the Morse Micro MM6108 and MM8108 transceivers as a network.HALOW interface in MicroPython.
I picked this as the first real job because it's the kind of work that eats months. A whole new radio, a vendor library, and three ports that each need their own integration. The driver has to build on stm32, alif, and mimxrt, and then actually associate and move real traffic on three physically different boards. Every fix has to be re-verified everywhere, which is thousands of small flash-and-check cycles, and historically every one of those cycles is me.
Work like that usually just gets deferred. It goes on the someday list while we ship the things that fit in a week.
So we gave it to Claude with the rack and let it grind.
We asked Claude for three things, in order. Get network.HALOW associating and passing real traffic on the N6, the AE3, and the RT1062. Then push the throughput up until it's close to what the radio can actually do. Then prove it stays up, because a radio driver that works for ten seconds isn't worth much.
That last part set the acceptance bar. A twenty minute soak on every board, hundreds of connect and transfer cycles, no leaks, no asserts, no hangs. And any change at all means all three boards get reflashed and rerun.
Getting off the ground meant the usual pile of build and linkage work to get a vendor library into three different ports. Once all three were building and associating, the interesting work started.
Four measurements per board: TCP up, TCP down, UDP up, UDP down. Five seconds per direction, cycled back to back for twenty minutes, which works out to hundreds of connect and transfer cycles. Channel 28 at 2MHz bandwidth, station to AP.
The buffers on the device are preallocated and fixed size, 1460 bytes for TCP and 1200 for UDP, and nothing in the test is sized by anything arriving off the wire. A throughput test that allocates per packet ends up measuring the allocator, and on MicroPython that means measuring the garbage collector instead of the radio.
Getting the results off the board turned out to be its own problem. Under sustained throughput, the HaLow RF couples into USB hard enough to drop the CDC serial connection. The board keeps running fine, but the terminal is gone and the test result with it.
Here's the part I think is awesome. I mentioned offhand that the antenna was sitting near the USB cable, and Claude worked out the rest on its own. It rewrote the test to log to /flash/hres.txt on the device as it goes instead of reporting over USB. USB re-enumerates by itself once HaLow goes idle, and then you read the file back.
| Board | MCU | Transport | UDP up | TCP up | TCP down | Assoc |
|---|---|---|---|---|---|---|
| OPENMV_N6 | STM32N6 (Cortex-M55) | hardware IRQ | 8.1 Mbit/s | 6.0 Mbit/s | 5.0 Mbit/s | 6.3s |
| OPENMV_RT1060 | i.MX RT1062 (Cortex-M7) | hardware IRQ | 6.1 Mbit/s | 2.7 Mbit/s | 3.2 Mbit/s | 5.0s |
| OPENMV_AE3 | Alif Ensemble E3 (Cortex-M55) | polled | 3.2 Mbit/s | 2.1 Mbit/s | 2.6 Mbit/s | 3.6s |
All three boards finished clean. No leaks, no asserts, and no hangs.
UDP up is the cleanest capability figure since there's no retransmit and no congestion control in the way, so it measures the radio and the driver path and not much else. About 8 Mbit/s on the N6 is close to the practical ceiling at 2MHz bandwidth, so the driver isn't what's limiting that board.
The useful thing to look at is the gap between UDP up and TCP up, since that gap is everything the stack and the link add on top of raw capability:
The AE3 is the easy one. Its UDP number is the lowest of the three, so TCP isn't its problem, the transport is. It services the transceiver by polling rather than off a hardware interrupt, so its ceiling is set before TCP gets involved at all. Fixing the transport is what moves it.
The N6 is doing about as well as TCP can do over a lossy sub-GHz link. HaLow retransmits, TCP reads loss as congestion and backs off, and you pay for that regardless of how good the driver is.
The RT1062 has hardware interrupt service like the N6 and better than half its raw UDP throughput, and then keeps less than half of that once TCP is involved. That puts the cost in the round trip rather than the radio. Sitting the three boards next to each other is the only reason that stands out at all. 2.7 Mbit/s looks perfectly reasonable otherwise.
Thanks to being able to test all three boards at once, we noticed outgoing packets were being queued and then sitting there until the next scheduler tick before they actually went out. On UDP this is invisible, because you just keep filling the pipe anyway. On TCP it's expensive, since TCP is waiting on a round trip before it sends more, so a delay on every acknowledgement throttles the whole transfer. Sending queued packets right away instead of waiting for the tick fixed this.
Every one of those numbers had to be re-measured on all three boards after every change. Twenty minutes a board, three boards, a fresh flash each time, and a soak that has to survive to the end to count.
The twenty minutes isn't padding either. That's the length that catches a slow leak, or an assert that only fires on an unlucky retransmit somewhere around cycle 300. It's also exactly the test that gets skipped when you're running it by hand at the end of a long day.
Doing all of that manually is most of a day, so in practice you don't. You measure the board on your desk, assume the other two moved the same way, and find out you were wrong months later when somebody files an issue.
A job I'd budgeted months for came back done, and I never sat at the bench for any of it.
Every problem was found the same way: build it, flash it, run it on real silicon, watch it fail, read why. The radio stepping on USB, heap corruption and random assert failures, the TCP gap that only shows up when you have three boards to compare. You can't get to any of that by reading source code, which is why "let the AI write the driver" hasn't been a great pitch for embedded work so far. The model could write code, but it couldn't run its own experiments.
Once it has a bench it can reach and a recovery path it can't wreck, that changes. It brings back results, not patches for you to go test.
OpenMV is a small team, a new radio across three ports is the kind of project a company our size doesn't normally get to take on. Between the models and the rack, work like that is now something we can actually do, and we have years of ideas that never got started because the hours weren't there!
Right now the rack's main job is over the air updates, which is the feature we most want finished, because it's what turns an OpenMV Cam into something you can ship and then maintain in the field. Remote update over Ethernet or Wi-Fi, and over HaLow eventually. We're aiming to get that out this year.
And yes, generative AI wrote all of the HaLow code (and most of this article!). It also got tested a lot harder than I would have tested it by hand. Twenty minute soaks, hundreds of connect and transfer cycles, on three different boards, re-run after every change. That's stronger evidence it works than me checking a few edge cases on whichever board happened to be on my desk.
Anyway, that's all for this one folks! Back to grinding on OTA.
Subscribe to receive our blog updates