Skip to content

Commit

Permalink
Drop waveform-generating write code in favor of maintainable inverse …
Browse files Browse the repository at this point in the history
…logic implementation - performance was a zero-sum game.
  • Loading branch information
dok-net committed Nov 21, 2019
1 parent b2ed06a commit 3bfd18d
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 130 deletions.
12 changes: 5 additions & 7 deletions examples/loopback/loopback.ino
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#ifdef ESP32
constexpr int IUTBITRATE = 57600;
#else
constexpr int IUTBITRATE = 74880;
constexpr int IUTBITRATE = 153600;
#endif

#if defined(ESP8266)
Expand Down Expand Up @@ -189,14 +189,10 @@ void loop() {
inCnt = 0;
while ((ESP.getCycleCount() - deadlineStart) < (1000000 * 10 * BLOCKSIZE) / IUTBITRATE * 8 * ESP.getCpuFreqMHz()) {
int avail = hwSerial.available();
if (0 >= avail) {
delay(1);
continue;
}
inCnt += hwSerial.readBytes(&inBuf[inCnt], min(avail, min(BLOCKSIZE - inCnt, hwSerial.availableForWrite())));
if (inCnt >= BLOCKSIZE) { break; }
// wait for more outstanding bytes to trickle in
deadlineStart = ESP.getCycleCount();
if (avail) deadlineStart = ESP.getCycleCount();
}
hwSerial.write(inBuf, inCnt);
#endif
Expand All @@ -217,17 +213,19 @@ void loop() {
++rxErrors;
expected = -1;
}
#ifndef HWSOURCESINK
if ((serialIUT.readParity() ^ static_cast<bool>(swSerialConfig & 010)) != serialIUT.parityEven(r))
{
++rxParityErrors;
}
#endif
++rxCount;
++inCnt;
}

if (inCnt >= BLOCKSIZE) { break; }
// wait for more outstanding bytes to trickle in
deadlineStart = ESP.getCycleCount();
if (avail) deadlineStart = ESP.getCycleCount();
}

const uint32_t interval = micros() - start;
Expand Down
114 changes: 57 additions & 57 deletions examples/repeater/repeater.ino
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#ifdef ESP32
constexpr int IUTBITRATE = 57600;
#else
constexpr int IUTBITRATE = 74880;
constexpr int IUTBITRATE = 153600;
#endif

constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8N1;
Expand Down Expand Up @@ -54,83 +54,83 @@ HardwareSerial& logger(Serial);
void setup() {
#ifdef HWLOOPBACK
#if defined(ESP8266)
repeater.begin(IUTBITRATE);
repeater.setRxBufferSize(2 * BLOCKSIZE);
repeater.swap();
logger.begin(9600, swSerialConfig, RX, TX);
repeater.begin(IUTBITRATE);
repeater.setRxBufferSize(2 * BLOCKSIZE);
repeater.swap();
logger.begin(9600, swSerialConfig, RX, TX);
#elif defined(ESP32)
repeater.begin(IUTBITRATE, SERIAL_8N1, D7, D8);
repeater.setRxBufferSize(2 * BLOCKSIZE);
logger.begin(9600);
repeater.begin(IUTBITRATE, SERIAL_8N1, D7, D8);
repeater.setRxBufferSize(2 * BLOCKSIZE);
logger.begin(9600);
#endif
#else
#if defined(ESP8266)
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, false, 2 * BLOCKSIZE);
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, false, 2 * BLOCKSIZE);
#elif defined(ESP32)
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, false, 2 * BLOCKSIZE);
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, false, 2 * BLOCKSIZE);
#endif
#ifdef HALFDUPLEX
repeater.enableIntTx(false);
repeater.enableIntTx(false);
#endif
Serial.begin(9600);
Serial.begin(9600);
#endif

start = micros();
rxCount = 0;
seqErrors = 0;
start = micros();
rxCount = 0;
seqErrors = 0;
}

