question on throughput using SoftwareSerial

Hi, I have been trying to use the Arduino SoftwareSerial functions to send
"blocks" of data between 2 Arduinos, and I am wondering what sort of actual
throughput people are getting with these functions.

Am using a variation on the basic code shown in loop() here:

Arduino #2 [Duemilanove] uses the hardware UART on pins 0,1 to talk
to Arduino #1.

Arduino #1 [UNO] uses the hardware UART to talk to the PC Serial Monitor
at 57600 via Serial.write(), and talks to Arduino #2 using the SoftwareSerial
port on pins 2 (Rx) and 3 (Tx).

The basic code example works ok at higher baudrates [57600] for simply typing
in data [ie very low thoughput], but when I send a block of 128-256 bytes back
from Arduino #2, I get mostly receive errors.

I found it transfers blocks of data ok if I slow the baudrate to 9600 and ALSO add
a 5-msec delay between characters sent in the block. Does this sound about like
what others have been finding, or are people getting better throughput than this
with SoftwareSerial?

Arduino #2 code is as follows now, pretty simple.

void setup()  
{
  Serial.begin(9600);
}

void loop()
{
  int ch;
  
  if( Serial.available() ) {
    Serial.write( (ch=Serial.read()) ); 
    if( ch == 'w') {
      ch=0;
      send_block(256);
    }
  }  
  delay(10);
}

void send_block(int cnt)
{
  int ch = '0';
  
  for( int i=0; i<cnt; i++) {
    Serial.write(ch++);
    delay(5);
    if( ch > 'z') ch = '0';
    if( (i % 64) == 0) {
      Serial.println(" "); delay(20);
    }
  }
  Serial.println("..done");
}

And the code for Arduino #1 is? ...

Just the basics. If this can't keep up, anything more complicated can't either.

#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3);

void setup()  
{
  Serial.begin(57600);
  Serial.println("Hello World");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
}

void loop() 
{
  if( mySerial.available() ) {
    Serial.write( mySerial.read() );
  }
  if( Serial.available() )
    mySerial.write( Serial.read() );
}

Yes but your bottleneck here is the 9600 baud.

 if( Serial.available() )
    mySerial.write( Serial.read() );

If you get a lot of data coming in at 57600 baud, it can't be cleared at 9600 baud and will eventually drop some, when the buffer fills up.

No, in this case, Arduino #2 is sending data to the #1 s.w. UART at only 9600,
and #1 is retransmitting to the Serial Monitor at 57600.

  if( mySerial.available() ) {
    Serial.write( mySerial.read() );
  }

In this case, the s.w. UART can keep up fine, as long as the delay(5) is
in the block transmit code. The problem is the s.w. UART cannot keep up
when the delay(5) is removed, or the transfer link is stepped up to 57600.

The problem is not in my code. Certainly the Arduino cpu is fast enough to
process a char and send out another one every 200 usec, or 1-msec when the
link is running at just 9600.

I was hoping someone had experience in using the s.w. UART for block transfers,
and I could get some feedback on the sort of throughput rates they are actually
getting.

You've lost me now. Where is the data coming from? What is connected to what?

I was hoping someone had experience in using the s.w. UART for block transfers, and I could get some feedback on the sort of throughput rates they are actually getting.

Let's clear up one point. Software serial sends a byte at a time. SoftwareSerial::write() sends one byte, and doesn't return until it has done that. So sending more than one is just the same as sending one, more times.

However, and this is important, SoftwareSerial::write() turns interrupts off, to get the timing right. So if you are trying to send multiple bytes, and receive multiple bytes at the send time (eg. from hardware serial) then the incoming bytes are likely to be lost due to the interrupt not being serviced in time. And in particular if you use a slow baud rate (as you are) then the interrupts are off for much longer.

So the throughput through SoftwareSerial is OK, as such. The side-effect is what is causing the trouble.

If you look at the original post, you'll see that Ard#2 is sending a block of chars
at 9600 baud with a delay(5) between each char.

Ard#1 is not transmitting anything "back to Ard#2" when it is receiving from
Ard#2. It is receiving a char every 5-msec on the s.w. UART at 9600, and
retransmitting the char to the PC at 57600 via the h.w. UART. It only sends
chars to Ard#2 when something is typed at the PC keyboard.

