Data dropouts in serial transfer over USB

If you take that and set useSendNow = false; to true, and stop the inversion [useSendNow = !useSendNow;] on completion it should repeat and give results like on the end comment. You can put the output back that you want and in it throttle where it works best.

Unless my code is off, it is showing something odd in my Usage.
 
Last edited:
Maybe the function Serial.availableForWrite() is useful?

I get values from 0-63 that don't seem correlated to the dropouts...
 
That is the right tyqt, drop it in a Directory and run the sketch against it. Watch the monitor and you can see message integrity.

Yes it self adjusts to the right delay for the message sent.. From this it could count msg per second and data rate with added code.

Then adjust output to your text and test with python , add throttle delay until it works

If you stay under that throttle there will be no corruption or bad delays
 
Last edited:
On the Tx side, I'm just calling "Serial.print()" with a port that I have set up using "Serial.begin(9600)" -- so whatever the defaults are I guess. (Also -- baud rate doesn't matter AFAIK)

On the Rx side, I'm using PySerial to set up a port using "serial.Serial(port, timeout=0.01)" and reading data using "serial.readline()"

What would you suggest for flow control?
With no intention to be sophomoric... in any communications from-to link, rare is the case where the receiver can always keep-up (accept all) arriving data without coordination with the sender. So an OSI stack of protocols is used (like TCP) or simpler, a header + data packet or frame is created by the sender. Header contains data length and an ascension number to detect duplicate and lost packets (data frames). And an agreement that the sender will stop sending after 1-n frames and wait for the receiver to acknowledge receipt. The ACK says "ready for more". No ack at all can cause the sender to timeout and retransmit. Apologies if the OP knows well these basics.
In a serial link with other than a very low channel utilization (i.e., most of the channel capacity is never used), such fancy or simply method brings flow control and error detection, with optional error correction. One can't fire-hose the receiver, to be explicit.
 
@stevech - I thought maybe you mean flow control on the serial connection itself. It would definitely be a good idea to move to a fault-tolerant protocol for high speed data transfer if these corruption/dropout problems aren't simple to fix... I guess I am just used to thinking of the usb serial connection on the Teensy as a 100% reliable channel because I usually send data at low rates. The silent failures are annoying and I wish I could learn to spot them (or at least anticipate them.) I don't have a deep understanding of the (presumably) buffer-related failures I'm seeing in these experiments and I would like to know more about what causes the results that seem unexpected to me.

Before I look for a solution up the OSI stack, I'd like to understand the channel I have... maybe the capacity varies too much based on the receiver code, OS, computational power, other load, etc, to make any universal statements?

