This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Documentation

This is a placeholder page that shows you how to use this template site.

This section is where the user documentation for your project lives - all the information your users need to understand and successfully use your project.

For large documentation sets we recommend adding content under the headings in this section, though if some or all of them don’t apply to your project feel free to remove them or add your own. You can see an example of a smaller Docsy documentation site in the Docsy User Guide, which lives in the Docsy theme repo if you’d like to copy its docs section.

Other content such as marketing material, case studies, and community updates should live in the About and Community pages.

Find out how to use the Docsy theme in the Docsy User Guide. You can learn more about how to organize your documentation (and how we organized this site) in Organizing Your Content.

1 - ORCA Software-Defined Radar

For a general overview of the ORCA system, we highly recommend reading through our publication:

T. O. Teisberg, A. L. Broome and D. M. Schroeder, “Open Radar Code Architecture (ORCA): A Platform for Software-Defined Coherent Chirped Radar Systems,” in IEEE Transactions on Geoscience and Remote Sensing, vol. 62, pp. 1-11, 2024, Art no. 5109411, doi: 10.1109/TGRS.2024.3446368.

The documentation is broken into two parts. Radar Code covers everything about the code that runs while the radar is actively collecting data. The Postprocessing section explains how data is saved and explains the basic processing scripts that we provide.

1.1 - Radar Code

Overview of SDR-interfacing code

1.1.1 - Compatible hardware

Options for SDR hardware and host computers

Selecting a Software-Defined Radio (SDR)

ORCA is built upon the USRP Hardware Driver (UHD). As such, it is theoretically compatible with any Ettus SDR. We have primarily tested with B and X series devices (B205mini and X310, in particular), however most other Ettus SDRs should work with minor tweaks.

The basic capabilities of the two models we regularly use are detailed below:

SDRFrequency Range (GHz)Bandwidth (MHz)ChannelsMass (kg)Approximate Cost (USD)
X310 with UBX1600.01-62002 TX, 2 RX1.7$14,0000
B205mini-i0.07-6561 TX, 1 RX0.024$1,600

In general, we recommend B series devices for applications with constraints on size, weight, power, or budget. When capabilities beyond the B series are needed, we recommend the X series.

If no device in either series suits your needs, consider other Ettus SDRs. Most should work with minor tweaks. The exception to note are E series devices, which include an embedded computer running Linux. In theory, this code could be run on that embedded computer, however the performance limitations would limit the use cases.

Host Computer

Unless you choose an E series device (see caution above), you will also need a computer to interface with the SDR. This computer is where the ORCA code will run.

The capabilities of the computer can make a significant difference in the system performance. The host computer must support an interface with enough bandwidth for the samples you want to transfer and be able to store these samples to an appropriate storage medium.

If you’re not resource constrained, a modern laptop will provide more than enough processing power, as long as it supports the interface you need. Keep in mind that X-series SDRs need a 10 gigabit ethernet (10 GigE) interface to achieve maximum sample rates. Few laptops natively support 10 GigE, however there are Thunderbolt adapters available. (Not all USB C ports support Thunderbolt and it may not be available on low-end laptops.)

For an embedded solution, Raspberry Pi’s are suitable for working with B series devices. The performance of the Raspberry Pi 5 greatly exceeds the Raspberry Pi 4, likely due to the introduction of a new USB interface chip.

Some caution is advised with other single-board computers. Their performance will likely be limited by their choice of USB controller chip.

For some examples of duty cycles achievable with various combinations of SDRs and host computers, see the figure below. Note that actual performance will depend on a variety of factors, including your exact configuration and the speed of your storage medium.

Example results from running error_code_late_command_sweep.py on several combinations of host computers and SDRs.

1.1.2 - Code overview

Overview of the code’s architecture

Conda environment setup

All of the required dependencies can be installed as a conda environment using the environment.yaml file in the repository. More instructions can be found in the README file.

Startup scripts

The X series devices require some initial network configuration. For convenience, a startup script is provided to automate this setup. You may need to tweak this file to your setup.

Runner scripts

The basic steps required to run the radar are:

  1. Build the C++ code
  2. Generate a chirp file to transmit based on your configuration
  3. Run the compiled radar code
  4. Move the collected data to an appropriate location

The main interface for running the radar code is through run.py, a Python script designed to automate the above process. This script is run as follows:

python run.py config/my_radar_configuration.yaml

At the end of the data collection, data will be saved with the current date to a location specified in the YAML config file.

Generally, three files are saved:

  • YYYYMMDD_hhmmss_rx_samps.bin - This is a binary file containing the raw samples recorded the SDR. Note that this file is not interpretable unless you also have the config file used.
  • YYYYMMDD_hhmmss_config.yaml - This is the YAML config file passed to run.py. It defines all parameters of the data collection, allowing for the rx_samps.bin file to be interpreted and processed.
  • YYYYMMDD_hhmmss_uhd_stdout.log - This is a text file containing the output of running the radar code. This is helpful for debugging and also contains a log of any errors encountered, which may be required to reconstruct the timing of each recording.

More details on the files stored and how these can be re-processed into a Zarr file are on the file formats page.

Note that there are also settings available to break rx_samps.bin into multiple files as needed.

SDR interface code

For performance reasons, the code directly interfacing with the SDR is written in C++. This code is all located in the sdr/ directory of the repository.

The main radar code is contained in main.cpp (with some SDR setup code located in rf_settings.cpp). The radar code runs in two threads, as shown in the figure below.

General architecture of the ORCA code

One thread is responsible for scheduling timed commands that are enqueued into FIFO queues within the SDR’s FPGA.

The other thead is responsible for pulling received samples from the SDR and writing them to a file on the host computer.

For a more complete overview, please refer to our paper:

T. O. Teisberg, A. L. Broome and D. M. Schroeder, “Open Radar Code Architecture (ORCA): A Platform for Software-Defined Coherent Chirped Radar Systems,” in IEEE Transactions on Geoscience and Remote Sensing, vol. 62, pp. 1-11, 2024, Art no. 5109411, doi: 10.1109/TGRS.2024.3446368.

1.1.3 - Configuration options

All of the options in config.yaml

The entire configuration that controls the radar is defined in a single configuration file. This file is provided at runtime to run.py and encapsulates all of the settings needed to run various experiments on different hardware setups. Configuration files are specified in the YAML format and contained in the config/ folder of the ORCA repository. Default configuration files are included to run the code on a B205mini-i (default_b205.yaml1) and on an X310 (default_x310.yaml). Here we explain the basics behind the settings available to the user via the config file.

Chirp and Pulse Parameters

  • sample_rate: Sample rate of the generated chirp (used as TX and RX rate too), specified in Hz
    • On the X310, the sample rate should be an even integer division of the main clock rate
  • chirp_type: Chirp frequency progression type
    • Supported options: “linear”, “hyperbolic”
  • chirp_bandwidth: Bandwidth of the chirp, specified in Hz
    • Should be less than or equal to sample_rate to satisfy Nyquist
  • lo_offset_sw: Center frequency of the chirp, relative to RF0:freq (the RF center frequency), specified in Hz
  • window: Window function applied to the chirp
    • Supported options: “rectangular”, “hamming”, “blackman”, “kaiser10”, “kaiser14”, “kaiser18”
    • Applied on transmit and receive by default
  • chirp_length: Chirp length without zero padding, specified in seconds
  • pulse_length: Total pulse length (chirp + symmetric zero padding), specified in seconds
    • set equal to chirp_length if no zero padding is desired
  • out_file: Name of the output binary file containing pulse samples
  • show_plot: Whether to display a time-domain plot of the generated chirp (True or False)