Ard#1:

void loop
{
  if( mySerial.available() ) {
    Serial.write( mySerial.read() );
  }
  if( Serial.available() )
    mySerial.write( Serial.read() );
}

Fairly extensive testing reveals that SoftwareSerial is indeed somewhat sensitive to what else is happening. These two sketches "work":

Arduino #1:

#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3);

void setup()  
{

  Serial.begin (115200);
  Serial.println("Hello World");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
}

char buf [500];
int pos = 0;

void loop() 
{
  while( mySerial.available() ) {
    if (pos < sizeof buf)
      buf [pos++] = mySerial.read ();
  }
  
  if( Serial.available() )
    mySerial.write( Serial.read() );
    
  if (pos > 300)
    {
    for (int i = 0; i < pos; i++)
      Serial.write (buf [i]);
    pos = 0;  
    }
}

Arduino #2:

void setup()  
{
  Serial.begin(9600);
}

void loop()
{
  int ch;
  
  if( Serial.available() ) {
    Serial.write( (ch=Serial.read()) ); 
    if( ch == 'w') {
      ch=0;
      send_block(256);
    }
  }  
}

void send_block(int cnt)
{
  int ch = '0';
  
  for( int i=0; i<cnt; i++) {
    if( (i % 64) == 0)
      Serial.println(); 
    Serial.write(ch++);
    if( ch > 'z') ch = '0';
  }
  Serial.println(); 
  Serial.println("..done");
}

It doesn't seem to work at much higher baud rates. I managed to get rid of the delays though.

Since the receiving end (Arduino #1) is using SoftwareSerial it is sensitive to interrupts being turned off, or indeed if it happens to be doing something else at the time (like sending something). I managed to get the block to appear alright (the first time anyway) by buffering up the read, and then sending when the block is over 300 bytes full. (Try doing "w" twice).

This is basically the disadvantage of "software" UARTs. They are very reliant on things happening when they should. I suggest either redesigning so you can use the hardware serial, or use another method (eg. I2C). Or, get a Mega that has more than one hardware serial port. If you just want to send data from one Uno to another, I2C is certainly a viable alternative, if the cable run is reasonably short.

This is funny. See the test code below that I've been trying, virtually identical
line for line to what you posted, LOL.

After 2 wasted days, I've about decided that SoftwareSerial really isn't very much
good for serious comms. Maybe if you use short block sizes, low baud rates
and some sort of software handshake for flow control.

I'm surprised. For something slow like 9600 baud or so, you should be able to get
rock-solid performance using a hardware interrupt on the Rx pin to trigger, and
timer interrupts to sample the following bits. Only has to interrupt every 104
usec, that's a snap. Unfortunately, I don't write AVR assembler.

Someone knowledgeable in assembler should write a more finely tuned
SoftwareSerial that's more bulletproof. I think the current library tries to be
too many things to too many people, so it's not well targeted for real
performance.

My original purpose was to control an existing RS232-based coprocessor chip,
so I2C/etc was not of interest. Thanks for the help. I'm surprised all this
hassle isn't more general knowledge.

#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3);

void setup()  
{
  Serial.begin(57600);
  Serial.println("Hello World");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
}

int buff[256];

void loop() 
{
  test2();  
}

void test2()
{
  int i, cnt=0;
  
  while( 1 ) {
    if( mySerial.available() ) {
      while( (cnt < 256) && mySerial.available() ) {
        buff[cnt++] = mySerial.read();
      }
      if( cnt > 0) {
        Serial.println("...");
        for( i=0; i<cnt; i++) {
          Serial.write( buff[i] );
        }
        cnt=0;
      }
    }
    if( Serial.available() )
      mySerial.write( Serial.read() );
  }
}

oric_dan(333):
Only has to interrupt every 104 usec, that's a snap.

Not quite, and that's the problem (BTW assembler won't help much if at all).