Do you know if there is a library that wraps a channel and makes all of this stuff easy? Ideally I would want something that would never lose information while maintaining the highest transfer rates possible... (Maybe it's actually TCP!)

Slowing down, either by tuning a delay parameter or by calling "send_now()" is preferable to losing data for me in this case... but if it isn't a simple check to see if a buffer is full or something like that, then maybe it's better to just move to a better transfer protocol...
 
rwalters - see what kind of mileage you can get from my sample and experiment. The Teensy can easily keep the pipe full. In my case I found the edge where my TYQT serial terminal while taking all the data let me see the point where the USB hardware was going into flow control blocking.

With my messages - longer than before - that was 1300 transactions per second. On the onehorse i2c 9dof on Teensy he claims 1,000/second is 'doable'. Meaning while the USB hardware does the work from the last message you have 230 usecs free time plus with good buffer control the rest of that 770 usecs to multitask get your data ready and formatted. This is probably ideal if there is any other USB contention then there will be lower throughput on the PC side.

If you can live with that from the Teensy - now you need to worry about the PC side using code like I dropped to emulate that half to find out how close to possible that is on the PC side getting the data pulled in. TYQT can do it - ideally you should have more power on the PC side - but the USB might end up being that slow link with the tools you are using.

But I would expect the USB to preserve data in any case - from what I am seeing any loss is from the PC side.
 
@defragster -- I appreciate that I can "tune" a delay parameter to optimize bandwidth, but it doesn't help me understand why I'm getting corruption/dropouts, and it also doesn't rule out infrequent errors. I'd like to know when Serial.print() is overrunning its buffer if that is indeed the cause of the problem.

I do think it's interesting to consider other improvements, including moving to an ARQ scheme... Some resources:
https://en.wikibooks.org/wiki/Serial_Programming/Complete_Wikibook
http://www.embeddedrelated.com/showarticle/113.php
https://en.wikipedia.org/wiki/Automatic_repeat_request

But at a minimum I should be able to find the buffer overrun register somewhere in the MK20 manual and slow down in a rational or adaptive way.
 
I'm not feeling that you do appreciate the value of this. It provides a baseline where the Teensy can keep up at what should be an error free rate free of 'unplanned' delays.

Given that one half of the 'equation' solved. Tuning it for your message constraints - the content as you had originally - and perhaps bump up the 'throttle' some x% and see it work on your computer with your hardware and TYQT for instance you will see there is in fact no data loss, I expect - unless this 4 year old i5 laptop has a better Teensy and USB cable - which this sketch will rule out when it works as presented.

So given one half is working from all indications to get data usably into the PC - if your application on the PC is seeing errors then you know it isn't the Teensy as was your first worry.

Indeed you may need more than throttled output - but since you can measurably control the Teensy output - you can find out where and IF you get reliable data reception in the python code.

This exercise I enjoyed doing ( which is why I have 1400+ posts here in under 10 months ) shows that the aberrant for() loop from the first posted code could never work as a baseline test as it overwhelmed the Teensy - but even then Teensy USB appears to me to be stable and just blocked with no apparent data loss using TYQT as the measuring stick.

<edit: I'm not sure how this post 'sounds' on your end - as I type I just hear "click click click" but to summarize: if you are going to analyze this with charts and graphs this would be a better starting point than prior posts. Current code will be posted below. >
 
Last edited:
THIS JUST IN:: TYQT may NOT be perfect - or maybe the Teensy or Windows USB . . . - UPDATE: SerMon shows the same behavior - and runs twice as fast!

<edit: IDE SerMon running at under 300usecs it much faster pulling data than TYQT at over 750 usecs!>

{ again note no data loss : all the printed output has all character strings of proper length and observed data shows each iteration is accounted for even when it 'stalls' }

I decided to try the Arduino Serial Monitor and after closing TYQT and setting up my COM5 to SerMon - I see my sketch pick up running!!!

Here are my results with lines typically 66 characters long ("TEST_SendNow____iterations=674__throttle=276__runningms=277______"):
40,000 with ___
__iterations=40000__throttle=130__runningms=133______

__throttle [ NO SEND NOW ] =130
__1000 xfer ms time [ NO SEND NOW ] =133
__1000 xfer Wait Loops [ NO SEND NOW ] =36731

__throttle [ with SEND NOW ] =259
__1000 xfer ms time [ with SEND NOW ] =262
__1000 xfer Wait Loops [ with SEND NOW ] =81778

Doing the math here (for the slower but reliable SendNow case - I see about a 2Mbit/sec transfer rate with the Teensy running roughly at the speed of the USB.

However - interestingly enough - IDE SerMon just cycled through and then STOPPED in the EXACT same way when send_now is NOT used!

I am going to clean up my sketch and will make my own thread for Paul to check - it seems something isn't right and I'm wondering if it is the Teensy?
 
Last edited:
Posting the updated sketch with some cleanup done. Lowered minimum tested throttle to 100usecs and on restart added longer delay after a send_now() to flush USB in all cases. I added qBlink() to show LED activity during prints (every 400 iterations is a nice blink).

IDE SerMon now showing throttle times down to 130 usecs not using the send_now()! with the send_now() the time is 260-280 usecs. This just means more wait loop() cycles and free time for other things.

My qBlink() shows the Teensy is still running! Not a Teensy problem - except it is too fast! It is the PC that is losing the USB sync! At 130usec per 66 bytes the PC just gets lost! Even with send_now() at 260 usecs/cycle it is hanging. It hangs on TYQT as well at much slower cycle times over 700 usecs - so it appears to be PC APP dependent on taking the USB data stream! I need to reboot my PC and let the bit bucket refill it is stalling more often now - in fact it won't complete again.

@rwalters: Do you have any other way to feed fast data to USB for python to process? If you could use that to establish a baseline for connectivity rates you could aim for that. Perhaps run two sets of python code out one USB port and back in the other? See how fast you can emulate the Teensy output and the PC receiving it error free? Then you could use an approach like my sketch to limit the number of messages/second the 'throttle' does - taking your message content size into account.

At 130 usecs that is 7692 prints per second of about 66 bytes or 4 MBits/sec.

My sketch: View attachment USB_Throttle.ino
 
Interesting link and metric:
spits out a bunch of measurement data every 1ms. I have a Raspberry Pi running Python to give the user a nice GUI to send the command, and to present the data in a readable form.

The problem I face: the Arduino is able to spit out 15 byte of data each millisecond (so that's only 15kbyte/s), but the code I'm running can only cope with about 15 byte each 10 milliseconds, so 1.5kB/s.

That Teensy sample to IDE SerMon is spitting out 66 bytes every 0.130ms - 4 times the data 7.69 times as often or over 460kbyte/s.

If your environment is anything like the linked example able to 'cope' with 1.5kB/s that generally explains the data problems.

For a simple sample you can control on both ends if you can get an out USB and an in USB running code of your making:

> On the first - use the program you have to receive data as you did from the Teensy.

> On the second out program emulate what you did with the earlier posted sketch - or the one from post #35 in some fashion [perhaps use a while rather than having Arduino calling loop()] printing out data you can verify that may be similar to your 9DOF data. One that allows you to monitor & throttle the output data rate.

In my case I saw that there was a time backlog when the USB filled and blocked to empty so I started fast and slowed down until the backlog didn't show. In your code you may want to start slow and use your input program to pass back success/fail to speed up until you see failures then slow down.

Since you are using the same machine for both you will have some contention unless the USB bus hardware is coming in unique hardware ports - not through a shared hub. And that link said the CPU went to 100%, in my sample the Teensy never went near 100% (though I didn't try to measure this except the number of idle passes when loop was entered was as the hardware did the work - over 250,000/sec). As good as the Teensy is the PC should have better hardware and buffer support, but based on your link the python code may put a heavy CPU burden on the I/O.
 
Serial.availableForWrite() takes on all values and seems uncorrelated with dropouts. So this is not useful to check...

Code:
#include <Arduino.h>
#include <cstdio>

void setup()
{
    Serial.begin(9600); // USB is always 12 Mbit/sec
    while ( !Serial || millis() < 10000); //wait for user to start up serial listener...
}

char datastring[100];
void fillbuffer(uint32_t iterationCount, uint32_t timeStart, uint32_t timeNow, uint32_t timeDelta)
{
    sprintf(datastring,"%08lu\n%010lu\n%08lu\n%08lu\n", iterationCount, timeNow - timeStart, timeDelta, (uint32_t) Serial.availableForWrite() ); 
    sprintf(&datastring[strlen(datastring)], "%04d\n", 5+8+strlen(datastring)); //account for header, footer, and this message itself
}

void loop() 
{
    uint32_t timePrev = 0;
    uint32_t timeStart = micros();
    uint32_t samples = 80000;
    timePrev = timeStart;
    for (uint32_t iterationCount = 0; iterationCount<samples; iterationCount++){ 
        delayMicroseconds(iterationCount * 2000.0/samples);
        uint32_t timeNow = micros();
        fillbuffer(iterationCount, timeStart, timeNow, timeNow-timePrev);
        timePrev = timeNow;
        Serial.print("START\n"); //header
        Serial.print(datastring);
        Serial.print("\n\n"); //footer
        //Serial.send_now();
    }
    while(1){};
}

This is a plot of the output from Serial.availableForWrite() taking on all possible values. (Note that I made the data string fixed length for this test.)


available bytes.png

If somebody could run this sketch with a linux machine and pipe the serial input directly to a file for parsing that would be very helpful for checking if PySerial is the issue here...

Here is the result using PySerial's readline() to grab the data:
available bytes.jpg

python serial reader and parser:
Code:
#!/usr/bin/env python


import serial  # requires pyserial
import sys, os

def collectRawData(port, samples):
    collected_data = False
    serial_timeouts = 0
    f = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'raw.txt'), 'w')
    f.seek(0)
    with serial.Serial(port, timeout=0.01) as ser:
        i = 0
        while i<samples:
            i += 1
            
            newline = ser.readline()
            if not newline:
                serial_timeouts+=1
            else:
                collected_data = True
                serial_timeouts = 0
            
            if collected_data and serial_timeouts>100:
                break
            
            f.write(newline)
            
            if (i%1000) == 0:
                print i
               
    f.close()


def parseRawData():
    
    message_length = 5 # iteration, ts, deltatime, serialbytes, bytelength
    
    with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'parsed.csv'), 'w') as out:
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'raw.txt'), 'r') as f:
            state = 0
            buffer = ""
            line_number = 0
            data_iteration = 0

            for line in f:
            
                if line.strip() and (line.strip() == "START"):
                    if state <= 0: #-1 when recovering from an error
                        state = 1
                    else:
                        #error!
                        print "****** unexpected start sequence: '", line.strip(), "' @ line ", line_number
                        data_iteration += 1
                        # don't put bad data line in our outfile
                        state = 0 # reset for next S
                elif state > 0 and state < message_length:
                    buffer = buffer + line.strip() + ", " 
                    state += 1
                elif state == message_length:
                    buffer = buffer + line.strip() + "\n"     
                    state += 1
                elif state == message_length + 1: #first tailing \n
                    if line.strip():
                        print "****** unexpected text: '", line.strip(), "' @ line ", line_number
                        data_iteration += 1
                        state = 0 #skip to START seek
                    else: 
                        state += 1
                elif state == message_length + 2: #second tailing \n
                    if line.strip():
                        print "****** unexpected text: '", line.strip(), "' @ line ", line_number
                        data_iteration += 1
                        state = 0 #skip to START seek 
                    else:
                        try:
                            data_iteration = int(buffer.split(',')[0])
                            out.write(buffer)
                            buffer = ""
                            state = 0 # reset for next S
                        except:
                            print "****** iteration can't be found: '", line.strip(), "' @ line ", line_number
                            state = 0 # reset for next S
                else:
                    print "syncing to 'START' @ line", line_number
                    if state == 0:
                        buffer = str(data_iteration) + ', ' + ( '-1, '*(message_length-1) ) + '\n'
                        out.write(buffer)
                        print "error line: ", buffer
                        buffer = ""
                        state = -1 #don't write multiple error lines
                        
                line_number +=1           
                
