Wiznet makers

Grace_Koo

Published August 18, 2026 ©

104 UCC

25 WCC

11 VAR

0 Contests

0 Followers

0 Following

Original Link

particle-iot /device-os

Particle's Device OS registers the W5500 as an lwIP netif: Socket 0 in MACRAW, the full 16 KB buffer, and pins configurable at runtime.

COMPONENTS Hardware components

WIZnet - W5500

x 1


PROJECT DESCRIPTION

Particle Device OS — How a Commercial IoT Platform Wires the W5500 into lwIP

#ParticleIoT #DeviceOS #lwIP #MACRAW #W5500 #ioLibrary #netif #EthernetFeatherWing #OpenSource

📚 Context: Device OS is the firmware that runs on Particle devices. LGPL-3.0, 1,074 stars, last pushed 2026-08-17. The WIZnet integration lives in hal/network/lwip/wiznet/ and carries a 2018 Particle Industries copyright.

Verification status: Every value below was read from wiznetif.cpp (752 lines) and wiznetif_config.h on the develop branch.


01 — What is this project?

Particle sells connected-device hardware and the cloud platform behind it. Device OS is the open-source firmware underneath all of it — the layer that gives a Particle board its network stack, its OTA, and its cloud session.

Most of Particle's catalogue is cellular or Wi-Fi. The platform also supports wired Ethernet, and when it does, the silicon underneath is a WIZnet W5500. What makes this worth reading is not that the chip is supported — it is how a commercial platform chose to integrate it.

Device OS does not hand applications a WIZnet socket API. It registers the W5500 as an ordinary lwIP network interface, so everything above it — DHCP, DNS, TLS, the cloud protocol, the user's TCPClient — is unaware that Ethernet is arriving over SPI from an external controller.


02 — Why integrate at the netif layer?

🔷 One network stack, several bearers

A Particle device may reach the cloud over cellular, Wi-Fi, or Ethernet. If each bearer exposed its own socket API, every layer above would have to branch. By making the W5500 a netif, Device OS keeps a single lwIP stack and lets the routing layer pick the interface. Application code does not change.

🔷 The chip's own stack would not survive the abstraction

Particle needs IPv6 neighbour handling, DHCP, DNS, and a TLS session to the cloud, all managed by lwIP. A hardwired TCP/IP engine terminating connections on its own would sit outside that machinery. So the driver puts the chip into raw-frame mode and feeds lwIP instead.

🔷 Field-configurable pins

Unusually, the CS, RESET, and INT pins are not compile-time constants. They live in a persisted configuration structure and can be changed at runtime, which lets one firmware image serve boards that wire the FeatherWing differently.


03 — System architecture

The WIZnet integration, hal/network/lwip/wiznet/

FileSizeRole

wiznetif.cpp

22.6 KB, 752 linesThe netif driver itself

wiznetif.h

3.2 KBInterface class declaration

wiznetif_config.cpp

7.5 KBPersisted pin configuration

wiznetif_config.h

3.1 KB

WizNetifConfigData structure

How the layers stack

LayerWhat happens
Application — TCPClient, cloud sessionSees ordinary sockets, unaware of SPI
lwIPDHCP, DNS, ARP, IPv6, routing across bearers

netifapi_netif_add(..., ethernet_input)

W5500 registered as a netif, MTU 1500

wiznetif.cpp

Pulls raw frames off the chip, pushes them into lwIP
WIZnet ioLibraryDriven through four registered callbacks
W5500 over SPISocket 0 in MACRAW, the full 16 KB buffer assigned to it

The ioLibrary is not called directly — it is given callbacks

The driver hands WIZnet's ioLibrary four function pointers so the library never touches Particle's HAL itself:

  • reg_wizchip_cris_cbfunc — critical-section enter and exit
  • reg_wizchip_cs_cbfunc — chip-select assert and deassert
  • reg_wizchip_spi_cbfunc — single-byte SPI read and write
  • reg_wizchip_spiburst_cbfunc — burst SPI read and write

Bring-up is a hardware reset on the RESET GPIO followed by wizchip_sw_reset().


04 — Why WIZnet W5500? ⭐

🔷 The chip runs in MACRAW, and the code says so plainly

This is the line that settles it, from wiznetif.cpp:

/* Set mode to MACRAW, enable MAC filtering and IPv6 packet filtering (not supported right now anyway) */
setSn_MR(0, Sn_MR_MACRAW | Sn_MR_MFEN | Sn_MR_MIP6B);
 