void loop() {
#ifdef HALFDUPLEX
unsigned char block[BLOCKSIZE];
unsigned char block[BLOCKSIZE];
#endif
int inCnt = 0;
// starting deadline for the first bytes to come in
uint32_t deadline = micros() + 200000;
while (static_cast<int32_t>(deadline - micros()) > 0) {
if (0 >= repeater.available()) {
delay(1);
continue;
}
int r = repeater.read();
if (r == -1) { logger.println("read() == -1"); }
if (expected == -1) { expected = r; }
else {
expected = (expected + 1) % 256;
}
if (r != (expected & ((1 << (5 + swSerialConfig % 4)) - 1))) {
++seqErrors;
expected = -1;
}
++rxCount;
// starting deadline for the first bytes to come in
uint32_t deadlineStart = ESP.getCycleCount();
int inCnt = 0;
while ((ESP.getCycleCount() - deadlineStart) < (1000000 * 10 * BLOCKSIZE) / IUTBITRATE * 2 * ESP.getCpuFreqMHz()) {
int avail = repeater.available();
for (int i = 0; i < avail; ++i)
{
int r = repeater.read();
if (r == -1) { logger.println("read() == -1"); }
if (expected == -1) { expected = r; }
else {
expected = (expected + 1) % 256;
}
if (r != (expected & ((1 << (5 + swSerialConfig % 4)) - 1))) {
++seqErrors;
expected = -1;
}
++rxCount;
#ifdef HALFDUPLEX
block[inCnt] = r;
block[inCnt] = r;
#else
repeater.write(r);
repeater.write(r);
#endif
if (++inCnt >= BLOCKSIZE) { break; }
// wait for more outstanding bytes to trickle in
deadline = micros() + static_cast<uint32_t>(1000000 * 10 * BLOCKSIZE / IUTBITRATE * 32);
}
}
if (++inCnt >= BLOCKSIZE) { break; }
// wait for more outstanding bytes to trickle in
if (avail) deadlineStart = ESP.getCycleCount();
}

#ifdef HALFDUPLEX
repeater.write(block, inCnt);
repeater.write(block, inCnt);
#endif

if (inCnt != 0 && inCnt != BLOCKSIZE) {
logger.print("Got "); logger.print(inCnt); logger.println(" bytes during buffer interval");
}
if (inCnt != 0 && inCnt != BLOCKSIZE) {
logger.print("Got "); logger.print(inCnt); logger.println(" bytes during buffer interval");
}

if (rxCount >= ReportInterval) {
auto end = micros();
unsigned long interval = end - start;
long cps = rxCount * (1000000.0 / interval);
long seqErrorsps = seqErrors * (1000000.0 / interval);
logger.println(bitRateTxt + 10 * cps + "bps, "
+ seqErrorsps + "cps seq. errors (" + 100.0 * seqErrors / rxCount + "%)");
start = end;
rxCount = 0;
seqErrors = 0;
expected = -1;
}
if (rxCount >= ReportInterval) {
auto end = micros();
unsigned long interval = end - start;
long cps = rxCount * (1000000.0 / interval);
long seqErrorsps = seqErrors * (1000000.0 / interval);
logger.println(bitRateTxt + 10 * cps + "bps, "
+ seqErrorsps + "cps seq. errors (" + 100.0 * seqErrors / rxCount + "%)");
start = end;
rxCount = 0;
seqErrors = 0;
expected = -1;
}
}
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.2.2",
"version": "6.3.0",
"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.2.2
version=6.3.0
author=Peter Lerup, Dirk Kaar
maintainer=Peter Lerup <[email protected]>
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.
Expand Down
90 changes: 34 additions & 56 deletions src/SoftwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,38 +210,26 @@ int SoftwareSerial::available() {
return avail;
}

void ICACHE_RAM_ATTR SoftwareSerial::preciseDelay(bool asyn, uint32_t savedPS) {
if (asyn)
void ICACHE_RAM_ATTR SoftwareSerial::preciseDelay(uint32_t cycles, bool relaxed)
{
m_periodDuration += cycles;
if (relaxed)
{
// Reenable interrupts while delaying to avoid other tasks piling up
if (!m_intTxEnabled) { xt_wsr_ps(savedPS); }
if (!m_intTxEnabled) { xt_wsr_ps(m_savedPS); }
auto expired = ESP.getCycleCount() - m_periodStart;
auto micro_s = expired < m_periodDuration ? (m_periodDuration - expired) / ESP.getCpuFreqMHz() : 0;
delay(micro_s / 1000);
if (expired < m_periodDuration)
{
auto ms = (m_periodDuration - expired) / ESP.getCpuFreqMHz() / 1000UL;
if (ms) delay(ms);
}
}
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) { if (asyn) optimistic_yield(10000); }
if (asyn)
while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) { if (relaxed) optimistic_yield(10000); }
// Disable interrupts again
if (relaxed)
{
resetPeriodStart();
// Disable interrupts again
if (!m_intTxEnabled) { savedPS = xt_rsil(15); }
}
}

void ICACHE_RAM_ATTR SoftwareSerial::writePeriod(
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit, uint32_t savedPS) {
preciseDelay(false, savedPS);
if (dutyCycle) {
digitalWrite(m_txPin, HIGH);
m_periodDuration += dutyCycle;
bool asyn = withStopBit && !m_invert;
if (asyn || offCycle) preciseDelay(asyn, savedPS);
}
if (offCycle) {
digitalWrite(m_txPin, LOW);
m_periodDuration += offCycle;
bool asyn = withStopBit && m_invert;
if (asyn) preciseDelay(asyn, savedPS);
if (!m_intTxEnabled) { m_savedPS = xt_rsil(15); }
}
}