if __name__ == '__main__':

    collectRawData('COM7', 500000)
    parseRawData()
 
Last edited:
If you take my sketch against your charts and graphs I think you will quickly find a sweet spot where the PC stops corrupting the data you get. Start the 'throttle' higher than 100 or even 700 usecs and then work up or down based on the results you see. Your linked story dealt in milliseconds and 10's of ms. These throttle limits I'm using are 0.130 to 0.280 milliseconds and more than 4 times the data transferred so they may be 16 to 300 times too fast based on that link you found.

It seems availableForWrite() would just allow doing what I did from the other direction. I stuffed the pipe and measured the time lost in blocking writes and prevented entering the code when too little time had passed to do the transfer while having no concern for how much data was going to be sent. To use availableForWrite() you would need to know how much data you wanted to write and not enter the loop() [or delay individual writes - not unlike blocking if you just sit and wait] unless the right portion of that space was available for the printing ahead of you, knowing that while you spent time in the loop and added bytes they would also be transferring out as the USB bus allowed. If the above sketch were rewritten to incorporate that the net effect would be the same if you got the balance right and knew how much data you wanted to send. As noted before I'm doing this from 10,000 feet - I can tell you where the forests (and the fires) are but have no idea what kind of trees.

I put a delayMicroseconds(5); on the else case when loop() was re-entered before the throttle wait had timed out. This caused throttle to rise for missing the entry window of 10usecs and made the same 66 bytes take 44% longer to transmit. And the above 81k of empty loop passes 315k/sec became 43000 5 usec waits per thousand transfer - meaning for the 379 ms spent on 1000 transfers that 215 ms of that - 66% - was in the delay while the data transferred through the hardware and only 164 ms was spent in the if() doing the prints and other code - while the transfer also continued. If any of the writes blocked it was for short times well under 0.164 ms - not like the 43ms in the original for() loop version.