It uses a pin change interrupt to detect the start bit (and indeed, each bit is 104 uS apart). Then it stays in the ISR with interrupts disabled while it waits for all 8 data bits, and the stop bit, to arrive, assemble them, and store them. So interrupts are turned off for 1042 uS while it does that. And if you are then transmitting via hardware serial an interrupt is queued to take care of that, when interrupts are turned back on again. And probably you have a timer interrupt queuing up as well.

So at the end of the first byte it has another one on its heels, plus these queued interrupts (particularly the ones you are causing by doing a Serial.write).

Things are tight, and I imagine the timing is starting to drift a bit. It services the second serial byte just a few microseconds slow. And so on. And if you have a continuous batch it doesn't get a chance to catch up. Hence after 20 or so you start getting garbage.

But if you use hardware serial, things are much better. The dedicated hardware takes care of all the 10 bits, and causes an interrupt once, when the entire byte has arrived.

It uses a pin change interrupt to detect the start bit (and indeed, each bit is 104 uS apart). Then it stays in the ISR with interrupts disabled while it waits for all 8 data bits, and the stop bit, to arrive, assemble them, and store them. So interrupts are turned off for 1042 uS while it does that. And if you are then transmitting via hardware serial an interrupt is queued to take care of that, when interrupts are turned back on again. And probably you have a timer interrupt queuing up as well.

