w550 + Teensy LC Max speed

Status
Not open for further replies.

Rotario

Member
Hi,
I've got a Teensy LC connected to a wiznet W550 using the Ethernet library.

I've edited the library slightly so when I call client.read everything happens as it should, except I've commented out the actual data read.
This is because I'm trying to get the max speed the network can provide.

I'm getting some interesting results and I was wondering if anyone could help please?

When I host a http server on my laptop, and download a 10MB test file from the local network, I get the following results:

4 sockets (small buffer) 4181kbps
1 socket (biggest buffer) 11766kbps

Which obviously shows the buffer size is affecting the max speeds. From this I would assume that I could download a file from the internet, and my internet connection speed of max 1000kbps (i know it's cheap) would be the bottleneck, not the W550.
However, when I download a file from a number of servers hosting the same 10MB test file I get the following results NOTE my laptop maxes out as expected at 1000kbps from these servers:

4 sockets 91kbps
1 max socket 500kbps

This shows the W550 is the bottleneck, and the connection speed is limited by the buffer size and Teensy speed.

Isn't this weird? If the W550 can download 10mbps on the local network, why is it bottlenecking at 500kbps when downloading from the internet? How are these situations changing its max download?

I'd appreciate it if anyone could shed some light on the situation.

Code:
/*
  Web client

 This sketch connects to a website (http://www.google.com)
 using an Arduino Wiznet Ethernet shield.

 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13

 created 18 Dec 2009
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe, based on work by Adrian McEwen

 */

#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
//https://www.futurehosting.com/network-test-files/
//IPAddress server(87,76,31,34);  // numeric IP for futurehosting
//IPAddress server(192,168,1,248);
//char server[] = "speedtest.tele2.net";    // name address for Google (using DNS)
//char server[] = "london.futurehosting.com";
//char server[] = "www.google.com";
char server[] = "www.ovh.net";

// Set the static IP address to use if the DHCP fails to assign
IPAddress ip(192, 168, 0, 177);
IPAddress myDns(192, 168, 0, 1);

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;

// Variables to measure the speed
unsigned long beginMicros, endMicros;
unsigned long byteCount = 0;
bool printWebData = true;  // set to false for better speed measurement

void setup() {
  // You can use Ethernet.init(pin) to configure the CS pin
  //Ethernet.init(10);  // Most Arduino shields
  //Ethernet.init(5);   // MKR ETH shield
  //Ethernet.init(0);   // Teensy 2.0
  //Ethernet.init(20);  // Teensy++ 2.0
  //Ethernet.init(15);  // ESP8266 with Adafruit Featherwing Ethernet
  //Ethernet.init(33);  // ESP32 with Adafruit Featherwing Ethernet

  // 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 the Ethernet connection:
  Serial.println("Initialize Ethernet with DHCP:");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
      while (true) {
        delay(1); // do nothing, no point running without Ethernet hardware
      }
    }
    if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip, myDns);
  } else {
    Serial.print("  DHCP assigned IP ");
    Serial.println(Ethernet.localIP());
  }

  // give the Ethernet shield a second to initialize:
  delay(1000);
  Serial.print("connecting to ");
  Serial.print(server);
  Serial.println("...");

  // if you get a connection, report back via serial:
  if (client.connect(server, 80)) {
    Serial.print("connected to ");
    Serial.println(client.remoteIP());
    // Make a HTTP request:
    client.println("GET /files/100Mb.dat");
    //client.println("Host: speedtest.tele2.net");
    //client.println("Host: 192.168.1.248");
    client.println("Host: www.ovh.com");
    client.println("Connection: close");
    client.println();
  } else {
    // if you didn't get a connection to the server:
    Serial.println("connection failed");
  }
  beginMicros = micros();
}

void loop() {

  // if there are incoming bytes available
  // from the server, read them and print them:
  if(client.available()) {
    uint16_t byteLen = client.readNoRead();
    //Serial.println(byteLen);
    byteCount = byteCount + byteLen;
    //Serial.write(client.read());
    //cpuLoadSleep();
  }

  

  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    endMicros = micros();
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    Serial.print("Received ");
    Serial.print(byteCount);
    Serial.print(" bytes in ");
    float seconds = (float)(endMicros - beginMicros) / 1000000.0;
    Serial.print(seconds, 4);
    float rate = (float)byteCount / seconds / 1000.0;
    Serial.print(", rate = ");
    Serial.print(rate);
    Serial.print(" kbytes/second");
    Serial.println();

    // do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
}

ethernetclient.cpp implements new function

Code:
int EthernetClient::readNoRead()
{
	return Ethernet.socketRecvNoRead(sockindex);
}

and socket.cpp implements recvnoread

Code:
int EthernetClass::socketRecvNoRead(uint8_t s)
{
	uint16_t len = 65535;
	// Check how much data is available
	int ret = state[s].RX_RSR;
	SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
	if (ret < len) {
		uint16_t rsr = getSnRX_RSR(s);
		ret = rsr - state[s].RX_inc;
		state[s].RX_RSR = ret;
		//Serial.printf("Sock_RECV, RX_RSR=%d, RX_inc=%d\n", ret, state[s].RX_inc);
	}
	if (ret == 0) {
		// No data available.
		uint8_t status = W5100.readSnSR(s);
		if ( status == SnSR::LISTEN || status == SnSR::CLOSED ||
		  status == SnSR::CLOSE_WAIT ) {
			// The remote end has closed its side of the connection,
			// so this is the eof state
			ret = 0;
		} else {
			// The connection is still up, but there's no data waiting to be read
			ret = -1;
		}
	} else {
		if (ret > len) ret = len; // more data available than buffer length
		uint16_t ptr = state[s].RX_RD;
		//if (buf) read_data(s, ptr, buf, ret);
		ptr += ret;
		state[s].RX_RD = ptr;
		state[s].RX_RSR -= ret;
		uint16_t inc = state[s].RX_inc + ret;
		if (inc >= 250 || state[s].RX_RSR == 0) {
			state[s].RX_inc = 0;
			W5100.writeSnRX_RD(s, ptr);
			W5100.execCmdSn(s, Sock_RECV);
			//Serial.printf("Sock_RECV cmd, RX_RD=%d, RX_RSR=%d\n",
			//  state[s].RX_RD, state[s].RX_RSR);
		} else {
			state[s].RX_inc = inc;
		}
	}
	SPI.endTransaction();
	//Serial.printf("socketRecv, ret=%d\n", ret);
	return ret;
}