__throttle [ with SEND NOW ] =373
__1000 xfer ms time [ with SEND NOW ] =379
__1000 xfer Wait Loops [ with SEND NOW ] =42999

Related Story from last month?: I spent about two weeks looking to understand the interrupt details needed to implement the ILI8341 touch controller interrupt code. In those two weeks I wrote zero lines of code and tried nothing as I was told it was a hard problem. When I started writing code I prepared for the worst with extra double checks and debug preparations. Problem is my over engineering caused more problems than I could hack my way out of. When I simplified it, it got easier and clearer and about 80 lines IIRC. When I put it directly into the existing touch driver it got even easier as it turns out I was trying to work around an internal problem from outside (rejected light touches). I ended up getting it done in 20 lines of code, some small half of which was just the header file prototypes and overhead for the cpp code.
 
Wow, I was away from my keyboard most of Friday... and now 39 replies! I've tried to read through all these messages, admittedly skimming some parts.

Before I dig into this problem, I'd like to ask you to try a small hack on the Teensy side. Edit hardware/teensy/avr/cores/teensy3/usb_serial.c. Find the usb_serial_write() function. Delete these 4 lines:

Code:
				if (++wait_count > TX_TIMEOUT || transmit_previous_timeout) {
					transmit_previous_timeout = 1;
					return -1;
				}

