The idea
The original idea was mine. I wanted a drone that could carry a ground robot, drop it near the interesting part of a disaster site, then keep looking from the air while the tank actually deals with the 2D mess on the ground. Drones are great until the battery says no. Ground robots can hang around longer, but they eat time just getting over rubble and fences. Pairing them felt obvious after we read enough papers and talked ourselves into “this could actually help firefighters / SAR / anyone who shouldn’t walk into a bad building first.”
The NSF REU was hosted at the University of Idaho and was supposed to be about UAS. I spent a couple of days convincing people this was the project: UAV/UGV for disaster relief, with a path toward something you could actually field, not just a lab demo. I directed the team a bit after that, on what to build next and who should sit on which problem. We called the team QuadBots even after we switched to a hexacopter. The name stuck. Sorry.
The picture in my head was pretty simple: fly in, park, pop the ramp, let the UGV drive off. Reverse it when you’re done.
I’m putting those CAD frames here because they’re how we explained the idea. I did not do the CAD. Mechanical folks on the team owned that.
We started smaller than the CAD, because none of us had built a serious drone before. F450 frame, FlySky FS i6X, Pixhawk 2.4.8 B Cube, ArduPilot Mission Planner. Assembly was fine. The motors did not spin. We checked the ESCs, the battery, and the radio. The problem was the ESC signal wires. They were plugged into the Pixhawk upside down. After that, the quad could spin motors.
Next milestone: pick up a small ground robot. The mechanical folks 3D-printed a PLA mount with a lock. After a couple of ugly versions, the quad could carry the little guy. Then we hung 915 MHz radios on it so Mission Planner wasn’t glued to a USB cable.
That was the whole point of the learning airframe. The quad could not lift the tank we actually wanted. So we pivoted to a carbon fiber hexacopter, still on a Pixhawk 2.4.8, still Mission Planner. Ground side was a Yahboom G1 tank on a Raspberry Pi. Treads, so grass and dry dirt weren’t an automatic no.
Computer vision (AI)
This was one of the tabs I actually owned. If the vehicles were going to be useful, they had to see people / buildings / cars / trees without me babysitting every frame. Companion computers: NVIDIA Jetson Orin Nano on the air side, Raspberry Pi 4B on the tank. YOLO on both, because we needed something that ran live, not a research model that thought about it for three seconds.
Pi setup: 64 GB card, Raspberry Pi OS, started headless, later I gave up and used a monitor like a normal person. Camera was an ArduCam 8MP on a 15-to-22 pin cable. The Pi pretended the camera didn’t exist until I turned off camera_auto_detect and added dtoverlay=imx219 in /boot/firmware/config.txt. That one line ate more of my week than I want to admit.
I trained our own YOLOv8. We scraped about 5000 images from Kaggle / Roboflow / GitHub, four classes, 80 to 20 split. I wanted to use the ONNX file on the Pi. The Pi is not a training machine (about 0.1 TOPS and 4 GB), so a usable model was the goal. It overfit. The dataset was too small and too clean. After more reading, we used the pretrained YOLOv8 with the 80 COCO classes. For what we had, it was better. Camera feed on the Pi sat around 12 FPS.
Jetson was the grown-up computer. JetPack / Jetson Linux on another 64 GB card, headed the whole time. Same ArduCam family, 15-to-15 CSI cable, CSI-Camera repo for bring-up. 24 FPS, which felt luxurious after the Pi. Because the Orin Nano is in a totally different weight class (~67 TOPS), I put YOLOv26s on it for better recognition and wrote the glue scripts around it. Before a run we’d SSH in (when 4G actually worked) and start the vision process. Not fancy. Repeatable enough.
The instinct was the same the whole summer: don’t wait on a slow round-trip if a person is already in the frame.
Embedded programming
The two boards I actually lived on were a Raspberry Pi 4B (tank / UGV) and an NVIDIA Jetson Orin Nano (air / UAV). A lot of my hours were not “AI” in the LinkedIn sense. They were: flash this card, fight this overlay, SSH in, talk to a modem in minicom, shove UDP out of the Jetson, remember which USB port the Pixhawk lied about.
Raspberry Pi
On the Pi I flashed a 64 GB SanDisk with Raspberry Pi OS using Pi Imager, started headless, then gave up and used a monitor. I hung the ArduCam 8MP off a 15-to-22 pin cable, killed camera_auto_detect, and added dtoverlay=imx219 in /boot/firmware/config.txt so the camera even showed up. I also booted the same Pi through Yahboom’s firmware and Ubuntu just to see what the tank would run. That’s the machine YOLO sat on at ~12 FPS, and the one I SSH’d into before a run.
NVIDIA Jetson
On the Jetson I flashed another 64 GB card with Jetson Linux via JetPack and kept a screen on it the whole summer. Same ArduCam family, 15-to-15 CSI cable, CSI-Camera repo for bring-up, ~24 FPS. That’s where YOLOv26s ran, and where I started the UDP process the dashboard listened to. If I was “on the drone computer,” I was on the Orin Nano.
ArduPilot and LTE
On the flight controller side we loaded ArduPilot through Mission Planner, mapped ESCs, and learned that one backwards connector looks exactly like a dead power system. The 4G dongles (Waveshare SIM7600G-H) were their own embedded side quest. No IMEI on the module, so we couldn’t register the SIM the normal way. We borrowed an IMEI from a spare phone that had extra SIM slots, which is the kind of sentence that should not appear in a professional paper and is exactly what we did. Then I plugged a dongle into a laptop and used minicom to see if the modem even existed. It did, until it didn’t. More on that in comms.
Controls (flight controls)
Flight controls was another tab that was actually mine, in the “make the airframe do modes a human can trust” way, not in the “I derived a new controller” way. Primary stick was the FlySky FS-i6X. 2.4 GHz, AFHDS 2A, hopping across a pile of channels. First four were throttle, yaw, pitch, roll. Five was loiter (GPS hold). Six was return-to-home. Radio itself was the least dramatic part of the summer, which I will take.
Autonomous missions were supposed to live in Mission Planner over 915 MHz telemetry so we weren’t waving a transmitter at a hex the whole time. In the lab we could track odometry, upload a path, pull logs, stop babysitting USB. Outside, the same radios ghosted us. We also couldn’t get the PID tune we wanted in the field, and Mission Planner got spicy about stabilization until we recalibrated GPS and compass. After that, hover / loiter / RTH actually happened, under a real remote PIC, in an FAA legal field, because our office was a no fly zone for a long time.
On the side I worked on GPS denied nav a bit. I am still working on it.
Communication
This was the biggest tab I worked on for comms. Three layers.
Radio control
LOS stick on 2.4 GHz, as above. That was the “don’t lose the airplane” path. Fine.
Telemetry
3DR 500 mW kit on 915 MHz. Lower frequency, less data, more distance — textbook. USB on the laptop, TELEM on the Pixhawk. In the lab it was great. On the field day it would not handshake, even though we’d done it a lot. I still don’t have a clean postmortem. That’s the honest version.
4G LTE + a VPN I now have opinions about
For AI-assisted stuff we needed a real network, not a phone hotspot that dies when you walk 30 feet. Two SIM7600G-H dongles, two SIMs, Jetson + Pi + a home-base laptop on Tailscale because the free tier was enough to try. Then latency went to something like 2500 ms. We wanted ~50. Tailscale couldn’t get P2P, parked us on a Seattle DERP relay, and the office walls / peak traffic probably didn’t help.
The day before field testing, both 4G modules just died. Best guess: the carrier noticed the SIMs were not in the phone we used to register them. If I did this again I would buy actual IoT SIMs on day one and stop being clever. I now have a tiny, expensive intuition for why you want more than one comms path.
Dashboard
We wanted one place to see camera, odometry, battery, and “is there a person who looks like they need help.” Half of that already lived in Mission Planner. The other half lived in a terminal. So I vibe-coded a dashboard in Cursor: live feed, telemetry, and a path that only asked a model to look closer if YOLO saw a person above 0.8 confidence for two frames in a row. During alpha we wired OpenAI in for that second look. The feed in the screenshot is redacted / swapped, which is why the neighborhood doesn’t match Idaho.
Bring-up: SSH to the Orin, start a process that sprays UDP (no handshake, no retries — we wanted speed, not politeness), then boot the dashboard. Same idea on the tank. If you’ve used a vehicle UI that has to stay boringly reliable while payloads yell, you already know why I cared about this tab.
Field testing
FAA + a no fly office meant we sat on this thing for way too long. When we finally got an approved area and a remote PIC, the hex flew. Hover, loiter, and return to home all happened. That part felt unreal in a good way. With the ground robot on it the hex was unstable at times.
What broke: telemetry link, PID tune, a compass/GPS fit we fixed by calibrating like we should have the first time. The tank still rolled around fine on its own. In the air, carrying it, the hex was not always settled.
I don’t want to oversell it. We did not finish the sci-fi docking loop. We did not get the 4G story to the field. We did get a carbon hex in the air with a tank underneath, vision running on boards I set up, a dashboard I could stare at, and a very specific list of what bites you when the vehicle leaves the carpet.
Final look
That’s the project. A summer of making unmanned stuff honest.