To mess with the buffer sizes, you need to edit Ethernet.h

and uncomment

Code:
#define ETHERNET_LARGE_BUFFER

and change the max socket num define to 1 or 4 as necessary
 
you might want to use wireshark to watch TCP traffic from your W5500 for different W5500 socket buffer size, and read about TCP delay-bandwidth product.

throughput = TCP buffersize/RTT

so for your non-local tests, throughput will be affected by round-trip time to server. A long RTT means you'll need really big buffer.

for 2KB buffer size and 80 ms RTT, max throughput would be about 205 kbps

congestion and packet loss over the Internet will further degrade throughput, and you are guaranteed not to go faster than the max speed of SPI on your LC. For small data transfers, TCP slow-start will limit throughput.
 
Last edited:
Thanks for the info! That's all exactly what I needed to know

If I can't change the round trip or the receive buffer. I guess there's nothing I can do to bottleneck the network over the controller. Except by maybe faking the RWIN? and heavily messing with the W550 to send the number of bytes received to Teensy.

Cheers for the help
 
You can edit the ethernet library to use larger buffers. W5500 has 16K for each direction, and by default supports up to 8 simultaneous sockets. That's why the default buffer size is 2K. Larger buffers mean fewer sockets, which usually isn't an issue if you're only using 2 of them (for client and DHCP).
 
Cheers for the info Paul,
Yeah I've set the max socket number to 1 to use max buffer size, achieving 500kbps.

I'd like to hit 10mbps ideally. Maybe I need an ethernet controller with a bigger buffer. Microchip do one with 24Kb buffer..
 
Reading about the Round trip time and max tcp buffer size. Could I get round this problem by using UDP? Then the buffer wouldn't be the bottleneck but the network would be
 
Using UDP would involve crafting your own protocol to manage the speed. You won't escape the fundamental problem of window size buffering. Odds are slim you'll reinvent a wheel anywhere near as good as TCP.

I've only worked with Microchip's parts briefly. The one I tried didn't do TCP internally, so that's left to library code on the microcontroller. Again, the window size issue is a fundamental issue, and Teensy LC has very little memory. If you go down that path, you'll probably need a board with more memory.
 
Reading about the Round trip time and max tcp buffer size. Could I get round this problem by using UDP? Then the buffer wouldn't be the bottleneck but the network would be

UDP can be effective on a local ethernet. On the Internet, blasting UDP packets is "socially unacceptable" -- your LC/host may be considered mounting a denial-of-service attack! Also UDP is unreliable -- packets can be lost, re-ordered, duplicated, damaged. TCP provides a reliable data stream across the Internet. As Paul notes, creating a rate-controlled reliable UDP stream of packets is a wheel you probably don't want to re-invent.
 
Using UDP would involve crafting your own protocol to manage the speed. You won't escape the fundamental problem of window size buffering. Odds are slim you'll reinvent a wheel anywhere near as good as TCP.

I've only worked with Microchip's parts briefly. The one I tried didn't do TCP internally, so that's left to library code on the microcontroller. Again, the window size issue is a fundamental issue, and Teensy LC has very little memory. If you go down that path, you'll probably need a board with more memory.

Hi Paul,
The max buffer size gets me about 430kBps, which is also close to the theoretical limit

16000(kB buf) * 35(ms round trip) ~= 430

So either I need to reduce round trip (not possible if I want to make this portable physically) or increase buffer size. 16kb is max buffer size for w5500 right?
 
And I guess the receive window parameter sent during the TCP handshake can't be fudged at all to artificially increase the bandwidth limit?
 
UDP can be effective on a local ethernet. On the Internet, blasting UDP packets is "socially unacceptable" -- your LC/host may be considered mounting a denial-of-service attack! Also UDP is unreliable -- packets can be lost, re-ordered, duplicated, damaged. TCP provides a reliable data stream across the Internet. As Paul notes, creating a rate-controlled reliable UDP stream of packets is a wheel you probably don't want to re-invent.

Thanks for the info - Really helpful!

If I control the server and the client (LC) then is it still considered suspect? Obviously the ISP can see the massive amount of packets I'm sending between them.

Would another way be to fudge the Receive Window (RWIN) greater than the actual buffer size available? The LC can read off the data much faster than the packets can be ACK'd, so a larger RWIN would increase the theoretical max bandwidth. I could do an ICMP ping, get the round trip time (RTT) then calculate a reasonable RWIN value that the data can be read off, and the bottleneck is no longer the TCP receive buffer size.

Would this work? Or am I misunderstanding the TCP process. I don't know if the RWIN can be manually changed on the W5500. Maybe I could at least test it by intercepting the packet and changing the value in something like wireshark?
 
Status
Not open for further replies.
Back
Top