Wiznet makers

Grace_Koo

Published December 31, 2025 ©

32 UCC

13 WCC

6 VAR

0 Contests

0 Followers

0 Following

Original Link

docwilco/w6100

W6100 ESP-IDF component driver for ESP32: MACRAW Ethernet frames into esp_eth/esp_netif, with IRQ/poll RX task, stable 16-bit reads, and PHY link handling.

COMPONENTS Hardware components

WIZnet - W6100

x 1


Espressif - ESP32

x 1


PROJECT DESCRIPTION

Cover image generated by Gemini.

W6100 ESP-IDF Component Driver – Key Characteristics

When using the WIZnet W6100 with an ESP32, this project organizes the driver in a way that fits the ESP-IDF standard Ethernet flow (esp_eth / esp_netif). ESP-IDF provides a consistent model for Ethernet drivers (including external SPI-Ethernet modules) and a standard path to attach them to the TCP/IP stack through esp_netif/lwIP.

Repository:

https://github.com/docwilco/w6100

1) Design direction visible in the project structure

(1) MAC/PHY separation aligned with ESP-IDF Ethernet framework (esp_eth)

The project follows ESP-IDF’s expected split between a MAC driver (esp_eth_mac_t) and a PHY driver (esp_eth_phy_t). With this structure, Ethernet can be integrated into an application using the same standard patterns as other ESP-IDF network interfaces.

(2) Receive path organized around MACRAW (frame-level processing)

In w6100_mac.c, the default setup configures SOCK0 in MACRAW mode and delivers received Ethernet frames upward. This approach matches well with using the ESP-IDF networking stack (esp_netif/lwIP) on the ESP32 side.

(3) Both interrupt-driven and polling-driven operation are considered

Some boards cannot easily use an INT pin. The project supports both interrupt mode and polling mode (via esp_timer), while keeping the actual frame processing consolidated in a dedicated RX task.


2) A convenience initialization API (w6100_init.c, w6100.h)

w6100_init() performs SPI bus initialization → MAC/PHY creation → Ethernet driver install in one place. It also validates configuration values, which helps reduce common integration mistakes.

  • Required GPIO checks and ISR service setup
ESP_RETURN_ON_FALSE(config && eth_handle_out, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(config->mosi_gpio >= 0, ESP_ERR_INVALID_ARG, TAG, "mosi_gpio required");
ESP_RETURN_ON_FALSE(config->miso_gpio >= 0, ESP_ERR_INVALID_ARG, TAG, "miso_gpio required");
ESP_RETURN_ON_FALSE(config->sclk_gpio >= 0, ESP_ERR_INVALID_ARG, TAG, "sclk_gpio required");
ESP_RETURN_ON_FALSE(config->cs_gpio >= 0, ESP_ERR_INVALID_ARG, TAG, "cs_gpio required");

// Install GPIO ISR service if using interrupt mode if (config->int_gpio >= 0) {
    ret = gpio_install_isr_service(0);
    if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
        ESP_LOGE(TAG, "GPIO ISR service install failed");
        return ret;
    }
}
  • SPI bus configuration + MAC/PHY creation + driver install
spi_bus_config_t buscfg = {
    .mosi_io_num = config->mosi_gpio,
    .miso_io_num = config->miso_gpio,
    .sclk_io_num = config->sclk_gpio,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 16384,  // 16KB DMA buffer
};
spi_bus_initialize(config->spi_host, &buscfg, SPI_DMA_CH_AUTO);

spi_device_interface_config_t spi_devcfg = {
    .mode = 0,
    .clock_speed_hz = config->spi_clock_mhz * 1000 * 1000,
    .spics_io_num = config->cs_gpio,
    .queue_size = 20,
};

eth_w6100_config_t w6100_cfg = {
    .int_gpio_num = config->int_gpio,
    .poll_period_ms = (config->int_gpio < 0) ? 10 : 0,
    .spi_host_id = config->spi_host,
    .spi_devcfg = &spi_devcfg,
    .custom_spi_driver = ETH_DEFAULT_SPI,
};
esp_eth_mac_t *mac = esp_eth_mac_new_w6100(&w6100_cfg, &mac_cfg);

eth_phy_config_t phy_cfg = ETH_PHY_DEFAULT_CONFIG();
phy_cfg.reset_gpio_num = config->rst_gpio;
esp_eth_phy_t *phy = esp_eth_phy_new_w6100(&phy_cfg);

esp_eth_config_t eth_cfg = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_driver_install(&eth_cfg, eth_handle_out);
  • Optional MAC address configuration
if (config->mac_addr) {
    esp_eth_ioctl(*eth_handle_out, ETH_CMD_S_MAC_ADDR, (void *)config->mac_addr);
}

In ESP-IDF, attaching the Ethernet driver to the TCP/IP stack is a separate step and is typically done using esp_netif glue in the standard Ethernet examples.


