Portenta H7: UART as a fast interface to a host

I was "surprised" why my UART between Portenta H7 and PC (using TeraTerm) was slow: I saw the characters tickling in, independent of baudrate (found the reason - see blow).

Also, people ask: "I have to bring data, e.g. with few Mbps (let's say 5 Mbps), to a PC as host. Is UART fast enough to do?"
YES - IT IS!

Portenta H7 UART is a VCP/CDC device!
For the UART via USB - there is NOT a real UART device involved. It is a native USB connection. Often called "USB VCP" - it is a USB CDC device: the serial characters are sent via an USB protocol (CDC), independent of the UART baudrate (but the HW protocol setting matters).
The only limiting factor is: such an USB CDC device runs in Fast Speed Mode (12 Mbps). So, potentially, you can have up to 6..8 Mbps throughput for sending data (but in bursts! see below).

USB UART is baudrate independent
As mentioned: the baudrate, usually used for UART config, does not matter (the value is not used).
If you setup UART in Portenta sketch, you can do also this one:

Serial.begin(5000000);   //set 5 Mbps as baudrate or even any higher speed

This value is not used. It can be any baudrate value. The only speed limitating factor is the USB CDC itself (e.g. 12 Mbps USB Fast Speed might give you 6..8 Mbps as effective "baudrate").

Even on the PC side: if you open UART terminal, e.g. TeraTerm: the baudrate value does not matter. Just connect your USB UART and any speed config selected is fine and works. (the same here: the USB speed matters, not UART baudrate, there is no real UART involved).

Why? Why sending in chunks improve the speed?
My mistake was this: I have implemented my own UART functions (I redirect prints to different devices, e.g. network):

void UART_Send(const char* str, int chrs, EResultOut out)
{
    if (out == UART_OUT)
    {
        while (chrs--)
            Serial.write(*str++);

It ends up in splitting a complete string into sending as single characters. This is VERY BAD and results in the dramatic performance degradation!
Keep sending in chunks, at least 32 bytes. This improves a lot.

USB CDC works this way:
it sends UART characters in USB packets. Such a packet might be sent every 1 ms (actually it is polled from the PC as USB host every 1 ms, MCU as device cannot actively sent via USB).
Such a USB CDC packet can contain up to 64 bytes, for one single transfer (chunk).
Now, when I send single characters - every character goes into a separate, new packet! And every packet is transmitted every 1 ms (example here). So, my characters come in on PC every 1 ms. This is an equivalent baudrate of 8000 (8 KHz) only.
Never mind what the baudrate set is - a character comes in just every 1 ms!

So, use this packet feature of USB CDC:
Send in chunks: collect up to 64 bytes and send it also in bursts. It speeds it up dramatically.

I see in "Serial.h" this definition:

const size_t WRITE_BUFF_SZ = 32;

So, I assume, Arduino LIB for code of Serial sends in chunks of 32 bytes (not 64 as max. possible). Anyway,
use this:

Serial.write(myStr, strLength);

instead of:

void SendMyString(const char *myStr) {
    l = strlen(myStr);
    while (l--)
       Serial.write(*myStr++);

I think you get the point: send in chunks, as packets, NOT as single characters.
You can manage (and improve) the speed by collecting several characters before you send via USB CDC. It sends anyway USB packets. And packets just filled with one byte - makes it really slow, esp. if these packets are sent only in intervals of 1 ms.

So, think about that the correct use of Serial, which is a USB CDC (VCP) device - has a pretty remarkable speed and throughput when used properly.

If you bear in mind that UART can be used in BINARY mode, not ASCII characters - all as 8bit values - the difference between TEXT (ASCII) mode and BINARY mode is a speed factor of *2!
So, UART might be fast enough for many purposes.

And the beauty: baudrate config value is not used on both sides (speed as fast as USB CDC can handle, USB does not have a baudrate). And no special driver needed. Works on all systems, Windows, MacOS, Linux ... where USB UART driver is part of the system (no need to install anything).

2 Likes

BTW: if 6 Mbps via USB UART is still too slow - other speed improvement ideas:

  • do a pre-processing of data on MCU: let's assume you want to do anyway a decimation, to build an average, e.g. over ADC samples ... do this processing already on MCU and send just the result. Bear in mind: for CM7 and Portenta H7 you can use ARM DSP functions, which can even do a Fourier Transformation (FFT). Send just the final result, not raw data.

  • Or: Run Length Encoding (RLE): when you know (or assume), the values to send, e.g. from an ADC conversion, are often the same, they repeat, they do not change value so fast (so that often you have a sequence of the same value) - you can 'pack' the results in a special format: a length field for the number of repetition of the same value and the value itself (a little bit like to ZIP the data before you send, just to unzip on receiver side).

The best option (for my requirements): use wired ETH connection.
This should be the fastest interface on Portenta H7 for data to/from a host PC.
Assume it is 100 Mbps ETH, potentially you can achieve a data transfer rate of 10..30 Mbps (speed limitation comes from MCU core clock 480 MHz and a heavy IP stack).

But when you think: "WiFi is also network and 2.4/5 GHz - it should be fast" - NO!
Check the interface from MCU to the WiFi chip: it is a serial interface, like I2C, best case a 4bit SDIO interface. Your throughput via WiFi might be potentially lower as ETH cable or even as the USB UART cable.
I would assume, using the Portenta H7 WiFi connection gives you max. 1 Mbps, even slower as USB UART.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.