Device Connection and Data Transfer Parameters

  • device_args: USRP device arguments are used to identify specific SDRs (if multiple areconnected to the same computer), to configure model-specific parameters, and to set transport parameters of the link (USB, ethernet) between the SDR and the host computer.
    • See the default config file or Ettus UHD examples for the SDR you wish to use to set appropriately
  • subdev: Active SDR submodules
    • See the default config file or Ettus UHD examples for the SDR you wish to use to set appropriately
  • clk_ref: SDR clock reference source
    • Supported options: “internal”, “external”, “gpsdo”
    • “external” requires an external clock reference connected to the SDR
    • “gpsdo” requires a GPSDO module to be installed on the SDR
  • clk_rate: SDR main clock frequency, specified in Hz
    • only specific frequencies are allowable for each SDR, see Ettus documentation for more information
  • tx_channels: list of TX channels to use (comma separated)
  • rx_channels: list of RX channels to use (comma separated)
  • cpu_format: CPU-side sample format
    • Supported options: “fc32”, “sc16”, “sc8”
  • otw_format: On the wire format
    • any format supported

GPIO Configuration Parameters

Many of the Ettus SDRs have GPIO pins which can be used for conveying automatic transmit/receive signals or other general signals to external devices. The parameters in this section are specific to how MAPPERR and PEREGRINE use the SDR GPIO and can be adapted for other use cases.

  • gpio_bank: Which GPIO bank to use
    • “FP0” is the front panel and default bank on the X310
  • pwr_amp_pin: Which GPIO pin to use fo external power amplifier control
    • set to “-1” if not using
  • ref_out: Turns the 10 MHz reference out signal on the X310 on (1) or off (0)
    • set to (-1) if the SDR does not support a 10 MHz reference out signal

RF Frontend 0 Configuration Parameters

RF configuration parameters for a single-channel radar setup.

  • rx_rate: RX sample rate, specified in Hz
    • defaults to be equal to sample_rate
  • tx_rate: TX sample rate, specified in Hz
    • defaults to be equal to sample_rate
  • freq: Center frequency (LO/mixer frequency), specified in Hz
  • lo_offset: hardware LO/mixer offset, specified in Hz
  • rx_gain: RX gain, specified in dB
    • available gain range dependent on specific SDR
  • tx_gain: TX gain, specified in dB
    • available gain range dependent on specific SDR
  • bw: Configurable hardware filter bandwidth, specified in Hz
    • not supported on all SDRs
    • set to 0 if not supported
  • tx_ant: Port to be used for TX
  • rx_ant: Port to be used for RX
  • transmit: Whether to transmit data samples or not
    • set to “true” (or leave blank) to transmit samples (normal operation)
    • set to “false” to completely disable transmit
  • tuning_args: Set integer-N or fractional tuning arguments
    • only supported on some SDRs
    • leave as "" to do nothing

RF Frontend 1 Configuration Parameters

RF configuration parameters for the second channel of a multi-channel radar setup. This is only supported on SDRs with more than 2 TX/RX ports. Parameters are the same as in RF Frontend 0 Configuration.

Pulse Timing Parameters

  • time_offset: Offset time after set up before the first received sample, specified in seconds
  • tx_duration: Transmission duration, specified in seconds
    • defaults as, and should be, equal to pulse_length
  • rx_duration: Receive duration, specified in seconds
    • should be long enough to capture echoes from the expected most distant target
  • pules_rep_int: pulse repetition interval, specified in seconds
    • should be longer than or equal to rx_duration
  • tx_lead: Time between start of TX and RX, specified in seconds
    • can be used for psuedo-blanking
  • num_pulses: Number of chirps/pulses to transmit and receive
    • set to -1 to continuously transmit and record until stopped by CTRL-C
    • code will record num_pulses of error-free pulses before terminating
  • num_presums: Number of received pulses to coherently average before writing to file
  • phase_dithering: Whether to enable phase dithering or not

During Recording File Location Parameters

  • chirp_loc: Which chirp file to transmit
    • in most cases, should be the same as out_file
  • save_loc: (Temporary) location to write received samples to
  • gps_loc: (Temporary) location to save GPS data
    • only works if “gpsdo” is selected for clk_ref
  • max_chirps_per_file: Maximum number of chirps (after presumming) to write to a single file
    • set to -1 to avoid breaking into multiple files

File Save Locations for run.py

These settings are only used by run.py, they are not read by main.cpp.

  • final_save_loc: Save location for the big final file, set to null if you don’t want to save a big file
    • if max_chirps_per_file -= -1 (i.e. all data will be written directly to a single file), then final_save_loc and save_partial_files will be ignored
  • save_partial_files: Set to True if you want individual small files to be copied with the timestamp, set to False if you just want the big merged file to be copied with the timestamp
    • if max_chirps_per_file == -1 (i.e. all data will be written directly to a single file), then final_save_loc and save_partial_files will be ignored
  • save_gps: Set to True if using GPS and wanting to save GPS location data, set to False otherwise

1.1.4 - Host computer transport parameter tuning

Overview of host interface tuning to achieve maximum duty cycle

The maximum duty cycle you can achieve is a function of the SDR you use, the host computer, and the bandwidth you need. This page outlines suggestions for how to figure out an achievable duty cycle and what parameters can be tweaked to maximize this.

General process for benchmarking duty cycle

Determine the maximum achievable bandwidth with benchmark_rate

Use the USRP example benchmark_rate program (located at <path to conda install>/envs/<uhd environment name>/lib/uhd/examples) to determine the approximate maximum duty cycle you can achieve. For instance, with a Pi 4 and b205mini, I can reach about a 25% duty cycle – defined as sample rate divided by desired sample rate (14 Msps / 56 Msps in this case) – while consistently getting 0 dropped packets.

./benchmark_rate --tx_rate 14e6 --rx_rate 14e6 --tx_otw sc12 --rx_otw sc12 --args="num_recv_frames=512,num_send_frames=512,recv_frame_size=16000,send_frame_size=16000"

This is also the stage to experiment with what transport parameters allow for the highest throughput on your combination of computer/interface/SDR. However the choice of (send/recv)_frame_size should be based on the number of samples per chirp and won’t have the same kind of impact in this continuous streaming case. In other words, you can get close but more tweaking later will help – probably.

Read about USB transport parameters on the Ettus website.

Figure out desired pulse lengths (RX and TX) and set frame sizes appropriately

Basically you want one full set of received samples to fit in a frame. See many more details about this in “Transport layer - throughput limitations” below.

Run tests/error_code_late_command_sweep.py to figure out your max duty cycle

This will sweep across effective duty cycles from 100% to 1, testing the equivalent pulse_rep_int for each one. The script produces a plot (saved as error_code_late_command.png) that shows percent of errors versus duty cycle.

Example results from running error_code_late_command_sweep.py on several combinations of host computers and SDRs.

Tuning transport parameters

In addition to getting late commands, the other issues we can run into are overflows (D on network-based devices, O on other devices) and underflows (U on all devices). There are also sequence errors (S) that often go along with late commands (L). Overflows mean that the buffers filled up and samples from the SDR are not being consumed fast enough. Underflows mean that the SDR did not receive samples to transmit before it needed to transmit those samples.