Socket 0 is opened in MACRAW with MAC filtering on and IPv6 packets blocked at the chip. The driver then asserts the resulting state:

int st = !(getSn_SR(0) == SOCK_MACRAW);
LOG(TRACE, "Opened MACRAW socket, err = %d", st);
 

The W5500's hardwired TCP/IP engine is not terminating connections here. It delivers raw Ethernet frames, and lwIP does the rest.

🔷 All of the buffer memory given to one socket

Because only Socket 0 is used, the driver reassigns the chip's buffer memory:

// Use maximum buffer sizes for MACRAW socket 0
uint8_t bufSizes[_WIZCHIP_SOCK_NUM_] = {16};
 

Socket 0 receives 16 KB instead of the default per-socket split. This is a neat illustration of the tradeoff: give up eight independent sockets and you get one very deep raw pipe — which is exactly what a software IP stack wants.

🔷 The pins are runtime state, not build-time constants

wiznetif_config.h defines a versioned, packed structure that is persisted on the device:

struct __attribute__((packed)) WizNetifConfigData {
uint16_t size;
uint16_t version;
uint16_t cs_pin;
uint16_t reset_pin;
uint16_t int_pin;
};
 

It is managed by a singleton WizNetifConfig class with a recursive mutex and a request() entry point reachable through the driver-specific interface API. A deployed device can be told which pins its Ethernet hardware sits on without a firmware rebuild.

🔷 SPI settings are explicit and overridable

#define WIZNET_SPI_MODE SPI_MODE3
#define WIZNET_SPI_BITORDER MSBFIRST
#define WIZNET_SPI_CLOCK HAL_PLATFORM_ETHERNET_FEATHERWING_SPI_CLOCK
 

All three are #ifndef-guarded, so a platform can override mode, bit order, or clock. The default clock is named after the Ethernet FeatherWing, the accessory this driver was written for.

🔷 The same vendor ships the opposite choice — and that is the clearest lesson here

Particle also publishes IsolatedEthernet, an Apache-2.0 application library for reaching devices on a separate Ethernet LAN. It drives the same W5500, and it does the exact opposite:

int8_t res = wiznet::socket((uint8_t) sock, Sn_MR_TCP, port, 0);
int8_t res = wiznet::socket((uint8_t) sock, Sn_MR_UDP, port, 0x00);
int8_t res = wiznet::socket((uint8_t) sock, Sn_MR_UDP, _port, Sn_MR_MULTI);
 

Sn_MR_TCP, Sn_MR_UDP, and Sn_MR_MULTI for multicast — that library uses the chip's hardwired TCP/IP engine and builds its TCPClient, TCPServer, and UDP classes directly on the W5500's eight hardware sockets. No lwIP, no netif, no MACRAW.

The reason sits in the word isolated. Device OS wants the W5500 to be the device's network interface, so it must feed the system stack. IsolatedEthernet wants the W5500 to be a second network the system stack must not touch — so letting the chip terminate connections on its own is not a compromise, it is the entire point.

One vendor, one chip, both modes, chosen by what the network is for. That is the cleanest available answer to the question of when the offload engine is the right choice.

🔷 The third independent MACRAW data point

Zephyr's eth_w5500 does this. The ESP8266 Arduino core's lwIP_w5500 library does this. Particle's Device OS does this. Three unrelated platforms, three software IP stacks, and the same conclusion each time: when the host already owns a network stack, the W5500 earns its place as a clean MAC and PHY on one SPI bus, not as an offload engine.

🔷 Verified evidence ✅

  • setSn_MR(0, Sn_MR_MACRAW | Sn_MR_MFEN | Sn_MR_MIP6B) and the SOCK_MACRAW state check, both in wiznetif.cpp
  • bufSizes[_WIZCHIP_SOCK_NUM_] = {16} under a comment naming MACRAW socket 0
  • netifapi_netif_add(interface(), nullptr, nullptr, nullptr, this, initCb, ethernet_input) with netif_.mtu = 1500 — a genuine lwIP netif, not a shim
  • ✅ Four ioLibrary callbacks registered: reg_wizchip_cris_cbfunc, reg_wizchip_cs_cbfunc, reg_wizchip_spi_cbfunc, reg_wizchip_spiburst_cbfunc
  • ✅ Hardware reset on the RESET GPIO followed by wizchip_sw_reset()
  • ✅ Runtime pin configuration through the packed, versioned WizNetifConfigData
  • SPI_MODE3 / MSBFIRST / HAL_PLATFORM_ETHERNET_FEATHERWING_SPI_CLOCK, all overridable
  • ✅ Directory carries a 2018 Particle Industries copyright; repository last pushed 2026-08-17

