Over the past few months, I've been working on a communications system for a high altitude balloon. Although I'm most interested to talk about the high speed downlink, I'll be using this post to document everything I've worked on.
There are three transmitters of interest on this balloon: an APRS transmitter, an ADS-B aircraft transponder, and the high speed downlink itself.
For tracking the balloon at long distances, we're using an APRS transmitter. This is useful because there are plenty of digipeaters and I-gates here in New Brunswick. Even if we don't receive its beacons, chances are someone will, and then we can just poll the APRS-IS network.
The particular transmitter we're using is the MicroTrak 1000. This was picked before I joined the project. I don't love it - it would be nice to use something that can receive packets, giving us a very simple uplink, but this is a bit of a nitpick. I also don't love that it runs proprietary software. I'd prefer something hackable! That said, the 1000 works fine as an APRS transmitter.
On the ground station side, we're receiving these packets using an Arrow II antenna and a TM-231A radio. The audio is piped into a laptop running Direwolf. Xastir is used to visualize these packets, as well as packets coming in over APRS-IS.
As an alternative to Xastir, I wrote a program I call balloon-aprs-util. Creative name, I know. It listens to APRS packets from Direwolf and from APRS-IS, and prints them to the terminal. It also calculates distance from the ground station to the balloon and antenna azimuth/elevation. The idea is to offload as much from the humans as possible - just present the useful information and nothing else. Xastir is meant to be used for a richer view of the situation, if required.
Our payload is pretty enormous. So much so, that we're required to have an ADS-B aircraft transponder on board! This has presented a variety of technical challenges. Did you know they transmit at 250W? I didn't! That amount of power has a lot of potential to interfere with onboard systems - something we witnessed firsthand.
The transponder is effectively a standalone unit, similar to the APRS transmitter. This makes sense, as it needs to be pretty idiot-proof. It has a bunch of regulations to follow, after all!
As part of the ground station, we decided to decode ADS-B packets. This is primarily done for redundancy, as it doesn't provide any insights beyond the APRS transmitter. The system is fairly simple - we're using a ~$50 RTL-SDR connected to a dipole for receiving the packets, piped into dump1090. This is probably the most plug-and-play part of the system. No custom software at all!
High Speed Downlink
The high speed downlink is where I've spent the vast majority of my effort. The goal is to send down high-resolution images in real-time. It's a very elegant system, in my opinion, but also has a lot of moving parts.
We're using an RF4463F30 module to transmit FSK-modulated packets in the 70cm band at 1W. These packets are LDPC-encoded for error correction. On the ground station, we're using an SDRPlay for packet reception. Sure, we could use another RF4463F30 - it's certainly cheaper - but it's not as sensitive, and wouldn't allow us to use soft bit decoding. That alone would eat up around 6dB of our link budget - clearly a non-starter.
The SDRPlay is connected to the 70cm portion of the Arrow II antenna. This means we only need to aim one antenna for APRS and the high speed downlink.
The system is capable of 500 Kbps raw, or about 350 Kbps after overhead (mainly the LDPC encoding). This is fast enough to send high resolution images in just a few seconds!
Originally, the plan was to use a Pi Zero to communicate with the RF4463 directly. Unfortunately, its tiny 129 byte buffer empties every few milliseconds at 500 Kbps. I found that the Pi couldn't keep up with this and would sometimes drop packets - while using 100% CPU the whole time!
The original board design. The RF4463 transceiver - the metal rectangle - is wired directly to the Pi. They communicate with each other over SPI.
The solution was to insert an STM32F411 between the Pi and the RF4463. It basically acts like a huge buffer - the Pi transmits data in bursts to the STM32 via UART, and the STM32 is responsible for feeding the RF4463 with data via SPI. This is super reliable, and it results in only a few percent CPU usage on the Pi. Truthfully, I'm pretty sure the STM32F411 is overkill, especially once I get DMA figured out, and I plan to downsize the chip eventually. In a future revision of the PCB, I'll get rid of the daughterboard and have the microcontroller live on the main board. Remember, this is just a prototype!
Here's the new board. I changed the colour (Gotta try them all!) and added the microcontroller. I'm using a first generation Raspberry Pi for testing, but we'll be using a Pi Zero in the payload.
You may be wondering why there's space for a second RF4463. The idea is to use a 33cm variant for an uplink, if needed. For now we have a different system for sending commands. Due to the mass of the payload, we're required to have a way to remotely terminate the mission (i.e. blow up the balloon). This is the main reason for an uplink.
Let's talk about software! Almost every part of this communication system, on the payload side at least, was written by me. It consists of the following components:
balloon-tx-monolith - Runs on the Pi Zero. It's responsible for choosing which images to send, generating packets (including LDPC encoding), sending them over UART, and monitoring the RF4463's temperature (which is also sent over the downlink). There is a very simple UART protocol, which also incorporates recovery if a UART byte is ever dropped (or an erroneous one is inserted). Otherwise, the STM32's state machine would get screwed up!
pi-transceiver-firmware - Runs on the STM32. Initially this gets the RF4463 set up with the right configuration. Then it listens for packets on the UART to send to the RF4463 (with buffering, of course. That's the entire point!). This software is pretty simple and doesn't even understand the packets it's sending. It also gets the RF4463's temperature and sends it over UART to the Pi.
rf4463-lib - A Rust library I wrote for interfacing with the RF4463. Technically, it works with any Si4463 transceivers, which the RF4463 is based on. Ultimately this runs on the STM32, but in the past I had it running on the Pi.
The ground station software. Images are displayed on the left. Debugging graphs and status logs are displayed on the right.
On the ground, I'm using a forked version of Wenet. My fork lives here. Getting this working was a lot more work than I expected! A lot of the issues came from moving from the RTL-SDR to the SDRPlay, which was due to bandwidth concerns. I also had to rip out the RS-232 deframing - Wenet's transmitter works by bit-banging the Si4463, which introduces overhead that needs to be removed on the receiving side. I made some other minor changes but those two were the most important.
A huge thanks to everyone who worked on Wenet. It provided an amazing starting point for this entire system. This is the real beauty of open source!
One thing I greatly dislike about the SDRPlay is that its driver is closed source. I didn't even realize this was an option, and would have purchased something else if I knew at the time.
These systems will fly on their maiden voyage on this upcoming Saturday, 17 Jun 2023. I'm most excited to see how the high speed downlink performs, especially at long distances.
Outside of this team, I have plans to launch my own high altitude balloon. It will be much smaller, primarily consisting of the high speed downlink, a Pi Zero, a camera, and a GPS. I plan to rewrite things slightly so that the GPS data is sent over the downlink instead of via a separate transmitter.
I hope other people pursuing high altitude balloons may find some use in my software, hardware, or both! 500 Kbps should be able to support a modest real-time video link, if the monolith software was changed. Speaking of which, I think that could be useful for a robot or quadcopter! Clearly I have much more to pursue.