Dual Teensy 3.2 High Speed Serial Test Results

I have a project that uses I2C to collect data from four Nanos into one Mega. The buss speed has been increased to it's max, 888,888(TWBR=1) but is still too slow for my application, 4D sonar. The four Nanos and one Mega have been replaced with three Teensy 3.2 boards. Two of the Teensies will collect the same data as four of the Nanos using dual synced ADCs. The other Teensy will have the same task as the Mega, collection and transmission to shore. Instead of using I2C, I wanted to try hardware serial and find it's maximum speed, 4.5Mbps. Here are the details.

Connect two Teensy 3.2 boards with the following chart.

Receiver Gnd <--> Sender Gnd
Receiver TX1 --> 1K resistor --> Sender RX1 // Not really needed for this test but more later
Receiver RX1 <-- 1K resistor <-- Sender TX1
Each Teensy will be powered by their own USB cable

Open Arduino IDE using desktop shortcut, 1.65-rc5
Connect sender board USB to PC
Select the comport displayed in tools
Load sender sketch shown below
Upload
Open serial monitor
Note that baud value is about twice that expected
This will be discussed below

Open another Arduino IDE using desktop shortcut, 1.65-rc5
Connect receiver board USB to PC
Select the comport displayed in tools, not the previous
Load receiver sketch shown below
Upload
Open serial monitor
Note that baud value is about that expected
This will be discussed below

Sender sketch ****

// Teensy 3.2 hardware serial port speed test sender

#define SERIAL_PORT     Serial1     // Use ports 1, 2, or 3
#define SERIAL_RATE     4500000     // Max speed, 1/16 of CPU clocck
#define SERIAL_DELAY    1250        // Takes this long for Serial.begin() to respond
#define ARRAY_SIZE      100         // Use a convenient number
#define LED_PIN         13          // Built-in LED
 
// Global variables
uint8_t  bytebuffer[ARRAY_SIZE];    // A byte data array
uint32_t startTime, stopTime;       // For CPU clock values

void setup()
{
    Serial.begin( 115200 );             // Monitor port
    startTime = millis();               // Present CPU clock
    while( !Serial && ( millis() - startTime ) < SERIAL_DELAY ) { yield(); } // Ready or timeout
    
    SERIAL_PORT.begin( SERIAL_RATE );   // Hardware test port
    startTime = micros();               // Present CPU clock
    while( !SERIAL_PORT && ( micros() - startTime ) < SERIAL_DELAY ) { yield(); } // Ready or timeout
    
    // Load an array with phony data for transmission speed test
    for( uint8_t i = 0; i < ARRAY_SIZE; i++ )
        bytebuffer[i] = i;
    
    pinMode( LED_PIN, OUTPUT );
    digitalWrite( LED_PIN, HIGH );      // Show that sketch made it this far
    Serial.println( "End setup()" );
}
 
void loop()
{
    uint8_t     written;                // Bytes written to hardware serial port
    uint32_t    elapsed;                // Write time
    
    startTime = micros();               // When write started
    written   = SERIAL_PORT.write( bytebuffer, ARRAY_SIZE ); // Write the buffer
    stopTime  = micros();               // When write finished ????
    elapsed   = stopTime - startTime;   // Write time ????
    
    // Show the results
    Serial.print( written );
    Serial.print( " bytes written in " );
    Serial.print( elapsed );
    Serial.print( " usec.  Baud value = " );
    Serial.print( ARRAY_SIZE * 10000 / elapsed );
    Serial.println( " Kbps" );
    delay( 1000 ); // Wait long enough to read monitor screen
}

Receiver sketch ****

// Teensy 3.2 hardware serial port speed test receiver

#define SERIAL_PORT     Serial1     // Use ports 1, 2, or 3
#define SERIAL_RATE     4500000     // Max speed, 1/16 of CPU clocck
#define SERIAL_DELAY    1250        // Takes this long for Serial.begin() to respond
#define ARRAY_SIZE      100         // Use a convenient number
#define LED_PIN         13          // Built-in LED

// Global variables
uint8_t  bytebuffer[ARRAY_SIZE];    // A byte data array
uint32_t startTime, stopTime;       // For CPU clock values

void setup()
{
    Serial.begin( 115200 );             // Monitor port
    startTime = millis();               // Present CPU clock
    while( !Serial && ( millis() - startTime ) < SERIAL_DELAY ) { yield(); } // Ready or timeout
    
    SERIAL_PORT.begin( SERIAL_RATE );   // Hardware test port
    startTime = micros();               // Present CPU clock
    while( !SERIAL_PORT && ( micros() - startTime ) < SERIAL_DELAY ) { yield(); } // Ready or timeout
    
    pinMode( 13, OUTPUT );
    digitalWrite( 13, HIGH );
    Serial.println( "End setup()" );
}

void loop()
{
    uint8_t     bbread;                     // Bytes read from hardware serial port
    uint32_t    elapsed;                    // Read time
    
    if( SERIAL_PORT.available() > 0 )
    {
        startTime = micros();               // Read time start
        bbread    = SERIAL_PORT.readBytes( bytebuffer, ARRAY_SIZE );
        stopTime  = micros();               // When read finished ????
        elapsed   = stopTime - startTime;   // Read time ????
    
        // Show the results
        Serial.print( bbread );
        Serial.print( " bytes read in " );
        Serial.print( elapsed );
        Serial.print( " usec.  Baud value = " );
        Serial.print( ARRAY_SIZE * 10000 / elapsed );
        Serial.println( " Kbps" );
    }
}

Here is the screenshot of both serial monitor windows running. The receiver is on the left and sender on the right. Notice on the right that the sender appears to be running at about twice the value expected while the receiver is about right. This supposed discrepancy occurs at all baud values. Apparently, the sender is running as a background task even though the yield() function is not evoked but was seen during the compile. The stoptime statement is reached before the TX buffer is empty. The receiver does not have this same problem so the computed value is about right. I also tested Serial2 and Serial3 and got the same sender results. I believe that Serial1 has a 128 byte dedicated buffer so my application will use that value for data packet size and not the 100 used in this test.

I hope this info is helpful for those wanting to use Teensy 3.2 hardware serial at it's maximum speed.

Good to hear. I've had a lot of problems with Teensy3.1, trying to use SPI and soft serial, for instance.

Do you have any idea what the max usable Serial baudrate is for a regular UNO/etc, in case you want to talk between T3.x and UNO?

What the heck is 4D sonar?

The rule for max hardware serial that I've seen is bit rate no greater than 1/16 of CPU clock. This seems to be true with the Teensy as it would not work above 4.5Mbps. For the Uno and any other 328P chipped board with a 16MHz clock, that will be 1Mbps. You can use the same sketch to test any Arduino but you may have to use the only TX/RX pins and it may interfere with the upload.

The sonar application uses four receiver sensors spaced evenly around a central transmit module, a 40KHz 50W ultrasonic cleaning tank unit. The receivers are paired as front/back, Teensy1, and right/left, Teensy2, and are used to detect the angle of the echo in Y and X respectively. The time difference and spacing is used to calculate angle. This data is painted on an open box 2D display with 3D appearance. The fourth dimension comes from echo strength mapped to a rainbow color, faint blue = weak and bright red = strong. Echo transparency is also mapped to signal strength. This allows you to see a hard object lying on a soft bottom, bright red on pale blue but at the same depth. The shore station software package is written in Lazarus, visual Pascal. Here is a screenshot of the present program. The front/back echo pair is used for the right/left data, at present, so all echoes appear to be on the diagonal.

Cool. So the 3D display is done on the PC. You're just measuring triangulation angles, and not trying to decipher the echo waveforms [to produce a small ship :slight_smile: ], I presume? What is the update rate for the display using the high-speed Teensy serial?

All the heavy work is on a laptop at the shore station using Lazarus. The boat, 4' dual pontoon barge, sends only the raw ADC data. The barge motion is controlled by a completely separate nRF24 channel running two bilge pumps as a water jet system. Steering is accomplished by varying the pump speeds. Joystick Y is paired speeds and joystick X is differential speed. If the pumps work in reverse then I'll add a backup feature to get out of deadend places.

