Tennsy 4.1 Native Ethernet tcp server differences from W5500

Andrewier

Member
Greetings to all. I'm implementing a very simple tcp server with Teensy 4.1 and I've run into two situations where I would need some guidance.

First, I realized that I can only connect up to 7 clients (simultaneously). Since the Ethernet chip in T4.1 is just a phy frontend, I believe this limit is set somewhere in the NativeEthernet lib, but I couldn't find where.
Using a UNO R3 and a W5500 shield, the same exact code (with Ethernet2 instead of native lib), serves up to 8 clients.

Additionally, in T4.1 with native Ethernet, the server only starts sending data after receiving some (any) character from a client. Except if port 23 is used (telnet std port), where the server sends data without waiting for clients.
Again, with the W5500 and UNO R3, all ports behave similarly, sending data independently of receiving anything from any client.

Any suggestions?
Thank you in advance.


The code:

/* A simple server that distributes a simple text line to all connected clients. */

#include <NativeEthernet.h>
#include <SPI.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10, 0, 3, 88);

// telnet defaults to port 23
int porta = 4000;
EthernetServer server(porta);


void setup() {

// initialize the ethernet device
Ethernet.begin(mac, ip);

// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// start listening for clients
server.begin();

Serial.print("Server address:");
Serial.print(Ethernet.localIP());
Serial.print(":"); Serial.println(porta);
}

void loop() {

while(true){
server.println("Simple text for testing...");
delay(1000);
}
}
 
(Pro tip: Wrap your code in "CODE" blocks so that it shows up as monospaced and properly formatted. The '#' button on the toolbar also does this for you.)

May I suggest having a look at the 'ServerWithListeners' example in the QNEthernet library. It shows a variety of ways the code could be constructed.

Under the covers, the library uses lwIP, and the configuration is statically allocated at compile time. To change things such as maximum number of allowed TCP sockets, you need to modify the _lwipopts.h_ file. (The current default is 8 TCP connections plus 8 listening sockets.) The README, in my opinion, is pretty complete and, in conjunction with the examples, should describe all the things you need to know to get started.

Note that there are a few differences from NativeEthernet. One is that you don't need to specify a MAC address. Another is that things like `Ethernet.begin()` have non-blocking behaviour. There's also lots of other features and convenience features not defined by the Arduino-style Ethernet API.
See also: Complete list of features section in the README

See the latest `master` branch here: https://github.com/ssilverman/QNEthernet
(Latest version is 0.19.0-snapshot, but 0.18.0 is available through the library manager in either PlatformIO or the Arduino IDE. PlatformIO can also use the latest version from GitHub by adding a `lib_deps` value: "https://github.com/ssilverman/QNEthernet.git")
 
Last edited:
Thank you very much for your help!

Sorry for the code formatting - tip noted. Although I'm an old guy, I'm new to this forum - any forum to be honest - most of the time I prefer to dig up and read things and figure out solutions myself rather than resorting to borrowing other people's time.
But things are becoming more and more complex and programming isn't exactly my specialty, so...

I haven't paid much attention to the QNEthernet lib before because I usually try to stick with the basics and the defaults as much as possible.
My application being so simple, I never thought to try it out before I got used to and better understood the original native library.

I'll look into QNEthernet and all the links you suggested.


Just a few follow-up questions, if you'll excuse me:
- Should this selective behavior depending on the port number be the norm or just a thing in the native library?
- Do you know if the maximum of 7 clients at the same time is set for some T4.1 security/stability reason?


Thank you one more time!
 
Happy to help. The PJRC forum is a very helpful and professional place. I think it's one of the things that makes the products here so amazing: they're well-designed, robust, _and_ there's a very helpful place to go should one need guidance. There doesn't seem to be any drama nor snarkiness. There does seem to be a lot of respect and straightforward answers and posts.

On the structure of Teensyduino

Teensyduino, the set of tools for Teensys, contains three main things plus one extra:
1. A compiler suite, gcc-based (toolchain-gccarmnoneeabi in PlatformIO lingo),
2. Some extra tools for things like a firmware upload tool that sends binaries to the Teensy (tool-teensy (PlatformIO lingo)),
3. The "core" framework that contains drivers and startup code for all the hardware and an implementation of the Arduino-style API (framework-arduinoteensy (PlatformIO lingo)), and
4. A set of default libraries for the convenience of users.

