Skip to content

Commit 842ec24

Browse files
Add W5100, W5500, and ECN28J60 interrupt-driven mode (#1986)
No polling needed and massively reduces latency by using the GPIO interrupt to signal the Pico to read a received packet. Also drops CPU load when no packets are incoming.
1 parent a41618f commit 842ec24

File tree

12 files changed

+216
-55
lines changed

12 files changed

+216
-55
lines changed

cores/rp2040/lwip_wrap.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@
3333

3434
extern void ethernet_arch_lwip_begin() __attribute__((weak));
3535
extern void ethernet_arch_lwip_end() __attribute__((weak));
36+
extern void ethernet_arch_lwip_gpio_mask() __attribute__((weak));
37+
extern void ethernet_arch_lwip_gpio_unmask() __attribute__((weak));
3638

3739
auto_init_recursive_mutex(__lwipMutex); // Only for case with no Ethernet or PicoW, but still doing LWIP (PPP?)
3840

3941
class LWIPMutex {
4042
public:
4143
LWIPMutex() {
44+
if (ethernet_arch_lwip_gpio_mask) {
45+
ethernet_arch_lwip_gpio_mask();
46+
}
4247
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
4348
if (rp2040.isPicoW()) {
4449
cyw43_arch_lwip_begin();
@@ -56,13 +61,18 @@ class LWIPMutex {
5661
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
5762
if (rp2040.isPicoW()) {
5863
cyw43_arch_lwip_end();
59-
return;
64+
} else {
65+
#endif
66+
if (ethernet_arch_lwip_end) {
67+
ethernet_arch_lwip_end();
68+
} else {
69+
recursive_mutex_exit(&__lwipMutex);
70+
}
71+
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
6072
}
6173
#endif
62-
if (ethernet_arch_lwip_end) {
63-
ethernet_arch_lwip_end();
64-
} else {
65-
recursive_mutex_exit(&__lwipMutex);
74+
if (ethernet_arch_lwip_gpio_unmask) {
75+
ethernet_arch_lwip_gpio_unmask();
6676
}
6777
}
6878
};

docs/ethernet.rst

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ Pico may not have enough time to service the Ethernet port before the timer fire
8686
leading to a lock up and hang.
8787

8888

89+
Using Interrupt-Driven Handling
90+
-------------------------------
91+
92+
The WizNet and ENC28J60 devices support generating an interrupt when a packet is received,
93+
removing the need for polling and decreasing latency. Simply specify the SPI object to use and the
94+
interrupt pin when instantiating the Ethernet object:
95+
96+
.. code:: cpp
97+
98+
#include <W5100lwIP.h>
99+
Wiznet5100lwIP eth(SS /* Chip Select*/, SPI /* SPI interface */, 17 /* Interrupt GPIO */ );
100+
101+
89102
Adjusting SPI Speed
90103
-------------------
91104

@@ -114,12 +127,12 @@ For example, to set the W5500 to use a 30MHZ clock:
114127
Using the WIZnet W5100S-EVB-Pico
115128
--------------------------------
116129

117-
You can use the onboard Ethernet chip with these drivers by utilizing the following options:
130+
You can use the onboard Ethernet chip with these drivers, in interrupt mode, by utilizing the following options:
118131

119132
.. code:: cpp
120133
121134
#include <W5100lwIP.h>
122-
Wiznet5100lwIP eth(17); // Note chip select is **17**
135+
Wiznet5100lwIP eth(17, SPI, 21); // Note chip select is **17**
123136
124137
void setup() {
125138
// Set SPI to the onboard Wiznet chip

libraries/lwIP_Ethernet/src/LwipEthernet.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,45 @@ void __removeEthernetPacketHandler(int id) {
7272
ethernet_arch_lwip_end();
7373
}
7474

75+
#define GPIOSTACKSIZE 8
76+
static uint32_t gpioMaskStack[GPIOSTACKSIZE][4];
77+
static uint32_t gpioMask[4] = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
78+
79+
void ethernet_arch_lwip_gpio_mask() {
80+
noInterrupts();
81+
memmove(gpioMaskStack[1], gpioMaskStack[0], 4 * sizeof(uint32_t) * (GPIOSTACKSIZE - 1)); // Push down the stack
82+
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
83+
for (int i = 0; i < 4; i++) {
84+
gpioMaskStack[0][i] = irq_ctrl_base->inte[i];
85+
irq_ctrl_base->inte[i] = irq_ctrl_base->inte[i] & gpioMask[i];
86+
}
87+
interrupts();
88+
}
89+
90+
void ethernet_arch_lwip_gpio_unmask() {
91+
noInterrupts();
92+
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
93+
for (int i = 0; i < 4; i++) {
94+
irq_ctrl_base->inte[i] = gpioMaskStack[0][i];
95+
}
96+
memmove(gpioMaskStack[0], gpioMaskStack[1], 4 * sizeof(uint32_t) * (GPIOSTACKSIZE - 1)); // Pop up the stack
97+
interrupts();
98+
}
99+
100+
// To be called after IRQ is set, so we can just rad from the IOREG instead of duplicating the calculation
101+
void __addEthernetGPIO(int pin) {
102+
int idx = pin / 8;
103+
int off = (pin % 8) * 4;
104+
gpioMask[idx] &= ~(0xf << off);
105+
}
106+
107+
void __removeEthernetGPIO(int pin) {
108+
int idx = pin / 8;
109+
int off = (pin % 8) * 4;
110+
gpioMask[idx] |= 0xf << off;
111+
}
112+
113+
75114
static volatile bool _dns_lookup_pending = false;
76115

77116
static void _dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
@@ -134,6 +173,7 @@ static uint32_t _pollingPeriod = 20;
134173
static void ethernet_timeout_reached(__unused async_context_t *context, __unused async_at_time_worker_t *worker) {
135174
assert(worker == &ethernet_timeout_worker);
136175
__ethernet_timeout_reached_calls++;
176+
ethernet_arch_lwip_gpio_mask(); // Ensure non-polled devices won't interrupt us
137177
for (auto handlePacket : _handlePacketList) {
138178
handlePacket.second();
139179
}
@@ -144,6 +184,7 @@ static void ethernet_timeout_reached(__unused async_context_t *context, __unused
144184
#else
145185
sys_check_timeouts();
146186
#endif
187+
ethernet_arch_lwip_gpio_unmask();
147188
}
148189

149190
static void update_next_timeout(async_context_t *context, async_when_pending_worker_t *worker) {

libraries/lwIP_Ethernet/src/LwipEthernet.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727

2828
void ethernet_arch_lwip_begin() __attribute__((weak));
2929
void ethernet_arch_lwip_end() __attribute__((weak));
30+
void ethernet_arch_lwip_gpio_mask() __attribute__((weak));
31+
void ethernet_arch_lwip_gpio_unmask() __attribute__((weak));
32+
33+
void __addEthernetGPIO(int pin);
34+
void __removeEthernetGPIO(int pin);
3035

3136
// Internal Ethernet helper functions
3237
void __startEthernetContext();

libraries/lwIP_Ethernet/src/LwipIntfDev.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class LwipIntfDev: public LwipIntf, public RawDev {
144144
return _packetsSent;
145145
}
146146

147+
147148
// ESP8266WiFi API compatibility
148149

149150
wl_status_t status();
@@ -156,6 +157,7 @@ class LwipIntfDev: public LwipIntf, public RawDev {
156157
static err_t netif_init_s(netif* netif);
157158
static err_t linkoutput_s(netif* netif, struct pbuf* p);
158159
static void netif_status_callback_s(netif* netif);
160+
static void _irq(void *param);
159161
public:
160162
// called on a regular basis or on interrupt
161163
err_t handlePackets();
@@ -358,7 +360,9 @@ bool LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
358360
return false;
359361
}
360362

361-
_phID = __addEthernetPacketHandler([this] { this->handlePackets(); });
363+
if (_intrPin < 0) {
364+
_phID = __addEthernetPacketHandler([this] { this->handlePackets(); });
365+
}
362366

363367
if (localIP().v4() == 0) {
364368
// IP not set, starting DHCP
@@ -393,7 +397,11 @@ bool LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
393397

394398
if (_intrPin >= 0) {
395399
if (RawDev::interruptIsPossible()) {
396-
// attachInterrupt(_intrPin, [&]() { this->handlePackets(); }, FALLING);
400+
noInterrupts(); // Ensure this is atomically set up
401+
pinMode(_intrPin, INPUT);
402+
attachInterruptParam(_intrPin, _irq, LOW, (void*)this);
403+
__addEthernetGPIO(_intrPin);
404+
interrupts();
397405
} else {
398406
::printf((PGM_P)F(
399407
"lwIP_Intf: Interrupt not implemented yet, enabling transparent polling\r\n"));
@@ -406,7 +414,12 @@ bool LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
406414

407415
template<class RawDev>
408416
void LwipIntfDev<RawDev>::end() {
409-
__removeEthernetPacketHandler(_phID);
417+
if (_intrPin < 0) {
418+
__removeEthernetPacketHandler(_phID);
419+
} else {
420+
detachInterrupt(_intrPin);
421+
__removeEthernetGPIO(_intrPin);
422+
}
410423

411424
RawDev::end();
412425

@@ -415,6 +428,14 @@ void LwipIntfDev<RawDev>::end() {
415428
_started = false;
416429
}
417430

431+
template<class RawDev>
432+
void LwipIntfDev<RawDev>::_irq(void *param) {
433+
LwipIntfDev *d = static_cast<LwipIntfDev*>(param);
434+
ethernet_arch_lwip_begin();
435+
d->handlePackets();
436+
sys_check_timeouts();
437+
ethernet_arch_lwip_end();
438+
}
418439

419440
template<class RawDev>
420441
wl_status_t LwipIntfDev<RawDev>::status() {

libraries/lwIP_enc28j60/src/utility/enc28j60.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
#define ECON2_AUTOINC 0x80
7070
#define ECON2_PKTDEC 0x40
7171

72+
#define EIE_PKTIE 0x40
73+
#define EIE_INTIE 0x80
74+
7275
#define EIR_TXIF 0x08
7376

7477
#define ERXTX_BANK 0x00
@@ -153,8 +156,7 @@
153156
// The ENC28J60 SPI Interface supports clock speeds up to 20 MHz
154157
static const SPISettings spiSettings(20000000, MSBFIRST, SPI_MODE0);
155158

156-
ENC28J60::ENC28J60(int8_t cs, SPIClass& spi, int8_t intr) : _bank(ERXTX_BANK), _cs(cs), _spi(spi) {
157-
(void)intr;
159+
ENC28J60::ENC28J60(int8_t cs, SPIClass& spi, int8_t intr) : _bank(ERXTX_BANK), _cs(cs), _intr(intr), _spi(spi) {
158160
}
159161

160162
void ENC28J60::enc28j60_arch_spi_select(void) {
@@ -488,6 +490,12 @@ bool ENC28J60::reset(void) {
488490
/* Turn on autoincrement for buffer access */
489491
setregbitfield(ECON2, ECON2_AUTOINC);
490492

493+
/* Enable interrupt on packet receive if desired */
494+
if (_intr >= 0) {
495+
setregbitfield(EIE, EIE_PKTIE);
496+
setregbitfield(EIE, EIE_INTIE);
497+
}
498+
491499
/* Turn on reception */
492500
writereg(ECON1, ECON1_RXEN);
493501

@@ -511,6 +519,8 @@ bool ENC28J60::begin(const uint8_t* address, netif *net) {
511519
uint16_t ENC28J60::sendFrame(const uint8_t* data, uint16_t datalen) {
512520
uint16_t dataend;
513521

522+
ethernet_arch_lwip_gpio_mask(); // So we don't fire an IRQ and interrupt the send w/a receive!
523+
514524
/*
515525
1. Appropriately program the ETXST pointer to point to an unused
516526
location in memory. It will point to the per packet control
@@ -582,6 +592,8 @@ uint16_t ENC28J60::sendFrame(const uint8_t* data, uint16_t datalen) {
582592
}
583593
#endif
584594

595+
ethernet_arch_lwip_gpio_unmask();
596+
585597
// sent_packets++;
586598
// PRINTF("enc28j60: sent_packets %d\n", sent_packets);
587599
return datalen;

libraries/lwIP_enc28j60/src/utility/enc28j60.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class ENC28J60 {
9999
netif *_netif;
100100
protected:
101101
static constexpr bool interruptIsPossible() {
102-
return false;
102+
return true;
103103
}
104104

105105
/**
@@ -154,6 +154,7 @@ class ENC28J60 {
154154

155155
uint8_t _bank;
156156
int8_t _cs;
157+
int8_t _intr;
157158
SPIClass& _spi;
158159

159160
const uint8_t* _localMac;

libraries/lwIP_w5100/examples/WiFiClient-W5100/WiFiClient-W5100.ino

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const char* host = "djxmmx.net";
99
const uint16_t port = 17;
1010

1111
Wiznet5100lwIP eth(1 /* chip select */);
12+
// To use Interrupt-driven mode, pass in an SPI object and an IRQ pin like so:
13+
// Wiznet5100lwIP eth(17, SPI, 21);
1214

1315
void setup() {
1416
// Set up SPI pinout to match your HW

libraries/lwIP_w5100/src/utility/w5100.cpp

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include <SPI.h>
3636
#include "w5100.h"
37+
#include <LwipEthernet.h>
3738

3839
uint8_t Wiznet5100::wizchip_read(uint16_t address) {
3940
uint8_t ret;
@@ -178,8 +179,7 @@ void Wiznet5100::wizchip_sw_reset() {
178179
setSHAR(_mac_address);
179180
}
180181

181-
Wiznet5100::Wiznet5100(int8_t cs, SPIClass& spi, int8_t intr) : _spi(spi), _cs(cs) {
182-
(void)intr;
182+
Wiznet5100::Wiznet5100(int8_t cs, SPIClass& spi, int8_t intr) : _spi(spi), _cs(cs), _intr(intr) {
183183
}
184184

185185
bool Wiznet5100::begin(const uint8_t* mac_address, netif *net) {
@@ -189,13 +189,6 @@ bool Wiznet5100::begin(const uint8_t* mac_address, netif *net) {
189189
pinMode(_cs, OUTPUT);
190190
wizchip_cs_deselect();
191191

192-
#if 0
193-
_spi.begin();
194-
_spi.setClockDivider(SPI_CLOCK_DIV4); // 4 MHz?
195-
_spi.setBitOrder(MSBFIRST);
196-
_spi.setDataMode(SPI_MODE0);
197-
#endif
198-
199192
wizchip_sw_reset();
200193

201194
// Set the size of the Rx and Tx buffers
@@ -213,6 +206,11 @@ bool Wiznet5100::begin(const uint8_t* mac_address, netif *net) {
213206
return false;
214207
}
215208

209+
if (_intr >= 0) {
210+
setSn_IR(0xff); // Clear everything
211+
setIMR(IM_IR0);
212+
}
213+
216214
// Success
217215
return true;
218216
}
@@ -245,6 +243,8 @@ uint16_t Wiznet5100::readFrame(uint8_t* buffer, uint16_t bufsize) {
245243
}
246244

247245
uint16_t Wiznet5100::readFrameSize() {
246+
setSn_IR(Sn_IR_RECV);
247+
248248
uint16_t len = getSn_RX_RSR();
249249

250250
if (len == 0) {
@@ -273,25 +273,18 @@ uint16_t Wiznet5100::readFrameData(uint8_t* buffer, uint16_t framesize) {
273273
wizchip_recv_data(buffer, framesize);
274274
setSn_CR(Sn_CR_RECV);
275275

276-
#if 1
277276
// let lwIP deal with mac address filtering
278277
return framesize;
279-
#else
280-
// W5100 doesn't have any built-in MAC address filtering
281-
if ((buffer[0] & 0x01) || memcmp(&buffer[0], _mac_address, 6) == 0) {
282-
// Addressed to an Ethernet multicast address or our unicast address
283-
return framesize;
284-
} else {
285-
return 0;
286-
}
287-
#endif
288278
}
289279

290280
uint16_t Wiznet5100::sendFrame(const uint8_t* buf, uint16_t len) {
281+
ethernet_arch_lwip_gpio_mask(); // So we don't fire an IRQ and interrupt the send w/a receive!
282+
291283
// Wait for space in the transmit buffer
292284
while (1) {
293285
uint16_t freesize = getSn_TX_FSR();
294286
if (getSn_SR() == SOCK_CLOSED) {
287+
ethernet_arch_lwip_gpio_unmask();
295288
return -1;
296289
}
297290
if (len <= freesize) {
@@ -311,9 +304,11 @@ uint16_t Wiznet5100::sendFrame(const uint8_t* buf, uint16_t len) {
311304
} else if (tmp & Sn_IR_TIMEOUT) {
312305
setSn_IR(Sn_IR_TIMEOUT);
313306
// There was a timeout
307+
ethernet_arch_lwip_gpio_unmask();
314308
return -1;
315309
}
316310
}
317311

312+
ethernet_arch_lwip_gpio_unmask();
318313
return len;
319314
}

0 commit comments

Comments
 (0)