Rainboard NewSoftSerial
During testing an issue appeared while using version 11b of NewSoftSerial.
In order to make sure everything was going correctly, the arduino was set up to echo back any bytes that it received via the software serial connection. This presented an error. In total, 16 bytes were send in one burst and 15 bytes were received back.
f0:7f:7f:03:00:00:7f:01:7f:00:00:02:00:7f:00:f7 // Data Sent
f0:7f:7f:03:80:80:bf:80:bf:80:80:81:80:01:dd //Data received
As you can see by the data received the first few bytes work fine, but as time goes on the bytes are not correct but still seem to be in some sort of order.
Lets look closer at the binary version of the data
Byte | Sent Hex | Received Hex | Sent Binary | Received Binary |
---|---|---|---|---|
1 | 0xF0 | 0XF0 | 11110000 | 11110000 |
2 | 0x7F | 0X7F | 01111111 | 01111111 |
3 | 0x7F | 0X7F | 01111111 | 01111111 |
4 | 0x03 | 0X03 | 00000011 | 00000011 |
5 | 0x00 | 0X80 | 00000000 | 10000000 |
6 | 0x00 | 0X80 | 00000000 | 10000000 |
7 | 0x7F | 0XBF | 01111111 | 10111111 |
8 | 0x01 | 0X80 | 00000001 | 10000000 |
9 | 0x7F | 0XBF | 01111111 | 10111111 |
10 | 0x00 | 0X80 | 00000000 | 10000000 |
11 | 0x00 | 0X80 | 00000000 | 10000000 |
12 | 0x02 | 0X81 | 00000010 | 10000001 |
13 | 0x00 | 0X80 | 00000000 | 10000000 |
14 | 0x7F | 0X01 | 01111111 | 00000001 |
15 | 0x00 | 0XDD | 00000000 | 11011101 |
16 | 0xF7 | 11110111 |
Everything up to bytes 5 is identical. Lets layout the binary in a stream.
| byte5| byte 6| byte 7| byte 8| byte 9| byte10| byte11| byte12| byte13| byte14| byte15| byte16|
000000000000000001111111000000010111111100000000000000000000001000000000011111110000000011110111 //sent
1000000010000000101111111000000010111111100000001000000010000001100000000000000111011101 //recv
For byte 5 and bytes 6 the only problem is that the first bit of each packet has been changed to a 1 from a 0. Byte 7 received then begins with a 1 when it should also start with a 0. However, if we were to erase the first bit of byte 7, things start lining up again:
| byte5| byte 6| byte 7| byte 8| byte 9| byte10| byte11| byte12| byte13| byte14| byte15| byte16|
000000000000000001111111000000010111111100000000000000000000001000000000011111110000000011110111 //sent
100000001000000001111111000000010111111100000001000000010000001100000000000000111011101 //recv
1 was erased
Now bytes 7, 8, and 9 are correct but start going bad at byte 10 again. At this point we can assume that maybe the timing of reading data is slightly off (as we are using software not hardware). During Serial Communication there are actually 10 bits sent per byte. The first or "start" bit signals the start of a transmitting byte and occurs when the line goes from 1 to 0 and the last bit (stop bit) is a 1. If the process of grabbing the data off the line was slightly slow, it would be possible to accidentally read the stop bit as if it were actually part of the data. At first you might think that if this was the case the bad 1 value would occur at the end of the byte. However, the serial line is transferring the bits with the least significant bit (lsb) first.
For example:
Sending 0x00 // hex
Byte: 00000000 //binary
With Start and stop Bits:0000000001 //
First we change the serial line from 1 to 0 this causes the receiver to know a byte is coming:
0000000001
|
Then we start sending data from right to left (not including the stop byte)
0000000001
|
0000000001
|
0000000001
|
0000000001
|
0000000001
|
0000000001
|
0000000001
|
0000000001
|
0000000001
|
Another example:
If we number all the bits: 12345678
We send the data like so: Start Bit 8 7 6 5 4 3 2 1 Stop Bit
So the end result that if the timing is off and the stop bit is accidentally read, it will cause the most significant bit (msb) of the byte to be a 1.
Solution
The solution is to alter the timing of the NewSoftSerial parameters to work slightly faster than they are.
In our particular example we are operating at 57600 baud on a 16Mhz processor. If we look in the cpp file for NewSoftSerial library we can find this table.
static const DELAY_TABLE PROGMEM table[] =
{
// baud rxcenter rxintra rxstop tx
{ 115200, 1, 17, 17, 12, },
{ 57600, 10, 37, 37, 33, },
{ 38400, 25, 57, 57, 54, },
{ 31250, 31, 70, 70, 68, },
{ 28800, 34, 77, 77, 74, },
{ 19200, 54, 117, 117, 114, },
{ 14400, 74, 156, 156, 153, },
{ 9600, 114, 236, 236, 233, },
{ 4800, 233, 474, 474, 471, },
{ 2400, 471, 950, 950, 947, },
{ 1200, 947, 1902, 1902, 1899, },
{ 300, 3804, 7617, 7617, 7614, },
};
There are three timings we are interested in.
rxcenter: This is the time it takes to go from the edge of the start bit to about half way through the bit (in order to center reading).
rxintra: This is the time to wait between sampling bits of the data
rxstop: This is the time to wait for the stop bit to finish being sent.
In order to speed things up slightly the rxcenter has been modified to be 8 so the bits are caught slightly closer to the start and the rxstop has been altered so we don't take quite as long waiting for the stop bit. This solves the problem by kicking out of the interrupt slightly quicker which allows it to be called again faster in situations where a lot of data is being sent all at once.
static const DELAY_TABLE PROGMEM table[] =
{
// baud rxcenter rxintra rxstop tx
{ 115200, 1, 17, 17, 12, },
{ 57600, 8, 37, 30, 33, }, //Changed rxCenter to 8 and rxstop to 30
{ 38400, 25, 57, 57, 54, },
{ 31250, 31, 70, 70, 68, },
{ 28800, 34, 77, 77, 74, },
{ 19200, 54, 117, 117, 114, },
{ 14400, 74, 156, 156, 153, },
{ 9600, 114, 236, 236, 233, },
{ 4800, 233, 474, 474, 471, },
{ 2400, 471, 950, 950, 947, },
{ 1200, 947, 1902, 1902, 1899, },
{ 300, 3804, 7617, 7617, 7614, },
};
The new values were just found by trial and error. Perhaps with some more time and greater insight more accurate values could be used.