Skip to content

Commit

Permalink
In timing critical sections, use direct register access for digital r…
Browse files Browse the repository at this point in the history
…ead/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.
  • Loading branch information
dok-net authored Apr 11, 2022
1 parent d6af416 commit 1906d2f
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 30 deletions.
61 changes: 61 additions & 0 deletions examples/bitpattern/bitpattern.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "SoftwareSerial.h"

#ifndef D5
#if defined(ESP8266)
#define D5 (14)
#define D6 (12)
#elif defined(ESP32)
#define D5 (18)
#define D6 (19)
#endif
#endif

SoftwareSerial swSer;
#ifdef ESP8266
auto logSer = SoftwareSerial(-1, TX);
auto hwSer = Serial;
#else
auto logSer = Serial;
auto hwSer = Serial1;
#endif

void setup() {
delay(2000);
#ifdef ESP8266
hwSer.begin(115200, SERIAL_8N1);
hwSer.swap();
#else
hwSer.begin(115200, SERIAL_8N1, -1, D5);
#endif
logSer.begin(115200);
logSer.println(PSTR("\nOne Wire Half Duplex Bitpattern and Datarate Test"));
swSer.begin(115200, SWSERIAL_8N1, D6, -1);
swSer.enableIntTx(true);
logSer.println(PSTR("Tx on hwSer"));
}

uint8_t val = 0xff;