The Ettus knowledge base has a page detailing theoretical maximum transfer rates for each device.

Depending on the device, data is transferred over USB using libUSB, GigE, 10GigE, or PCI-E. For each option, there are various tunable parameters that impact the packet sizes and buffers used.

For libUSB, the transport parameters are:

(send/recv)_frame_size is the maximum packet payload size in bytes. The maximum number of samples that fits into a packet is <packet size in bytes>/<bytes per sample>. The bytes per sample is determined by the over-the-wire format (see otw_format). For example, sc12 requires 24 bit/samples, which is 3 bytes. It’s named sc12 because each value is 12 bits, but there are two values per samples (I, Q).

You can query the max number of samples based on your selected (send/recv)_frame_size through the TX or RX streamers: tx_stream->get_max_num_samps() or rx_stream->get_max_num_samps()

The default is around 8 MB (on my laptop and on the Pi 4B, both running Ubuntu-derived variants). If you set it too high, you’ll get an error message.

In most cases one full chirp (TX) or the received samples associated with one full chirp should fit and a good choice is to set the frame sizes to be equal to (or just barely larger than) the expected number of TX and RX samples, respectively, per chirp.

num_(send/recv)_frames controls how many buffers of size (send/recv)_frame_size are created. The more buffers you allocate for receiving, the longer your host program can lag for before causing an overflow.

LibUSB will give you a memory error if you try to allocate too much memory overall (roughly frame size * number of frames).

This is also why you don’t want arbitrarily large frame sizes. You end up wasting RAM if you’re allocating buffers that are longer than the maximum packet size you expect.

num_recv_frames is one of the most commonly talked about points on the USRP mailing list for throughput issues on the b2xx devices. Apparently the default value is very small (though I wasn’t able to figure out how to find the actual default).

You set these parameters in the device arguments string. For us, that means something like this in the config YAML:

device_args: "num_recv_frames=700,num_send_frames=700,recv_frame_size=11000,send_frame_size=11000"

As described above, the USRP example program benchmark_rate is very useful for playing with these parameters quickly and figuring out what the limits are.

The host side matters a lot here too. The amount of RAM available on the host directly limits the num_(send/recv)_frames. Processing speed of the host in general can easily be the bottleneck. Finally, some USB 3.0 controllers are known to work better than others. The Ettus knowledge base used to have a table of performance on a few selected USB controllers. An archive of that page is available here.

(The Pi 4 uses a VL805 controller. The Pi 5 has a new USB controller that performs far better.)

1.2 - Postprocessing

Working with data recorded using ORCA

ORCA provides pre-processing and post-processing code that supports operation of SDRs as chirped radar systems. These functions are meant to provide basic support and analysis for ice-penetrating radar data. More advanced processing can be built off this code. While we built this code to process data collected with radars running ORCA, we hope that the documentation makes it flexible enough to process other forms of ice-penetrating radar data as well.

Data Format

Pre-processing Functions

Pre-processing functions available in ORCA are centered around waveform (chirp) generation. They are found in the preprocessing/ folder of the main repo.

  • preprocessing/generate_chirp.py Generates digital IQ (in-phase and quadrature) samples based on configuration parameters provided in a YAML configuration file
    • Support for the following window functions is currently provided: Rectangular, Hamming, Blackman-Harris, Kaiser10, Kaiser14, and Kaiser18
    • We have found this document by G. Heinzel and others to be a very useful resource for understanding and implementing window functions

Post-processing Functions

A variety of post-processing functions are available in ORCA, ranging from basic radar processing, to radar system coherence characterization.

  • processing.py
  • processing_dask.py
  • save_data.py
  • merge_data.py

Post-processing Notebooks

We provide several starter notebooks to enable radar data processing and system characterization.

  • notebooks/Field Processing.ipynb A basic one stop radar processing code notebook, intended to produce ample information for in-field (or lab) debugging
    • Loads a binary file containing IQ radar data and resaves it into Zarr format
    • Loads the metadata associated with that binary file (YAML configuration file and log output)
    • Generates a reference chirp (by calling preprocessing/generate_chirp.py) to use for pulse compression
    • Displays a single received pulse in the time domain
    • Removes errors (if necessary) from the collected radar data
    • Coherently averages (stacks) the data based on user-input stacking parameter
    • Pulse compresses the data
    • Displays a 1D radargram of the first and last pulse compressed pulse
    • Displays a 2D radargram of the entire dataset
    • Displays spectrogram (fast-time) of received data
    • Displays power spectrum of received data
  • notebooks/Radar 1D File Compare.ipynb A notebook to compare 1D pulse compressed radar data in different files
    • Facilitates comparison between different radar configurations
  • notebooks/Radar 1D Stacking.ipynb A notebook to display a 1D radargram with different amounts of stacking
    • Facilitates comparison of stacking benefits and a quick check on coherence of data
  • notebooks/Radar Spectrogram.ipynb A notebook to display a fast time and slow time spectrogram of radar data
    • Facilitates debugging of RFI, frequency spurs, and other noise sources
  • notebooks/Dask Noise Statistics.ipynb A notebook to compute and display mean noise power and variance for radar data
    • Facilitates analysis of coherence by computing the mean noise power and variance for different amounts of radar stacking

The notebooks/orca_paper subdirectory contains notebooks used to produce the figures in our IEEE paper:

T. O. Teisberg, A. L. Broome and D. M. Schroeder, “Open Radar Code Architecture (ORCA): A Platform for Software-Defined Coherent Chirped Radar Systems,” in IEEE Transactions on Geoscience and Remote Sensing, vol. 62, pp. 1-11, 2024, Art no. 5109411, doi: 10.1109/TGRS.2024.3446368.

1.2.1 - File Formats

How data is stored from the ORCA system

1.2.2 - Processing Functions

Basic postprocessing functions for working with radar data

1.2.3 - Postprocessing Notebooks

An overview of ready-to-go notebooks for processing your data

2 - Peregrine UAV-Borne Radar

Details about the Peregrine UAS

Peregrine is an ice-penetrating radar-equipped small UAS designed to be low-cost and field portable.

2.1 - Peregrine Payload

Details about the Peregrine Radar Payload

Peregrine is an ice-penetrating radar-equipped small UAS designed to be low-cost and field portable.

2.1.1 - Peregrine Radar Payload Box Assembly

Build instructions for the core of the radar payload

Major components of the Peregrine radar payload box

Bill of Materials

A complete bill of materials for the payload box is available in this Google Sheet, embedded below.

Some parts are custom made. See the Custom Parts page for suggestions on how to build these yourself or source them from reliable vendors.

Assembly instructions

Preparing enclosure halves

The payload box is a clamshell-style design with two interlocking halves. The “upper” half contains the SDR. The “lower” half holds the Raspberry Pi. Sandwiched between the two halves is the “payload divider” PCB, which houses some sensors and provides for external connections.

Starting with the Pi Shell:

  1. Place 4x M2.5 heat inserts into the four mounting holes at the bottom of the box.
  2. Place 2x M2.5 heat inserts into the two front panel mounting holes on the front of the box.

Continuing with the SDR Shell:

  1. Place 4x M2.5 heat inserts into the four holes in the tabs sticking up, to accept bolts from the outside to connect the two shells together.

Copper foil wrapping

Print out the cutout templates TODO LINK. The first two pages are the cutout templates for the copper foil wrapping on the top and bottom of the enclosure.