3) MACRAW default configuration is explicit (w6100_mac.c)

The default setup makes the following decisions clear:

  • Only SOCK0 is used for MACRAW
  • TX/RX buffers (16KB each) are assigned to SOCK0
  • Only the RECV interrupt is enabled for SOCK0
  • Interrupt re-assert level is set to maximum (to reduce the chance of missing an interrupt)
  • Global interrupts are enabled
// Unlock network configuration uint8_t unlock = W6100_NETLCKR_UNLOCK;
w6100_write(emac, W6100_REG_NETLCKR, &unlock, sizeof(unlock));

// Only SOCK0 can be used as MAC RAW mode, so we give the whole buffer (16KB TX and 16KB RX) to SOCK0 uint8_t reg_value = 16;
w6100_write(emac, W6100_REG_SOCK_RX_BSR(0), &reg_value, sizeof(reg_value));
w6100_write(emac, W6100_REG_SOCK_TX_BSR(0), &reg_value, sizeof(reg_value));
reg_value = 0;
for (int i = 1; i < 8; i++) {
    w6100_write(emac, W6100_REG_SOCK_RX_BSR(i), &reg_value, sizeof(reg_value));
    w6100_write(emac, W6100_REG_SOCK_TX_BSR(i), &reg_value, sizeof(reg_value));
}

/* Enable MAC RAW mode for SOCK0, enable MAC filter */
reg_value = W6100_SMR_MACRAW | W6100_SMR_MF;
w6100_write(emac, W6100_REG_SOCK_MR(0), &reg_value, sizeof(reg_value));

/* Enable receive event for SOCK0 */
reg_value = W6100_SIR_RECV;
w6100_write(emac, W6100_REG_SOCK_IMR(0), &reg_value, sizeof(reg_value));

/* Set the interrupt re-assert level to maximum (~1.5ms) to lower the chances of missing it */ uint16_t int_level = __builtin_bswap16(0xFFFF);
w6100_write(emac, W6100_REG_INTPTMR, &int_level, sizeof(int_level));

/* Enable global interrupt */
reg_value = W6100_SYCR1_IEN;
w6100_write(emac, W6100_REG_SYCR1, &reg_value, sizeof(reg_value));

4) RX processing: ISR/polling → RX task → stack_input() (w6100_mac.c)

(1) Interrupt mode: notify RX task from ISR

IRAM_ATTR static void w6100_isr_handler(void *arg)
{
    emac_w6100_t *emac = (emac_w6100_t *)arg;
    BaseType_t high_task_wakeup = pdFALSE;
    vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup);
    if (high_task_wakeup != pdFALSE) {
        portYIELD_FROM_ISR();
    }
}

(2) Polling mode: notify RX task from esp_timer

static void w6100_poll_timer(void *arg)
{
    emac_w6100_t *emac = (emac_w6100_t *)arg;
    xTaskNotifyGive(emac->rx_task_hdl);
}

(3) RX task: handle RECV event and pass frames to the upper stack

Core flow:

  • Check SIR_RECV on SOCK0
  • Clear interrupt using IRCLR
  • Allocate receive buffer → call receive() to read the frame
  • Deliver to upper layers using stack_input()
static void emac_w6100_task(void *arg)
{
    emac_w6100_t *emac = (emac_w6100_t *)arg;
    uint8_t status = 0;
    uint8_t *buffer = NULL;
    uint32_t frame_len = 0;
    uint32_t buf_len = 0;
    esp_err_t ret;

    while (1) {
        if (emac->int_gpio_num >= 0) {
            if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 &&
                gpio_get_level(emac->int_gpio_num) != 0) {
                continue;
            }
        } else {
            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        }

        w6100_read(emac, W6100_REG_SOCK_IR(0), &status, sizeof(status));
        if (status & W6100_SIR_RECV) {
            uint8_t clr = W6100_SIR_RECV;
            w6100_write(emac, W6100_REG_SOCK_IRCLR(0), &clr, sizeof(clr));

            do {
                frame_len = ETH_MAX_PACKET_SIZE;
                if ((ret = emac_w6100_alloc_recv_buf(emac, &buffer, &frame_len)) == ESP_OK) {
                    if (buffer != NULL) {
                        buf_len = W6100_ETH_MAC_RX_BUF_SIZE_AUTO;
                        if (emac->parent.receive(&emac->parent, buffer, &buf_len) == ESP_OK) {
                            if (buf_len == 0) {
                                free(buffer);
                            } else if (frame_len > buf_len) {
                                ESP_LOGE(TAG, "received frame was truncated");
                                free(buffer);
                            } else {
                                emac->eth->stack_input(emac->eth, buffer, buf_len);
                            }
                        } else {
                            ESP_LOGE(TAG, "frame read from module failed");
                            free(buffer);
                        }
                    }
                } else if (ret == ESP_ERR_NO_MEM) {
                    ESP_LOGE(TAG, "no mem for receive buffer");
                    emac_w6100_flush_recv_frame(emac);
                }
            } while (emac->packets_remain);
        }
    }
}

