Skip to content

Add Ethernet support for RAK4631 + RAK13800 (W5100S)#48

Open
metrafonic wants to merge 10 commits into
attermann:masterfrom
metrafonic:pr/rak4631-eth
Open

Add Ethernet support for RAK4631 + RAK13800 (W5100S)#48
metrafonic wants to merge 10 commits into
attermann:masterfrom
metrafonic:pr/rak4631-eth

Conversation

@metrafonic
Copy link
Copy Markdown

Summary

Adds RNode-over-TCP support for the RAK4631 + RAK13800 WisBlock Ethernet module (W5100S chip), listening on TCP port 7633.

ETH support is added within the existing BOARD_RAK4631 build — the W5100S is detected at runtime, so the same firmware binary works for both plain RAK4631 boards and the ETH variant with no separate build needed. On boards without the RAK13800 module, ETH is skipped after the first failed init with negligible overhead.

EthernetRemote.h handles the full ETH lifecycle: DHCP, TCP accept/read/write, connect and read timeouts, and W5100S brownout recovery (PoE power instability can reset the chip registers while the MCU keeps running). MAC address is derived from the nRF52840 FICR device address.

Hardware

  • RAK4631 (nRF52840)
  • RAK13800 Ethernet module (W5100S)
  • Optional RAK19018 PoE module

SPI buses

The SX1262 modem uses spiModem (NRF_SPIM2, Core slot P1.11–P1.13). The W5100S uses the board default SPI (NRF_SPIM3, IO slot P0.03/P0.29/P0.30). The two instances are independent — no conflict.

Tested

The following scenarios were tested on real hardware:

  • USB serial with ETH connected and disconnected
  • Power on without cable → plug in cable → TCP connection established
  • PoE-powered deployment

Firmware: https://github.com/metrafonic/RNode_Firmware_RAK_Ethernet/releases/


This PR was developed with AI assistance (Claude). The implementation and all test scenarios were verified on physical hardware.

The RAK13800 W5100S module on the WisBlock IO slot is now supported as
part of the standard BOARD_RAK4631 (0x51) build. The W5100S is detected
at runtime via Ethernet.hardwareStatus(); boards without the module
initialise and fall back to serial/BLE gracefully.

Key details:
- ETH pins defined in the BOARD_RAK4631 block (CS=26, RST=21, IO slot SPI)
- 3V3_S rail (WB_IO2 / pin 34) enabled at startup for all RAK4631 builds
- TCP KISS server on port 7633 with two-phase connect/idle timeout
- Brownout detection re-initialises W5100S if its MAC registers reset
- MAC derived from nRF52840 FICR device address (same source as BLE)
- No rnodeconf changes needed; existing RAK4631 provisioning flow works
Ethernet.begin() with an 8s/2s DHCP timeout replaces the original 60s
default. If no W5100S hardware is detected, begin() returns immediately
(W5100.init() fails before attempting DHCP). If hardware is present but
no cable is connected, the retry loop pays 8s instead of 60s per attempt.
@metrafonic
Copy link
Copy Markdown
Author

metrafonic commented May 24, 2026

Replaces #47

IRQ_HEADER_DET is latched and only cleared by handleDio0Rise() on
RX_DONE. A partial or foreign packet that never completes leaves the
bit set permanently, so dcd() reports a forever-busy channel and CSMA
can never transmit (medium_free() stays false). Clear it and re-arm RX
once it has been latched longer than a max-length packet could take,
mirroring the existing false-preamble recovery.
Ethernet.maintain() blocks while it re-requests the lease (~halfway
through the lease, i.e. after hours). Run it only when the radio is
idle (no carrier and no queued TX) so it cannot interrupt an in-flight
packet, and cut the DHCP timeout from 8s to 3s so an unresponsive
server cannot stall the main loop. The SX1262 dcd self-heal covers any
stall that does occur.
The header-detect self-heal timeout was built from lora_us_per_byte, which
is the nominal LoRa bitrate. That undercounts real airtime when Low Data Rate
Optimize is active (SF11/SF12), where LDRO inflates payload time by SF/(SF-2).
A near-max-length packet at e.g. SF12/BW125k would exceed the estimate and be
aborted mid-reception, dropping a valid frame.

Correct the payload term by SF/(SF-2) when LDRO is on and add a 1.25x margin
for the omitted symbol-count quantization. Over-estimating only delays this
last-resort heal; the stuck-IRQ wedge it guards against is far worse.
The dcd() self-heal clears a stuck header-detect and re-arms RX via
receive(), but that does not clear a latched RX_DONE or re-attach the
DIO0 interrupt. A receiver that has stopped completing receptions
therefore keeps detecting headers (channel load still rises) while
delivering no packets, and only a full radio re-init recovers it.

Count completed receptions (rx_done_events, bumped in handleDio0Rise)
against self-heal aborts (rx_header_aborts). When RX_STALL_ABORT_LIMIT
header-detects abort with no completed reception in between, loop() runs
stopRadio()/startRadio() -- the same recovery a host reconnect performs,
which is the only thing that re-arms the RX_DONE path.
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.

1 participant