Spread a layer of adhesive-backed copper out on a cutting-safe surface (such as a sacrificial piece of wood) and use some masking tape to hold it down. Tape the cutout template on top. Use an X-Acto knife (or similar) to cut:

  1. A small “X” across each of the external bolt holes (to allow a bolt to go through – no need to try to cut out the circle itself)
  2. The internal cutout for the heatsink
  3. The exterior outline of the entire piece.

If you’re not sure which lines to cut, take a moment to see how the paper folds around the 3D printed pieces. Your goal is to fully cover the exterior.

Carefully peel away the adhesive from part of the copper foil and start pasting it onto the 3D printed piece. It’s easier to do this bit by bit, rather than pulling the entire backing off at once.

Repeat with the other half of the enclosure.

2.1.2 - External button

An external lighted push button can be added to turn the radar on and off. A connector for this is included on the Payload Divider PCB.

Button choices

The intended button for the UAV version is HB15CKW01-5C-CB.

Most other similar buttons can be used. For the ground-based version, we use ULV4F2BSS311. Some of the photos on this page are taken with this button, which can be mounted externally.

Wiring the button

The button connector is a 5 position JST GH connector. The connectors and pre-crimped wires are available separately or you can buy a kit.

Schematic section for the button connector. Right side shows recommended connections to the lighted button.

If you buy pre-crimped wires, you’ll need to cut the connector on one side off.

For JST connectors, the small notch on the contact aligns with the bottom side of the connector. On the bottom of the connector housing, there are a series of small plastic pins that will raise up as you insert the contact. Once you get the contact fully inserted, this pin will fall back down to lay flush.

To assemble the button:

  1. Cut pre-crimped JST wires to length
  2. Following the image above, strip and solder the free end of each wire to the specified terminals on the switch.
  3. Place a piece of heatshrink over each terminal and shrink.
  4. (Optional) Twist the wires gently and use small pieces of heat shrink to keep them in a neat bundle.
  5. Insert the contacts into the JST GH 5 position housing, following the diagram above.
  6. Mount the button and insert the connector into the plug on the payload divider PCB.

Components of the button assembly. Note that these pictures were taken with the alternative version of the button designed to be mounted on the outside of a Pelican case.

Cables soldered to the terminals of the button

2.1.3 - Raspberry Pi Setup

In order to standardize between units, much of the Pi setup is automated or semi-automated. This guide will walk you through the steps of setting up your Pi the way we do. Along the way, there are also links for more information on how to customize this setup. This is an area where you will almost certainly need to customize some aspects of the setup.

Initial setup with cloud-init

Setup of the Raspberry Pi is semi-automated using cloud-init.

Cloud-init customization

The cloud-init setup is controlled by two files: user-data and network-config. (You’ll use these files a couple of steps down.)

Examples of each are shown below, but you will likely need to modify these to suit your purpose. We have pages on how to customize user-data and network-config.

user-data Example

#cloud-config

# This is the user-data configuration file for cloud-init.
# The cloud-init documentation has more details:
#
# https://cloudinit.readthedocs.io/

system_info:
  default_user:
    name: ubuntu # Allow the default user to shutdown or reboot the system without entering a password (used by our automated scripts)
    sudo: "ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown"

# On first boot, set the (default) ubuntu user's password to "cryosphere"
chpasswd:
  expire: false
  list:
  - ubuntu:$6$rounds=4096$aQ7tu0.beL3WAL32$fKxKYvZpY7EMCoxAU1heRomA3v8WvgbqBhhz08QwOtQdlP/DJOP2BThqZFoRW8d2a9PaIKK9BC9NHs1qNnkya1

# Enable password authentication with the SSH daemon
ssh_pwauth: true

# Set a default timezone
timezone: Etc/UTC

## On first boot, use ssh-import-id to give the specific users SSH access to
## the default user
ssh_import_id:
- gh:thomasteisberg
- gh:albroome
- gh:dfxmay

## Update apt database and upgrade packages on first boot
package_update: true
package_upgrade: true

## Install additional packages on first boot
packages:
- net-tools
- git
- cmake
- g++
- mosh
- exfat-fuse
- i2c-tools
- rpi.gpio-common
- util-linux-extra

## Write arbitrary files to the file-system
write_files:
- path: /home/ubuntu/initial_setup.sh
  content: |
    #!/bin/bash
    exec > >(tee -a "initial_setup_output.log") 2>&1
    # Miniconda Setup
    wget --progress=bar:force:noscroll "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-aarch64.sh" -O $HOME/miniconda.sh
    bash $HOME/miniconda.sh -b -p $HOME/miniconda
    cd $HOME
    source .profile
    source miniconda/etc/profile.d/conda.sh
    conda init bash
    # Setup logger environment
    git clone git@github.com:thomasteisberg/uav_radar_logger.git
    # Clone uhd_radar repo
    git clone git@github.com:radioglaciology/uhd_radar.git
    cd uhd_radar
    #git checkout thomas/dask # Uncomment if you want to check out a specific branch other than main
    conda env create -n uhd -f environment-rpi.yaml
    conda activate uhd
    python /home/ubuntu/miniconda/envs/uhd/lib/uhd/utils/uhd_images_downloader.py
    systemctl --user enable radar.service
    systemctl --user enable logger.service
    ifconfig
    sudo reboot    
  append: true
- path: /home/ubuntu/.profile
  content: |
    PATH=/home/ubuntu/miniconda/bin:$PATH
    source /home/ubuntu/.bashrc    
  append: true
- path: /home/ubuntu/.ssh/known_hosts
  content: |
    github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
    github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
    github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=    
- path: /etc/security/limits.conf # Recommended by Ettus https://kb.ettus.com/USRP_Host_Performance_Tuning_Tips_and_Tricks
  content: |
    ubuntu    - rtprio    99    
  append: true
- path: /etc/systemd/user/radar.service
  content: |
    [Unit]
    Description=Service to run the radar code on startup

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uhd_radar/
    ExecStart=/home/ubuntu/uhd_radar/manager/radar_service.sh

    Restart=always
    RestartSec=10

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target    
- path: /etc/systemd/user/logger.service
  content: |
    [Unit]
    Description=Service to log data from I2C sensors and automatically shutdown below a voltage threshold

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uav_radar_logger/
    ExecStart=/home/ubuntu/uav_radar_logger/logger_service.sh

    Restart=always
    RestartSec=60

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target    

# Run arbitrary commands at rc.local like time
# These commands are run with root permissions
# If you want commands run as a normal user, put them in initial_setup.sh (see above)
# which is run as the "ubuntu" user (see below)
runcmd:
- chown -R ubuntu:ubuntu /home/ubuntu
- chmod +x /home/ubuntu/initial_setup.sh
- wget -O /etc/udev/rules.d/uhd-usrp.rules https://raw.githubusercontent.com/EttusResearch/uhd/master/host/utils/uhd-usrp.rules
- usermod -a -G i2c ubuntu
- usermod -a -G dialout ubuntu
- usermod -a -G tty ubuntu
- apt remove -y modemmanager
- systemctl stop serial-getty@ttyS0.service && systemctl disable serial-getty@ttyS0.service
- i2cdetect -y 1
- echo "dtoverlay=i2c-rtc,pcf8523" >> /boot/firmware/config.txt
- loginctl enable-linger ubuntu
- mkdir /media/ssd
- chown ubuntu /media/ssd
- chgrp ubuntu /media/ssd
- echo "/dev/sda2  /media/ssd  exfat  defaults,nofail,uid=1000,gid=1000  0  2" | tee -a /etc/fstab