What the repository does not tell us: the driver directory does not state a numeric default SPI clock — HAL_PLATFORM_ETHERNET_FEATHERWING_SPI_CLOCK is defined per platform elsewhere in the tree, and this write-up did not chase it down. There are no throughput measurements in the directory, and no statement of which shipping Particle devices enable Ethernet by default. Those claims are therefore not made here.


05 — Key components

🌐 WIZnet W5500 — Socket 0 in MACRAW, MAC filter on, IPv6 blocked at the chip

10/100 Mbps hardwired TCP/IP Ethernet controller on SPI. Device OS uses none of its TCP/IP offload: it opens one MACRAW socket, hands that socket the full 16 KB buffer, and treats the chip as a raw frame source and sink beneath lwIP. Raw buffer access goes through getSn_RX_RD, WIZCHIP_RXBUF_BLOCK, and WIZCHIP_READ_BUF.

🧩 wiznetif.cpp — the netif driver

752 lines. Registers the interface with netifapi_netif_add using ethernet_input, sets MTU 1500, copies the MAC into netif_.hwaddr with ETHARP_HWADDR_LEN, and allocates receive buffers with pbuf_alloc(PBUF_RAW, pktSize, PBUF_POOL).

⚙️ WIZnet ioLibrary — driven by callback injection

Rather than forking the library, Device OS registers four callbacks so ioLibrary calls back into Particle's HAL for critical sections, chip select, and both single and burst SPI transfers.

🔧 WizNetifConfig — persisted pin configuration

A singleton guarded by a StaticRecursiveMutex, exposing setConfigData, getConfigData, lock, unlock, and request. The stored structure is versioned as WIZNETIF_CONFIG_DATA_VERSION_V1, so the format can evolve without stranding deployed devices.

🌐 lwIP — the stack that actually speaks IP

DHCP, DNS, ARP, IPv6, and routing all live here. Because the W5500 is a netif, a Particle device can hold a cloud session over Ethernet using the same code path it uses over Wi-Fi.


06 — Application scenarios

01. Fixed installations that should not depend on Wi-Fi

A Particle device in a plant room or a rack gets a wired bearer without any application change — the cloud session, OTA, and user sockets are untouched because the interface change happens below lwIP.

02. One firmware image, several board layouts

Because CS, RESET, and INT are persisted configuration rather than compile-time constants, a single build can serve hardware revisions that route the FeatherWing differently.

03. Ethernet as a fallback bearer

With both a radio and a W5500 registered as interfaces, lwIP's routing layer can prefer one and fall back to the other. That is the payoff of integrating at netif level rather than at the socket API.

04. A reference for porting a WIZnet chip to another RTOS

The callback-injection pattern is directly reusable: register four functions, keep ioLibrary unmodified, expose the chip as a netif. That is a cleaner path than forking the driver.


Conclusion

Device OS is a working answer to a question every RTOS integrator faces: what do you do with a chip that brings its own TCP/IP stack when your platform already has one? Particle's answer is to switch the chip to MACRAW, give that one socket all the buffer, and let lwIP do the thinking.

  • ✅ A commercial IoT platform's production firmware, open source and actively developed — last pushed 2026-08-17
  • ✅ The W5500 is registered as a real lwIP netif with ethernet_input and MTU 1500, not wrapped behind a bespoke socket API
  • ✅ MACRAW mode is explicit in the source, with the socket state verified after opening
  • ✅ Socket 0 is given the chip's full 16 KB buffer because the other seven are unused
  • ✅ WIZnet's ioLibrary is used unmodified through four registered callbacks — a clean porting pattern
  • ✅ CS, RESET, and INT pins are runtime configuration, versioned and persisted
  • ✅ Particle's own IsolatedEthernet library uses the opposite mode on the same chip, which makes the offload-versus-MACRAW decision unusually easy to see
  • ✅ Third independent platform, after Zephyr and the ESP8266 Arduino core, to reach the same MACRAW-under-a-software-stack conclusion

