PaulS
Well-known member
While playing with MicroPython on a Teensy 4.0, I wondered at what speed I could send serial data over USB from the Teensy to my PC.
Ran this MicroPython script as main.py on the device:
On my Windows PC, I ran this Python script:
Result: ~130 Mbps. Not too bad.
I also ran this C program on my PC to check whether Python was limiting here:
Result: again ~130 Mbps. Apparently Python was not limiting here (on an Intel i9-13900K, that is).
Next step was to compare the MicroPython script with this C++ program running on the Teensy:
Now the results on the PC side (both the Python script and C program) were: ~260 Mbps.
So the take-away I guess is: MicroPython does pretty well on the Teensy 4, but for real speed move to C++.
Paul
Ran this MicroPython script as main.py on the device:
Python:
import sys
import time
# create a pre-allocated payload of 512 bytes
payload = bytes([i % 256 for i in range(512)])
while True:
sys.stdout.buffer.write(payload)
On my Windows PC, I ran this Python script:
Python:
import serial
import time
# Open the device port (the baudrate parameter is ignored by USB CDC)
ser = serial.Serial('COM1', 115200, timeout=1)
print("Measuring USB data transfer rate for 5 seconds...")
total_bytes = 0
checksum = 0
start_time = time.time()
while time.time() - start_time < 5:
data = ser.read(512) # 64 bytes for USB 1.1 Full Speed, 512 bytes for USB 2.0 High Speed
total_bytes += len(data)
duration = time.time() - start_time
speed_kbs = (total_bytes / 1024) / duration
speed_mbps = (total_bytes * 8 / 1000000) / duration
print(f"Total bytes received: {total_bytes} bytes")
print(f"Average throughput: {speed_kbs:.2f} KB/s") # 1024 bytes/s
print(f"Data line speed: {speed_mbps:.2f} Mbps") # 1000000 bytes/s
ser.close()
Result: ~130 Mbps. Not too bad.
I also ran this C program on my PC to check whether Python was limiting here:
C:
#include <windows.h>
#include <stdio.h>
#include <time.h>
#define BUFFER_SIZE 512
#define TEST_DURATION_SEC 5.0
int main() {
int port_number = 0;
char port_name[64];
printf("--- High-Speed Serial Benchmark Utility ---\n");
printf("Enter the COM port number for your device (e.g., enter 3 for COM3): ");
// Validate that the user entered a valid positive integer
if (scanf_s("%d", &port_number) != 1 || port_number <= 0) {
printf("Error: Invalid COM port number entered.\n");
return 1;
}
// Automatically construct the required Win32 namespace path format: \\.\COMxx
// This format is mandatory in Windows for accessing ports higher than COM9
sprintf_s(port_name, sizeof(port_name), "\\\\.\\COM%d", port_number);
printf("Attempting to connect to COM%d...\n\n", port_number);
// Open the serial port with low-level Win32 file access
HANDLE h_serial = CreateFileA(
port_name,
GENERIC_READ,
0, // Shared mode: 0 means exclusive access
NULL, // Security attributes
OPEN_EXISTING, // Port must already exist
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (h_serial == INVALID_HANDLE_VALUE) {
printf("Error: Could not open %s. Ensure it is connected and not open in another app.\n", port_name + 4);
return 1;
}
// Configure the serial port parameters
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
if (!GetCommState(h_serial, &dcb_serial_params)) {
printf("Error: Could not get serial port state.\n");
CloseHandle(h_serial);
return 1;
}
// Baudrate parameters are ignored by USB CDC firmware but required by the Win32 API
dcb_serial_params.BaudRate = CBR_115200;
dcb_serial_params.ByteSize = 8;
dcb_serial_params.StopBits = ONESTOPBIT;
dcb_serial_params.Parity = NOPARITY;
// Assert DTR to notify the device hardware that the PC is actively streaming
dcb_serial_params.fDtrControl = DTR_CONTROL_ENABLE;
if (!SetCommState(h_serial, &dcb_serial_params)) {
printf("Error: Could not set serial port parameters.\n");
CloseHandle(h_serial);
return 1;
}
// Set aggressive non-blocking execution timeouts for maximum polling speed
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = MAXDWORD; // Return instantly if bytes are available
timeouts.ReadTotalTimeoutConstant = 0; // No constant waiting delay
timeouts.ReadTotalTimeoutMultiplier = 0; // No multiplier delay
if (!SetCommTimeouts(h_serial, &timeouts)) {
printf("Error: Could not set serial port communication timeouts.\n");
CloseHandle(h_serial);
return 1;
}
printf("Successfully connected to %s.\n", port_name + 4);
printf("Measuring raw native USB data transfer rate for %.1f seconds...\n", TEST_DURATION_SEC);
unsigned char buffer[BUFFER_SIZE];
unsigned long long total_bytes = 0;
unsigned int checksum = 0;
// Get precise tracking timestamps
clock_t start_time = clock();
clock_t current_time;
double elapsed_time = 0.0;
while (elapsed_time < TEST_DURATION_SEC) {
DWORD bytes_read = 0;
// Directly pull data out of the Windows Kernel buffer
if (ReadFile(h_serial, buffer, BUFFER_SIZE, &bytes_read, NULL) && bytes_read > 0) {
total_bytes += bytes_read;
}
current_time = clock();
elapsed_time = (double)(current_time - start_time) / CLOCKS_PER_SEC;
}
// Calculate final metrics
double speed_kbs = ((double)total_bytes / 1024.0) / elapsed_time;
double speed_mbps = ((double)total_bytes * 8.0 / 1000000.0) / elapsed_time;
printf("\n--- BENCHMARK RESULTS ---\n");
printf("Total bytes received: %llu bytes\n", total_bytes);
printf("Average throughput: %.2f KB/s\n", speed_kbs); // 1024 bytes/s
printf("Data line speed: %.2f Mbps\n", speed_mbps); // 1000000 bytes/s
// Cleanly drop the DTR signal and release the OS port handle
dcb_serial_params.fDtrControl = DTR_CONTROL_DISABLE;
SetCommState(h_serial, &dcb_serial_params);
CloseHandle(h_serial);
return 0;
}
Result: again ~130 Mbps. Apparently Python was not limiting here (on an Intel i9-13900K, that is).
Next step was to compare the MicroPython script with this C++ program running on the Teensy:
C++:
uint8_t payload[512];
void setup() {
// The baudrate parameter is ignored by the USB controller.
// It automatically runs at the full 480 Mbps physical hardware link speed.
Serial.begin(9600);
// Fill the buffer with dummy test data
for (int i = 0; i < 512; i++) {
payload[i] = i % 256;
}
}
void loop() {
// Check if the PC is actively connected and reading the port
if (Serial && Serial.dtr()) {
// Push raw bytes directly to the USB DMA registers at hardware speed
Serial.write(payload, 512);
}
}
Now the results on the PC side (both the Python script and C program) were: ~260 Mbps.
So the take-away I guess is: MicroPython does pretty well on the Teensy 4, but for real speed move to C++.
Paul
Last edited: