Skip to content

fix: enable LoRa CRC on SX127x for RNode interop#45

Merged
attermann merged 1 commit into
attermann:masterfrom
0xSeren:lora-crc-sx127x
Apr 17, 2026
Merged

fix: enable LoRa CRC on SX127x for RNode interop#45
attermann merged 1 commit into
attermann:masterfrom
0xSeren:lora-crc-sx127x

Conversation

@0xSeren
Copy link
Copy Markdown
Contributor

@0xSeren 0xSeren commented Apr 16, 2026

Summary

Enable LoRa CRC in the SX127x branch of the example LoRaInterface.
Without this, T-Beam and LoRa32 V2.1 boards running microReticulum
cannot interoperate with real RNode hardware: outbound frames are
silently dropped at the RNode, and inbound frames lack the CRC check
that Arduino-LoRa-era receivers used to rely on.

Why

After reset, REG_MODEM_CONFIG_2 bit 2 (RxPayloadCrcOn) on SX127x
is 0, and SX127x::begin() does not touch it. SX127x::config()
only disables frequency hopping. So CRC stays off.

Upstream RNode firmware explicitly enables CRC on SX127x:

  • RNode_Firmware/RNode_Firmware.ino:531: LoRa->enableCrc();
  • RNode_Firmware/sx127x.cpp:160: enableCrc(); called from the
    equivalent of begin().

So any peer speaking the RNode wire format expects a CRC on every
LoRa frame and drops those that arrive without one. microReticulum's
example interface needs to match that expectation.

What changed

One file, inside LoRaInterface::start() in the
BOARD_TBEAM / BOARD_LORA32_V21 branch, immediately after
chip->begin(...):

if (state == RADIOLIB_ERR_NONE) state = chip->setCRC(true);

Gated on the prior begin() having succeeded; its own return value
is folded into state so the existing RADIOLIB_ERR_NONE check
below catches a CRC-enable failure too.

Not changed

SX126x branches (RAK4631, Heltec V3, Heltec V4) are untouched.
RadioLib's SX126x::begin() already calls setCRC(2) internally
(SX126x.cpp:61,114), so those boards inherit CRC-on by default.
Adding an explicit call there would be defensive but noisy; happy to
add it if you prefer.

Test plan

  • T-Beam or LoRa32 V2.1 running this branch can send LoRa
    announces that a real RNode (or Python RNS with
    RNodeInterface) receives and delivers into transport.
  • Announces originating from a real RNode arrive intact at the
    microReticulum node.
  • SX126x targets (Heltec V3, RAK4631, etc.) continue to build
    and interoperate unchanged.

Note: this PR was prepared with the help of generative AI as part of work on the RTReticulum project. While the problem was manually identified, the patch, commit message, and this description were drafted by an AI assistant and reviewed by a human before submission.

After reset, SX127x hardware leaves REG_MODEM_CONFIG_2 bit 2
(RxPayloadCrcOn) cleared, and RadioLib's SX127x::begin() does not
touch it. Upstream RNode firmware explicitly enables CRC on every
LoRa transmit and receive path (sx127x::enableCrc in
RNode_Firmware.ino:531, sx127x.cpp:160), so a real RNode will
silently drop frames that arrive without a valid CRC.

This means T-Beam and LoRa32 V2.1 users running the example
LoRaInterface cannot interop with real RNodes over LoRa today:
outbound packets are dropped at the RNode, inbound packets may be
accepted but silently corrupted.

Fix by calling chip->setCRC(true) after SX1276::begin() in the
SX127x branch of LoRaInterface::start(). The new call is gated on
the prior begin() having succeeded, and its own error is folded
into state so the existing RADIOLIB_ERR_NONE check catches it.

No change required on the SX126x branches: RadioLib's
SX126x::begin() already calls setCRC(2), so RAK4631, Heltec V3,
and Heltec V4 inherit CRC-on.
@attermann attermann merged commit d538ca2 into attermann:master Apr 17, 2026
8 checks passed
@attermann
Copy link
Copy Markdown
Owner

Many thanks for the patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants