OpenSourceSDRLab PortaRF · Volume 10

OpenSourceSDRLab PortaRF Volume 10 — Custom Firmware Development

Mayhem fork pattern, build toolchain, adding a custom app, PortaRF UI considerations, common pitfalls

Contents

SectionTopic
1About this volume
2Forking Mayhem — the standard pattern
3Build toolchain setup
4Mayhem source-tree orientation
5Adding a custom Mayhem app — worked example
6PortaRF-specific UI considerations
7Build + flash workflow
8Cherry-picking features from Havoc + other forks
9Common build errors + fixes
10Maintenance + upstream sync
11Resources

1. About this volume

Vol 10 covers custom Mayhem firmware development for PortaRF. This is the deepest-rabbit-hole volume — most operators never need it. It’s relevant when:

  • A protocol you care about isn’t in mainline Mayhem
  • You want UI customizations specific to your workflow
  • You’re contributing to Mayhem upstream
  • You’re researching PortaPack firmware internals

Cross-reference: HackRF One Vol 10 covers the canonical Mayhem development workflow (build toolchain, source organization, worked custom-app example). This volume adds PortaRF-specific considerations — sealed-enclosure debugging constraints, integrated-UI-hardware variations, single-box thermal limits that may affect sustained-development testing cycles.

For tjscientist: this is aspirational content — relevant only if PortaRF is acquired and a specific custom-firmware need emerges. Until then, mainline Mayhem covers all typical use cases.


2. Forking Mayhem — the standard pattern

2.1 GitHub fork workflow

1. Browse to https://github.com/portapack-mayhem/mayhem-firmware
2. Click "Fork" → creates github.com/<your-username>/mayhem-firmware
3. Clone locally:
   git clone --recursive https://github.com/<your-username>/mayhem-firmware.git
   cd mayhem-firmware
4. Set up upstream remote:
   git remote add upstream https://github.com/portapack-mayhem/mayhem-firmware.git
   git fetch upstream
5. Create a feature branch:
   git checkout -b my-custom-feature

The --recursive clone is important — Mayhem has Git submodules (chibios-rt, libopencm3, etc.) that need to be cloned alongside.

2.2 Maintaining sync with upstream

# Periodically sync with upstream
git fetch upstream
git checkout main  # or master, depending on branch convention
git merge upstream/main
git push origin main

# For your feature branches:
git checkout my-custom-feature
git rebase main

For long-running features: rebase weekly or at least when starting a new development session.

2.3 Why fork rather than just download

  • Reproducible builds: your commit history records exactly what changed
  • Contribute upstream: if your work is generally useful, PR to mainline
  • Branch isolation: experiment on branches without breaking your stable build
  • Backup: GitHub serves as a backup of your work

2.4 What never to do

  • Never commit secret material (firmware-side keys, signed binaries from vendor) — once on GitHub, public
  • Never force-push to mainline — breaks anyone else who’s pulled
  • Never run experimental builds on field PortaRF — use a dev unit (or your spare unit)

3. Build toolchain setup

3.1 Prerequisites

ToolPurposeInstall
gcc-arm-none-eabiARM cross-compilerPlatform-specific (below)
cmakeBuild systemMost package managers
libopencm3Low-level Cortex-M supportSubmodule of Mayhem
dfu-utilDFU mode flashingMost package managers
gitSource managementMost package managers
A text editorCode editingVS Code, vim, emacs

3.2 macOS setup

# Install Homebrew if not already present
brew install --cask gcc-arm-embedded
brew install cmake dfu-util
brew install libopencm3   # (or build from Mayhem submodule)

3.3 Linux setup (Debian/Ubuntu)

sudo apt update
sudo apt install gcc-arm-none-eabi cmake libopencm3-dev dfu-util git

3.4 Windows setup

  • WSL2 (recommended): install Ubuntu in WSL2, follow Linux instructions
  • Native Windows: install gcc-arm-none-eabi from arm.com, cmake from cmake.org, dfu-util from sourceforge

WSL2 is the easier path for Windows users — Mayhem’s build system is bash-friendly.

3.5 Verify toolchain

arm-none-eabi-gcc --version
# Expected: arm-none-eabi-gcc (Arm GNU Toolchain ...) X.Y.Z

cmake --version
# Expected: cmake version X.Y.Z

dfu-util --version
# Expected: dfu-util X.Y

If any of these fail: re-install the missing tool.

3.6 First build

cd mayhem-firmware
mkdir build && cd build
cmake ..
make -j

