Go Down

Topic: fast serial sendin (Read 21688 times) previous topic - next topic


Im trying to increase the rate at which I can sample the analog and digitals and send to the PC.. I need to know the sampling rate accurately so Im using an interrupt. I was wondering though is there a faster way than calling serial.print so many times? Could i send all my bytes in one call at the end of the reading? Or does the serial.print handle the buffering and sending effeciently? Heres my code for read and send so far. I can send 2 analog and 6 digital in 7ms. I really nee to get faster. Maybe Im being unrealistic?
Thanks for any pointers

Code: [Select]
 /* get analog in, for the number enabled */
     for(analogPin=0; analogPin<analogInputsEnabled; ++analogPin)
           analogData = analogRead(analogPin);
Serial.print(analogData >> 7, BYTE); // shift high bits into output byte
           Serial.print(analogData % 128, BYTE); // mod by 32 for the small byte

               Serial.print(255, BYTE); // end signifier  
//digital pin checking stuff

for(n=2; n<8; ++n) {
                       digitalData = (byte) digitalRead(n);
                       if (digitalData == LOW)
                   x |= (1 << (n - 2));       // forces nth bit of x to be 1.  all other bits left alone.
                         x &= ~(1 << (n - 2));    // forces nth bit of x to be 0.  all other bits left alone

            // if (x != lastDigital){
             Serial.print(x, BYTE);
               Serial.print(254, BYTE); // end signifier  

 looptime = millis() - starttime;


Have you tried increasing the baud rate?

At 9600 baud, maximum throughput is going to be 0.96 bytes/ms.  If you're sending 5 bytes (two for each analog, one for all the digital measurements) in 7ms you are sending 0.71bytes/ms which is approaching that maximum.

Code: [Select]
Serial.print(analogData % 128, BYTE)
I'm not sure how the % operator gets compiled, but you may get a tiny improvement by usign a logical and and a typecast, e.g.
Code: [Select]
Serial.print((byte) analogData & 0xFF80, BYTE);

Put all the digital lines on one port (check Atmel docs or the pin mapping) and read them in one chunk using PINx.  For example, here's a bit of my code where I read 4 bits at once:
Code: [Select]
tone = PIND >> 4;
In this example, I want to read the high 4 bits of port D (which maps to arduino pins 4-7) and store them as 0-15 (low 4 bits) in "tone".  I do this in 2 operations (register read and a shift) rather than 4 function calls and a bunch of bit wiggling to get 'em stored the way I want.

Oh, I just saw "Serial.println(looptime)".  If that isn't a measurement then get rid of it.

These are all incremental things, though.  Basically you're going to have to increase the baud rate.



Feb 08, 2007, 06:29 pm Last Edit: Feb 08, 2007, 06:30 pm by nickytl Reason: 1
Kg4wsv thanks for the tips. I have baud at 115200. I commented out all the serial writes except for the timer one and no improvement. So i had a look in wiring.c where I found a delay(1); inserted in the code for analogread. Because Im cycling through the 6 analogues it adds up[ to 6ms to execute the reads. The comment says
// without a delay, we seem to read from the wrong channel

Ah well. I changes it to delayMicroseconds(200); and all seems fine.

Ill keep testing.


Could you try to set the serial speed to a LOWER setting and measure the time again?

According to an article published in Elektor (European electronics magazine):

The often considerably slower data transfer rate of a virtual
RS232 interface connected via USB compared to a
'genuine' RS232 interface deserves some explanation.
Since USB is considerably faster than RS232, it should
be possible to emulate an RS232 interface running at a
speed of say 115,200 bit/s without difficulty: by comparison,
even low-speed USB devices like keyboards and
mice can operate at rates of 1.5 Mbit/s. On the USB
side, the USB-to-RS232 adaptors from [manufacturer1] and
from [manufacturer2] both operate in USB version 1.1 full speed
mode, with a data transfer rate of 12 Mbit/s. One would
think that this would be more than enough to emulate the
fastest RS232 port, but unfortunately there is a snag. Serial
data is transferred over USB in data packets. The data
packets are sent out at one millisecond intervals. The
receiver must check that a complete and correct data
packet has been received and send back acknowledgement
data. The shortest possible turn-around time for
sending a single byte over USB is three milliseconds. As
long as the data packets are sufficiently long, continuous
communication at a speed of 9600 bit/s is feasible over
the virtual RS232 port. This in any case presupposes that
large amounts of data are to be transmitted, for example
for driving a printer or modem. (...) Comparing
the speeds of virtual and real RS232 ports shows that the
USB-to-RS232 adaptor can guarantee to match the speed
of the real port up to about 9600 bit/s. At higher communication
speeds it starts to fall behind, since each byte
must be sent individually on the one millisecond timebase.
The transfer in this case will thus take exactly
60 ms for the 60 bytes.
The behaviour described above was observed under
Windows XP for both the adaptors tested. In comparison,
a genuine RS232 interface running at 115200 bit/s will
transfer 60 bytes in approximately 5 ms; the USB-to-
RS232 adaptor takes twelve times as long!
Things take even longer when relatively small quantities of
data need to be transferred back and forth alternately. For
example, in a microcontroller system a series of command
bytes may need to be transmitted and the microcontroller
may need to reply to each byte received: the USB protocol
will now bring the system grinding practically to a
halt. Irrespective of the direction of transfer, each byte will
take three milliseconds to send and hence the effective
data transfer rate will be just 167 bytes per second.
This example also shows that the data transfer rate will
increase if it is not sent one byte at a time, but rather in
groups of bytes.

From: USB-to-RS232 Hurdle Race
adaptor problems and tuning tips
elektor electronics - 9/2005 - page 38


I'll have a look at lower baud but this looks like it may explain a further problem. In maxmsp the serial object gets no data for about 30ms and then 120bytes all at once. It gives very bursty and high latency at high datarates. I figured it was the serial object but perhaps it is related to this.


okay. Ive been testing with sending data betweeen arduino and MAXmsp. I noticed that when i sample at 1000 samples persec the data stream becomes very bursty. In MAXMSp i can see i am receiving the data in 4KB chunks every 640ms. My data packet from the arduino is 6 bytes long. So that would be 6 * 8 * 1000 = 48000kbps. I believe the explanation lies here on page 8 http://www.ftdichip.com/Documents/AppNotes/AN232B-04_DataLatencyFlow.pdf

I dont have anymore time to spend on this but it looks like i need to recompile the ftdi driver with a lower USB block request size or something like that.

3.3 Effect of USB Buffer Size and the Latency Timer on Data
An effect that is not immediately obvious is the way the size of the USB total packet request has on
the smoothness of data flow.  When a read request is sent to USB, the USB host controller will
continue to read 64 byte packets until one of the following conditions is met:
1. It has read the requested size (default is 4 Kbytes).
2. It has received a packet shorter than 64 bytes from the chip.
3. It has been cancelled.
While the host controller is waiting for one of the above conditions to occur, NO data is received by
our driver and hence the user's application.  The data, if there is any, is only finally transferred after
one of the above conditions has occurred.
Normally condition 3 will not occur so we will look at cases 1 and 2.  If 64 byte packets are
continually sent back to the host, then it will continue to read the data to match the block size
requested before it sends the block back to the driver.  If a small amount of data is sent, or the
data is sent slowly, then the latency timer will take over and send a short packet back to the host
which will terminate the read request.  The data that has been read so far is then passed on to the
users application via the FTDI driver.  This shows a relationship between the latency timer, the data
rate and when the data will become available to the user.  A condition can occur where if data is
passed into the FTDI chip at such a rate as to avoid the latency timer timing out, it can take a long
time between receiving data blocks.  This occurs because the host controller will see 64 byte
packets at the point just before the end of the latency period and will therefore continue to read the
data until it reaches the block size before it is passed back to the user's application.
The rate that causes this will be:
62 / Latency Timer    bytes/Second
(2 bytes per 64 byte packet are used for status)
For the default values: -
62 / 0.016 ~= 3875 bytes /second ~= 38.75 KBaud
Therefore if data is received at a rate of 3875 bytes per second (38.75 KBaud) or faster, then the
data will be subject to delays based on the requested USB block length.  If data is received at a
slower rate, then there will be less than 62 bytes (64 including our 2 status bytes) available after 16
milliseconds.  Therefore a short packet will occur, thus terminating the USB request and passing
the data back.  At the limit condition of 38.75 KBaud it will take approximately 1.06 seconds
between data buffers into the users application (assuming a 4Kbyte USB block request buffer size).
To get around this you can either increase the latency timer or reduce the USB block request.
Reducing the USB block request is the preferred method though a balance between the 2 may be
sought for optimum system response.


I think that this is the key sentence relating to this issue:

For application programmers it must be stressed that data should be sent or received using buffers and not individual characters.

Basically, the maximum latency will be 16 ms by default.  But once the FT232's buffer hits 64 bytes (62 data bytes and 2 status bytes), then it will be sent.  So if the ATmega8 sends 62 bytes every 1ms (USB's poll time), then the FT232 will send data every 1ms.  At a bitrate of 115200, the ATmega8 can send about 14 bytes per ms, so it seems that the quickest the Arduino will send data is every 5ms.

But looking at the code in wiring.c, it seems that the Receive/RX code is already buffered.  The output does not seem to be.  It might make sense to add a circular buffer to the serial write as well, then automatically dump only when it hits 62 bytes.


After reading the FTDI application note, it sounds as though the best thing to do is reduce the size of the USB block request from 4096 to 64 (instructions below).  Then, every 64 bytes (i.e. about 5 ms at 115200 baud), the USB host will pass data on to the FTDI driver and from there to your application.  The other option is to lower the timeout on the FTDI chip to 1 ms, meaning that the FTDI chip will send data back to the computer every 1 ms.  Since it's impossible to transmit 64 bytes to the chip in 1 ms, these packets will all be shorter than 64 bytes, meaning that the USB host will pass them directly to the FTDI driver and thus to your applicaion.  This should give you even lower latency than the first method, but I think that's it's not recommended because it may interfere with other USB devices (or slow down your computer).  Does someone want to give either of these methods a try?

I don't think adding code to wiring.c to buffer outgoing serial data will help.  I think the applications that sentence refers to are on the computer, not on the device, though I may be wrong.  But, in any case, I don't think such a buffer would decrease latency.

Again, when using the FTDI Virtual COM Port drivers the USB Transfer (buffer) size can be set in
the port properties page.  The initial buffer size is calculated from entries in the ftdiport.inf file - with
the size of buffer allocated being equal to the .inf entry plus 1 multiplied by 64 (bytes).
So 0 is 64 bytes, and 3F is (63+1)*64 = 4096.
There are two entries in the INF file - the first one is the transmit buffer and the second is the
receive buffer.
In the example above the two 3F's are the entries in question, with this line being set for 4k byte
buffer size operation and
being set for 64 byte buffer size operation.


Right, I hadn't gotten thru the rest of the FTDI application note... arg, what a whack-ass system.

Anyway, it seems that the latency timer would be better set to 4 ms (62 bytes @ 115200 needs 4.3 ms).  Sending a packet every 1 ms would slow the whole USB down a lot.  FWIW, most USB HID mice are polled every 10ms.  Having the data reliably sent for a fixed interval is better than lower latency.  Humans are much more sensitive to jitter than latency.

Is there anything that can be set in the EEPROM of the FTDI that might help?  I did find one possibility: "event characters".  Basically you set one specific byte to be the trigger for the buffer to dump.  This would work very well in the context of the current Firmata.  Basically, if the event character was higher than 127, it could be used explicitly to dump the data every 4ms using a timer, thereby preventing the 4k buffer crap.  This would have undefined behavior when people send whole bytes as data (SimpleMessageSystem?), but I don't think this behavior would be any worse than the current one.

How do I go about changing the EEPROM values in the FTDI?


If the event character is enabled and it is detected in the data stream, then the contents of the devices buffer is sent immediately. The event character is not stripped out of the data stream by the device or by the drivers, it is up to the application to remove it. Event characters may be turned on and off depending on whether large amounts of random data or small command sequences are to be sent. The event character will not work if it is the first character in the buffer. It needs to be the second or higher. The reason for this being applications that use the Internet for example, will program the event character as '$7E'. All the data is then sent and received in packets that have '$7E' at the start and at the end of the packet. In order to maximise throughput and to avoid a packet with only the starting '$7E' in it, the event character does not trigger on the first position.


How do I go about changing the EEPROM values in the FTDI?

I used a utility that I downloaded from the FTDI web site to put serial numbers on some unserialized devices (not Arduino).  Unfortunately it's windows only.  I don't have the windows machine handy to check the details.



I found a BSD one too, unfortunately Darwin/Mac OS X has different USB stuff, so it doesn't work.  It's at the bottom of the FTDI page on EEPROM utils.  Anyone want to try a port to GNU/Linux and/or Mac OS X?



A port to libusb would make it work on both GNU/Linux and Mac OS X (and perhaps even Windows).

I just had another related thought: it would be awesome if you could access the FTDI EEPROM from the Arduino code.  Then you could set the "event character" in your code.  I don't know whether it's feasible though.



Code: [Select]
Serial.print(analogData % 128, BYTE)
I'm not sure how the % operator gets compiled, but you may get a tiny improvement by usign a logical and and a typecast, e.g.
Code: [Select]
Serial.print((byte) analogData & 0xFF80, BYTE);

I thought I should point out that the % modulo replacement bit is reversed.  Modulo with 0xFF80 would produce 128, 256, 384, 512, ad naseum. It should be:
Code: [Select]
Serial.print((byte) analogData & 0x7F, BYTE);


you're right, thanks for the correction.  probably one of those posts I made before I had my caffeine.



Im just returning to this issue again. Im mainly on OS X so im wondering what is the equivalent to the ftdiport.inf file? Or is there one? I might have a shot on my windows machine first to see if that helps.
Ill report back in a day or so.
Thanks for the pointers.


Maybe something in /System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist

Just a guess...  if it is possible, it would probably be in some plist file somewhere.

Go Up