It's designed to be used as a component in scripts or in other IDE's.

On the Arduino and other IDE's

(An IDE means "integrated development environment", for those that don't know.)

First there was the Arduino IDE, pre-2.0.0. That's the IDE that, likely, most people have started with, up until recently. It's okay for smaller projects, but it's not a full-featured IDE, and projects with a large number of files quickly get unmanageable. Teensyduino fits in there either as a kind of plugin that is added on top and modifies the Arduino IDE install, or included with a whole build of the Arduino IDE, depending on which OS you're using. For example, later versions of macOS don't allow you to modify a signed program, hence the "complete download" version. See the "Arduino 1.8.x" section here: https://www.pjrc.com/teensy/td_download.html

For larger and more complex projects, it's much more helpful to use a more robust IDE. Because Teensy programs can be built and uploaded with make and other command-line tools, there exist projects where those tools are integrated, say, as scripts. For example, https://forum.pjrc.com/threads/72344-Geany-IDE-meets-arduino-cli.

Then came PlatformIO. This is a project that aims to make it easy to build and use many types of embedded hardware. One of the boards it supports is the Teensy. It uses SCons under the covers (a Python-based build system), and its creators have painstakingly integrated "all the toolchains" so that it can be used, say, in headless systems or command-line-only systems. It also has IDE plugins for things like VSCode — this is the IDE and configuration that I currently use — and Atom, and I think CLion (in my opinion, JetBrains makes one the greatest Java-based IDE's ever).

Next came Arduino CLI and Arduino IDE v2.0.0+. The Arduino CLI is Arduino's newer toolset that makes it easier to build Arduino-style projects from the command-line without an IDE. The IDE v2.0.0+ is an improved version of the IDE, a little nicer to use than the v1.8.x versions. It also has the advantage where it can have plugins for new boards without modifying the IDE base directory, as was the case with pre-2.0.0 versions.

Libraries

Think of Teensy (and Arduino) libraries as just projects in directories. Most of them have not been created by PJRC (a bunch have), and were included because PJRC wants to make it easy for users to have a wide array of choices and conveniences. Note that they go in the Arduino-defined libraries folder, the exact same place that the Arduino IDE puts them. Every library included with Teensyduino can be downloaded and installed separately.

The NativeEthernet library is one such library. Its author (@vjmuzik on this forum) named it that probably because it supports "Teensy 4.1's native Ethernet hardware". It's not meant to imply that it's the "standard" way to access that Ethernet hardware. It's there merely as one of many libraries conveniently included by the Teensyduino install. Its philosophy is to stick as close to the behaviour of previous Ethernet libraries as possible, likely to reduce unexpected behaviour with existing code.

It was included in PJRC's default set of libraries because it was the first library to provide Teensy 4.1 Ethernet support and an implementation for the Teensy 4.1 of the Arduino-style Ethernet API (See https://reference.arduino.cc/reference/en/libraries/ethernet/). It's based on an embedded IP stack called FNET and requires a separate, custom FNET version to be installed too (it's included as a second library, I believe). You could install NativeEthernet separately if you wanted, or if it gets updated and the Arduino IDE (or whatever system you use) detects that it needs an update. Here's its link: https://github.com/vjmuzik/NativeEthernet

On one hand, having a set of libraries already installed for you might be convenient. On the other hand, they're fixed to a specific Teensyduino version, and any install of a current Teensyduino might require you to upgrade to a library's latest version. You'll see this often then you start the latest Arduino IDE, and it tells you that boards or libraries need an update.

One point is that there's many, many useful libraries that aren't included in the default Teensyduino installation. They're easy to find and install yourself. In the Arduino IDE, for example, all you need to do is search for something you need and then press the "Install" button. Libraries you add this way are indistinguishable from libraries installed by default with Teensyduino. They all go in the same place.

The QNEthernet library

And now we get to the QNEthernet part of our story. This is the Ethernet library I wrote. QNEthernet is another library that supports Teensy 4.1's native Ethernet hardware. It's based on an embedded IP stack called lwIP (not required to be installed separately — it's included with the library install). To install from the Arduino IDE, simply search for "QNEthernet" in the Library Manager tab. (Note that there's lots of noise because a bunch of libraries use that word in their description; if you don't see it, try searching for "QNEthernet Silverman" instead.) You could, in theory, also manually install it by downloading the latest release from https://github.com/ssilverman/QNEthernet and then putting the contents inside a QNEthernet directory inside Arduino's libraries folder.

Note that PlatformIO has a way of getting the latest from GitHub by using its `lib_deps` directive.

This library also (mostly) follows the Arduino-style Ethernet API, but there are some differences and lots of improvements, additions, and other features. I'm also currently maintaining it. The latest version is 0.19.0-snapshot on GitHub (v0.18.0 in the Arduino API, until I release v0.19.0). My philosophy for this library is to provide utility and convenience, but not to strictly adhere to how previous Ethernet libraries (for other hardware) behaved. One example is being able to access a UDP packet's data directly without having to first copy into a buffer — this saves memory and makes the code simpler. It's also not part of the standard Arduino-style Ethernet API.

See the project's README at https://github.com/ssilverman/QNEthernet, and check out some of the examples. Note that in the Arduino IDE, you can find library examples from File->Examples->Examples from Custom Libraries section (scroll down). You'll note that you can find NativeEthernet examples there too, in addition to the QNEthernet examples.

A digression: I suspect that the QNEthernet library isn't included in the default Teensyduino install because (one or more of):
1. It's not yet past a 1.0.0 release; it's still at 0.x.x. But I'm picky, and just because it's not yet past 1.0.0, I think it's stable and excellent, and I've used it in several commercial-level projects. In fact, that's what's guided me in its creation, design, and maintenance: real-world usage. I've been using it and modifying it to meet my needs as I go.
2. There's already an Ethernet library, NativeEthernet, but that one got there first.
3. It's so easy to install more libraries, and I make efforts to keep the Arduino IDE-visible version up-to-date.

Answers to your two questions

Maximum TCP connections: I don't know how to change the maximum socket count in NativeEthernet, but you can change QNEthernet's by modifying the `MEMP_NUM_TCP_PCB` value in the lwipopts.h file in the library source location (Arduino IDE's libraries folder — on a Mac, it's in ~/Documents/Arduino/libraries). Note that the current default is 8, so you should be fine if you need 7.

Port 23: As for how the server behaves with specific ports, there should be no difference between any of the ports, depending on how you write your program. Are you using a different program when listening on port 23? From the looks of your program, you're never accepting any client connections via the `server.accept()` function. See here: https://www.arduino.cc/reference/en/libraries/ethernet/server.accept/ Also note that with QNEthernet, you don't need to specify a MAC address, it uses the Teensy's internal one.

See also the `ServerWithListeners` and SNTPClient QNEthernet examples. I know those examples are a little more complex, but keep in mind the code is just for illustration and you don't actually need all the things that are there. Definitely a steeper learning curve, but rewarding. :) I tried to make the examples not be just toy examples. This is another reason why I didn't include any of the Arduino Ethernet API examples out there; I wanted something new, especially since the QNEthernet API allows you to do a lot more, for example non-blocking behaviour. This isn't to imply simpler examples aren't useful, it's just that there's already lots of them out there. The Teensy 4.1 is a serious device; it demands serious examples.

Let me ask you another question: What specifically would you like your program to do?

Summary

In summary, the NativeEthernet library isn't a "basic" or "default" library. It's just one of many libraries and the first one to have provided support for Teensy 4.1's Ethernet hardware. Same with the QNEthernet library. It's just another library for the Teensy 4.1. But I wrote it and can support it (up to a point — I may respond sometimes with "that's not a QNEthernet issue, it's a how-you-wrote-your-program issue"). It's a labour of love.

It will most often be the case that any Arduino-style Ethernet programs or examples you find online will work almost the same with both NativeEthernet and with QNEthernet (except for the `begin()` functions being non-blocking with QNEthernet).

Ultimately, which libraries you use is up to you, and none of the choices will be wrong or incorrect. It's a matter of personal preference and convenience.
 
Last edited:
Wow! What a lecture!

I'm not exactly new to embedded systems - including Arduino - but you gave me extra context that made things a lot clearer.
I have been using PlatformIO also due its capacity of swapping hardware in the middle of code writing. It is a very enjoyable tool indeed.

My basic/standard concept for a native library was in the sense that (I assumed) it was the first and simplest library I could use to do some feasibility testing.
As you posted, the QNE library has more elaborate examples that require more reading (and may be more C++ knowledge) for a very limited programmer (like me).

Anyway, I tried (QNE) as you suggested and... after some namespace issues and "deprecated" warnings management... Voilà! It works!
With exactly the same code (almost, if you take in account the namespace and deprecated stuff).

But it works, thank you!

The application I'm trying to make is a kind of data collector, receiving three or four GPSs and other sensors that can be seen from several different computers on a small network.
I usually do this through very expensive industrial serial servers, which for my needs are an overkill in terms of price and functionality.

Having a non-blocking lib option is also great, as it will probably be easier to handle streaming coming in from serial and going out to Ethernet.

Regarding the different behavior for different ports: I was always using the same listeners: Teraterm Pro, in telnet mode and a simple C++ Builder application made by a friend of mine.
I don't know about the inner workings of Teraterm, but my friend's program is a simple tcp socket that capture everything that arrives on the port buffer and print it in a text box.
Since Teraterm is a terminal, I could hit a key to make the server start sending. The C++ program was just sitting there listening without sending anything.
Once a client (any one of several Teraterm instances) sent a character, all connected clients started to receive the streams.

This behavior was only with NativeEthernet on Teensy 4.1. Both W5500 + Ethernet.h and T4.1 + QNEthernet don't require such a start character, doesn't matter which port is used.

About the number of sockets: seven is how many the NativeEthernet lib handles by default. Not a particularly bad number, only a bit strange (2^n-1); W5100 chip gives 4, W5500 and QNEthernet 8.
Anything between 6 and 8 to me is ok, but I will keep the 8, as both T4.1 and the W5500 shield can provide easily.


I will study about the `server.accept()` function, as well other functions to make sure that the sockets don't get stuck (if you break a client connection sometimes they do).
My first code was a simple (may be too simple) test to see what could be achieved. Well, seems that all I need is reachable, just takes some extra digging, extra coffee and less sleeping to close it.


And yes, as you said, "The PJRC forum is a very helpful and professional place" and it's people like you and Paul who make it that way.


Regards,
André W.
 
I'm glad it helped. Yeah, adding detail like that is always tricky because of the different experience levels of everyone (I try to write here so that there's enough context for newer people — it's also my hope (with a related fear) that anyone with experience doesn't take offence to the level of detail :)), but I'm glad it added some extra context for you.

(Note that I'm repeating a few details here so that anyone reading this post doesn't have to wade through all that other text. And forgive me in advance if you already know the details I outline. :))

I'm surprised about the different NativeEthernet behaviour. Maybe it's a bug or maybe it has something to do with "write immediacy", TCP buffering, flushing, and the behaviour of the different stacks. I know NativeEthernet uses FNET, QNEthernet uses lwIP, and I'm not sure exactly what the W5500 uses. (See this link for some extra context.)

My very first thought when I read your latest post was that it's a Telnet negotiation or protocol thing. Also, if 2/3 of the stacks behave the same and one doesn't, then it might be something internal to the stacks. I'd have to see a complete program to really judge what's going on.

On non-blocking:
You can do most things in the library in a non-blocking way, including using listeners for certain kinds of things such as link-state, address-changed, and interface-status. The things that don't have listeners will usually have "is there something available?"-type functions.

In the Arduino-style Ethernet API, the `server.accept()` function is basically for polling, and you call it regularly, often in `loop()`. The flow is as follows:
1. Check `server.accept()`.
2. If it returns a valid connection then either: 1. Process that connection, or 2. Add it to some connection list, where the list is processed later. This is what the `ServerWithListeners` example does.

In pseudocode:
Code:
void loop() {
  checkForConnections();
  processConnectionList();
}

void checkForConnections() {
  EthernetClient client = server.accept();
  if (client) {
    // Add to the connection list and set up any state for that connection
  }
}

void processConnectionList() {
  // Loop through connections
  // Mark any finished connections as finished

  // Remove any finished connections from the list
}

It is my opinion that the more complex examples are what the simpler programs tend to when they get larger.

As for the deprecated behaviour, I did that on purpose (with `[[deprecated]]` attributes in the code), mostly to encourage developers to use the internal Teensy MAC address. How to avoid:
1. Use the `Ethernet.begin(...)` functions that don't take a MAC address.
2. `Ethernet.init(sspin)` does nothing.
3. Call `Ethernet.macAddress()` instead of `Ethernet.MACAddress()`. I did this one for naming consistency, but perhaps there really isn't a point to deprecating `MACAddress()`...
4. `Ethernet.maintain()` does nothing because there's an internal DHCP client. DHCP can be started with the `Ethernet.begin()` form. It doesn't block so you either have to wait for an IP address (`Ethernet.waitForLocalIP(timeout)`) or use the `onAddressChanged()` and `onLinkState()` listeners. Setting a static IP will cause the address-changed event to occur before the link is necessarily up, so that's why I like to only proceed when there an address and a link.
5. `Ethernet.setDnsServerIP()`: Again, a naming thing to encourage use of `setDNSServerIP()`. Another perhaps-this-shouldn't-really-be-deprecated case. You know how developers love to enforce their code style on _everyone else_... (I'll eye-roll myself here.)