# Result: portapack-*-mayhem.bin in build directory

If the build succeeds, you have a Mayhem .bin ready to flash. Run ls *.bin to find it.


4. Mayhem source-tree orientation

4.1 Top-level structure

mayhem-firmware/
├── firmware/                Main firmware tree
│   ├── application/         Application-layer code
│   │   ├── apps/            Per-app sources
│   │   ├── ui/              UI primitives (widgets, layout)
│   │   ├── disp/            Display abstraction
│   │   ├── encoder.cpp      Encoder handling
│   │   ├── battery.cpp      Battery management
│   │   └── main.cpp         Top-level entry
│   ├── baseband/            DSP / signal processing
│   │   ├── apps/            Baseband apps (RX/TX waveform processing)
│   │   └── proc_*.cpp       Per-mode processing
│   ├── common/              Shared utilities
│   ├── chibios-portapack/   ChibiOS RTOS subset
│   └── CMakeLists.txt       Top-level build config
├── flashloader/             Bootloader code
├── docs/                    Architecture documentation
├── tools/                   Build helpers + utility scripts
└── README.md

4.2 The application/apps directory

Each Mayhem app is one or more .cpp + .hpp files in firmware/application/apps/:

firmware/application/apps/
├── ais_app.cpp / .hpp           # AIS decoder
├── adsb_app.cpp / .hpp          # ADS-B decoder
├── analog_audio_app.cpp / .hpp  # Audio receiver
├── replay_app.cpp / .hpp        # Replay attack
├── capture_app.cpp / .hpp       # I/Q capture
└── ...

The analog_audio_app.cpp is a useful reference — it’s a relatively simple app that exercises the standard Mayhem patterns (RX, demodulation, audio output, UI).

4.3 The baseband directory

Baseband apps run on the Cortex-M0 second core, doing real-time DSP. Each app has both an application-side (firmware/application/apps/) and a baseband-side (firmware/baseband/) component.

For most custom development, you modify the application side. Baseband modifications are advanced.

4.4 The UI subsystem

firmware/application/ui/
├── button.hpp        # Button widget
├── checkbox.hpp      # Checkbox widget
├── grid.hpp          # Menu grid
├── label.hpp         # Text label
├── menu.hpp          # Menu primitive
├── text_edit.hpp     # Text entry
└── ...

For UI customization (new buttons, custom layouts), these are your starting points.

4.5 Key utility classes

ClassPurpose
ViewBase class for all UI views
NavigationViewManages view stack (push, pop)
ReceiverAbstracts RX state
TransmitterAbstracts TX state
ApplicationLifecycle hooks

Read firmware/application/main.cpp for the entry-point flow. New apps are registered through Mayhem’s app-registry mechanism.


5. Adding a custom Mayhem app — worked example

A minimal “hello world” app pattern.

5.1 Create the source files

firmware/application/apps/hello_app.hpp:

#pragma once

#include "ui_navigation.hpp"
#include "ui_widget.hpp"

namespace ui {

class HelloAppView : public View {
public:
    HelloAppView(NavigationView& nav);

    void focus() override;
    std::string title() const override { return "Hello"; }

private:
    NavigationView& nav_;

    Text text_hello {
        { 0, 0, 240, 16 },
        "Hello, PortaRF!"
    };
};

} /* namespace ui */

firmware/application/apps/hello_app.cpp:

#include "hello_app.hpp"

namespace ui {

HelloAppView::HelloAppView(NavigationView& nav) :
    nav_(nav)
{
    add_children({ &text_hello });
}

void HelloAppView::focus() {
    text_hello.focus();
}

} /* namespace ui */

5.2 Register the app

In firmware/application/main.cpp (or wherever the app registry lives in current Mayhem — verify against source):

#include "hello_app.hpp"

// In the appropriate registration block:
nav.push<HelloAppView>();   // or add to the menu structure

(The exact registration mechanism varies between Mayhem versions; check current source for the specific pattern.)

5.3 Update CMakeLists.txt

In firmware/application/CMakeLists.txt:

# Add hello_app to the source list
target_sources(application PRIVATE
    # ... existing sources ...
    apps/hello_app.cpp
)

5.4 Build + flash + test

cd mayhem-firmware/build
make -j
# Result: portapack-*-mayhem.bin

# Copy to SD card; flash via SD-card method (Vol 8 § 3)
cp portapack-h1_h2-mayhem.bin /Volumes/PORTARF/
# Insert SD into PortaRF, reboot