It's obviously not very well put together. What I was saying is, if you have a timer
interrupt going off every 104-usec after the initial interrupt, your ISR is only a few
lines of code and you only need to have interrupts disabled for a few cycles with
each received bit. I could whip this together in PIC assembler in a short while [but
most PICs don't have a timer to spare, :-)].

Seems to me that having a really well built s.w. UART would be a huge plus
for Arduino. Allocate one timer, plus pin 3 for RX h.w. interrupt. Would be
worth the co-opting of one timer.

It's obviously not very well put together.

Let us know when YOUR version is ready to test, then.

Paul Stoffgren (sp?) put together AltSoftwareSerial to address some of your concerns. He didn't get much response. Likely, this is because most people understand the limitations of software serial data processing much better than you, and accept that high speed serial communication requires that there be hardware support for it.

Then it stays in the ISR with interrupts disabled while it waits for all 8 data bits, and the stop bit, to arrive, assemble them, and store them. So interrupts are turned off for 1042 uS while it does that.

most people understand the limitations of software serial data processing much better than you

Duh. ????????????

Anything that turns off interrupts for 1042 usec is not very well built. The very
first rule you learn about writing interrupt service routines is:

Rule #1: get in, do the minimum processing necessary, and return control back
to the main program.

And Rules #2 and #3 are probably:

Rule #2: get in, do the minimum processing necessary, and return control back
to the main program.

Rule #3: get in, do the minimum processing necessary, and return control back
to the main program.

??????????????

oric_dan(333):
I could whip this together in PIC assembler in a short while [ but ] most PICs don't have a timer to spare, :-)].

Well, as PaulS says, feel free to show us how it's done. However you are likely to hit at least one barrier, which you acknowledge. Whilst the Atmega328 has 3 timers, whether they are "spare" in a given application would remain to be seen.

Already people complain that the use of the pin change interrupts makes other libraries (which also use pin change interrupts) not work.

The other issue is that at 57600 baud (I believe the highest speed the SoftwareSerial supports) you will have an interrupt every 17.3 uS. An interrupt takes around 3 uS to set up and tear down, so you have 14 uS left to do the processing of that particular bit. I don't know for sure how long that would take, but you have to read the port, maybe invert the bit, shift it into a byte, and put that somewhere, and check if you have 8 bits yet. Maybe 5 uS? Maybe less.

So you have replaced interrupts being off all the time to most of the time. And then if another interrupt gets in first (like a timer, or sending a byte somewhere else) then the resulting delay might just mean you miss one of your bits. And then you are back to where you are now.

You don't need assembler, after all, everything ends up in machine code, either C code or assembler code.

This morning, I downloaded and tried the AltSoftSerial library that Paul mentioned,
and it worked perfectly well the first time, using the simple 4-line Ard#1 code.
Completely unlike SoftwareSerial - which I've totally given up on as unusable.
[sorry to whoever spent the time writing it].

As regards PICs, well I did say I don't know AVR assembler. Thinking more about
it, it would probably be usable up to about 9600, even in the face of competing
interrupts, like h.w. UART, getting in just before time to sample the s.w.
UART bits.

Likewise, I imagine AltSoftSerial will suffer if there is a lot of h.w. UART traffic.

For that matter, I've dealt with competing interrupts before. If you're writing
all of your own [assembler] code, whenever an interrupt occurs, you can actually
interpose a couple of checks on each interrupt-flag within the ISR before exiting,
and place the more critical IF checks [ie, s.w. UART] first and last, and it radically
cuts down on the save-restore context overhead. You might lose as few as only 4-5
clocks, which is < 1% error at 9600.

I would have to rewrite the AVR h.w. UART Serial.xx interrupt ISRs to do it well.

Glad it works for you. If found a copy here, if anyone is interested:

http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

As it says on that page "it consumes a hardware timer" as I warned it would. So, horses for courses. If you need the extra speed/accuracy, and don't need that timer, sounds like this is the way to go.

That's the same link I found - forgot to post it previously. I did mention
on the other SoftwareSerial thread that it uses Timer1.

I've been playing around some more with the AltSoftSerial library. It seems to
work pretty well with my coprocessor chip up to 38400, but shows a lot of
errors at 57600, and crashes completely at 115200. So, all in all, it's a huge
improvement over SoftwareSerial.

The UNO is running the simple 4-line loop from before here, and is talking to
the coprocessor on pins 8,9 rather than Arduino#2.

The coprocessor has a mode where it immediately echos back any chars received,
so that is an acid test on how well AltSoftSerial does transmit and receive
simultaneously. In the results below, when in this mode, the coprocessor echos
back a prompt char, '>', when you send a CR. There is also an eeprom dump
shown, which basically sends about 800 ASCII chars in a single transmission, and
that works perfectly at 38400.

"Hello World" is the UNO signon msg, the other is the coprocessor. I had to edit the
data received at 57600 and 115200, as there were some unprintable chars in there
that crashed the Arduino Forum preview routine.

38400:
-----
Hello World
>
>v
02.02 Oxxxxx BCP28-MSC (c)2005
>                                <--- (CR's entered)
>
>cs
>z
02.02 Oxxxxx BCP28-MSC (c)2005
>
>cd
00: 4D 53 43 43 32 30 20 76 02 02 2F FF FF FF FF FF
10: A5 5A 00 07 22 2F FC BF 00 00 0C C0 C4 00 00 06
20: 80 02 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
30: 00 03 00 00 2C 90 FF FF FF FF FF FF FF FF FF FF
40: 06 28 10 06 28 10 05 DC 10 05 DC 10 05 DC 10 05
50: DC 10 05 DC 10 05 DC 10 05 DC 10 05 DC 10 05 DC
60: 10 05 DC 10 05 DC 10 05 DC 10 05 DC 10 05 DC 10
70: 05 DC 10 05 DC 10 05 DC 10 05 DC 10 FF FF FF FF
80: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
90: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
A0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
B0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
C0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
D0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
E0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
F0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
>

57600:
-----
Hello World
?&>*>.*>*>v*02.02 Oxxxxx BCP28-MSC (c)2005>f}Ã

115200:
------
Hello World
W#$%$%^&%$%^&*     <---(no signon msg received)

Switching over to AltSoftSerial solved the corrupt data I received from my Bluetooth GPS. The BT-module (a "HC-05") is connected to the AltSoftSerial, and the Serial sends the received data to the Serial monitor.

I would like to thank all participants in this discussion for the increased insight you have given me.

My next issues are, how to pair with with the BT-WiiMote and how to make my program failsafe, because the HC-05 does not always allow disconnection and does not stop inquiring, even if I tell it to. But that's material for another subject.

/Dick

Switching over to AltSoftSerial solved the corrupt data I received from my Bluetooth GPS.

Good to hear your experiences. About time this thread was going on, there were
a couple of other threads regarding use of the standard SoftSerial libraries, and
they seemed to have no problems. Whereas, both myself and Nick clearly had the
same problem with dropped characters when running the most simplistic of code,
as you apparently did.

I have no idea why the others seemed to have no troubles.

I can't help you with the BT Stuff.