network-config Example

# This file contains a netplan-compatible configuration which cloud-init will
# apply on first-boot (note: it will *not* update the config after the first
# boot). Please refer to the cloud-init documentation and the netplan reference
# for full details:
#
# https://cloudinit.readthedocs.io/en/latest/topics/network-config.html
# https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html
# https://netplan.io/reference

version: 2
ethernets:
  eth0:  # Your ethernet name.
    dhcp4: no
    addresses: [192.168.11.137/24]
    gateway4: 192.168.11.1
    nameservers:
      addresses: [8.8.8.8,8.8.4.4]
wifis:
  renderer: networkd
  wlan0:
    dhcp4: true
    optional: true
    access-points:
      "<YOUR WIFI SSID HERE>":
        password: "<YOUR WIFI PASSWORD HERE>"

Imaging your Pi

To start, download the Raspberry Pi Imager tool (or use your preferred software for imaging SD cards). On Ubuntu, you can install it like this:

sudo apt install rpi-imager

For other operating systems, see the website.

Launch Imager. After clicking on “Choose OS,” navigate through the general purpose category to find Ubuntu Server 22.04.xx LTS 64-bit. 64-bit is important – 32-bit will not work.

You want Ubuntu Server 22.04 LTS 64-bit.

Insert your MicroSD card and select it as the location to write to.

After imaging is complete, you will see two drives mounted: writable and system-boot.

Copying cloud-init config files

After customizing the user-data and network-config files (see above), copy user-data and network-config to the system-boot volume, replacing the existing files.

Eject the microSD and put it back in the Pi.

Running cloud-init

Power up the Pi and wait for cloud-init to run.

Within about a minute, your Pi should connect to whatever network interface(s) are described in network-config and you should be able to find it on the network. If you setup some sort of key-based authentication (such as by importing a key from GitHub), it may take an extra couple of minutes for this to be ready.

After the network setup is complete, you should be able to login over SSH.

For instructions on SSH-ing into your Pi, see here. In particular, please note that you need to have SSH agent forwarding working. Instructions for this are on that page.

When you first login, cloud-init may not have finished running. To check the status, run:

cloud-init status --long

There are also logs in /var/log/cloud-init-output.log.

To keep an eye on the entire process, you can run:

watch "cloud-init status --long && tail -n 10 /var/log/cloud-init-output.log"

Expect this process to take a few minutes to complete.

Running initial_setup.sh

After the cloud-init process is complete, you’ll also need to run initial_setup.sh:

./initial_setup.sh

This will log to /home/ubuntu/initial_setup_output.log. It may take around 10 minutes to complete. It will automatically reboot your Pi at the end. If you don’t want this, feel free to comment out the last line.

Setting up the logger service

More details on the logging service will eventually be here.

For now, if you’re building a Peregrine system, the default setup should be fine.

If you’re building Eyas, first run this command to create a place for logs:

mkdir /media/ssd/logger

And then modify the last line of /home/ubuntu/uav_radar_logger/logger_service.sh to look like this:

python -u logger.py --shutdown-voltage 11.8 --log-dir /media/ssd/logger/

Replacing 11.8 with an appropriate threshold (in volts) at which to shutdown.

Setting up the radar service

More details on the radar service will eventually be here.

For now, if you’re building a Peregrine system, the default setup should be fine.

If you’re building Eyas, run these commands to create a location for logging data and update the default configuration:

mkdir /media/ssd/radar
cd /home/ubuntu/uhd_radar/config
mv default.yaml default-old.yaml
cp default-eyas.yaml default.yaml

Using overlayroot to protect the file system

Let’s take a quick step back and talk about the problem before we talk about (perhaps drastic) solutions:

MicroSD card corruption is an ongoing challenge for any device (such as the Pi) that boots from a MicroSD card. Most cases can be traced to either (a) junk MicroSD cards or (b) sudden power loss during a write operation.

You can mostly avoid problem (a) by carefully sourcing your MicroSD cards. If you’re considering saving $20 by buying an off-brand MicroSD card or, worse, a possible fake, don’t do it.

Sudden power loss is harder to completely guard against. Ideally, you want to shutdown your Pi before removing power. This can be done by SSH-ing into it and running sudo shutdown -h now and waiting about a minute. If you use our radar systemd service, you can also perform a safe shutdown by pressing and holding the button for 5 seconds, waiting for the light to turn off, and then giving it about a minute to fully shut down.

For Peregrine, this should all be good enough.

For Eyas and other longer-term installations, the risk of a battery discharging faster than expected is relatively high.

The first line of defense is the automatic shutdown provided by the logger service that will safely shut everything down when the battery voltage gets too low.

If you have a separate data storage device, such as an external USB-connected SSD, you can go a step further and make your Pi’s MicroSD card read-only.

A utility called overlayroot (which uses OverlayFS) can be used to make the root filesystem read-only and create an “overlay filesystem” stored only in RAM. This means that all changes to the MicroSD card file system are temporary and are lost upon reboot.

This is great for keeping a standardized configuration, but you need to be aware that this means most log files are lost on reboot, any config files on the SD card are lost on reboot, and all radar data you want to keep around must be stored to your external storage device.

You can read a more detailed tutorial about Overlayroot here.

If you’re going to do this, be sure that you’ve tested your setup in advance so you know if the data you want to keep is being stored.

Enabling Overlayroot

If you still want to set this up, it’s quite easy to enable.

Simply edit the last line of /etc/overlayroot.conf to this:

overlayroot="tmpfs:recurse=0"

tmpfs specifies that we want to use a RAM-based filesystem to store the “overlay” part of the filesystem. recurse=0 says that we want to apply this only to the / mount and not to other mounts below that.

(You need to edit this file as root. For example: sudo vim /etc/overlayroot.conf)

Disabling Overlayroot

You can temporarily re-mount the root filesystem to edit things using the built-in utility:

sudo overlayroot-chroot

To exit, just type exit.

If you want to permenantly disable it, just edit the last line of /etc/overlayroot.conf back to:

overlayroot=""

and reboot.

2.1.3.1 - Connecting to the Raspberry Pi

By default, the cloud-init script sets up a static IP of 192.168.11.137, but you could choose to configure this to something different for each payload box.

Our usual way of connecting is by plugging an ethernet cable into the Pi and connecting it to a laptop. You can read about all the networking options here.

SSH agent forwarding

If you need to authenticate to GitHub to download code, the recommended way is using SSH agent forwarding.

To summarize the above link, you should make sure you have an SSH key setup with your GitHub account.

Test this by running ssh -T git@github.com.

Then modify your ~/.ssh/config file to have an entry like this enabling SSH agent forwarding:

Host 192.168.11.137
    HostName 192.168.11.137
    User ubuntu
    ForwardAgent yes

SSH’ing to your Pi

Connect to the Raspberry Pi over SSH:

ssh ubuntu@192.168.11.137

You can test that your SSH agent forwarding is working by running ssh -T git@github.com again while SSH’d into your Pi.

2.1.3.2 - Debugging: Real-Time Clocks (RTC)

RTC choices and possible solutions to RTC issues

A real-time clock (RTC) is a small device equipped with a crystal oscillator and a battery that is responsible for keeping track of the current time, even while your computer (or Raspberry Pi) is powered off.

