Teensy 4.1 QNEthernet low throughput

anatoledp

Active member
Im trying to interface a teensy 4.1 to a luckfox pico max using ethernet. The teensy is utlilizing the QNEthernet library and just some basic socket code on the side of the luckfox. The purpose of the setup is to use the teensy as a display driver and provide an simplified interface for the pico to draw to . . . but i also need to be able to push full framebuffers of data to the teensy as well in which the teensy should recieve the data and immediately draw the full framebuffer to the display (basically act like a passthrough to driectly write to the display). Since the teensy has 100Mbits ethernet capability i thought this should be no issue and it can handle the data load allowing the pico to send data fast enough to achieve a stable 30 frames a second. however just sending a single full buffer takes around a second even though its only 150kb of data (320 x 240 resolution rgb565). This seems like a very very very low throughput for the capabilities of teensy ethernet and would love some input on how to speed up the transfers. When writing to a null buffer where the teensy just discards the data it will go through it quickly but not when storing the data to send to the display. Please let me know if anything on top of this needs to be added in order to better help. if recieving even 150kb of data takes so long then it will also struggle with the hmi interface speeds as well as i expect that should also be transfering and parsing through 10's of kb of data over the wire and that also needs to be fast enough to be able to run at 30fps.

Teensy 4.1 code . . .
C++:
#include <Arduino.h>
#include <QNEthernet.h>

#include "./common.h"

namespace qn = qindesign::network;

IPAddress staticIP{192, 168, 1, 101};
IPAddress subnetMask{255, 255, 255, 0};
IPAddress gateway{0, 0, 0, 0};

qn::EthernetServer server{80};
qn::EthernetClient rvCore;
bool rvCoreConnected = false;

int fbLoc = 0;
int fbLocMax = DISPLAY_WIDTH * DISPLAY_HEIGHT * 2;
char* cfb = reinterpret_cast<char*>(fb);

void link_initialize()
{
    // get mac address
    //uint8_t mac[6];
    //qn::Ethernet.macAddress(mac);
    //printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    
    // Listen for link changes
    qn::Ethernet.onLinkState([](bool state) {
        // state ? ON : OFF
    });

    // start ethernet with static settings
    if (!qn::Ethernet.begin(staticIP, subnetMask, gateway))
    {
        // failed to start ethernet
    }

    // start server
    server.begin();
    if (!server)
    {
        // failed to start server
    }

    //
}

void link_update()
{
    if (rvCoreConnected)
    {
        // rvCore connected
        if (rvCore.available() > 0)
        {
            rvCore.readBytes(cfb, DISPLAY_HEIGHT * DISPLAY_WIDTH * 2);
            fbLoc = fbLocMax;
        }

        if (!rvCore.connected())
        {
            rvCore.stop();
            rvCoreConnected = false;
        }
    }
    else
    {
        // rvCore not connected
        qn::EthernetClient client = server.accept();
        if (client)
        {
            //IPAddress ip = client.remoteIP();
            //Serial.printf("rvCore connected: %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
            rvCore = client;
            rvCore.setNoDelay(true);
            rvCoreConnected = true;
        }
    }

    //qn::Ethernet.loop();
}

Luckfox Pico Max code
C++:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cerrno>
#include <netinet/tcp.h>

#define INTERFACE "eth0"

static const int fbSize = 320 * 240 * 2;

int link_send_fb(uint16_t* fb)
{
    int sock = 0;
    int valread;
    struct sockaddr_in serv_addr;
    int optval = 1;
    socklen_t optlen = sizeof(optval);

    char buffer[1024] = {0};

    std::cout << "Creating socket . . ." << std::endl;
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "Socket creation error!" << std::endl;
        return -1;
    }

    // Set TCP_NODELAY flag
    if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0) {
        std::cerr << "Error setting TCP_NODELAY option" << std::endl;
        close(sock);
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(80);
    std::cout << "Setting connection parameters . . ." << std::endl;
    if (inet_pton(AF_INET, "192.168.1.101", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported!" << std::endl;
        return -1;
    }

    std::cout << "Connecting to server . . ." << std::endl;
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection Failed!" << std::endl;
        std::cerr << errno << std::endl;
        return -1;
    }
    
    // Send data
    int bytes_sent = send(sock, fb, fbSize, 0);
    if (bytes_sent < 0) {
        perror("Send failed");
        close(sock);
        return -1;
    } else if (bytes_sent != fbSize) {
        std::cerr << "Not all bytes sent. Sent: " << bytes_sent << ", Expected: " << fbSize << std::endl;
        close(sock);
    } else {
        std::cout << "Message sent successfully" << std::endl;
        close(sock);
    }
    
    return 0;
}

1000008740.jpg
 
What happens if you use read(buf, size) instead of readBytes() — and also managing when to repeat the read() call if it reads less than expected (or timeout)?

Also, what happens when you check the value returned from readBytes()? That would indicate whether there was a timeout.

In my experience, problems are, more likely than not, due to the use of the library or the connection itself rather than the library. When running the IPerfServer example, I often see at least 90Mbps when the Teensy is connected directly to a computer via an Ethernet cable.
 
Last edited:
What happens if you use read(buf, size) instead of readBytes() — and also managing when to repeat the read() call if it reads less than expected (or timeout)?

Also, what happens when you check the value returned from readBytes()? That would indicate whether there was a timeout.

In my experience, problems are, more likely than not, due to the use of the library or the connection itself rather than the library. When running the IPerfServer example, I often see at least 90Mbps when the Teensy is connected directly to a computer via an Ethernet cable.
hey thanks for that tidbit . . . updated the buffer part of the code to do what u said . . .
C++:
if (rvCore.available() > 0)
        {
            // data available
            while (true)
            {
                int avail = rvCore.available();
                if (avail <= 0) break;

                int leftToFillBuffer = fbLocMax - fbLoc;
                if (leftToFillBuffer <= 0)
                {
                    fbLoc = fbLocMax;
                    break;
                }

                uint8_t* cfbCurrent = cfb + fbLoc;
                int read = rvCore.read(cfbCurrent, leftToFillBuffer);
                fbLoc += read;
            }
        }

aaaand . . . yeah . . . now it seems to push the data as fast as it is received and after a bit of timing tests is well within the margin needed to achieve the fps desired . . . idk why i didnt think of this, i guess i assumed readBytes does what read does instead of iterating over each byte like it actually does instead of just reading the underlying code in the first place . . . thanks again
 
I’m glad you got it working! :)

I don’t love much of the Arduino-style coding (eg. badly-initialized global objects, and global variables in general) and Arduino-provided functionality. I personally do my best to avoid it if I can, and only use what I really have to.

With the QNEthernet library, I’m doing my best to both maintain Arduino-style compatibility, and at the same time offer much more, such as a non-blocking API and a bunch of other things. It’s challenging keeping Arduino-style compatibility at the same time.

I wish much of the Stream class would let me override its behaviour.
 
Back
Top