Please try removing this and see if it makes the dropouts disappear. Removing this may have other unpleasant consequences, but for now let's just see if these 4 lines of code are responsible for the dropouts you're seeing?

I can (and probably will) write a lengthy message about the USB protocols and I might even set up a Windows machine here and recreate the problem. Or maybe try on Linux (which I suspect will be quite different).

But first, please give this simple hack a try. Just delete those 4 lines and rerun the test to see how dropouts are impacted?
 
@ Paul:

Here's the baseline result again:

baseline.png

I commented lines near 203 in "usb_serial.c" -- when I recompiled I got a warning about an unused variable, so I know the right file got changed:
Code:
C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3\usb_serial.c -o C:\Users\Robb\AppData\Local\Temp\build8800891962364582893.tmp\usb_serial.c.o 
C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3\usb_serial.c: In function 'usb_serial_write':
C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3\usb_serial.c:184:11: warning: variable 'wait_count' set but not used [-Wunused-but-set-variable]
uint32_t wait_count;

With the change to usb_serial_write, I get no dropouts and more "slow iterations":

modified.png
 
As an intermediate test, I increased the base timeout value to 90:

Code:
// #define TX_TIMEOUT_MSEC 70
#define TX_TIMEOUT_MSEC 90

This results in more slow loops and fewer dropouts:

2.png

I guess your usb_serial.c code is pretty explicit in the comments that data will be discarded when the sending times out. I can understand this as a design decision, but it may not be best for all situations and wasn't the behavior I was expecting... I think I would rather have extra delays to explain rather than bad data streams.

Maybe there could be a separate function or a flag for a safer, but slower serial_write... Or possible a utility function to query the appropriate variable:

Code:
static uint8_t transmit_previous_timeout=0;

In any case, I appreciate you taking the time to help me out on this -- I feel like I understand what I'm seeing a lot better now!

(latest code:View attachment teensyserialtest.zip)
 
Last edited:
Thanks for running those tests!

Really, there's 2 issues here.

#1 - Indeed that timeout code deep within usb_serial_write() could be better. It's served us all pretty well over the last few years, being used in thousands of successful projects. But still, it's a pretty important decision being made very arbitrarily. That simple code is far from ideal. Unfortunately, we can't simply get rid of it. A lot of complex requirements are in play here. I may write on this in a future message, but for now let's think about the other issue...