The max digitizing rate for dual synced Teensy ADCs is 62.5Ksps. Will have to use full wave AM detection circuits on the ADC inputs and digitize the envelope shape. If I can get the ADCs to run faster then I might consider waveshape analysis.

The present refresh rate with four Nanos is about 1Hz due to the number of data relay situations in the chain. That's the reason for using Teensies so as to speed up my slow link, I2C at 888Kbps. My goal was to allow each Teensy and the controller to all share one ESP8266 channel directly to Lazarus. That part has not been successful yet and was hoped to be about 20Hz. The new Teensy based design might get me 5 - 10Hz. Any improvement will help the situation.

The boat in the screenshot was something I found in clipart and rotated/pasted at the sonar origin location. I originally had a rubber duck but it was facing the wrong direction. When rotated, it looked like it was diving.

I've not tried fast ADC sampling on the T3.1, but this thread indicates at least 400 Ksps.

Also, FWIW, I know the ADC sampling rates on the old 40-MHz PIC24 is 1000 Ksps, and it's comparable on the PIC32 chips, such as on the Chipkit uC32 and Max32 boards [basically Arduino compatible IDE].

I'll have to dig into that tomorrow. The best I could do with a single 10 bit read and array store was 135Ksps. It should be possible to go much higher. The PlainADC library can do 130Ksps on the 328P chip using direct hardware access. The same scheme on the Teensy should be near the 1M region. I'm stuck at the moment with the dual synced ADC library as my data is time sensitive with the angular calculations.

Thanks for the link.

The two sketches were recompiled using the 96MHz overclock setting. It is confirmed that the max rate is now 6Mbps. There was a very slight warming of the CPU chip.

Yes, the max uart speed supported by teensy 3.2 (the ARM MK20 chip actually) is 6megabaud. You will need hardware handshake enabled to accomplish this in a real world application. You cannot guarantee that you will be able to read data as fast as it comes if your program needs to do something else in the main loop. The serial driver is designed to toss out data if buffer is overrun.

I am running esp8266 at it's maximum supported baud rate of 4608000 baud with no problem. Hardware handshake is enabled.

I contributed the "true" hardware handshake serial1 code to teensy. This is guaranteed to work. Somehow, the "software" version of the code that supports RTS/CTS handshake was made the default. This version fails right out the door on my application testing. Sure, it works in a simple send/receive test, but the true test is in an actual application.

My implementation is located in

install location
/hardware/teensy/avr/cores/teensy3/serial1_doughboy.txt

simply rename that to serial1.c to use true hardware handshake.

Thanks for the link.

Fortunately, my sketch has nothing else to do during the data transfers. One side sends out the packet number to be returned and then waits for the data. The other side waits for the packet number then sends the data. Both are idol at the beginning of the transfer. I checked the integrity of the transfer in my test and the data was correct as long as I did not exceed the rate and size.

I see that argument all the time, from myself included. I wasted so much time tracking down why the data is getting corrupted, and ended up writing the library.

you mentioned you are using adc library, and plan to use esp8266. As you add more code to your program, sooner or later (inevitable), you will get corrupted data. You might as well start using hardware handshake now rather than later when you start seeing corrupted data.

Good advice, I'll start working on it now. Are there any examples on how to initiate the port, pins, and baud?

All you need to do is

    Serial1.begin(4608000);  //or 6000000 for max baud 
    if (!serial_set_cts(18)) Serial.println("cts set failed");
    if (!serial_set_rts(19)) Serial.println("rts set failed");

wire rts to cts and cts to rts.

Got it.

Thanks for your help.

@doughboy
I see there is already a copy of your TXT file in /hardware/teensy/avr/cores/teensy3/ along with serial1.c, serial2.c, and serial3.c. They are all dated 2/27/16 and probably the day I updated the Teensyduino package. Your file and serial1 look the same internally with a quick view but they are slightly different in size. Is your file intended to replace the existing serial1.c?