Skip to content

Radio and Regions

Muhammad Nuzaihan edited this page Aug 22, 2026 · 1 revision

Radio and Regions

DuckLoRa

src/radio/DuckLoRa.h/.cpp wraps RadioLib's SX127x/SX126x interrupt-driven driver classes to talk to the external LoRa transceiver chips used by all currently supported boards (see Supported Boards). Configuration (frequency, spreading factor, bandwidth, etc.) comes from src/include/cdpcfg.h and the per-board headers under src/include/boards/.

Runtime-selectable regions

src/radio/RadioRegionConfig.h/.cpp lets a single firmware build be field-provisioned for the regulatory frequency band of the country it's deployed in, instead of requiring a separate firmware build per country.

enum class RadioRegion : uint8_t { MY = 0, SG = 1, PH = 2, ID = 3, US = 4, UK = 5 };
  • begin() — loads a previously field-provisioned region from flash (falls back to the MY preset, which matches this repo's long-standing hardcoded defaults, if none was ever set or storage fails an integrity check). Called during setupWithDefaults(), before the radio is initialized.
  • setRegion(region) — applies and persists a region, updating the default radio band and uplink channel pool for the next boot. Does not retune an already-initialized radio, so callers reboot shortly after a successful call.
  • regionName() / regionFromName() — convert between the enum and short string codes ("MY", "US", etc.), shared by the serial provisioning command below and any future BLE/app-based settings screen.
  • Field provisioning over USB serial: AT+RADIOREGION=<code> (region choice isn't secret, so unlike other provisioning commands this can be re-sent at any time, no reset step required first).

Regulatory note: selecting a region only changes which frequencies are used — it does not enforce that region's duty-cycle or dwell-time rules (e.g. EU868/UK's ~1% duty cycle, US915 dwell-time/frequency-hopping requirements). This is called out explicitly in the source and is a real compliance gap to close before fielding devices in a region with such rules.

Uplink channel spreading

When a MamaDuck sends a packet it originated (not one it's relaying) directly to a PapaDuck, it picks a random channel from an 8-channel pool (921.4–922.8 MHz, 200 kHz spacing, all AS923 channels) before transmitting, instead of always using the shared mesh channel. This spreads uplink traffic across all the demodulators on a multi-channel SX1302 LoRa concentrator gateway, reducing last-hop collisions when many nodes are within direct range of the gateway.

Rules:

  • Channel switching applies only when the packet is addressed to PAPADUCK_DUID and this device is the original sender (sduid == this->duid).
  • Any relayed packet (sduid != this->duid), regardless of destination, stays on the shared mesh channel (922.8 MHz) so the next hop can hear it.
  • The mesh channel is automatically restored after each transmission (in the radio's TX-done interrupt handler), so a node always listens on the shared channel between sends.
File Symbol Role
src/Ducks/Duck.h sendToRadio() Decides whether to apply the channel switch
src/radio/DuckLoRa.h/.cpp setUplinkFrequency() Retunes the radio via RadioLib
src/radio/DuckLoRa.h/.cpp getRandomUplinkChannel() Picks uniformly from the pool
src/include/cdpcfg.h CDPCFG_UPLINK_CHANNEL_POOL Defines the 8-channel pool

See docs/uplink-channel-spreading.md in the repository for the full write-up with packet-flow diagrams.

Clone this wiki locally