fast serial sendin

Hi,
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

{
  
  /* 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.
                        }
                        else
                        {
                          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;
  Serial.println(looptime);
                       
}

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.

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.

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:

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.

-j

Hi,
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
Throughput
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.
[FtdiPort.NT.HW.AddReg]
HKR,,ConfigData,1,01,00,3F,3F,10,27,88,13,C4,09,E2,04,71,02,38,41,9c,80,4E,C0,34,00,
1A,00,0D,00,06,40,03,80,00,00,00,00
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
[FtdiPort.NT.HW.AddReg]
HKR,,ConfigData,1,01,00,00,00,10,27,88,13,C4,09,E2,04,71,02,38,41,9c,80,4E,C0,34,00,
1A,00,0D,00,06,40,03,80,00,00,00,00
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.

-j

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?

http://www.ftdichip.com/Resources/Utilities.htm

http://www.bwct.de/ftdi-eeprom-1.0.tar.gz

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.

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.

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:

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.

-j

Hi,
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.
Nicky

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.

nope nothing there. this must be a windows vibe only i guess

Just another point on this. I hadnt realised that ftdiport.inf is part of the driver download on windows. You must edit it before you install the driver on windows. I could find no equivalent on OS X though.

Heres a solution to the 4K block problem outlined in this topic. Solder a wire to X3 pad 1 on the arduino. This is the CTS pin on the ftdi chip. Toggling this pin will cause the ftdi to flush its send buffer up to the PC. Works like a charm. I connected this to pin 13 and set pin 13 high after a read through the analogues and low while waiting for an interrupt. Nasty. Can someone show me a really quick way to toggle each time throug the loop in hardly any code?

this code is now a complete mess but i'll clear it up later.

Mellis: I guess thats a use for X3 :slight_smile:

#include <avr/interrupt.h>
#include <avr/io.h>

#define INIT_TIMER_COUNT 6
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT

volatile int Ft = 1;
int int_counter = 0;
volatile int count = 149;
int oldcount = 0;
long starttime = 0;
long time;
long start;

int ledPin = 13;
int analogPin = 0;
int analogInputsEnabled = 4;
int i;
int analogData;            // outgoing ADC value
int n = 0; //index for digital read loop
int digitalPin;

byte x = 0; //byte to store digital pin status


// Arduino runs at 16 Mhz, so we have 1000 Overflows per second...
// 1/ ((16000000 / 64) / 256) = 1 / 1000
ISR(TIMER2_OVF_vect) {
  RESET_TIMER2;
  int_counter += 1;
  
  if (int_counter == 2) //set sampling frequency here 1 = 1kHz, 2 = 500Hz etc
  {
    if (count == 250){count = 149;}
    count+=1; 
    int_counter = 0;
  } 
};

void setup() {
  Serial.begin(115200); //setup serial
  Serial.flush();
  int resp = 0;
   pinMode(ledPin,OUTPUT);   // declare the LED's pin as output
  
  //set up the timer 2 interrupt
  //Timer2 Settings: Timer Prescaler /64, 
  TCCR2A |= (1<<CS22);    
  TCCR2A &= ~((1<<CS21) | (1<<CS20));     
  // Use normal mode
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));  
  // Use internal clock - external clock not used in Arduino
  ASSR |= (0<<AS2);
  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= (1<<TOIE2) | (0<<OCIE2A);  
  RESET_TIMER2;               
  sei();
             
             
  //enable digital pullups
  for(i=2;i<8;++i) 
                   {
                digitalPin = i;
                    digitalWrite(digitalPin, HIGH); //enable internal pullups
                    }


 digitalWrite(ledPin, LOW);


}

void loop() { start = millis();

  digitalWrite(13, LOW);  
  if (oldcount != count) {
   /* 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 128 for the small byte

      }

Serial.print(255, BYTE); // end of analogs signifier   
                
                
//digital pin checking stuff
               x = PIND >> 2;
             Serial.print(x, BYTE);
               Serial.print(count, BYTE); // a counter that runs from 150 to 250 to check for missing bytes
               Serial.print(254, BYTE); // end of digital signifier 
                  
    //a timer to see how long through the loop;
    time = (millis() - start);
    Serial.println(time);
                
               
      } 
    
    oldcount = count;
    digitalWrite(13, HIGH);
  }

It's pretty simple code but I don't know what library for USB they are using.

What use is this for the average user? I didn't know that the USB device on the board had an eeprom. :slight_smile:

I've had the same problem - regular prints of data from Arduino, but bursts of data turning up in Flash, but of varying sizes. Wow, this gets pretty confusing pretty quick. Ok, the conclusion as I see it is there are 5 options:

  • Edit the ini file before installing the drivers on a Windows machine. Easy, but not platform independant, and not flexible (able) to be changed for different uses. Has this been verified to work?
  • Get a pin on the Atmel chip to directly trigger a flush on the FTDI chip. Not so easy (not sure I could do it with just the instructions on the forum), requires customised hardware (?) and lengthy code.
  • Recompile the FTDI drivers. Did I get this right? Is this an option? I don't think the complete process has been defined for this yet.
  • Trigger a flush by sending a signal (adjusting a register value?) to the FTDI chip from the COMPUTER end. Did I get this right? Sounds possibly easy, flexible, not require hardware customisation and possibly platform independent. But I don't think the complete process has been defined for this yet.
  • Trigger a flush by sending a signal (adjusting a register value?) to the FTDI chip from the ARDUINO end. Did I get this right? Sounds possibly easy, flexible, not require hardware customisation and definitely platform independent. But I don't think the complete process has been defined for this yet.

I'm not sure if I have got all of this right. Im keen for others to improve upon this summary, and maybe have it as a sticky at the start of the post or in the playground or something.

Cheers for all the hard work.

Eric