In fact... I might just un-deprecate `MACAddress()` and `setDnsServerIP()`... Thinking about it...

(This is all in the README and in the code itself, which you've probably already seen; I'm putting the above list here for posterity (and for windbaggery, of course :)).)

I'm curious, for which functions are/were you seeing deprecated warnings?

To save you some time, here's some links to useful details:
1. A survey of how connections work: This describes how to detect a few client states, for example, disconnection.
2. On connections that hang around after cable disconnect: This one describes an issue (linked in the text) where a link-down aborted the connection on the Windows side but not on the Teensy side, so it took several minutes for a connection to time out and be freed. There was some resource exhaustion that could only be mitigated by calling `client.abort()` on the Teensy side upon link-down. (Note that this function isn't in the Arduino-style API.)
3. Write immediacy: Describes how data is buffered and what to do if you don't want the data to be buffered, such that it's sent immediately. (Also mentioned above.)
 
Last edited:
I'm glad it helped. Yeah, adding detail like that is always tricky because of the different experience levels of everyone ...

I follow the "if you don't want an explanation, don't ask" policy... Sometimes people ask me something without knowing how deep that rabbit hole would go, because they just want a simple and shallow answer, which is not always possible to give.
Rest assured that even if part of your answer is redundant (to me), 1 - at least out of politeness, I would never complain, as you are willingly spending your time as an act of goodwill; 2 - nobody knows everything, and even being a seasoned developer, there will always be something new to learn from another tech person, especially if this is an expert on the subject being questioned.

I'm surprised about the different NativeEthernet behaviour...

As part of stack handling on the W5500 is made inside the chip, I would expect some different behavior, eventually, due a software and hardware from different authors interoperating.

I'd have to see a complete program to really judge what's going on.
The code I used for testing is the very badly formatted one in my first post. No more, no less. There is just another code that simulates a GPS telegram (that was written over the first one), sending a static Lat Lon position and moving forward in time to connect to a data logging program.
Both work the same way, including port specific behavior, exactly as you'd expect since the Ethernet part in both codes is exactly the same.

As for the deprecated behaviour, I did that on purpose (with `[[deprecated]]` attributes in the code), mostly to encourage developers to use the internal Teensy MAC address.
I will take your orientation for granted at this moment, as I don't have the knowledge to argue about it. I just changed the code to comply with the warnings because, while it works anyway, my OCD and the good coding practices dictates so.

I'm curious, for which functions are/were you seeing deprecated warnings?
Everything related to the undue use of MAC address. After using Ethernet.begin() correctly, things went smooth.


I will follow your advises and use the links to educate myself a bit better. What I'm trying to do is just an workbench substitute for an expensive piece of hardware, but beyond that is a great opportunity to learn something new (as if any excuse was needed for...)


Regards,
André W.
 
The code I used for testing is the very badly formatted one in my first post. No more, no less. There is just another code that simulates a GPS telegram (that was written over the first one), sending a static Lat Lon position and moving forward in time to connect to a data logging program.
Both work the same way, including port specific behavior, exactly as you'd expect since the Ethernet part in both codes is exactly the same.

I'm having some confusion because on one hand, your test program doesn't accept client connections nor reads from any connected clients (because nothing is connected), but on the other, you describe "all connected clients started to receive the streams." That's why I asked if you had a complete program. If we're talking about the code in your first post, then I'd expect nothing much to happen since the program doesn't really do anything with TCP connections. In this case, any NativeEthernet vs. W5500 vs. QNEthernet differences don't mean much.

Perhaps what you're seeing from the client side (I'm assuming the Teensy is your server) is the differences between how the stacks behave when a connection attempt is made but nothing actually accepts a connection.
 