#2 - In your application, I'm pretty sure the fundamental problem is you're generating data at a fixed rate which is faster than it can be consumed by the software on your PC. The USB protocol is indeed a reliable flow controlled stream. With that code removed, Teensy will simply wait as long as necessary for your PC to slowly digest the data. I've seen this behavior before, where it takes quite some time for the PC-side buffers to fill up. But when they do, the PC stops talking to Teensy. Those timeouts were set based on quite a bit of experimentation I did about 4 years ago, using Windows XP, Mac OSX 10.6 and (probably) Ubuntu9. Obviously my focus is on improving how Teensy handles these sorts of situations, which should make your code wait rather than discard data, but also needs to properly deal with a number of other PC-side issues *without* stalling a sketch running on the Teensy side.

No matter what is done on Teensy, I'm pretty sure you're not going to escape the reality that your PC-side Python script is simply too slow. You've created a real-time data source and you're feeding its output over a realiable flow-controlled link to a non-realtime data sink. There plenty of buffering on both the Teensy and PC side to deal with short-term issues. But long-term, if your application can't receive the data as fast as it's being produced, something has to give. That's what's happening here. Increasing buffering or smarter schemes to recognize problems will never cure the fundamental problem of creating data faster than you can process it. Sooner or later, buffers will all fill up and you'll either stall or have to drop data.

Obviously the most desirable fix is to increase the speed of your script. Modern PCs can easily keep up with native compiled code, as long as you don't end up waiting in blocking functions (especially GUI updates). Unfortunately, that's more easily said than done, especially if you're building on top of a scripting language and serial communication libraries. The other thing that's commonly done for real-time data streams is gracefully dropping data. For example, nearly all video players automatically skip frames and attempt to recover gracefully, when they're unable to keep up. Hopefully you won't need to do that, but if your application simply can't handle the incoming rate, gracefully discarding data is a lot better than letting the problem fester with so many buffers filling up, until other code ultimately drops the data in much less desirable ways.
 
I think my ideal behavior would be for the serial write to timeout even faster and drop even more data so that I don't get weird 100ms drops in my teensy code when my pc isn't keeping up. At the same time, I would like to be able to know when the serial write fails -- maybe with an error interrupt? or a variable that latches for a second or something like that.

The python code can be sped up by collecting data to RAM in a separate thread, but I think it might be nice to first improve my test code a bit more while the dropouts and stalls are easier to spot.

I'd also like to test if the length of data written per loop matters... I can also improve the python script to plot vs attempted transfer rate over the course of each experiment using matplotlib.
 
My finding was that the sketch could detect the blocking and reduce data output - for contrived data flow - to the point that the PC wasn't overwhelmed on receive.

Using send_now() the pc [IDE SerMon] keeps up and runs very well. As noted with tuning it could be artificially slowed to the point where the python code should get reliable data.

The initial thing I saw was that there would be blocking over 40ms as the cycle time was reported in millis or elapsedMicroseconds when the writes went overboard - that is where delaying of writes helped.

I never detected any dropouts - just the long waits I used as my metric of success when slowing the output stream.

It seemed to be working so well - then IDE SerMon started to stall and lose the connection and needed closed and re-opened to continue. TYQT did this stall well, and it runs at a slower rate - which was accommodated without apparent data loss. I just tried MegunoLink Pro and it was worse - would hang and freeze the app. Oddly a CMD/DOS box does not exhibit this behavior with "type com3" it seems to run well - but not usable for viewing output dynamically to screen or redirect to file.

I removed those lines in usb_serial.c and saw no change in the behavior. Perhaps in cleaning up I broke something - but it fails more than works now as the rate of output nears stasis. I tried on a second faster Windows 10 machine to the same end.

Here is my final code that might do well with a review and test run: View attachment USB_Throttle2.ino
 
I think my ideal behavior would be for the serial write to timeout even faster and drop even more data so that I don't get weird 100ms drops in my teensy code when my pc isn't keeping up. At the same time, I would like to be able to know when the serial write fails -- maybe with an error interrupt? or a variable that latches for a second or something like that.

The python code can be sped up by collecting data to RAM in a separate thread, but I think it might be nice to first improve my test code a bit more while the dropouts and stalls are easier to spot.

I'd also like to test if the length of data written per loop matters... I can also improve the python script to plot vs attempted transfer rate over the course of each experiment using matplotlib.

If I'm reading this right - it is exactly what my code was designed to correct and detect. Any blocking on USB write is detected in the 10 usec range and the output rate is adjusted so it won't happen again - if it does the wait is increased - decreasing the output rate.
 