Expand All @@ -264,24 +252,20 @@ size_t ICACHE_RAM_ATTR SoftwareSerial::write(const uint8_t * buffer, size_t size
if (m_txEnableValid) {
digitalWrite(m_txEnablePin, HIGH);
}
// Stop bit : LOW if inverted logic, otherwise HIGH
bool b = !m_invert;
// Force line level on entry
uint32_t dutyCycle = b;
uint32_t offCycle = m_invert;
uint32_t savedPS = 0;
// Stop bit: HIGH
bool b = true;
uint32_t curBitCycles = 0;
if (!m_intTxEnabled) {
// Disable interrupts in order to get a clean transmit timing
savedPS = xt_rsil(15);
m_savedPS = xt_rsil(15);
}
resetPeriodStart();
const uint32_t dataMask = ((1UL << m_dataBits) - 1);
for (size_t cnt = 0; cnt < size; ++cnt, ++buffer) {
bool withStopBit = true;
uint8_t byte = (m_invert ? ~*buffer : *buffer) & dataMask;
for (size_t cnt = 0; cnt < size; ++cnt) {
uint8_t byte = buffer[cnt] & dataMask;
// push LSB start-data-parity-stop bit pattern into uint32_t
// Stop bits : LOW if inverted logic, otherwise HIGH
uint32_t word = m_invert ? 0UL : ~0UL << (m_dataBits + static_cast<bool>(parity));
// Stop bits: HIGH
uint32_t word = ~0UL << (m_dataBits + static_cast<bool>(parity));
// parity bit, if any
if (parity)
{
Expand All @@ -295,42 +279,36 @@ size_t ICACHE_RAM_ATTR SoftwareSerial::write(const uint8_t * buffer, size_t size
parityBit = !parityEven(byte);
break;
case SWSERIAL_PARITY_MARK:
parityBit = !m_invert;
parityBit = 1;
break;
case SWSERIAL_PARITY_SPACE:
parityBit = m_invert;
break;
default:
// suppresses warning parityBit uninitialized
default:
parityBit = 0;
break;
}
word |= parityBit << m_dataBits;
}
word |= byte;
// Start bit : HIGH if inverted logic, otherwise LOW
// Start bit: LOW
word <<= 1;
word |= m_invert;
for (int i = 0; i <= m_pduBits; ++i) {
bool pb = b;
b = word & (1 << i);
if (!pb && b) {
writePeriod(dutyCycle, offCycle, withStopBit, savedPS);
withStopBit = false;
dutyCycle = offCycle = 0;
}
if (b) {
dutyCycle += m_bitCycles;
}
else {
offCycle += m_bitCycles;
if (pb != b)
{
preciseDelay(curBitCycles, false);
curBitCycles = 0;
digitalWrite(m_txPin, (b ^ m_invert) ? HIGH : LOW);
}
curBitCycles += m_bitCycles;
}
preciseDelay(curBitCycles, true);
curBitCycles = 0;
}
writePeriod(dutyCycle, offCycle, true, savedPS);
if (!m_intTxEnabled) {
// restore the interrupt state
xt_wsr_ps(savedPS);
xt_wsr_ps(m_savedPS);
}
if (m_txEnableValid) {
digitalWrite(m_txEnablePin, LOW);
Expand Down
11 changes: 3 additions & 8 deletions src/SoftwareSerial.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,8 @@ class SoftwareSerial : public Stream {
m_periodDuration = 0;
m_periodStart = ESP.getCycleCount();
}
// If asyn, it's legal to exceed the deadline, for instance,
// by enabling interrupts.
void preciseDelay(bool asyn, uint32_t savedPS);
// 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.
void writePeriod(
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit, uint32_t savedPS);
// If relaxed is true, may relax timing to exceed cycle counts, by yielding.
void preciseDelay(uint32_t cycles, bool relaxed);
bool isValidGPIOpin(int8_t pin);
/* check m_rxValid that calling is safe */
void rxBits();
Expand Down Expand Up @@ -232,6 +226,7 @@ class SoftwareSerial : public Stream {
uint32_t m_periodStart;
uint32_t m_periodDuration;
bool m_intTxEnabled;
uint32_t m_savedPS = 0;
std::unique_ptr<circular_queue<uint8_t> > m_buffer;
std::unique_ptr<circular_queue<uint8_t> > m_parityBuffer;
uint8_t m_parityInPos;
Expand Down

0 comments on commit 3bfd18d

Please sign in to comment.