I should have said, "nothing actually processes a connection" because the connections are technically accepted under the covers, it's just that your program does nothing with that information.
 
Ah, I understand the confusion! Sorry about that. Of course, you have no idea how I started this, the path I took, why or where I want to go.

You got it right, Teensy 4.1 is the server and some PCs are the clients, receiving the stream.

And yes basically my code does nothing except start the tcp engine and try to send a transmission under the covers... a stream to anyone connected. To my satisfaction, it worked fine - once a client (either one of my PCs running Teraterm or my friend's little socket viewer code) tries to open the designated ip/port on T4.1, a connection is established and it starts to receive anything transmitted by the Teensy code.

I am aware that there is no client identification, no enumeration or indexing that can track connections/disconnects, etc. But as a first approximation, it served my purpose which was to see how a super Arduino aka Teensy does the network connections and serial data routing for it. And how fast and easy could I write such code. Other than needing to send a character to start when using the NativeEthernet lib on ports other than 23, everything went very well I think.

Of course, this lack of proper socket handling has its cons: e.g. if a client connection is lost, that channel is blocked, requiring a program reset, but that was by design (or lack thereof) - the idea was just to see it working at a bare minimum.
Using the W5500 shield with an Arduino UNO or a Teensy with QNE, the behavior is the same: up to 8 clients can find the specific ip/port and stay there connected, all receiving the same data at the same time (my original goal).
I understand that "connected" is a bit of stretch under this context, so please indulge my ignorance to find a better term.

It's up to me now to see if I can fully implement it, keeping up with clients, dealing with crashes and timeouts, doing the typical "housekeeping" (and "watchdog") service an application requires to stay alive and well, even unattended.

My goal is to hook up some serial ports (much to my delight, the T4.1 has 8 of them) and route the messages over the network.
The next step, then, will be to copy each received serial data to a specific ip port, so that the PCs only receive the stream they want, selecting the corresponding port.


I hope this clarifies my (bad) intentions and why I said it "works well" even in the midst of such a messy code...



Regards,
André W.
 
I’ll add this note for future readers: the reason it’s only able to accept 8 connections is because the connections are never closed, and because the library is configured by default for up to 8 connections. If there was no TCP traffic (eg. if the cable was unplugged), then those connections would eventually time out, freeing up some space and room for more connections.
 
Just trying to understand better and be clearer on my part:

Unless modified somewhere in the libraries, 8 is the maximum number of connections sustained at any given time, right? because in my application, when a terminal or device connects, it must stay there receiving telegrams forever, until I decide to turn it off.
Under this situation, all I have at this moment are 8 connections with W5500 or QNE and 7 with NativeEthernet.

If or when I eventually disconnect one of these clients, after a timeout, that connection will be free again for another (or the same) device to connect and receive my stream.

Does all this make sense?

AW.
 
I think I have more information than I can comfortably handle for now, having enough to keep me busy for awhile.

All the things I expected to work are working as expected (argh), so I'll consider this thread closed if you'd like.
Anything else related to multiple clients, instantiating sockets and the like will probably be better handled in a different thread.

And with all the information and tips you've given me, I hope I don't have to create a new thread and borrow your time again soon.


Thanks so much again, Shawn!
 
Back
Top