NewSoftSerial 8 for 8 (MHz)

Posted on April 23rd, 2009 at 9:08 pm by


Over the last couple of weeks I’ve spent a fair number of hours in front of the logic analyzer, and the result is NewSoftSerial version 8. The major delta in this release is support for 8MHz processors.

The half-speed clock makes accurate serial reception considerably more difficult, but NewSoftSerial 8 still manages to transmit with 100% reliability up to 57.6K baud and receive up to 14.4K baud on the Arduino Pro.  Reception at rates up to 38.4K baud is 99+% reliable.  For a better understanding of why the slower processor caps RX at a relatively low baud rate, and for a trick to significantly improve this, read on.

Flush

The new library supports the flush() method, which works the same way that HardwareSerial’s flush() does.

      void flush(); // discard the contents of the RX buffer.

PROGMEM use

NewSoftSerial 8 has been reorganized to move some of the logic into a PROGMEM-based lookup table. You may encounter the following spurious warning message when you build your first NewSoftSerial 8 application:

    NewSoftSerial.cpp:68: warning: only initialized variables
    can be placed into program memory area

Be assured that this message is completely harmless. As soon as I figure out how to remove it, I will.

Potential trouble spots

There are two problem areas that affect accuracy and cap RX speeds in all software serial libraries:

  • interference from other interrupts in the system
  • RX interrupt floods/bursts

Before I discuss these, an introduction to interrupt-driven serial reception may be in order.

Interrupt-driven RX

It all begins when the Arduino software RX pin, normally high, drops low. This is the “start bit” – the signal from the transmitter that bits of data are about to arrive in a sequence of pulses whose width is determined by the agreed-upon baud rate.  The pin’s state change triggers an interrupt which launches the NewSoftSerial recv() routine. Through a series of eight precisely timed samples, recv() assembles the new byte and stores it in a buffer before releasing the interrupt.

The top waveform in the image below shows the arrival of the letter ‘a’ (hex 61). The RX signal drops low (start bit) and then the data bits arrive, in order from lowest to highest, 1, 0, 0, 0, 0, 1, 1, 0, followed by a high “stop” bit. At each interval, the recv() routine samples the pin and records the bit value. The second waveform demonstrates this sampling process in action, one sample per falling edge.

serial-good

Fig 1. The letter ‘a’ (hex 61) being sampled correctly

If all goes well, the received byte is identical to the one transmitted. And this is usually the case, because once the pin-change interrupt fires, NewSoftSerial’s precisely tuned recv() routine gains absolute control of the processor. Interrupts are disabled during the sampling, so there is no danger that a competing interrupt will corrupt the process.

The problem is that if another interrupt handler is already active when the start bit arrives, then NewSoftSerial’s pin-change handler is necessarily delayed until the competing routine exits.  If the delay is too long, the sampling routine may read the incoming byte incorrectly, causing it to be mangled or even lost.

When you encounter RX corruption or loss at high baud rates, the most common culprit is Arduino’s own timer overflow interrupt handler – the routine responsible for millis() and delay().  Consider: on an 8MHz Arduino the timer interrupt lasts a full 20µs, compared to only about 70µs for the entire width of a bit transmitted at 14.4K baud. If the timer interrupt fires at precisely the “wrong” time, i.e. just as a new start bit arrives, important processing may get delayed long enough to cause an error.

The picture below shows just such an event taking place. Comparing to the figure above, you’ll see that the system timer interrupt fires just before the “Serial” line goes low, shifting the sampling process to the right just enough to distort the sampled byte.

serial-badFig 2. A timer interrupt fatally delaying the sampling sequence

In cases like this, you’ll experience an invalid byte read. On 16MHz processors, the experimentally determined failure rate is quite low – less than 0.5% – and is only seen at the very highest supported baud rates (57600 and 38400) .  But at 8MHz, the problem is exacerbated by the fact that the timer tick interrupt takes twice as long to execute.  Not only is there a greater likelihood that the interrupts will collide unhappily, but because the timer tick is longer, the damage done when there is a collision is proportionally greater.  What this means is that NewSoftSerial running at 8MHz receives data with 100% accuracy only at baud rates less than half those which work reliably at 16MHz. If you want perfect accuracy with an 8MHz clock, stick to rates of 19200 or 14400 baud or less.  Or…

Workaround: disabling the timer

…if your application can do without the system timer, it is possible to significantly improve RX reliability by turning it off.  NewSoftSerial 8 provides a new method to disable/enable the clock by masking off its interrupt:

      static void enable_timer0(bool enable);

While disabling the timer more than doubles the speeds at which 8MHz NewSoftSerial can reliably receive data, it is not recommended that you do so unless you understand the ramifications. Don’t do it unless you really need perfect reliability at speeds greater than 14400 baud and can live without millis() and delay().  Many applications can absorb the low failures rates (<1%) NewSoftSerial experiences at higher speeds.  For example, for serial GPS devices, TinyGPS automatically filters out NMEA sentences that fail the checksum.  This may be sufficient for your app… or not.

Flooding/Bursting

Another situation that can adversely affect data reception is when a peer sends rapid bursts of data (at any baud rate), flooding the Arduino. The bytes arrive one after another so quickly that NewSoftSerial stays constantly in the RX interrupt, filling the receive queue but never allowing it to be drained. This generates an overflow condition, which can be detected by examining NewSoftSerial’s overflow() routine – if your program can ever call it.

Even if it doesn’t trigger an overflow, a flood of bytes can cause problems at high speeds, because tiny errors in the timings that would otherwise have gone unnoticed accumulate.

The best way to avoid problems relating to “flooding” is to prevent them. Eventually I hope to add support for RTS/CTS flow control to NewSoftSerial. In the meanwhile, keep away from devices, especially high speed ones, that generate floods of data.

Timer based options?

I’ve had several discussions recently about whether it would be possible to implement a timer-based (as opposed to pin-change-based) software serial device in Arduino. Such a library would theoretically allow a number of simultaneously operating serial devices to coexist, because the interrupt times would be much shorter. However, based on my recent studies with the logic analyzer, I have begun to doubt whether a library that worked at reasonably high speeds could be built. There is just too much interference from other interrupts in the system.

Conclusions

Software serial devices are best running at lower baud rates, especially on 8MHz processors. If you need multiple reliable, high speed serial interfaces, consider ATmega328-based Duemilanove, which sports three hardware serial ports.

Future Directions

As I mentioned earlier, I will be working soon on implementing an RTS/CTS flow control mechanism to improve the interface with serial devices that tend to transmit in bursts.

The next version of NewSoftSerial will also likely support optional signal inversion.

A couple of people have also expressed interest in being able to more precisely configure the data and parity bits of serial protocol, especially, for example, the relatively popular “E/7/1”. It’s hard to assess exactly how desirable this is.  Who would support increasing the NewSoftSerial footprint by a few bytes in exchange for this kind of configurability?

Download

You can get the new version, as usual, in the “Libraries” section at NewSoftSerial.

Mikal Hart