The “Payload Enclosure Divider” includes a PCF8523 RTC and a holder for a CR1220 coin cell battery. The provided user-config file should automatically set this up. As long as you provide internet access to the Pi once, it will automatically synchronize the RTC with a network time server and everything will just work.

Tell me more about the PCF8523 and how to use it

Adafruit makes a great breakout for the same chip available here.

They also have a great tutorial on setting up various RTCs with the Pi.

My Pi 5 already has an RTC built-in, right?

Yes, there is. Documentation for the built-in RTC is here.

The built-in RTC still requires adding a rechargable lithium manganese battery to the Pi’s J5 connector. Charging of this battery is disabled by default and must be manually enabled.

In theory, you should be able to add the official Pi RTC battery, follow the official instructions to enable charging, and everything should work.

I have a Pi 5 but still want to use

2.1.3.3 - Customizing user-data

The default user-data we start from is as shown below. You will likely need to tweak many of these settings. Descriptions and tips for the most important sections are below.

Note that this is one of two key configuration files. You can read about network-config here.

Starting point user-data file

#cloud-config

# This is the user-data configuration file for cloud-init.
# The cloud-init documentation has more details:
#
# https://cloudinit.readthedocs.io/

system_info:
  default_user:
    name: ubuntu # Allow the default user to shutdown or reboot the system without entering a password (used by our automated scripts)
    sudo: "ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown"

# On first boot, set the (default) ubuntu user's password to "cryosphere"
chpasswd:
  expire: false
  list:
  - ubuntu:$6$rounds=4096$aQ7tu0.beL3WAL32$fKxKYvZpY7EMCoxAU1heRomA3v8WvgbqBhhz08QwOtQdlP/DJOP2BThqZFoRW8d2a9PaIKK9BC9NHs1qNnkya1

# Enable password authentication with the SSH daemon
ssh_pwauth: true

# Set a default timezone
timezone: Etc/UTC

## On first boot, use ssh-import-id to give the specific users SSH access to
## the default user
ssh_import_id:
- gh:thomasteisberg
- gh:albroome
- gh:dfxmay

## Update apt database and upgrade packages on first boot
package_update: true
package_upgrade: true

## Install additional packages on first boot
packages:
- net-tools
- git
- cmake
- g++
- mosh
- exfat-fuse
- i2c-tools
- rpi.gpio-common
- util-linux-extra

## Write arbitrary files to the file-system
write_files:
- path: /home/ubuntu/initial_setup.sh
  content: |
    #!/bin/bash
    exec > >(tee -a "initial_setup_output.log") 2>&1
    # Miniconda Setup
    wget --progress=bar:force:noscroll "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-aarch64.sh" -O $HOME/miniconda.sh
    bash $HOME/miniconda.sh -b -p $HOME/miniconda
    cd $HOME
    source .profile
    source miniconda/etc/profile.d/conda.sh
    conda init bash
    # Setup logger environment
    git clone git@github.com:thomasteisberg/uav_radar_logger.git
    # Clone uhd_radar repo
    git clone git@github.com:radioglaciology/uhd_radar.git
    cd uhd_radar
    #git checkout thomas/dask # Uncomment if you want to check out a specific branch other than main
    conda env create -n uhd -f environment-rpi.yaml
    conda activate uhd
    python /home/ubuntu/miniconda/envs/uhd/lib/uhd/utils/uhd_images_downloader.py
    systemctl --user enable radar.service
    systemctl --user enable logger.service
    ifconfig
    sudo reboot    
  append: true
- path: /home/ubuntu/.profile
  content: |
    PATH=/home/ubuntu/miniconda/bin:$PATH
    source /home/ubuntu/.bashrc    
  append: true
- path: /home/ubuntu/.ssh/known_hosts
  content: |
    github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
    github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
    github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=    
- path: /etc/security/limits.conf # Recommended by Ettus https://kb.ettus.com/USRP_Host_Performance_Tuning_Tips_and_Tricks
  content: |
    ubuntu    - rtprio    99    
  append: true
- path: /etc/systemd/user/radar.service
  content: |
    [Unit]
    Description=Service to run the radar code on startup

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uhd_radar/
    ExecStart=/home/ubuntu/uhd_radar/manager/radar_service.sh

    Restart=always
    RestartSec=10

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target    
- path: /etc/systemd/user/logger.service
  content: |
    [Unit]
    Description=Service to log data from I2C sensors and automatically shutdown below a voltage threshold

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uav_radar_logger/
    ExecStart=/home/ubuntu/uav_radar_logger/logger_service.sh

    Restart=always
    RestartSec=60

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target    

# Run arbitrary commands at rc.local like time
# These commands are run with root permissions
# If you want commands run as a normal user, put them in initial_setup.sh (see above)
# which is run as the "ubuntu" user (see below)
runcmd:
- chown -R ubuntu:ubuntu /home/ubuntu
- chmod +x /home/ubuntu/initial_setup.sh
- wget -O /etc/udev/rules.d/uhd-usrp.rules https://raw.githubusercontent.com/EttusResearch/uhd/master/host/utils/uhd-usrp.rules
- usermod -a -G i2c ubuntu
- usermod -a -G dialout ubuntu
- usermod -a -G tty ubuntu
- apt remove -y modemmanager
- systemctl stop serial-getty@ttyS0.service && systemctl disable serial-getty@ttyS0.service
- i2cdetect -y 1
- echo "dtoverlay=i2c-rtc,pcf8523" >> /boot/firmware/config.txt
- loginctl enable-linger ubuntu
- mkdir /media/ssd
- chown ubuntu /media/ssd
- chgrp ubuntu /media/ssd
- echo "/dev/sda2  /media/ssd  exfat  defaults,nofail,uid=1000,gid=1000  0  2" | tee -a /etc/fstab

Password-less shutdown

system_info:
  default_user:
    name: ubuntu # Allow the default user to shutdown or reboot the system without entering a password (used by our automated scripts)
    sudo: "ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown"

One of the features supported by the uav_radar_logger utility is to automatically cleanly shutdown the system if the measured battery voltage drops below a threshold. To facilitate this, the default user must be able to shutdown the system without needing additional authentication. This gives permission for the ubuntu user to call sudo shutdown or sudo reboot without entering a password.

Password authentication

# On first boot, set the (default) ubuntu user's password to "cryosphere"
chpasswd:
  expire: false
  list:
  - ubuntu:$6$rounds=4096$aQ7tu0.beL3WAL32$fKxKYvZpY7EMCoxAU1heRomA3v8WvgbqBhhz08QwOtQdlP/DJOP2BThqZFoRW8d2a9PaIKK9BC9NHs1qNnkya1

# Enable password authentication with the SSH daemon
ssh_pwauth: true

This sets up a default password for the ubuntu user. You should change this to something else (or disable password authentication completely, if you prefer).

Passwords are stored in a hashed format. You can generate password hashes using this utility:

mkpasswd --method=SHA-512 --rounds=4096

Timezone

# Set a default timezone
timezone: Etc/UTC

