diff --git a/examples/bitpattern/bitpattern.ino b/examples/bitpattern/bitpattern.ino new file mode 100644 index 0000000..c7e6a17 --- /dev/null +++ b/examples/bitpattern/bitpattern.ino @@ -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"); + } +} diff --git a/library.json b/library.json index 18f587d..0a46e21 100644 --- a/library.json +++ b/library.json @@ -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" diff --git a/library.properties b/library.properties index fd8378b..7907d4e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=EspSoftwareSerial -version=6.15.2 +version=6.16.0 author=Dirk Kaar, Peter Lerup maintainer=Dirk Kaar sentence=Implementation of the Arduino software serial for ESP8266/ESP32. diff --git a/src/SoftwareSerial.cpp b/src/SoftwareSerial.cpp index a83f590..3767122 100644 --- a/src/SoftwareSerial.cpp +++ b/src/SoftwareSerial.cpp @@ -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((bufCapacity > 0) ? bufCapacity : 64)); if (m_parityMode) { @@ -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); @@ -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(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(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(); } } @@ -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 @@ -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(*self->m_rxReg & self->m_rxBitMask) != level) { if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true); level = !level; diff --git a/src/SoftwareSerial.h b/src/SoftwareSerial.h index 6142a6c..d440756 100644 --- a/src/SoftwareSerial.h +++ b/src/SoftwareSerial.h @@ -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); @@ -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. @@ -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;