Fast and reliable data exchange between Raspberry pi and teensy 4.1?

Status
Not open for further replies.

kwang12

New member
Hello all.

TLDR:
I need help in developing fast and reliable data exchange method between Teensy 4.1 and RPi4.
At the moment, I can use UART reliably at 2 million baud. But the speed is not fast enough for my application.
Ethernet communication can only be used when wifi is diabled which makes the option very unattractive at the moment.
I need help in implementing ethernet connection.



The application I'm developing requires frequent data exchanges between Teensy 4.1 (T4.1) and Raspberry Pi 4 (RPi4).
The communication is two way.
T4.1 sends data packages to RPi4 at 200 Hz. Each data package size is around 200 bytes.
After receiving a data package from T4.1, RPi4 sends a data package back to T4.1 as the response. The response's size is also around 200 Bytes.

I have been trying to implement this for couple days now.
So far, I have experimented with Serial and ethernet connection.

For Serial, the setup is as the following:

T4.1 pin 0 <-----> GPIO 14 RPi4
pin 1 <-----> GPIO 15
GND <-----> GND
2 pair twisted wires are used for the connection.

I configured the RPi4 to use UART0 and connected GPIO 14 and 15 to T4.1's hardware serial pins respectively.
Before that, miniUART was used instead which bottle-necked the baud rate to 1million. Any baud rate higher than this is too lossy to be useful.
With UART0, I'm able to reliably exchange data between RPi4 and T4.1 at 2million without loss.
The communication between the two needs at least 2 milliseconds to complete (2 messages * 2e6 baud rate / (200 bytes * (8+2 bit)/ byte))
This means that I only have 3 millisecond to do all the processing on RPi4 if I want to meet the 200 Hz communication frequency.


So I tried to improve it with Ethernet connection.
The setup is as the following:

T4.1 <---- cat6 ethernet cable ----> RPi4 <---- wifi ----> router <----> internet

I downloaded and installed Teensyduino 1.5.3.
The UDPSendReceiveString example sketch is uploaded to T4.1.
On RPi4, I wrote a simple testing code in C++ to test the ethernet conneciton.
Code:
#include <iostream>
#include <libsocket/exception.hpp>
#include <libsocket/inetserverdgram.hpp>
#include <string.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>

// can send when wifi is OFF but can't receive?
// can receive when wifi is ON but can't send?

std::string toip = "192.168.1.177";
std::string toport = "8888";

std::string ip = "169.254.115.171";
std::string port = "5001";
libsocket::inet_dgram_server server(ip, port, LIBSOCKET_IPv4);

void *readUDP_thread(void *arg)
{
    pid_t thread_id = syscall(__NR_gettid);
    printf("UDP receiving thread --- id: %d\n", thread_id);
    std::string buf;
    std::string from;
    std::string fromport;
    buf.resize(1024);
    try
    {
        while (true)
        {
            server.rcvfrom(buf, from, fromport);
            std::cout << buf << std::endl;
        }
    }
    catch (const libsocket::socket_exception &exc)
    {
        std::cerr << exc.mesg << '\n';
    }
    pthread_exit(NULL);
}

void *sendUDP_thread(void *arg)
{
    pid_t thread_id = syscall(__NR_gettid);
    printf("UDP sending thread --- id: %d\n", thread_id);
    // create periodic timer
    int fd = timerfd_create(CLOCK_MONOTONIC, 0);
    if (fd == -1)
    {
        printf("Periodic timer creation failed\n");
        exit(EXIT_FAILURE);
    }
    unsigned int micros_period = *(unsigned int *)arg;
    unsigned int sec = micros_period / 1000000;
    unsigned int nsec = (micros_period - (sec*1000000)) * 1000;
    itimerspec itimer;
    itimer.it_interval.tv_sec = sec;
    itimer.it_interval.tv_nsec = nsec;
    itimer.it_value.tv_sec = sec;
    itimer.it_value.tv_nsec = nsec;
    timerfd_settime(fd, 0, &itimer, NULL); 

    // send message to teensy periodically
    std::string buf = "hello from c++...";
    try
    {
        while (true)
        {
            printf("sending requests\n");
            server.sndto(buf, toip, toport);
            
            uint64_t tmp;
            int ret = read(fd, &tmp, sizeof(tmp));
            if (ret == -1)
            {
                printf("Periodic timer read error\n");
                pthread_exit(NULL);
            }
        }
    }
    catch (const libsocket::socket_exception &exc)
    {
        std::cerr << exc.mesg << '\n';
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t tid[2];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    pthread_create(&tid[0], &attr, readUDP_thread, NULL);
    unsigned int micros_period = 1e6;   // send message every 1e6 micros (1 sec)
    pthread_create(&tid[1], &attr, sendUDP_thread, &micros_period);

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);

    return 0;
}
In addition to the standard libraries, the code above uses libsocket++ which can be found here: https://github.com/dermesser/libsocket.
The code can be compiled by the following command: g++ file_name.cpp -o executable_name -lsocket++ -lpthread
Note that file_name and executable_name should be selected accordingly.

I noticed that I can only send data but not receive when wifi is turned off on RPi4.
In addition to that, I can only receive data but not send when wifi is turned on on RPi4.

I suspected it was the library's problem so I tried to create a python program:
Code:
import time
import socket
import threading