Teensyduino's design goal is for usb_serial_write() to wait as long as necessary. USB serial is supposed to be a reliable, flow controlled stream. If software is running slowly and buffers are full on the PC side, waiting is the proper thing to do.

However, another goal is to detect conditions where your data will never manage to transmit. That's what the timeout is all about. If the PC is taking *that* long, something has gone very wrong and it's unlikely to ever work. Detecting such conditions is critically important, so Serial.print() doesn't forever stall. In the early days of Teensy 1.0 we had this stalling behavior and nobody liked it. People want their sketch to keep running on Teensy and the data to simply be discarded in those cases!

When/if I work on this code I'll probably add more state and even longer timeouts if we're previously managed to deliver data. Or perhaps if we've successfully received any control messages with the PC side driver.

Another function that could use improvement is Serial.availableForWrite(). Currently it reports only the number of bytes that can fit into the next USB packet. That's why you see numbers from 0 to 64. It has no visibility of how many more packets the USB stack could provide for it to put data into. However, if it returns zero, that's a pretty sure bet all the Teensy-side USB packet buffers are full and your next write will block.
 
Last edited:
In this particular application, probably the best design would have the PC side occasionally transmit a status message. You want to do this infrequently, because sending lots of data in both directions can run into even more troubles! Keep the status messages short (under 64 bytes) and infrequent, ideally over 50 ms apart.

There's a number of things you might put into the status message, other than the received message count. A high-res timestamp and perhaps a measure of the PC's CPU load might help?

When your code on the Teensy side is getting status messages from the PC, even if they're significantly delayed, it can do some simply math to learn if the PC is actually digesting the data slower than it's sending. If so, you'd be able to make smart decisions about how to throttle your transmission *before* calling Serial.write().

But this data stream is at most about 1 Mbyte/sec. With more efficient programming, pretty much any modern PC ought to be able to handle that speed. Improving the PC side performance is probably easier than so much trouble to receive status messages and throttle data rates.
 
I don't know if this is a terribly useful suggestion but the Open6022 project to accept data from a Hantek USB oscilloscope has the source for a dll that accepts data from the o'scope to a PC over a USB port. In that case, the data are put into fairly large blocks that are sent as one transmission. Although Hantek claims 48 MSPS, that appears to be false, however, it does appear they are sending and receiving data at a much higher rate than the Teensy can send (maybe...it's a little hard to tell).

There is a very long forum post at: http://www.eevblog.com/forum/testgear/hantek-6022be-20mhz-usb-dso/
 
That would be beyond USB 2 speeds? It might help TYQT or somebody maximize the serial data if it is a robust solution app though - not sure if it could catch the data and feed it to python at a good rate?

The Teensy 3.x I have it working at 4.2 Mbps, when I push the data straight to disk in Win10:: "CMD Window C:> type com3 > logfile.txt". SerMon runs about that fast but hangs after short sessions and needs restarted. In fact I slowed it down about 10x with throttling and it still only did 22 SHORT 10K cycles. I waited for one 'CMD Type' file to grow to 198 longer 40K sessions and got 529MB of useful (nonsense) data.

I get good formatted data in the logfile and it runs well and fast. I got PUTTY and it seems to keep up on screen - but data is unformatted to read. When I do a Putty LOG file it runs well and continuously it seems - but very slow - it throttles to run at over 1400 usec per cycle - but it does run well.

The 4.2 Mbps rate is without send_now in under 5 seconds, same loop with send_now used takes 11 seconds because the transfer is done in loop rather than outside during waiting.

I've learned a bit more with my code and options that inloop timing only takes 20 usecs to process and buffer ~70 bytes - if I prevent re-entry for 100 usecs the next loop takes the same time, (when CMD Type is buffering to disk).

I've also just discovered hitting Program Button results in invalid USB device repeatedly - even when I have throttled I/O taking 1200 usecs per loop. It takes some port swapping and button holding or good Teensy USB connecting to get it back online.

As noted I think this new variant sketch is the answer to OP's request in post #44 from the Teensy side if interested in looking at it.
 
Back
Top