Skip to content

Commit 2c5ce57

Browse files
committed
Adds support for parity bit, including mark / space, and 2 stop bits.
Parity errors must be checked and handled in user code, the API provides access to parity for write() and read().
1 parent 9bb7388 commit 2c5ce57

File tree

4 files changed

+265
-61
lines changed

4 files changed

+265
-61
lines changed

README.md

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ Except at high bitrates, depending on other ongoing activity,
1212
interrupts in particular, this software serial adapter
1313
supports full duplex receive and send. At high bitrates (115200bps)
1414
send bit timing can be improved at the expense of blocking concurrent
15-
full duplex receives, with the SoftwareSerial::enableIntTx(false) function call.
15+
full duplex receives, with the ``SoftwareSerial::enableIntTx(false)`` function call.
1616

1717
The same functionality is given as the corresponding AVR library but
1818
several instances can be active at the same time. Speed up to 115200 baud
19-
is supported. Diverging from the AVR SoftwareSerial class, the constructor takes
20-
no arguments, instead the begin() function handles pin assignments and logic inversion.
19+
is supported. Besides a constructor compatible to the AVR SoftwareSerial class,
20+
and updated constructor that takes no arguments exists, instead the ``begin()``
21+
function can handle the pin assignments and logic inversion.
2122
It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer.
2223
This way, it is a better drop-in replacement for the hardware serial APIs on the ESP MCUs.
2324

@@ -26,12 +27,11 @@ ongoing, there will be some inexactness in interrupt timings. This may
2627
lead to inevitable, but few, bit errors when having heavy data traffic
2728
at high baud rates.
2829

29-
3030
## Resource optimization
3131

3232
The memory footprint can be optimized to just fit the amount of expected
3333
incoming asynchronous data.
34-
For this, the SoftwareSerial constructor provides two arguments. First, the
34+
For this, the ``SoftwareSerial`` constructor provides two arguments. First, the
3535
octet buffer capacity for assembled received octets can be set. Read calls are
3636
satisfied from this buffer, freeing it in return.
3737
Second, the signal edge detection buffer of 32bit fields can be resized.
@@ -41,7 +41,7 @@ to assemble received octets, thus promoting completed octets to the octet
4141
buffer, freeing fields in the edge detection buffer.
4242

4343
Look at the swsertest.ino example. There, on reset, ASCII characters ' ' to 'z'
44-
are send. This happens not as a block write, but in single write calls per
44+
are sent. This happens not as a block write, but in a single write call per
4545
character. As the example uses a local loopback wire, every outgoing bit is
4646
immediately received back. Therefore, any single write call causes up to
4747
10 fields - depending on the exact bit pattern - to be occupied in the signal
@@ -57,43 +57,67 @@ data until the next read call.
5757
For the swsertest.ino example, this results in the following optimized
5858
constructor arguments to spend only the minimum RAM on buffers required:
5959

60-
The octet buffer capacity (bufCapacity) is 93 (91 characters net plus two tolerance).
61-
The signal edge detection buffer capacity (isrBufCapacity) is 10, as each octet has
60+
The octet buffer capacity (``bufCapacity``) is 93 (91 characters net plus two tolerance).
61+
The signal edge detection buffer capacity (``isrBufCapacity``) is 10, as each octet has
6262
10 bits on the wire, which are immediately received during the write, and each
6363
write call causes the signal edge detection to promote the previously sent and
6464
received bits to the octet buffer.
6565

6666
In a more generalized scenario, calculate the bits (use message size in octets
6767
times 10) that may be asynchronously received to determine the value for
68-
isrBufCapacity in the constructor. Also use the number of received octets
69-
that must be buffered for reading as the value of bufCapacity.
68+
``isrBufCapacity`` in the constructor. Also use the number of received octets
69+
that must be buffered for reading as the value of ``bufCapacity``.
7070
The more frequently your code calls write or read functions, the greater the
71-
chances are that you can reduce the isrBufCapacity footprint without losing data,
71+
chances are that you can reduce the ``isrBufCapacity`` footprint without losing data,
7272
and each time you call read to fetch from the octet buffer, you reduce the
7373
need for space there.
7474

75+
## SoftwareSerialConfig and parity
76+
The configuration of the data stream is done via a ``SoftwareSerialConfig``
77+
argument to ``begin()``. Word lengths can be set to between 5 and 8 bits, parity
78+
can be N(one), O(dd) or E(ven) and 1 or 2 stop bits can be used. The default is
79+
``SWSERIAL_8N1`` using 8 bits, no parity and 1 stop bit but any combination can
80+
be used, e.g. ``SWSERIAL_7E2``. If using EVEN or ODD parity, any parity errors
81+
can be detected with the ``peekParityError()`` function. Note that parity
82+
checking must be done before ``read()``, as the parity information is removed
83+
from the buffer when reading the corresponding byte.
84+
85+
To allow flexible 9-bit and data/addressing protocols, the additional parity
86+
modes MARK and SPACE are also available. Furthermore, the parity mode can be
87+
individually set in each call to ``write()``.
88+
89+
This allows a simple implementation of protocols where the parity bit is used to
90+
distinguish between data and addresses/commands ("9-bit" protocols). First set
91+
up SoftwareSerial with parity mode SPACE, e.g. ``SWSERIAL_8S1``. This will add a
92+
parity bit to every byte sent, setting it to logical zero (SPACE parity).
93+
94+
To detect incoming bytes with the parity bit set (MARK parity), use the
95+
``peekParityError()`` function. To send a byte with the parity bit set, just add
96+
``MARK`` as the second argument when writing, e.g. ``write(ch, MARK)``.
7597

7698
## Using and updating EspSoftwareSerial in the esp8266com/esp8266 Arduino build environment
7799

78-
The EspSoftwareSerial is both part of the BSP download for ESP8266 in Arduino,
100+
EspSoftwareSerial is both part of the BSP download for ESP8266 in Arduino,
79101
and it is set up as a Git submodule in the esp8266 source tree,
80-
specifically in .../esp8266/libraries/SoftwareSerial when using a Github
102+
specifically in ``.../esp8266/libraries/SoftwareSerial`` when using a Github
81103
repository clone in your Arduino sketchbook hardware directory.
82104
This supersedes any version of EspSoftwareSerial installed for instance via
83105
the Arduino library manager, it is not required to install EspSoftwareSerial
84-
for the ESP8266 separately at all.
106+
for the ESP8266 separately at all, but doing so has ill effect.
85107

86108
The responsible maintainer of the esp8266 repository has kindly shared the
87109
following command line instructions to use, if one wishes to manually
88110
update EspSoftwareSerial to a newer release than pulled in via the ESP8266 Arduino BSP:
89111

90112
To update esp8266/arduino SoftwareSerial submodule to lastest master:
91113

92-
```
93-
(optional, clean it:)
114+
Clean it (optional):
115+
```shell
94116
$ rm -rf libraries/SoftwareSerial
95117
$ git submodule update --init
96-
(now update it:)
118+
```
119+
Now update it:
120+
```shell
97121
$ cd libraries/SoftwareSerial
98122
$ git checkout master
99123
$ git pull

examples/loopback/loopback.ino

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ constexpr int IUTBITRATE = 64000;
3636
constexpr int IUTBITRATE = 153600;
3737
#endif
3838

39-
#if defined(ESP8266) || defined(ESP32)
40-
constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8N1;
39+
#if defined(ESP8266)
40+
constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E2;
41+
constexpr SerialConfig hwSerialConfig = SERIAL_8E2;
42+
#elif defined(ESP32)
43+
constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E2;
44+
constexpr uint32_t hwSerialConfig = SERIAL_8E2;
4145
#else
4246
constexpr unsigned swSerialConfig = 3;
4347
#endif
@@ -51,6 +55,7 @@ int txCount;
5155
int rxCount;
5256
int expected;
5357
int rxErrors;
58+
int rxParityErrors;
5459
constexpr int ReportInterval = IUTBITRATE / 8;
5560