07 — Similar Projects on WIZnet Makers

  • IsolatedEthernet (particle-iot) is the most instructive comparison on the platform, because it is the same vendor and the same chip as this project used the opposite way. That library drives the W5500 through its hardwired TCP/IP engine — Sn_MR_TCP, Sn_MR_UDP, Sn_MR_MULTI — to reach a separate, isolated LAN, while Device OS puts the chip in MACRAW beneath lwIP so it can serve as the device's own interface. Read together, the two show both W5500 modes justified by purpose rather than preference.
  • How to Use W5500 Ethernet as the Primary Network Interface on Particle Muon? is the closest neighbour on the platform and the best companion read. It approaches the same subsystem from the user's side — bringing up a Muon M524 so that its onboard W5500 becomes the preferred interface. This post goes the other direction and opens the driver underneath that behaviour, so the two together cover the configuration and the implementation of the same path.
  • AkiraOS is the closest architectural sibling: another operating system that drives the W5500 in MACRAW beneath its own software IP stack. The difference is the layer each one exposes — AkiraOS hands sandboxed WebAssembly applications a general socket API, while Device OS hides the interface entirely so application code cannot tell Ethernet from Wi-Fi.
  • Particle Ethernet FeatherWing PoE covers the hardware accessory this driver was written for. The difference is the subject: that post is about the board and its PoE arrangement, whereas this one is about the firmware that drives it — the MACRAW setup, the buffer reassignment, and the pin configuration mechanism.
  • Moddable SDK is related as another runtime carrying its own W5500 support so that scripts never see the chip. The difference is scope: Moddable brings a JavaScript engine to the device, while Device OS brings a whole managed platform including cloud session and OTA, with Ethernet as one bearer among several.

Q&A

Q. Does Device OS use the W5500's hardwired TCP/IP offload? No. wiznetif.cpp opens Socket 0 with setSn_MR(0, Sn_MR_MACRAW | Sn_MR_MFEN | Sn_MR_MIP6B) and then confirms the socket reached SOCK_MACRAW. The chip delivers raw Ethernet frames and lwIP performs all IP, TCP, DHCP, and DNS work. The offload engine is unused by design, because Particle needs one stack shared across cellular, Wi-Fi, and Ethernet.

Q. Why give Socket 0 the entire buffer? Because the other seven sockets are never opened. The driver sets bufSizes[_WIZCHIP_SOCK_NUM_] = {16} under a comment reading "Use maximum buffer sizes for MACRAW socket 0", so the single raw socket gets 16 KB instead of the default allocation. Depth on one pipe is worth more than eight shallow ones when a software stack does the multiplexing.

Q. How is the ioLibrary integrated without forking it? Through four registered callbacks: reg_wizchip_cris_cbfunc for critical sections, reg_wizchip_cs_cbfunc for chip select, reg_wizchip_spi_cbfunc for byte transfers, and reg_wizchip_spiburst_cbfunc for burst transfers. WIZnet's library stays unmodified and calls back into Particle's HAL. This is a reusable pattern for anyone porting the chip to another RTOS.

Q. Can the SPI pins be changed after a device ships? Yes. WizNetifConfigData is a packed, versioned structure holding cs_pin, reset_pin, and int_pin, persisted on the device and managed by a mutex-guarded singleton with a request() entry point. That is unusual — most drivers fix these at build time.

Q. What SPI settings does the driver use? SPI_MODE3 and MSBFIRST, with the clock taken from HAL_PLATFORM_ETHERNET_FEATHERWING_SPI_CLOCK. All three are #ifndef-guarded so a platform can override them. The numeric default is defined elsewhere in the platform headers and is not stated in this directory.

Q. Does every Particle integration of the W5500 work this way? No, and the exception is the useful part. Particle's separate IsolatedEthernet library opens sockets with Sn_MR_TCP and Sn_MR_UDP and builds TCP and UDP classes straight onto the chip's hardware sockets, with no lwIP involved. It exists to reach an isolated LAN that the system stack must stay out of, so the chip's own TCP/IP engine is exactly what is wanted there. Same company, same chip, opposite mode, different job.

Q. What does this say about choosing between W5500 modes? That the host's existing software matters more than the chip's feature list. Three independent platforms — Zephyr, the ESP8266 Arduino core, and Particle Device OS — all bring their own IP stack, and all three land on MACRAW. Where a project has no stack of its own and needs a handful of fixed connections, the offload engine is the better answer.


Original Link: https://github.com/particle-iot/device-os

Driver path: hal/network/lwip/wiznet/wiznetif.cpp

Documents
Comments Write