Skip to content

Commit 1906d2f

Browse files
authored
In timing critical sections, use direct register access for digital read/write. (#231)
* In timing critical sections, use direct register access for digital read/write. Fixes #230 * ESP8266 specific register optimization. * Polymorphism over parameter evaluation. * It's OK to load lazyDelay from ROM on request. * Minor release, use port macros and new bit pattern test * Fix for specifics of ESP8266 UART's swap.
1 parent d6af416 commit 1906d2f

File tree

5 files changed

+130
-30
lines changed

5 files changed

+130
-30
lines changed

examples/bitpattern/bitpattern.ino

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "SoftwareSerial.h"
2+
3+
#ifndef D5
4+
#if defined(ESP8266)
5+
#define D5 (14)
6+
#define D6 (12)
7+
#elif defined(ESP32)
8+
#define D5 (18)
9+
#define D6 (19)
10+
#endif
11+
#endif
12+
13+
SoftwareSerial swSer;
14+
#ifdef ESP8266
15+
auto logSer = SoftwareSerial(-1, TX);
16+
auto hwSer = Serial;
17+
#else
18+
auto logSer = Serial;
19+
auto hwSer = Serial1;
20+
#endif
21+
22+
void setup() {
23+
delay(2000);
24+
#ifdef ESP8266
25+
hwSer.begin(115200, SERIAL_8N1);
26+
hwSer.swap();
27+
#else
28+
hwSer.begin(115200, SERIAL_8N1, -1, D5);
29+
#endif
30+
logSer.begin(115200);
31+
logSer.println(PSTR("\nOne Wire Half Duplex Bitpattern and Datarate Test"));
32+
swSer.begin(115200, SWSERIAL_8N1, D6, -1);
33+
swSer.enableIntTx(true);
34+
logSer.println(PSTR("Tx on hwSer"));
35+
}
36+
37+
uint8_t val = 0xff;
38+
39+
void loop() {
40+
hwSer.write((uint8_t)0x00);
41+
hwSer.write(val);
42+
hwSer.write(val);
43+
auto start = ESP.getCycleCount();
44+
int rxCnt = 0;
45+
while (ESP.getCycleCount() - start < ESP.getCpuFreqMHz() * 1000000 / 10) {
46+
if (swSer.available()) {
47+
auto rxVal = swSer.read();
48+
if ((!rxCnt && rxVal) || (rxCnt && rxVal != val)) {
49+
logSer.printf(PSTR("Rx bit error: tx = 0x%02x, rx = 0x%02x\n"), val, rxVal);
50+
}
51+
++rxCnt;
52+
}
53+
}
54+
if (rxCnt != 3) {
55+
logSer.printf(PSTR("Rx cnt error, tx = 0x%02x\n"), val);
56+
}
57+
++val;
58+
if (!val) {
59+
logSer.println("Starting over");
60+
}
61+
}

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "EspSoftwareSerial",
3-
"version": "6.15.2",
3+
"version": "6.16.0",
44
"description": "Implementation of the Arduino software serial for ESP8266/ESP32.",
55
"keywords": [
66
"serial", "io", "softwareserial"

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=EspSoftwareSerial
2-
version=6.15.2
2+
version=6.16.0
33
author=Dirk Kaar, Peter Lerup
44
maintainer=Dirk Kaar <dok@dok-net.net>
55
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.

src/SoftwareSerial.cpp

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ void SoftwareSerial::begin(uint32_t baud, SoftwareSerialConfig config,
148148
m_bitCycles = (ESP.getCpuFreqMHz() * 1000000UL + baud / 2) / baud;
149149
m_intTxEnabled = true;
150150
if (isValidRxGPIOpin(m_rxPin)) {
151+
m_rxReg = portInputRegister(digitalPinToPort(m_rxPin));
152+
m_rxBitMask = digitalPinToBitMask(m_rxPin);
151153
m_buffer.reset(new circular_queue<uint8_t>((bufCapacity > 0) ? bufCapacity : 64));
152154
if (m_parityMode)
153155
{
@@ -162,6 +164,10 @@ void SoftwareSerial::begin(uint32_t baud, SoftwareSerialConfig config,
162164
}
163165
}
164166
if (isValidTxGPIOpin(m_txPin)) {
167+
#if !defined(ESP8266)
168+
m_txReg = portOutputRegister(digitalPinToPort(m_txPin));
169+
#endif
170+
m_txBitMask = digitalPinToBitMask(m_txPin);
165171
m_txValid = true;
166172
if (!m_oneWire) {
167173
pinMode(m_txPin, OUTPUT);
@@ -302,44 +308,69 @@ int SoftwareSerial::available() {
302308
return avail;
303309
}
304310

305-
void IRAM_ATTR SoftwareSerial::preciseDelay(bool sync) {
306-
if (!sync)
311+
void SoftwareSerial::lazyDelay() {
312+
// Reenable interrupts while delaying to avoid other tasks piling up
313+
if (!m_intTxEnabled) { restoreInterrupts(); }
314+
const auto expired = ESP.getCycleCount() - m_periodStart;
315+
const int32_t remaining = m_periodDuration - expired;
316+
const int32_t ms = remaining > 0 ? remaining / 1000L / static_cast<int32_t>(ESP.getCpuFreqMHz()) : 0;
317+
if (ms > 0)
307318
{
308-
// Reenable interrupts while delaying to avoid other tasks piling up
309-
if (!m_intTxEnabled) { restoreInterrupts(); }
310-
const auto expired = ESP.getCycleCount() - m_periodStart;
311-
const int32_t remaining = m_periodDuration - expired;
312-
const int32_t ms = remaining > 0 ? remaining / 1000L / static_cast<int32_t>(ESP.getCpuFreqMHz()) : 0;
313-
if (ms > 0)
314-
{
315-
delay(ms);
316-
}
317-
else
318-
{
319-
optimistic_yield(10000UL);
320-
}
319+
delay(ms);
320+
}
321+
else
322+
{
323+
optimistic_yield(10000UL);
321324
}
322-
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) {}
323325
// Disable interrupts again if applicable
324-
if (!sync && !m_intTxEnabled) { disableInterrupts(); }
326+
if (!m_intTxEnabled) { disableInterrupts(); }
327+
preciseDelay();
328+
}
329+
330+
void IRAM_ATTR SoftwareSerial::preciseDelay() {
331+
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) {}
325332
m_periodDuration = 0;
326333
m_periodStart = ESP.getCycleCount();
327334
}
328335

329336
void IRAM_ATTR SoftwareSerial::writePeriod(
330337
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) {
331-
preciseDelay(true);
338+
preciseDelay();
332339
if (dutyCycle)
333340
{
334-
digitalWrite(m_txPin, HIGH);
341+
#if defined(ESP8266)
342+
if (16 == m_txPin) {
343+
GP16O = 1;
344+
}
345+
else {
346+
GPOS = m_txBitMask;
347+
}
348+
#else
349+
*m_txReg |= m_txBitMask;
350+
#endif
335351
m_periodDuration += dutyCycle;
336-
if (offCycle || (withStopBit && !m_invert)) preciseDelay(!withStopBit || m_invert);
352+
if (offCycle || (withStopBit && !m_invert)) {
353+
if (!withStopBit || m_invert) {
354+
preciseDelay();
355+
} else {
356+
lazyDelay();
357+
}
358+
}
337359
}
338360
if (offCycle)
339361
{
340-
digitalWrite(m_txPin, LOW);
362+
#if defined(ESP8266)
363+
if (16 == m_txPin) {
364+
GP16O = 0;
365+
}
366+
else {
367+
GPOC = m_txBitMask;
368+
}
369+
#else
370+
*m_txReg &= ~m_txBitMask;
371+
#endif
341372
m_periodDuration += offCycle;
342-
if (withStopBit && m_invert) preciseDelay(false);
373+
if (withStopBit && m_invert) lazyDelay();
343374
}
344375
}
345376

@@ -568,7 +599,7 @@ void SoftwareSerial::rxBits(const uint32_t isrCycle) {
568599

569600
void IRAM_ATTR SoftwareSerial::rxBitISR(SoftwareSerial* self) {
570601
uint32_t curCycle = ESP.getCycleCount();
571-
bool level = digitalRead(self->m_rxPin);
602+
bool level = *self->m_rxReg & self->m_rxBitMask;
572603

573604
// Store level and cycle in the buffer unless we have an overflow
574605
// cycle's LSB is repurposed for the level bit
@@ -590,7 +621,7 @@ void IRAM_ATTR SoftwareSerial::rxBitSyncISR(SoftwareSerial* self) {
590621

591622
// Store level and cycle in the buffer unless we have an overflow
592623
// cycle's LSB is repurposed for the level bit
593-
if (digitalRead(self->m_rxPin) != level)
624+
if (static_cast<bool>(*self->m_rxReg & self->m_rxBitMask) != level)
594625
{
595626
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true);
596627
level = !level;

src/SoftwareSerial.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ class SoftwareSerial : public Stream {
102102
/// @param invert true: uses invert line level logic
103103
/// @param bufCapacity the capacity for the received bytes buffer
104104
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
105-
/// bit receive buffer, a suggested size is bufCapacity times the sum of
106-
/// start, data, parity and stop bit count.
105+
/// bit receive buffer, a suggested size is bufCapacity times the sum of
106+
/// start, data, parity and stop bit count.
107107
void begin(uint32_t baud, SoftwareSerialConfig config,
108108
int8_t rxPin, int8_t txPin, bool invert,
109109
int bufCapacity = 64, int isrBufCapacity = 0);
@@ -211,9 +211,11 @@ class SoftwareSerial : public Stream {
211211
using Print::write;
212212

213213
private:
214-
// If sync is false, it's legal to exceed the deadline, for instance,
214+
// It's legal to exceed the deadline, for instance,
215215
// by enabling interrupts.
216-
void preciseDelay(bool sync);
216+
void lazyDelay();
217+
// Synchronous precise delay
218+
void preciseDelay();
217219
// If withStopBit is set, either cycle contains a stop bit.
218220
// If dutyCycle == 0, the level is not forced to HIGH.
219221
// If offCycle == 0, the level remains unchanged from dutyCycle.
@@ -237,7 +239,13 @@ class SoftwareSerial : public Stream {
237239

238240
// Member variables
239241
int8_t m_rxPin = -1;
242+
volatile uint32_t* m_rxReg;
243+
uint32_t m_rxBitMask;
240244
int8_t m_txPin = -1;
245+
#if !defined(ESP8266)
246+
volatile uint32_t* m_txReg;
247+
#endif
248+
uint32_t m_txBitMask;
241249
int8_t m_txEnablePin = -1;
242250
uint8_t m_dataBits;
243251
bool m_oneWire;

0 commit comments

Comments
 (0)