5661
#if defined(ESP8266)
@@ -83,20 +88,20 @@ HardwareSerial& logger(Serial);
8388
void setup() {
8489
#if defined(ESP8266)
8590
#if defined(HWLOOPBACK) || defined(HWSOURCESINK) || defined(HWSOURCESWSINK)
86-
Serial.begin(IUTBITRATE);
91+
Serial.begin(IUTBITRATE, hwSerialConfig);
8792
Serial.swap();
8893
Serial.setRxBufferSize(2 * BLOCKSIZE);
89-
logger.begin(9600, swSerialConfig, -1, TX);
94+
logger.begin(9600, SWSERIAL_8N1, -1, TX);
9095
#else
9196
Serial.begin(9600);
9297
#endif
9398
#elif defined(ESP32)
9499
#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK)
95-
Serial2.begin(IUTBITRATE);
100+
Serial2.begin(IUTBITRATE, hwSerialConfig);
96101
Serial2.setRxBufferSize(2 * BLOCKSIZE);
97102
logger.begin(9600);
98103
#elif defined(HWSOURCESINK)
99-
serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6);
104+
serialIUT.begin(IUTBITRATE, hwSerialConfig, D5, D6);
100105
serialIUT.setRxBufferSize(2 * BLOCKSIZE);
101106
logger.begin(9600);
102107
#else
@@ -126,6 +131,7 @@ void setup() {
126131
txCount = 0;
127132
rxCount = 0;
128133
rxErrors = 0;
134+
rxParityErrors = 0;
129135

130136
logger.println("Loopback example for EspSoftwareSerial");
131137
}
@@ -198,13 +204,10 @@ void loop() {
198204
deadlineStart = ESP.getCycleCount();
199205
inCnt = 0;
200206
while ((ESP.getCycleCount() - deadlineStart) < (1000000 * 10 * BLOCKSIZE) / IUTBITRATE * 2 * ESP.getCpuFreqMHz()) {
201-
int avail;
202-
if (0 >= (avail = serialIUT.available())) {
203-
continue;
204-
}
205-
avail = serialIUT.readBytes(inBuf, min(avail, 2 * BLOCKSIZE));
206-
for (int i = 0; i < avail; ++i) {
207-
unsigned char r = inBuf[i];
207+
int avail = serialIUT.available();
208+
for (int i = 0; i < avail; ++i)
209+
{
210+
unsigned char r = serialIUT.read();
208211
if (expected == -1) { expected = r; }
209212
else {
210213
expected = (expected + 1) % 256;
@@ -213,25 +216,40 @@ void loop() {
213216
++rxErrors;
214217
expected = -1;
215218
}
219+
if ((serialIUT.readParity() ^ static_cast<bool>(swSerialConfig & 010)) != serialIUT.parityEven(r))
220+
{
221+
++rxParityErrors;
222+
}
216223
++rxCount;
217224
++inCnt;
218225
}
226+
219227
if (inCnt >= BLOCKSIZE) { break; }
220228
// wait for more outstanding bytes to trickle in
221229
deadlineStart = ESP.getCycleCount();
222230
}
223231

224232
const uint32_t interval = micros() - start;
225233
if (txCount >= ReportInterval && interval) {
234+
uint8_t wordBits = (5 + swSerialConfig % 4) + static_cast<bool>(swSerialConfig & 070) + 1 + ((swSerialConfig & 0300) ? 1 : 0);
226235
logger.println(String("tx/rx: ") + txCount + "/" + rxCount);
227236
const long txCps = txCount * (1000000.0 / interval);
228237
const long rxCps = rxCount * (1000000.0 / interval);
229-
logger.println(effTxTxt + 10 * txCps + "bps, "
230-
+ effRxTxt + 10 * rxCps + "bps, "
238+
logger.print(effTxTxt + wordBits * txCps + "bps, "
239+
+ effRxTxt + wordBits * rxCps + "bps, "
231240
+ rxErrors + " errors (" + 100.0 * rxErrors / (!rxErrors ? 1 : rxCount) + "%)");
241+
if (0 != (swSerialConfig & 070))
242+
{
243+
logger.println(String(" (") + rxParityErrors + " parity errors)");
244+
}
245+
else
246+
{
247+
logger.println();
248+
}
232249
txCount = 0;
233250
rxCount = 0;
234251
rxErrors = 0;
252+
rxParityErrors = 0;
235253
expected = -1;
236254
// resync
237255
delay(static_cast<uint32_t>(1000 * 10 * BLOCKSIZE / IUTBITRATE * 16));

0 commit comments

Comments
 (0)