Skip to content

Commit

Permalink
Support wired network interfaces (W5500, W5100, ENC28J60) (#1703)
Browse files Browse the repository at this point in the history
Enable use of wired Ethernet modules as first-class LWIP citizens.  All
networking classes like MDNS, WebServer, HTTPClient, WiFiClient, and OTA
can use a wired Ethernet adapter just like built-in WiFi.

Two examples updated to show proper use.

Uses the Async Context support built into the Pico SDK.  When running on the
Pico  it will use the CYW43 async instance.

Uses modified wired Ethernet drivers, thanks Nicholas Humfrey!

Note, the classic, non-LWIP integrated `Ethernet` and related libraries
should still work fine (but not be able to use WebServer/HTTPS/etc.)

Fixes #775
  • Loading branch information
earlephilhower committed Sep 15, 2023
1 parent 3950b94 commit 1f3d501
Show file tree
Hide file tree
Showing 43 changed files with 3,739 additions and 176 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
* Bluetooth on the PicoW (Classic and BLE) with Keyboard, Mouse, Joystick, and Virtual Serial
* Generic Arduino USB Serial, Keyboard, Joystick, and Mouse emulation
* WiFi (Pico W)
* Ethernet (Wired W5500, W5100, ENC28J60)
* HTTP client and server (WebServer)
* SSL/TLS/HTTPS
* Over-the-Air (OTA) upgrades
Expand Down
46 changes: 44 additions & 2 deletions cores/rp2040/lwip_wrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <Arduino.h>
#include <pico/mutex.h>
#include <lwip/pbuf.h>
#include <lwip/udp.h>
Expand All @@ -26,20 +27,61 @@
#include <lwip/raw.h>
#include <lwip/timeouts.h>
#include <pico/cyw43_arch.h>
#include <pico/mutex.h>
#include <sys/lock.h>

#if !defined(ARDUINO_RASPBERRY_PI_PICO_W)
extern void ethernet_arch_lwip_begin() __attribute__((weak));
extern void ethernet_arch_lwip_end() __attribute__((weak));

auto_init_recursive_mutex(__lwipMutex); // Only for non-PicoW case
#endif

class LWIPMutex {
public:
LWIPMutex() {
cyw43_arch_lwip_begin();
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
if (rp2040.isPicoW()) {
cyw43_arch_lwip_begin();
return;
}
#else
if (ethernet_arch_lwip_begin) {
ethernet_arch_lwip_begin();
} else {
recursive_mutex_enter_blocking(&__lwipMutex);
}
#endif
}

~LWIPMutex() {
cyw43_arch_lwip_end();
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
if (rp2040.isPicoW()) {
cyw43_arch_lwip_end();
return;
}
#else
if (ethernet_arch_lwip_end) {
ethernet_arch_lwip_end();
} else {
recursive_mutex_exit(&__lwipMutex);
}
#endif
}
};

extern "C" {

// Avoid calling lwip_init multiple times
extern void __real_lwip_init();
void __wrap_lwip_init() {
static bool initted = false;
if (!initted) {
__real_lwip_init();
initted = true;
}
}

extern u8_t __real_pbuf_header(struct pbuf *p, s16_t header_size);
u8_t __wrap_pbuf_header(struct pbuf *p, s16_t header_size) {
LWIPMutex m;
Expand Down
139 changes: 139 additions & 0 deletions docs/ethernet.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
EthernetLWIP (Wired Ethernet) Support
=====================================

Wired Ethernet interfaces are supported for all the internal networking
libraries (``WiFiClient``, ``WiFiClientSecure``, ``WiFiServer``,
``WiFiServerSecure``, ``WiFiUDP``, ``WebServer``, ``Updater``,
``HTTPClient``, etc.).

Using these wired interfaces is very similar to using the Pico-W WiFi
so most examples in the core only require minor modifications to use
a wired interface.

Supported Wired Ethernet Modules
--------------------------------

* Wiznet W5100

* Wiznet W5500

* ENC28J60


Enabling Wired Ethernet
-----------------------

Simply replace the WiFi include at the top with:

.. code:: cpp
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60.h
And add a global Ethernet object of the same type:

.. code:: cpp
Wiznet5500lwIP eth(1); // Parameter is the Chip Select pin
In your ``setup()`` you may adjust the SPI pins you're using to
match your hardware (be sure they are legal for the RP2040!), or
skip this if you're using the default ones:

.. code:: cpp
void setup() {
SPI.setRX(0);
SPI.setCS(1);
SPI.setSCK(2);
SPI.setTX(3);
....
}
And finally replace the ``WiFi.begin()`` and ``WiFi.connected()``
calls with ``eth.begin()`` and ``eth.connected()``:

.. code:: cpp
void setup() {
....
// WiFi.begin(SSID, PASS)
eth.begin();
//while (!WiFi.connected()) {
while (!eth.connected()) {
Serial.print(".");
}
Serial.print("IP address: ");
//Serial.println(WiFi.localIP());
Serial.println(eth.localIP());
....
}
Adjusting LWIP Polling
----------------------

LWIP operates in a polling mode for the wired Ethernet devices. By default it will run
every 20ms, meaning that on average it will take half that time (10ms) before a packet
received in the Ethernet module is received and operated upon by the Pico. This gives
very low CPU utilization but in some cases this latency can affect performance.

Adding a call to ``lwipPollingPeriod(XXX)`` (where ``XXXX`` is the polling period in
milliseconds) can adjust this setting on the fly. Note that if you set it too low, the
Pico may not have enough time to service the Ethernet port before the timer fires again,
leading to a lock up and hang.


Adjusting SPI Speed
-------------------

By default a 4MHz clock will be used to clock data into and out of the Ethernet module.
Depending on the module and your wiring, a higher SPI clock may increase performance (but
too high of a clock will cause communications problems or hangs).

This value may be adjusted using the ``eth.setSPISpeed(hz)`` call **before** starting the
device. (You may also use custom ``SPISettings`` instead via ``eth.setSPISettings(spis)```)

For example, to set the W5500 to use a 30MHZ clock:

.. code:: cpp
#include <W5500lwIP.h>
Wiznet5500lwIP eth(1);
void setup() {
eth.setSPISpeed(30000000);
lwipPollingPeriod(3);
...
eth.begin();
...
}
Example Code
------------

The following examples allow switching between WiFi and Ethernet:

* ``WebServer/AdvancedWebServer``

* ``HTTPClient/BasicHTTPSClient``

Caveats
-------

The same restrictions for ``WiFi`` apply to these Ethernet classes, namely:

* Only core 0 may run any networking related code.

* In FreeRTOS, only the ``setup`` and ``loop`` task can call networking libraries, not any tasks.

Special Thanks
--------------

* LWIPEthernet classes come from the ESP8266 Arduino team

* Individual Ethernet drivers were written by Nicholas Humfrey

1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
FreeRTOS SMP (multicore) <freertos>

WiFi (Pico-W Support) <wifi>
Ethernet (Wired) <ethernet>
WiFiClient <wificlient>
WiFiServer <wifiserver>
WiFiUDP <wifiudp>
Expand Down
2 changes: 2 additions & 0 deletions lib/platform_wrap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
-Wl,--wrap=realloc
-Wl,--wrap=free

-Wl,--wrap=lwip_init

-Wl,--wrap=pbuf_header
-Wl,--wrap=pbuf_free
-Wl,--wrap=pbuf_alloc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@
*/

#include <Arduino.h>


// Example works with either Wired or WiFi Ethernet, define one of these values to 1, other to 0
#define USE_WIFI 1
#define USE_WIRED 0

#if USE_WIFI
#include <WiFi.h>
#elif USE_WIRED
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60lwIP.h
Wiznet5500lwIP eth(1 /* chip select */); // or Wiznet5100lwIP or ENC28J60lwIP
#endif

#include <HTTPClient.h>

#ifndef STASSID
Expand Down Expand Up @@ -34,8 +46,33 @@ void setup() {
delay(1000);
}

#if USE_WIFI
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, pass);
#elif USE_WIRED
// Set up SPI pinout to match your HW
SPI.setRX(0);
SPI.setCS(1);
SPI.setSCK(2);
SPI.setTX(3);

// Start the Ethernet port
if (!eth.begin()) {
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
while (1) {
delay(1000);
}
}

// Wait for connection
while (eth.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("IP address: ");
Serial.println(eth.localIP());
#endif

}

const char *jigsaw_cert = R"EOF(
Expand Down Expand Up @@ -74,8 +111,12 @@ N6K5xrmaof185pVCxACPLc/BoKyUwMeC8iXCm00=
static int cnt = 0;

void loop() {
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
#if USE_WIFI
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
#elif USE_WIRED
if (eth.connected()) {
#endif
HTTPClient https;
switch (cnt) {
case 0:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Example works with either Wired or WiFi Ethernet, define one of these values to 1, other to 0
#define USE_WIFI 1
#define USE_WIRED 0

#if USE_WIFI
#include <WiFi.h>
#elif USE_WIRED
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60lwIP.h
Wiznet5500lwIP eth(1 /* chip select */); // or Wiznet5100lwIP or ENC28J60lwIP
#endif

#include <WiFiClient.h>
#include <WebServer.h>
#include <LEAmDNS.h>
Expand Down Expand Up @@ -58,13 +68,13 @@ void handleRoot() {
temp.printf("<html>\
<head>\
<meta http-equiv='refresh' content='5'/>\
<title>Pico-W Demo</title>\
<title>" BOARD_NAME " Demo</title>\
<style>\
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
</style>\
</head>\
<body>\
<h1>Hello from the Pico W!</h1>\
<h1>Hello from the " BOARD_NAME "!</h1>\
<p>Uptime: %02d:%02d:%02d</p>\
<p>Free Memory: %d</p>\
<p>Page Count: %d</p>\
Expand Down Expand Up @@ -117,6 +127,8 @@ void setup(void) {
pinMode(led, OUTPUT);
digitalWrite(led, 0);
Serial.begin(115200);

#if USE_WIFI
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
Expand All @@ -130,8 +142,32 @@ void setup(void) {
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);

Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#elif USE_WIRED
// Set up SPI pinout to match your HW
SPI.setRX(0);
SPI.setCS(1);
SPI.setSCK(2);
SPI.setTX(3);

// Start the Ethernet port
if (!eth.begin()) {
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
while (1) {
delay(1000);
}
}

// Wait for connection
while (eth.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("IP address: ");
Serial.println(eth.localIP());
#endif

if (MDNS.begin("picow")) {
Serial.println("MDNS responder started");
Expand Down
2 changes: 1 addition & 1 deletion libraries/WiFi/examples/WiFiClient/WiFiClient.ino
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void loop() {
// This will send a string to the server
Serial.println("sending data to server");
if (client.connected()) {
client.println("hello from ESP8266");
client.println("hello from RP2040");
}

// wait for data to be available
Expand Down

0 comments on commit 1f3d501

Please sign in to comment.