A self-contained C++20 class for DMA-driven WSPR (Weak Signal Propagation Reporter) transmission on Raspberry Pi–class systems. The transmitter uses the Broadcom mailbox, DMA, and PWM hardware to generate precisely timed RF with sub-millHertz stability when properly calibrated.
The library can be used:
- Standalone, via the included
main.cpptest/demo program, or - Embedded as a submodule in a larger project by including
wspr_transmit.hppandwspr_transmit.cpp.
This project is designed for precise timing, safe thread control, and runtime reconfiguration, including the ability to halt an in-progress transmission, adjust parameters, and restart cleanly.
- Repository Layout
- Dependencies
- Building
- Runtime Requirements
- Public API
- Demo / Test Rig
- Design Notes
- License
/src
├── main.cpp # Test rig / demo application
├── Makefile # Build rules for standalone use
├── wspr_transmit.hpp # Public API and documentation
└── wspr_transmit.cpp # Implementation
/external # Optional helpers used by the demo
├── config_handler.hpp
├── config_handler.cpp
├── utils.hpp
└── utils.cpp # PPM estimation helper (e.g. from chrony)
When used as a submodule, only wspr_transmit.hpp/.cpp are required.
This project expects the following sibling projects when built as part of a larger tree:
-
WSPR-Message Encodes WSPR symbols and message data. Expected path:
../../WSPR-Message/src -
Mailbox Provides mailbox, DMA-safe memory allocation, and peripheral mapping. Expected path:
../../Mailbox/src
The provided Makefile automatically includes these paths:
SUBMODULE_SRCDIRS := $(wildcard ../../WSPR-Message/src)
SUBMODULE_SRCDIRS += $(wildcard ../../Broadcom-Mailbox/src)Adjust paths if your project layout differs.
To build the standalone demo:
cd src
make debug
sudo ./build/bin/wspr-transmitter_test- Requires linking against pthreads (
-pthread). - Must be run as root (for
/dev/memaccess).
release Build optimized binary
debug Build debug binary
test Run test target
gdb Launch under gdb
lint Static analysis
macros Dump compile-time macros
clean Remove build artifacts
help Show targets
- Root privileges (
/dev/memaccess required) - Linux on Raspberry Pi–class hardware
- A free GPIO suitable for GPCLK / PWM output
- CPU isolation and
SCHED_FIFOrecommended for best timing
WsprTransmitter();
~WsprTransmitter();The destructor is safe and blocking:
- Stops scheduler
- Requests transmit stop
- Joins threads
- Tears down DMA, PWM, and clocks
Optional hooks for transmission lifecycle:
using StartCallback = std::function<void(
const std::string &msg,
double frequency)>;
using EndCallback = std::function<void(
const std::string &msg,
double elapsed_secs)>;
void setTransmissionCallbacks(
StartCallback on_start = {},
EndCallback on_end = {});Callbacks execute on the transmit thread, not the caller thread.
Configure frequency, power, PPM correction, and optional message content:
void configure(
double frequency,
int power,
double ppm,
std::string_view call_sign = {},
std::string_view grid_square = {},
int power_dbm = 0,
bool use_offset = false);Apply a new PPM correction after stopping TX:
void applyPpmCorrection(double ppm_new);Configure thread scheduling (recommended for precision):
void setThreadScheduling(int policy, int priority);
// Example: SCHED_FIFO, priority 80The scheduler will launch exactly one WSPR transmission and then stop without scheduling further windows.
void setOneShot(bool enable) noexcept;WSPR mode bypasses the next-window scheduler and starts immediately (useful for testing):
void setTransmitNow(bool enable) noexcept;void startAsync(); // Non-blocking start
void requestStopTx(); // Stop in-flight transmission and wait
void stopAndJoin(); // Stop scheduler + TX threads
void shutdown(); // Full hardware + thread teardown
void requestSoftOff() noexcept; // Stop after finishing current transmission
void clearSoftOff() noexcept; // Clears previous soft offImportant guarantee:
After requestStopTx() returns, it is safe to call:
configure()applyPpmCorrection()startAsync()
without races or DMA corruption.
WSPR message mode uses an internal scheduler aligned to WSPR windows:
- Supports one-shot mode
- Supports immediate start (test mode)
- Supports soft-off to prevent new windows
Tone mode bypasses the scheduler and transmits immediately.
WsprTransmitter::State getState() const noexcept;
void dumpParameters();
static std::string formatFrequencyMHz(double frequency_hz);State values:
DISABLEDENABLEDTRANSMITTINGHUNG(reserved)
The included main.cpp demonstrates:
- Signal handling (
SIGINT,SIGTERM) - Safe shutdown paths
- Tone vs WSPR message mode
- Runtime PPM adjustment
- Scheduler and immediate modes
Run it directly to validate hardware, timing, and calibration.
- DMA control blocks are updated in-place for tight symbol timing
- Absolute sleeps use interruptible + spin-tail logic
- Watchdog detects stalled DMA engines
- Thread-safe stop/join prevents reuse hazards
- Designed for global instantiation
MIT — see LICENSE.md.