ESP-IDF Ethernet drivers typically deliver frames upward through stack_input() as part of the standard Ethernet-to-TCP/IP stack integration path.


5) Stabilized reads for 16-bit size registers (w6100_mac.c)

RX/TX size registers can change while being read, producing inconsistent high/low bytes. The driver avoids this by reading twice until the values match.

static esp_err_t w6100_get_tx_free_size(emac_w6100_t *emac, uint16_t *size)
{
    uint16_t free0, free1 = 0;
    do {
        w6100_read(emac, W6100_REG_SOCK_TX_FSR(0), &free0, sizeof(free0));
        w6100_read(emac, W6100_REG_SOCK_TX_FSR(0), &free1, sizeof(free1));
    } while (free0 != free1);

    *size = __builtin_bswap16(free0);
    return ESP_OK;
}

static esp_err_t w6100_get_rx_received_size(emac_w6100_t *emac, uint16_t *size)
{
    uint16_t received0, received1 = 0;
    do {
        w6100_read(emac, W6100_REG_SOCK_RX_RSR(0), &received0, sizeof(received0));
        w6100_read(emac, W6100_REG_SOCK_RX_RSR(0), &received1, sizeof(received1));
    } while (received0 != received1);

    *size = __builtin_bswap16(received0);
    return ESP_OK;
}

6) PHY handling note: inverted bit semantics vs. W5500 (w6100_phy.c)

w6100_phy.c clearly documents PHY status bit semantics, including a common pitfall: Speed/Duplex bits are inverted compared to W5500.

/* W6100 PHYSR bit semantics are inverted from W5500:
 * - SPD: 1=10Mbps, 0=100Mbps (W5500: 1=100Mbps)
 * - DPX: 1=half, 0=full (W5500: 1=full)
 */
if (physr.speed) {
    speed = ETH_SPEED_10M;
} else {
    speed = ETH_SPEED_100M;
}
if (physr.duplex) {
    duplex = ETH_DUPLEX_HALF;
} else {
    duplex = ETH_DUPLEX_FULL;
}
ESP_LOGI(TAG, "Link Up: %s %s",
         speed == ETH_SPEED_100M ? "100Mbps" : "10Mbps",
         duplex == ETH_DUPLEX_FULL ? "Full-Duplex" : "Half-Duplex");

Link changes and speed/duplex updates are also propagated upward via the ESP-IDF driver state mechanism (on_state_changed()).


7) When this project is useful

This project is best viewed as a driver/reference for integration rather than a “demo application”. It is particularly useful if you want to:

  • Integrate W6100 as a standard ESP-IDF Ethernet interface on ESP32
  • Keep RX processing organized around an RX task while supporting both interrupt and polling environments
  • Handle PHY link state (speed/duplex included) at the driver level for robust operation

8) Minimal usage example (integration-level)

Below is a minimal example showing how w6100_init() can be called. Pin numbers and SPI host should be adjusted for your board.

#include "w6100.h"

void app_main(void)
{
    esp_eth_handle_t eth_handle = NULL;

    w6100_init_config_t cfg = W6100_INIT_CONFIG_DEFAULT();
    cfg.spi_host = SPI2_HOST;
    cfg.mosi_gpio = 23;
    cfg.miso_gpio = 19;
    cfg.sclk_gpio = 18;
    cfg.cs_gpio   = 5;
    cfg.int_gpio  = 4;     // set to -1 if INT is not used (polling)
    cfg.rst_gpio  = -1;    // set if your board connects RST
    cfg.spi_clock_mhz = 33;

    ESP_ERROR_CHECK(w6100_init(&cfg, &eth_handle));

    // Next step: attach to TCP/IP stack using ESP-IDF Ethernet example flow.
    // Typically: esp_eth_new_netif_glue() + esp_netif_attach()
}

ESP-IDF’s standard Ethernet examples show the complete sequence: driver install → esp_netif attach → DHCP.


Summary

In short, this project provides a structured way to integrate W6100 with ESP32 in an ESP-IDF-friendly form. From the code itself, the following are notable implementation points:

  • w6100_init() consolidates SPI/MAC/PHY/driver install into a single entry point
  • Clear MACRAW-centric default configuration (buffer allocation, interrupts, interrupt re-assert level)
  • RX processing converges on an RX task, while supporting both interrupt and polling wake-up methods
  • Stabilized double-read for 16-bit size registers
  • Explicit PHY status semantics (inverted vs. W5500) and link-state prop
Documents
  • GitHub:w6100

Comments Write