void loop() {
hwSer.write((uint8_t)0x00);
hwSer.write(val);
hwSer.write(val);
auto start = ESP.getCycleCount();
int rxCnt = 0;
while (ESP.getCycleCount() - start < ESP.getCpuFreqMHz() * 1000000 / 10) {
if (swSer.available()) {
auto rxVal = swSer.read();
if ((!rxCnt && rxVal) || (rxCnt && rxVal != val)) {
logSer.printf(PSTR("Rx bit error: tx = 0x%02x, rx = 0x%02x\n"), val, rxVal);
}
++rxCnt;
}
}
if (rxCnt != 3) {
logSer.printf(PSTR("Rx cnt error, tx = 0x%02x\n"), val);
}
++val;
if (!val) {
logSer.println("Starting over");
}
}
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "EspSoftwareSerial",
"version": "6.15.2",
"version": "6.16.0",
"description": "Implementation of the Arduino software serial for ESP8266/ESP32.",
"keywords": [
"serial", "io", "softwareserial"
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=EspSoftwareSerial
version=6.15.2
version=6.16.0
author=Dirk Kaar, Peter Lerup
maintainer=Dirk Kaar <[email protected]>
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.
Expand Down
79 changes: 55 additions & 24 deletions src/SoftwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ void SoftwareSerial::begin(uint32_t baud, SoftwareSerialConfig config,
m_bitCycles = (ESP.getCpuFreqMHz() * 1000000UL + baud / 2) / baud;
m_intTxEnabled = true;
if (isValidRxGPIOpin(m_rxPin)) {
m_rxReg = portInputRegister(digitalPinToPort(m_rxPin));
m_rxBitMask = digitalPinToBitMask(m_rxPin);
m_buffer.reset(new circular_queue<uint8_t>((bufCapacity > 0) ? bufCapacity : 64));
if (m_parityMode)
{
Expand All @@ -162,6 +164,10 @@ void SoftwareSerial::begin(uint32_t baud, SoftwareSerialConfig config,
}
}
if (isValidTxGPIOpin(m_txPin)) {
#if !defined(ESP8266)
m_txReg = portOutputRegister(digitalPinToPort(m_txPin));
#endif
m_txBitMask = digitalPinToBitMask(m_txPin);
m_txValid = true;
if (!m_oneWire) {
pinMode(m_txPin, OUTPUT);
Expand Down Expand Up @@ -302,44 +308,69 @@ int SoftwareSerial::available() {
return avail;
}

void IRAM_ATTR SoftwareSerial::preciseDelay(bool sync) {
if (!sync)
void SoftwareSerial::lazyDelay() {
// Reenable interrupts while delaying to avoid other tasks piling up
if (!m_intTxEnabled) { restoreInterrupts(); }
const auto expired = ESP.getCycleCount() - m_periodStart;
const int32_t remaining = m_periodDuration - expired;
const int32_t ms = remaining > 0 ? remaining / 1000L / static_cast<int32_t>(ESP.getCpuFreqMHz()) : 0;
if (ms > 0)
{
// Reenable interrupts while delaying to avoid other tasks piling up
if (!m_intTxEnabled) { restoreInterrupts(); }
const auto expired = ESP.getCycleCount() - m_periodStart;
const int32_t remaining = m_periodDuration - expired;
const int32_t ms = remaining > 0 ? remaining / 1000L / static_cast<int32_t>(ESP.getCpuFreqMHz()) : 0;
if (ms > 0)
{
delay(ms);
}
else
{
optimistic_yield(10000UL);
}
delay(ms);
}
else
{
optimistic_yield(10000UL);
}
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) {}
// Disable interrupts again if applicable
if (!sync && !m_intTxEnabled) { disableInterrupts(); }
if (!m_intTxEnabled) { disableInterrupts(); }
preciseDelay();
}

void IRAM_ATTR SoftwareSerial::preciseDelay() {
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) {}
m_periodDuration = 0;
m_periodStart = ESP.getCycleCount();
}

void IRAM_ATTR SoftwareSerial::writePeriod(
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) {
preciseDelay(true);
preciseDelay();
if (dutyCycle)
{
digitalWrite(m_txPin, HIGH);
#if defined(ESP8266)
if (16 == m_txPin) {
GP16O = 1;
}
else {
GPOS = m_txBitMask;
}
#else
*m_txReg |= m_txBitMask;
#endif
m_periodDuration += dutyCycle;
if (offCycle || (withStopBit && !m_invert)) preciseDelay(!withStopBit || m_invert);
if (offCycle || (withStopBit && !m_invert)) {
if (!withStopBit || m_invert) {
preciseDelay();
} else {
lazyDelay();
}
}
}
if (offCycle)
{
digitalWrite(m_txPin, LOW);
#if defined(ESP8266)
if (16 == m_txPin) {
GP16O = 0;
}
else {
GPOC = m_txBitMask;
}
#else
*m_txReg &= ~m_txBitMask;
#endif
m_periodDuration += offCycle;
if (withStopBit && m_invert) preciseDelay(false);
if (withStopBit && m_invert) lazyDelay();
}
}

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

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

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

// Store level and cycle in the buffer unless we have an overflow
// cycle's LSB is repurposed for the level bit
if (digitalRead(self->m_rxPin) != level)
if (static_cast<bool>(*self->m_rxReg & self->m_rxBitMask) != level)
{
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true);
level = !level;
Expand Down
16 changes: 12 additions & 4 deletions src/SoftwareSerial.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ class SoftwareSerial : public Stream {
/// @param invert true: uses invert line level logic
/// @param bufCapacity the capacity for the received bytes buffer
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
/// bit receive buffer, a suggested size is bufCapacity times the sum of
/// start, data, parity and stop bit count.
/// bit receive buffer, a suggested size is bufCapacity times the sum of
/// start, data, parity and stop bit count.
void begin(uint32_t baud, SoftwareSerialConfig config,
int8_t rxPin, int8_t txPin, bool invert,
int bufCapacity = 64, int isrBufCapacity = 0);
Expand Down Expand Up @@ -211,9 +211,11 @@ class SoftwareSerial : public Stream {
using Print::write;

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

// Member variables
int8_t m_rxPin = -1;
volatile uint32_t* m_rxReg;
uint32_t m_rxBitMask;
int8_t m_txPin = -1;
#if !defined(ESP8266)
volatile uint32_t* m_txReg;
#endif
uint32_t m_txBitMask;
int8_t m_txEnablePin = -1;
uint8_t m_dataBits;
bool m_oneWire;
Expand Down

0 comments on commit 1906d2f

Please sign in to comment.