On PortaRF: navigate to the menu where you registered the app; launch; should display “Hello, PortaRF!“

5.5 Iterating

For development cycles:

  1. Edit source on host
  2. make (incremental build is fast — a few seconds)
  3. cp .bin to SD
  4. Reboot PortaRF
  5. Test on device

The flash + reboot cycle takes ~1 minute. For rapid iteration, use a dedicated dev SD that lives in PortaRF.

5.6 Beyond hello world

The interesting Mayhem apps:

  • RX with demodulation: see analog_audio_app.cpp
  • TX with synthesis: see siggen_app.cpp (signal generator)
  • Custom decoder: see adsb_app.cpp or any decoder
  • Capture: see capture_app.cpp
  • Replay: see replay_app.cpp

Each is a self-contained example of a Mayhem app architecture. Read several before starting your own custom app.


6. PortaRF-specific UI considerations

6.1 Display hardware variations

Mayhem builds support different PortaPack revisions. PortaRF likely uses H2+ or H4M:

  • H2+ build: portapack-h1_h2-mayhem.bin — ST7789 display driver, no QWERTY
  • H4M build: portapack-h4m-mayhem.bin — ST7789P3 display driver, QWERTY support

If you’re developing for PortaRF: target the build that matches your unit. Building a custom .bin for the wrong revision will fail at boot.

6.2 If PortaRF has H4M

  • QWERTY input is available — your app can accept text entry
  • Slightly different display init — should be transparent if you use existing Mayhem primitives
  • Some apps assume H4M features — verify your custom app gracefully degrades on H2+ if you want cross-compatibility

6.3 Thermal considerations during dev

Sustained development cycling (build + flash + run repeatedly) can:

  • Warm the PortaRF case (especially if you’re running RF tests)
  • Drain the battery faster than typical use
  • Trigger thermal-protection behavior in extreme cases

For dev sessions: keep PortaRF on USB-C power. Don’t worry about battery; do worry about thermal if running constant TX or wideband sweep.

6.4 Test cases that matter on PortaRF

When developing a new Mayhem app, test on PortaRF for:

  • Display rendering — does the layout work at 320×240?
  • Button responsiveness — does encoder + D-pad input feel right?
  • Battery drain — does the app drain battery faster than expected?
  • SD usage — if the app writes data, does it cope with SD speed limits?
  • USB integration — if tethered, does the app coexist with host-side tools?
  • Field deployment — does the app work when battery is low / thermal is elevated?

Bench testing on porta works for most validation; PortaRF-specific testing matters for handheld-form-factor concerns.


7. Build + flash workflow

7.1 Development cycle

For a typical edit-build-test cycle:

# 1. Edit source
vim firmware/application/apps/my_app.cpp

# 2. Incremental build
cd build && make -j

# 3. Find the new .bin
ls -la portapack-*-mayhem.bin

# 4. Copy to dev SD card
cp portapack-h1_h2-mayhem.bin /Volumes/PORTARF_DEV/

# 5. Eject SD, swap into PortaRF, reboot
# 6. Test

Total cycle time: ~2 minutes for a small change. Larger refactors that trigger full rebuilds take longer.

7.2 Faster iteration patterns

  • Use a dedicated dev SD: small, cheap, lives in PortaRF for dev sessions
  • Build on a fast machine: full builds take ~5 minutes; incremental rebuilds <30 seconds
  • Use a script for the build+copy+eject sequence: scripts the SD handoff
  • Two-monitor setup: code on one, PortaRF screen visible on the other

7.3 Release builds

For a “stable” build you’ll deploy or share:

cd build
make clean
make -j

Verify the .bin works on PortaRF; then:

  • Tag the source revision: git tag v0.1-custom
  • Save the .bin to a release archive
  • Document changes for users

For sharing: the .bin is what others flash. The source is what others build from. Both have value.

7.4 CI/CD pattern

For maintained custom forks, a GitHub Actions workflow can build releases automatically:

on:
  push:
    tags: ['v*']
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-arm-none-eabi cmake libopencm3-dev
      - name: Build
        run: |
          cd firmware && mkdir build && cd build
          cmake .. && make -j
      - name: Upload .bin
        uses: actions/upload-release-asset@v1
        # ... attach .bin to GitHub release

For solo development this is overkill; for community projects with multiple maintainers, it’s worth setting up.


8. Cherry-picking features from Havoc + other forks

When you want a specific feature from another Mayhem fork.

8.1 Identify the feature