class pyUDP:
    def __init__(self, ip, port, to_ip, to_port):
        self.ip = ip
        self.port = port
        self.to_ip = to_ip
        self.to_port = to_port
        self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
        self.server.bind((self.ip, self.port))

    def send_udp(self):
        msg = bytearray("hello from python", 'utf-8')
        # send msg every 1 sec
        while(True):
            self.server.sendto(msg, (self.to_ip, self.to_port))
            time.sleep(1)
    
    def read_udp(self):
        while(True):
            msg, _ = self.server.recvfrom(1024)
            print("received: ",msg)

if __name__ == "__main__":
    ip = "169.254.115.171"
    port = 5001
    to_ip = "192.168.1.177"
    to_port = 8888

    py_udp = pyUDP(ip, port, to_ip, to_port)
    threads = list()
    threads.append(threading.Thread(target = py_udp.send_udp))
    threads.append(threading.Thread(target = py_udp.read_udp))

    threads[0].start()
    threads[1].start()

    threads[0].join()
    threads[1].join()

This time, both sending and receiving work simultaneously but it requires WiFi to be turned off!


I tried to ping T4.1 with and without wifi:

wifi on:
PING 192.168.1.177 (192.168.1.177) 56(84) bytes of data.
^C
--- 192.168.1.177 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 57ms

wifi off:
PING 192.168.1.177 (192.168.1.177) 56(84) bytes of data.
64 bytes from 192.168.1.177: icmp_seq=1 ttl=64 time=0.447 ms
64 bytes from 192.168.1.177: icmp_seq=2 ttl=64 time=0.129 ms
64 bytes from 192.168.1.177: icmp_seq=3 ttl=64 time=0.265 ms
64 bytes from 192.168.1.177: icmp_seq=4 ttl=64 time=0.287 ms
64 bytes from 192.168.1.177: icmp_seq=5 ttl=64 time=0.302 ms
^C
--- 192.168.1.177 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 139ms
rtt min/avg/max/mdev = 0.129/0.286/0.447/0.101 ms

Obviousily something is wrong with the network settings.


I'm completely noob when it comes to computer networking. So I am not sure what I can do to fix this.
This post seems to suggest that a network switch is needed in between T4.1 and RPi to enable them to talk to one another.
https://forum.pjrc.com/threads/60857-T4-1-Ethernet-Library


Has anybody tried to use ethernet to exchange data between teensy 4.1 and a pc?
What are your experiences?

Any help is greatly appreciated!!!
 
Just a thought, it ought to be possible to setup DMA driven SPI between RPi and Teensy...
At 16MHz clocking thats maybe as little as 100µs for 200 bytes. Might be quite complex to
set up though.
 
Just a thought, it ought to be possible to setup DMA driven SPI between RPi and Teensy...
At 16MHz clocking thats maybe as little as 100µs for 200 bytes. Might be quite complex to
set up though.

I think you are suggesting using Teensy4.1 as an SPI slave?
I did some search on the forum a couple of weeks ago. I saw many people asking for this feature. But I didn't find any working examples so far.
 
I managed to make this sort of work by setting up a DHCP server on RPi.
Libsocket++ can send normally but seems to be hoarding all the incoming messages and releasing them slowly.
I'm too tired to find a new c library for my application. So I wrote a short python script to relay the messages between teensy and the my application in c program.

The setup looks like this right now:

T4.1 <--Ethernet Cable--> RPi4 -- Python message relay script -- C application script

The C application script can exchange data with T4.1 now.
The downside is that RPi4 can't access the Internet when the cable is plugged in. But the wifi is useable (so are the VNC and etc)

Will do more testing to check the latency and bandwidth.
 
What's the highest baud rate you can use reliably with serialUSB?
I haven't tried it. But I assume it shouldn't be too different from the hardware serial pins?

Teensy 4: Hardware wise 480Mbit/s, minus some overhead.
So, orders of magnitudes faster than hardware serial.
 
Serial USB is probably limited by the ttyUSB driver on the RPi, I have a vague recollection its only around 1 or 2Mbaud
 
On a PC :: Net sustained throughput is perhaps 64-128 Mbit/sec (with USB err checking) unless the host (PC) is really good about managing buffers - and the receiver good at storing them. ( based on PJRC 'lines per second' printed test - that can do 250K to 500K 32 bytes lines/sec - it can peak at 800K+ and linux seems to sustain more )

Sounds like the rPi may be less capable ...
 
I am thinking from a few years ago, and it might have been a Python limit or MacOS rather than linux driver limit...
 
It has been awhile since I have done much with RPI, and more of my experiences were with RPI3... But at least back then, I knew when I was playing with ROS, that Python especially associated Serial sort of sucked. An example I remember was there was a simple joystick translation filter, that took Serial joystick input and mapped for example PS4 buttons/axis to maybe PS3 joystick data. It was a very simple program and it took almost all of one core of the RPI... Switched the code to use c or c++ and it it did not show up in the top list...

One thing I wondered about at different times, if on Teensy 4, if one could make a version of RAWHID that had packets of 512 instead of 64?
But I assuming the RPI can connect at high speed, the normal serial sends/receives 512 byte packets, so should be able to send your messages both ways in one packet. Then there is the question of latency. Probably on the Teensy side you should do things like: Serial.flush(); at the end of your packets to make sure it goes through as quick as possible.

On RPI side, this can be more tricky. If I remember correctly with termios, that if I were talking to an FTDI device (ttyUSBx) then calling the drain() method really helped on removing some latency issues,
but if I were talking through other drivers like to ttyACMx drain() was a real detriment. That is it was almost like the devices did not have any IOCTL support or the like, so they instead just put in a long delay()... So on those devices my code would not call drain()...

But again it has been awhile.
 
Status
Not open for further replies.
Back
Top