You could set this to other time zones (i.e. `America/Los_Angeles"), but really it would make everyone’s life easier if you just set your clock to UTC.

Add SSH keys through GitHub

## On first boot, use ssh-import-id to give the specific users SSH access to
## the default user
ssh_import_id:
- gh:thomasteisberg
- gh:albroome
- gh:dfxmay

You can very conveniently enable key-based authentication for specific GitHub user names. If your username is in here and you have a public key setup with GitHub, this public key will be imported and you will be able to SSH into your Pi with no additional setup. You might want to remove us from your list, though. :)

Files

Arbitrary files can be written to the system with cloud-init. Some of these are important.

initial_setup.sh

- path: /home/ubuntu/initial_setup.sh
  content: |
    #!/bin/bash
    exec > >(tee -a "initial_setup_output.log") 2>&1
    # Miniconda Setup
    wget --progress=bar:force:noscroll "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-aarch64.sh" -O $HOME/miniconda.sh
    bash $HOME/miniconda.sh -b -p $HOME/miniconda
    cd $HOME
    source .profile
    source miniconda/etc/profile.d/conda.sh
    conda init bash
    # Setup logger environment
    git clone git@github.com:thomasteisberg/uav_radar_logger.git
    # Clone uhd_radar repo
    git clone git@github.com:radioglaciology/uhd_radar.git
    cd uhd_radar
    #git checkout thomas/dask # Uncomment if you want to check out a specific branch other than main
    conda env create -n uhd -f environment.yaml
    conda activate uhd
    python /home/ubuntu/miniconda/envs/uhd/lib/uhd/utils/uhd_images_downloader.py
    systemctl --user enable radar.service
    systemctl --user enable logger.service
    ifconfig
    sudo reboot
  append: true

The initial_setup.sh script grabs copies of our code and sets up the radar and logging services. This script is intended to be manually run the first time you SSH into the system. This enables you to use SSH agent forwarding to provide any needed GitHub authentication to get the code.

This is also where you would customize the repositories to check out (if, for example, you’ve forked our code) and where you can pick a branch to automatically checkout.

Radar and Logger services

- path: /etc/systemd/user/radar.service
  content: |
    [Unit]
    Description=Service to run the radar code on startup

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uhd_radar/
    ExecStart=/home/ubuntu/uhd_radar/manager/radar_service.sh

    Restart=always
    RestartSec=10

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target
- path: /etc/systemd/user/logger.service
  content: |
    [Unit]
    Description=Service to log data from I2C sensors and automatically shutdown below a voltage threshold

    [Service]
    Type=simple
    WorkingDirectory=/home/ubuntu/uav_radar_logger/
    ExecStart=/home/ubuntu/uav_radar_logger/logger_service.sh

    Restart=always
    RestartSec=60

    KillSignal=SIGINT

    [Install]
    WantedBy=default.target

Two systemd services are used to manage everything. One run the radar code in its default button-controlled setup. The other runs basic logging of I2C-connected sensors and handles automatic low-battery shutdown.

Run commands

runcmd:
- chown -R ubuntu:ubuntu /home/ubuntu
- chmod +x /home/ubuntu/initial_setup.sh
- wget -O /etc/udev/rules.d/uhd-usrp.rules https://raw.githubusercontent.com/EttusResearch/uhd/master/host/utils/uhd-usrp.rules
- usermod -a -G i2c ubuntu
- usermod -a -G dialout ubuntu
- usermod -a -G tty ubuntu
- apt remove -y modemmanager
- systemctl stop serial-getty@ttyS0.service && systemctl disable serial-getty@ttyS0.service
- i2cdetect -y 1
- echo "dtoverlay=i2c-rtc,pcf8523" >> /boot/firmware/config.txt
- loginctl enable-linger ubuntu
- mkdir /media/ssd
- echo "/dev/sda2  /media/ssd  exfat  defaults,nofail,uid=1000,gid=1000  0  2" | tee -a /etc/fstab

Some final setup is done by running arbitrary commands. These are run as the root user.

One aspect of this you may wish to customize are the last two lines, which add settings to automatically mount an ExFAT-formatted SSD plugged into the Pi. This can be (optionally) used as a storage location for radar data.

Testing changes

You may want to test your changes before using them on your Pi. Options for doing that are described here. Note that the initial_setup.sh script downloads miniconda for the aarch64 architecture, which probably won’t work on your computer. If you want to test that part, you’ll need to change this.

2.1.3.4 - Raspberry Pi Network Connection

Most of the time, the Pi doesn’t need an internet connection. There are, however, a couple of cases where you may want some sort of a network connection to the Pi.

These are:

  1. Downloading data from the Pi to a computer (requires a network connection but not internet)
  2. Initial setup with cloud-init (requires internet)
  3. Updating code by pulling from GitHub (requires internet)

Setting up network interfaces

With cloud-init (first-time setup)

The preferred way to setup network interfaces is by providing them in the network-config file read by cloud-init when first setting up the Pi.

An example is shown below:

# This file contains a netplan-compatible configuration which cloud-init will
# apply on first-boot (note: it will *not* update the config after the first
# boot). Please refer to the cloud-init documentation and the netplan reference
# for full details:
#
# https://cloudinit.readthedocs.io/en/latest/topics/network-config.html
# https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html
# https://netplan.io/reference

version: 2
ethernets:
  eth0:  # Your ethernet name.
    dhcp4: no
    addresses: [192.168.11.137/24]
    gateway4: 192.168.11.1
    nameservers:
      addresses: [8.8.8.8,8.8.4.4]
wifis:
  renderer: networkd
  wlan0:
    dhcp4: true
    optional: true
    access-points:
      "<YOUR WIFI SSID HERE>":
        password: "<YOUR WIFI PASSWORD HERE>"

The above configuration sets up a static IP over the ethernet interface. It also configures 192.168.11.1 as the default gateway. This allows for sharing an internet connection from a computer over this interface if desired.

The configuration also provides an SSID and password for a WiFi network. In practice, we configure this to the settings for a phone hotspot that can be used to get internet when WiFi is not otherwise available. This is also a simpler setup for getting the Pi on the internet when needed.

Reconfiguring with netplan

By default, network interfaces are configured with netplan. See the netplan documentation for more details.

Configuring your computer

If you’re connecting to the Pi via a simple ethernet cable to your computer, you’ll want both configured with static IPs. The config file above will do this on the Pi side. On your computer, you’ll want to set a static IP of 192.168.11.1 and a netmask of 255.255.255.0. For example, your settings may look like this:

Example laptop configuration to connect to the Pi over an ethernet cable

Sharing internet from your computer

Sharing your computer’s internet connection from one network interface to another is a useful trick to get an internet connection for the Pi. This can be done between any two network interfaces (i.e. from your WiFi connection to the Pi over ethernet or from one WiFi card to a network created by a second WiFi card).

The Arch Linux wiki has useful instructions for configuring internet sharing on Linux.

To briefly summarize them:

All of this setup will happen on your laptop (or whatever computer has an internet connection).

  1. Enable IPv4 forwarding: sudo sysctl net.ipv4.ip_forward=1
  2. Setup NAT with iptables: (See note below if you have docker installed!)
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i net0 -o wlan0 -j ACCEPT

Where wlan0 should be replaced with the name of the network interface on your computer which is currently connected to the internet, and net0 should be replaced with the name of the network interface on the same computer which is connected to the Pi.

2.2 - 5V Power Board and Power Harnesses

Build instructions for 5V power system

5V Power Harness

All lengths are tip to tip (including any connectors)

Connector side 1: Female M8 connector (McMaster Part #8605N11)

M8 pinout:

  • Brown wire: 5V
  • Blue wire: GND

Cut brown wire ~0.5 cm shorter than blue

Connector side 2: Molex Micro-Fit 3.0 3 Pos connector (Molex Part #0436450300)

Molex Micro-Fit 3 Position (5V Power) pinout – note location of the line indicating pin 1

Molex Micro-Fit pinout:

  • Pin 1: unused
  • Pin 2: GND (black)
  • Pin 3: 5V (red)

Use 22 AWG female terminals

Pre-crimped wires are available. Molex part is 2147611201 (black) and 2147612201 (red)

Cut black ~0.5 cm shorter than red

Assembly instructions

Components of 5V power harness

  1. Cut all wires as shown in the photo above
  2. Solder M8 brown wire to red Micro-Fit
  3. Solder M8 blue wire to black Micro-Fit
  4. Heat shrink each individual connection
  5. Place larger (glue lined, if available) heat shrink over both connections and extending onto the yellow sheath of the M8 cable
  6. Insert female MicroFit terminals into 3 position connector

Result should look like below:

Completed 5V power harness (on left) connected to 5V power board

2.3 - Peregrine Radar Operating Instructions

Details about the Peregrine UAS

Operating instructions for the Peregrine radar system

2.3.1 - Logger Service Setup

TODO

2.4 - UAV Setup

Instructions for building and setting up the aircraft part

test test

2.5 - Other Resources

Other resources and tips

2.5.1 - Sourcing Custom Parts

Suggestions for getting custom parts made

Many of the parts used to build Peregrine are custom designs. In some instances, these may be things you can build yourself if you have the right tools (3D printer, laser cutter, etc). In almost all cases, however, it is also possible to use our provided design files to order parts from reliable vendors. For each type of manufacturing, this page includes some notes about how we produce parts or which external vendors we have worked with.

You can find design files for the hardware components of Peregrine in the TODO LINK TO PEREGRINE HARDWARE GITHUB.

3D Printing

All of our designs rely heavily on 3D printing. We use a Prusa MK3S+ and Prusament ASA Lipstick Red filament.

All of our designs should be printable on any common FDM printer.

Outsourced 3D printing is expensive relative to the low costs of desktop 3D printers, especially for parts (like ours) that generally don’t need sub-mm tolerances. That said, there many 3D printing services. I have had good experiences with Shapeways. If you have a vendor produce your parts and you are intending to use them in a UAV, be sure to specify that you don’t want them to be printed solid. We usually print at about 20% infill density. Solid parts will weigh a lot more.

ASA is our plastic of choice because it forms parts that are relatively strong, relatively light weight, work over an extended temperature range, and suffer minimal UV degradation. The downside is that it is somewhat more difficult to print with, as compared to PLA filament. We use an enclosure to provide a heated build volume. Almost all prints are done with a brim (setting available in your slicing software) to avoid warping.

PLA is great for prototyping but likely not a good choice for critical outdoor applications. PLA can easily get hot enough to warp in direct sunlight and may degrade under extended exposure to UV.

ABS or other similar materials should be fine.

Note that filament type, nozzle size, and slicing settings will all make a big difference to the final weight of your parts.

Laser Cutting

All laser cut parts are produced by SendCutSend. Ponoko, Xometry, and many other options also exist. If you want to match our materials exactly, though, SendCutSend will be easiest. If you have a laser cutter, you could also make them yourself.

CNC Machining

We usually have CNC machined parts made through HUBS, which distributes jobs to a network of machine shops. They are not the cheapest option, but they provide a nice balance between cost, speed, and reliability.

Printed Circuit Boards (PCBs) and PCB Assembly

You can send most of our design files to any PCB shop you like working with.

The one exception are the Peregrine under-wing antennas, which are larger (about 56 cm by 11 cm) than the maximum dimensions allowed for standard orders at many PCB manufacturers. Additionally, we usually get these printed on thinner FR4 to reduce weight. These two factors make them very expensive at some board shops. PCBWay will produce 5 (un-assembled boards) for less than $150 including shipping to the US.

3 - MAPPERR Multi-frequency Radar

Guidelines for building MAPPERR: a towable, coherent, ground-based, ice-penetrating radar

At its core MAPPERR, the Multi-frequency Active-Passive Exploration Radar-Radiometer, is a software-defined radio (SDR)-based ice-peentrating radar that runs Open Radar Code Architecture (ORCA). Operable as a snowmachine-towed radar, MAPPERR is one of the few coherent ground-based ice-penetrating radars that has sufficient flexibilty and low enough cost to be utilized by a wide swath of field-going glaciologists. These pages focus on outlining suggestions for building your own version of MAPPERR. For more information on the architecture of a multi-frequency joint radar-radiometer, we refer you to the following publications:

  • add list/section on publications>

3.1 - MAPPERR Core Hardware

Minimum Required Hardware

The core components of MAPPERR are an Ettus X310 software-defined radio (SDR), an external host computer, and two antennas (one for transmitting and one for receiving). We use these log-periodic dipole array antennas, as well as homemade resistively loaded dipoles, depending on our desired center frequency. For our host computer, we typically use a laptop running Ubuntu.

MAPPERR can be operated in a basic configuration by connecting the antennas as shown in the diagram below and utilizing the default_x310.yaml configuration file with ORCA.

  • ADD DIAGRAM

Interfacing with the X310

The Ettus X310 can connect to a host computer via either 1 Gb or 10 Gb ethernet. When connected via 1 Gb ethernet, the maximum radar sample rate that does not produce significant communications errors between the host computer and SDR is 25 MHz. When using 10 Gb ethernet, sample rates of up to 100 MHz are possible. To achieve the maximum possible sample rate (200 MHz), dual 10 Gb ethernet connection is needed.

Communicating with the SDR over ethernet requires network settings on the host computer to be set appropriately. See this guide and this guide from Ettus for more information. A startup script is available in ORCA that can be modified be each user. You can check that you are connected correctly to the X310 by pinging its IP address (typically 192.168.10.2 for a 1 Gb ethernet connection and 192.168.40.2 for a 10 Gb connection), or running uhd_find_devices and uhd_usrp_probe from a terminal.

UHD Version

Currently, ORCA has only been tested with an X310/MAPPERR when running and using UHD 3.15. In principle, upgrades to future UHD versions should be possible, however, we have previously run into some issues when upgrading.

3.1.1 - Alternative MAPPERR Configurations

3.2 - MAPPERR Antennas

Antennas are an important part of any radar system, and especially a multi-frequency system like MAPPERR. In a full multi-frequency active-passive configuration, we envision MAPPERR having active radar channels centered at 2, 22, 330, and 1000 MHz, along with passive radiometer channels at 300 and 1000 MHz. We use these log-periodic dipole array antennas to cover the 300 MHz and above bands. To cover the 2 and 22 MHz channels, we utilize homemade resistively loaded dipoles, each tuned to our desired center frequency.

Resistively Loaded Dipole Construction

Resistively loaded dipoles are built based on the Wu-King resistive loading method (Wu and King, 1965). We use #14 Flex-Weave^TM^ wire from Davis RF to construct our dipoles. Resistors should be rated for the appropriate peak and average power.

3.3 - Towing MAPPERR

Details about towing the MAPPERR system

Inner Tube Sled Design

Crossbar Sled Design

4 -

T. O. Teisberg, A. L. Broome and D. M. Schroeder, “Open Radar Code Architecture (ORCA): A Platform for Software-Defined Coherent Chirped Radar Systems,” in IEEE Transactions on Geoscience and Remote Sensing, vol. 62, pp. 1-11, 2024, Art no. 5109411, doi: 10.1109/TGRS.2024.3446368.