In the source fork (Havoc, a community fork, etc.), find:

  • The .cpp / .hpp files that implement the feature
  • Any CMakeLists.txt / build-config changes needed
  • Any resource files (.bin assets, etc.)
  • Any commit history that adds the feature

8.2 Generate a patch

# Add the source fork as a remote
git remote add havoc https://github.com/furrtek/portapack-havoc.git
git fetch havoc

# Find the relevant commit(s)
git log havoc/master -- firmware/application/apps/feature_name.cpp

# Generate a patch
git format-patch -1 <commit-sha>

# Or cherry-pick directly (often easier)
git cherry-pick <commit-sha>

8.3 Resolve conflicts

The fork’s tree may have diverged from current Mayhem. Common conflicts:

  • CMakeLists.txt has different content (resolve manually)
  • Adjacent code has been refactored (resolve manually)
  • API signatures have changed (port the feature to current API)

For complex ports: it may be easier to study the source feature and re-implement against current Mayhem than to merge mechanically.

8.4 Verify the port

After cherry-pick:

  • Build cleanly — no missing headers, no undefined references
  • Test on PortaRF — feature actually works
  • No regressions — existing apps still work

8.5 Upstream contribution

If the feature is generally useful:

  • PR to Mayhem mainline — community benefit
  • Maintain in your fork only — if it’s PortaRF-specific or experimental

Most cherry-picks from Havoc have already been merged into Mayhem over the years. Check Mayhem before assuming a Havoc feature is unique.


9. Common build errors + fixes

SymptomCauseFix
arm-none-eabi-gcc: command not foundToolchain not installed or not in PATHInstall gcc-arm-none-eabi
error: 'libopencm3.h' not foundSubmodule not clonedgit submodule update --init --recursive
error: 'osmosdr.h' not foundWrong project (this is host-side, not firmware)Verify you’re in mayhem-firmware/firmware, not gnuradio
CMake Error: Could not find ... compilerWrong CMake configurationVerify CMake found the ARM compiler
link error: undefined reference to ...Missing source files in CMakeLists.txtAdd your new .cpp files to target_sources
link error: code size exceeds flashToo many features enabledDisable optional apps in CMakeLists.txt
make: *** No rule to make targetMissing or moved source fileCheck the file path; restore if accidentally deleted
error: expected ';' before (C++)C++ syntax errorCheck the specific line for typos
Build succeeds but flash failsWrong PortaPack revision targetVerify build target matches PortaPack hardware revision
App appears but crashes on launchStack overflow or null pointerReduce stack usage; check pointer initialization

For deeper troubleshooting, the Mayhem Discord channel is active and helpful.


10. Maintenance + upstream sync

For custom forks intended to be maintained:

10.1 Periodic sync

# Sync your fork with upstream Mayhem
git fetch upstream
git checkout main
git merge upstream/main

# Resolve any conflicts
# Rebuild + test
# Push to origin

10.2 Rebase feature branches

git checkout my-feature
git rebase main
# Resolve conflicts
# Test

10.3 When upstream breaks your features

Mayhem occasionally refactors:

  • API changes: your custom code references functions that no longer exist
  • Structural reorgs: file paths change; CMakeLists.txt references break
  • Subsystem replacement: features you depended on get replaced

For each: read the upstream changelog; port your changes to the new API. Allocate dev time for this — it’s normal maintenance.

10.4 Documentation

Document your fork:

  • README: what your fork does that mainline doesn’t
  • Build instructions: specific to your fork if different
  • Compatibility: which PortaPack revisions / Mayhem versions
  • Known issues: bugs you’re aware of

For PortaRF-specific forks, explicitly document the target revision — H2+ vs H4M vs H4M Clifford Edition. Users will appreciate not having to figure it out.

10.5 Releasing

For each release:

  1. Tag the source: git tag v0.x.y
  2. Build the .bin: make -j
  3. Create GitHub release with the .bin attached
  4. Update changelog
  5. Push tag: git push --tags

For users: they download the .bin, flash via SD card workflow (Vol 8 § 3).


11. Resources

Mayhem source + community

Toolchain

Cross-references

  • HackRF One Vol 10 (canonical custom-firmware reference): ../../../HackRF One/03-outputs/HackRF_One_Complete.html
  • Vol 6 § 4 (this project) — Mayhem feature catalog
  • Vol 8 (this project) — flashing custom builds

Sibling forks

End of Vol 10. Next: Vol 11 covers operational posture for PortaRF — regional rules, antenna safety, capture data discipline, pre-engagement checklist.