parent
49f77df902
commit
2f9882102d
|
@ -1,619 +0,0 @@
|
|||
/*
|
||||
|
||||
SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||
Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "SoftwareSerial.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
using namespace EspSoftwareSerial;
|
||||
|
||||
#ifndef ESP32
|
||||
uint32_t UARTBase::m_savedPS = 0;
|
||||
#else
|
||||
portMUX_TYPE UARTBase::m_interruptsMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
#endif
|
||||
|
||||
__attribute__((always_inline)) inline void IRAM_ATTR UARTBase::disableInterrupts()
|
||||
{
|
||||
#ifndef ESP32
|
||||
m_savedPS = xt_rsil(15);
|
||||
#else
|
||||
taskENTER_CRITICAL(&m_interruptsMux);
|
||||
#endif
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline void IRAM_ATTR UARTBase::restoreInterrupts()
|
||||
{
|
||||
#ifndef ESP32
|
||||
xt_wsr_ps(m_savedPS);
|
||||
#else
|
||||
taskEXIT_CRITICAL(&m_interruptsMux);
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast<uint8_t>(0);
|
||||
|
||||
UARTBase::UARTBase() {
|
||||
}
|
||||
|
||||
UARTBase::UARTBase(int8_t rxPin, int8_t txPin, bool invert)
|
||||
{
|
||||
m_rxPin = rxPin;
|
||||
m_txPin = txPin;
|
||||
m_invert = invert;
|
||||
}
|
||||
|
||||
UARTBase::~UARTBase() {
|
||||
end();
|
||||
}
|
||||
|
||||
void UARTBase::setRxGPIOPinMode() {
|
||||
if (m_rxValid) {
|
||||
pinMode(m_rxPin, m_rxGPIOHasPullUp && m_rxGPIOPullUpEnabled ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::setTxGPIOPinMode() {
|
||||
if (m_txValid) {
|
||||
pinMode(m_txPin, m_txGPIOOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::begin(uint32_t baud, Config config,
|
||||
int8_t rxPin, int8_t txPin,
|
||||
bool invert) {
|
||||
if (-1 != rxPin) m_rxPin = rxPin;
|
||||
if (-1 != txPin) m_txPin = txPin;
|
||||
m_oneWire = (m_rxPin == m_txPin);
|
||||
m_invert = invert;
|
||||
m_dataBits = 5 + (config & 07);
|
||||
m_parityMode = static_cast<Parity>(config & 070);
|
||||
m_stopBits = 1 + ((config & 0300) ? 1 : 0);
|
||||
m_pduBits = m_dataBits + static_cast<bool>(m_parityMode) + m_stopBits;
|
||||
m_bitTicks = (microsToTicks(1000000UL) + baud / 2) / baud;
|
||||
m_intTxEnabled = true;
|
||||
}
|
||||
|
||||
void UARTBase::beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity) {
|
||||
m_rxGPIOHasPullUp = hasPullUp;
|
||||
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)
|
||||
{
|
||||
m_parityBuffer.reset(new circular_queue<uint8_t>((m_buffer->capacity() + 7) / 8));
|
||||
m_parityInPos = m_parityOutPos = 1;
|
||||
}
|
||||
m_isrBuffer.reset(new circular_queue<uint32_t, UARTBase*>((isrBufCapacity > 0) ?
|
||||
isrBufCapacity : m_buffer->capacity() * (2 + m_dataBits + static_cast<bool>(m_parityMode))));
|
||||
if (m_buffer && (!m_parityMode || m_parityBuffer) && m_isrBuffer) {
|
||||
m_rxValid = true;
|
||||
setRxGPIOPinMode();
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::beginTx() {
|
||||
#if !defined(ESP8266)
|
||||
m_txReg = portOutputRegister(digitalPinToPort(m_txPin));
|
||||
#endif
|
||||
m_txBitMask = digitalPinToBitMask(m_txPin);
|
||||
m_txValid = true;
|
||||
if (!m_oneWire) {
|
||||
setTxGPIOPinMode();
|
||||
digitalWrite(m_txPin, !m_invert);
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::end()
|
||||
{
|
||||
enableRx(false);
|
||||
m_txValid = false;
|
||||
if (m_buffer) {
|
||||
m_buffer.reset();
|
||||
}
|
||||
m_parityBuffer.reset();
|
||||
if (m_isrBuffer) {
|
||||
m_isrBuffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t UARTBase::baudRate() {
|
||||
return 1000000UL / ticksToMicros(m_bitTicks);
|
||||
}
|
||||
|
||||
void UARTBase::setTransmitEnablePin(int8_t txEnablePin) {
|
||||
if (-1 != txEnablePin) {
|
||||
m_txEnableValid = true;
|
||||
m_txEnablePin = txEnablePin;
|
||||
pinMode(m_txEnablePin, OUTPUT);
|
||||
digitalWrite(m_txEnablePin, LOW);
|
||||
}
|
||||
else {
|
||||
m_txEnableValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::enableIntTx(bool on) {
|
||||
m_intTxEnabled = on;
|
||||
}
|
||||
|
||||
void UARTBase::enableRxGPIOPullUp(bool on) {
|
||||
m_rxGPIOPullUpEnabled = on;
|
||||
setRxGPIOPinMode();
|
||||
}
|
||||
|
||||
void UARTBase::enableTxGPIOOpenDrain(bool on) {
|
||||
m_txGPIOOpenDrain = on;
|
||||
setTxGPIOPinMode();
|
||||
}
|
||||
|
||||
void UARTBase::enableTx(bool on) {
|
||||
if (m_txValid && m_oneWire) {
|
||||
if (on) {
|
||||
enableRx(false);
|
||||
setTxGPIOPinMode();
|
||||
digitalWrite(m_txPin, !m_invert);
|
||||
}
|
||||
else {
|
||||
setRxGPIOPinMode();
|
||||
enableRx(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::enableRx(bool on) {
|
||||
if (m_rxValid && on != m_rxEnabled) {
|
||||
if (on) {
|
||||
m_rxLastBit = m_pduBits - 1;
|
||||
// Init to stop bit level and current tick
|
||||
m_isrLastTick = (microsToTicks(micros()) | 1) ^ m_invert;
|
||||
if (m_bitTicks >= microsToTicks(1000000UL / 74880UL))
|
||||
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitISR), this, CHANGE);
|
||||
else
|
||||
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitSyncISR), this, m_invert ? RISING : FALLING);
|
||||
}
|
||||
else {
|
||||
detachInterrupt(digitalPinToInterrupt(m_rxPin));
|
||||
}
|
||||
m_rxEnabled = on;
|
||||
}
|
||||
}
|
||||
|
||||
int UARTBase::read() {
|
||||
if (!m_rxValid) { return -1; }
|
||||
if (!m_buffer->available()) {
|
||||
rxBits();
|
||||
if (!m_buffer->available()) { return -1; }
|
||||
}
|
||||
auto val = m_buffer->pop();
|
||||
if (m_parityBuffer)
|
||||
{
|
||||
m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||
m_parityOutPos <<= 1;
|
||||
if (!m_parityOutPos)
|
||||
{
|
||||
m_parityOutPos = 1;
|
||||
m_parityBuffer->pop();
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
int UARTBase::read(uint8_t* buffer, size_t size) {
|
||||
if (!m_rxValid) { return 0; }
|
||||
int avail;
|
||||
if (0 == (avail = m_buffer->pop_n(buffer, size))) {
|
||||
rxBits();
|
||||
avail = m_buffer->pop_n(buffer, size);
|
||||
}
|
||||
if (!avail) return 0;
|
||||
if (m_parityBuffer) {
|
||||
uint32_t parityBits = avail;
|
||||
while (m_parityOutPos >>= 1) ++parityBits;
|
||||
m_parityOutPos = (1 << (parityBits % 8));
|
||||
m_parityBuffer->pop_n(nullptr, parityBits / 8);
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
|
||||
size_t UARTBase::readBytes(uint8_t* buffer, size_t size) {
|
||||
if (!m_rxValid || !size) { return 0; }
|
||||
size_t count = 0;
|
||||
auto start = millis();
|
||||
do {
|
||||
auto readCnt = read(&buffer[count], size - count);
|
||||
count += readCnt;
|
||||
if (count >= size) break;
|
||||
if (readCnt) {
|
||||
start = millis();
|
||||
}
|
||||
else {
|
||||
optimistic_yield(1000UL);
|
||||
}
|
||||
} while (millis() - start < _timeout);
|
||||
return count;
|
||||
}
|
||||
|
||||
int UARTBase::available() {
|
||||
if (!m_rxValid) { return 0; }
|
||||
rxBits();
|
||||
int avail = m_buffer->available();
|
||||
if (!avail) {
|
||||
optimistic_yield(10000UL);
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
|
||||
void UARTBase::lazyDelay() {
|
||||
// Reenable interrupts while delaying to avoid other tasks piling up
|
||||
if (!m_intTxEnabled) { restoreInterrupts(); }
|
||||
const auto expired = microsToTicks(micros()) - m_periodStart;
|
||||
const int32_t remaining = m_periodDuration - expired;
|
||||
const uint32_t ms = remaining > 0 ? ticksToMicros(remaining) / 1000UL : 0;
|
||||
if (ms > 0)
|
||||
{
|
||||
delay(ms);
|
||||
}
|
||||
else
|
||||
{
|
||||
optimistic_yield(10000UL);
|
||||
}
|
||||
// Assure that below-ms part of delays are not elided
|
||||
preciseDelay();
|
||||
// Disable interrupts again if applicable
|
||||
if (!m_intTxEnabled) { disableInterrupts(); }
|
||||
}
|
||||
|
||||
void IRAM_ATTR UARTBase::preciseDelay() {
|
||||
uint32_t ticks;
|
||||
do {
|
||||
ticks = microsToTicks(micros());
|
||||
} while ((ticks - m_periodStart) < m_periodDuration);
|
||||
m_periodDuration = 0;
|
||||
m_periodStart = ticks;
|
||||
}
|
||||
|
||||
void IRAM_ATTR UARTBase::writePeriod(
|
||||
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) {
|
||||
preciseDelay();
|
||||
if (dutyCycle)
|
||||
{
|
||||
#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)) {
|
||||
if (!withStopBit || m_invert) {
|
||||
preciseDelay();
|
||||
}
|
||||
else {
|
||||
lazyDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (offCycle)
|
||||
{
|
||||
#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) lazyDelay();
|
||||
}
|
||||
}
|
||||
|
||||
size_t UARTBase::write(uint8_t byte) {
|
||||
return write(&byte, 1);
|
||||
}
|
||||
|
||||
size_t UARTBase::write(uint8_t byte, Parity parity) {
|
||||
return write(&byte, 1, parity);
|
||||
}
|
||||
|
||||
size_t UARTBase::write(const uint8_t* buffer, size_t size) {
|
||||
return write(buffer, size, m_parityMode);
|
||||
}
|
||||
|
||||
size_t IRAM_ATTR UARTBase::write(const uint8_t* buffer, size_t size, Parity parity) {
|
||||
if (m_rxValid) { rxBits(); }
|
||||
if (!m_txValid) { return -1; }
|
||||
|
||||
if (m_txEnableValid) {
|
||||
digitalWrite(m_txEnablePin, HIGH);
|
||||
}
|
||||
// Stop bit: if inverted, LOW, otherwise HIGH
|
||||
bool b = !m_invert;
|
||||
uint32_t dutyCycle = 0;
|
||||
uint32_t offCycle = 0;
|
||||
if (!m_intTxEnabled) {
|
||||
// Disable interrupts in order to get a clean transmit timing
|
||||
disableInterrupts();
|
||||
}
|
||||
const uint32_t dataMask = ((1UL << m_dataBits) - 1);
|
||||
bool withStopBit = true;
|
||||
m_periodDuration = 0;
|
||||
m_periodStart = microsToTicks(micros());
|
||||
for (size_t cnt = 0; cnt < size; ++cnt) {
|
||||
uint8_t byte = pgm_read_byte(buffer + cnt) & dataMask;
|
||||
// push LSB start-data-parity-stop bit pattern into uint32_t
|
||||
// Stop bits: HIGH
|
||||
uint32_t word = ~0UL;
|
||||
// inverted parity bit, performance tweak for xor all-bits-set word
|
||||
if (parity && m_parityMode)
|
||||
{
|
||||
uint32_t parityBit;
|
||||
switch (parity)
|
||||
{
|
||||
case PARITY_EVEN:
|
||||
// from inverted, so use odd parity
|
||||
parityBit = byte;
|
||||
parityBit ^= parityBit >> 4;
|
||||
parityBit &= 0xf;
|
||||
parityBit = (0x9669 >> parityBit) & 1;
|
||||
break;
|
||||
case PARITY_ODD:
|
||||
// from inverted, so use even parity
|
||||
parityBit = byte;
|
||||
parityBit ^= parityBit >> 4;
|
||||
parityBit &= 0xf;
|
||||
parityBit = (0x6996 >> parityBit) & 1;
|
||||
break;
|
||||
case PARITY_MARK:
|
||||
parityBit = 0;
|
||||
break;
|
||||
case PARITY_SPACE:
|
||||
// suppresses warning parityBit uninitialized
|
||||
default:
|
||||
parityBit = 1;
|
||||
break;
|
||||
}
|
||||
word ^= parityBit;
|
||||
}
|
||||
word <<= m_dataBits;
|
||||
word |= byte;
|
||||
// Start bit: LOW
|
||||
word <<= 1;
|
||||
if (m_invert) word = ~word;
|
||||
for (int i = 0; i <= m_pduBits; ++i) {
|
||||
bool pb = b;
|
||||
b = word & (1UL << i);
|
||||
if (!pb && b) {
|
||||
writePeriod(dutyCycle, offCycle, withStopBit);
|
||||
withStopBit = false;
|
||||
dutyCycle = offCycle = 0;
|
||||
}
|
||||
if (b) {
|
||||
dutyCycle += m_bitTicks;
|
||||
}
|
||||
else {
|
||||
offCycle += m_bitTicks;
|
||||
}
|
||||
}
|
||||
withStopBit = true;
|
||||
}
|
||||
writePeriod(dutyCycle, offCycle, true);
|
||||
if (!m_intTxEnabled) {
|
||||
// restore the interrupt state if applicable
|
||||
restoreInterrupts();
|
||||
}
|
||||
if (m_txEnableValid) {
|
||||
digitalWrite(m_txEnablePin, LOW);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void UARTBase::flush() {
|
||||
if (!m_rxValid) { return; }
|
||||
m_buffer->flush();
|
||||
if (m_parityBuffer)
|
||||
{
|
||||
m_parityInPos = m_parityOutPos = 1;
|
||||
m_parityBuffer->flush();
|
||||
}
|
||||
}
|
||||
|
||||
bool UARTBase::overflow() {
|
||||
bool res = m_overflow;
|
||||
m_overflow = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
int UARTBase::peek() {
|
||||
if (!m_rxValid) { return -1; }
|
||||
if (!m_buffer->available()) {
|
||||
rxBits();
|
||||
if (!m_buffer->available()) return -1;
|
||||
}
|
||||
auto val = m_buffer->peek();
|
||||
if (m_parityBuffer) m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||
return val;
|
||||
}
|
||||
|
||||
void UARTBase::rxBits() {
|
||||
#ifdef ESP8266
|
||||
if (m_isrOverflow.load()) {
|
||||
m_overflow = true;
|
||||
m_isrOverflow.store(false);
|
||||
}
|
||||
#else
|
||||
if (m_isrOverflow.exchange(false)) {
|
||||
m_overflow = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_isrBuffer->for_each(m_isrBufferForEachDel);
|
||||
|
||||
// A stop bit can go undetected if leading data bits are at same level
|
||||
// and there was also no next start bit yet, so one word may be pending.
|
||||
// Check that there was no new ISR data received in the meantime, inserting an
|
||||
// extraneous stop level bit out of sequence breaks rx.
|
||||
if (m_rxLastBit < m_pduBits - 1) {
|
||||
const uint32_t detectionTicks = (m_pduBits - 1 - m_rxLastBit) * m_bitTicks;
|
||||
if (!m_isrBuffer->available() && microsToTicks(micros()) - m_isrLastTick > detectionTicks) {
|
||||
// Produce faux stop bit level, prevents start bit maldetection
|
||||
// tick's LSB is repurposed for the level bit
|
||||
rxBits(((m_isrLastTick + detectionTicks) | 1) ^ m_invert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UARTBase::rxBits(const uint32_t isrTick) {
|
||||
const bool level = (m_isrLastTick & 1) ^ m_invert;
|
||||
|
||||
// error introduced by edge value in LSB of isrTick is negligible
|
||||
uint32_t ticks = isrTick - m_isrLastTick;
|
||||
m_isrLastTick = isrTick;
|
||||
|
||||
uint32_t bits = ticks / m_bitTicks;
|
||||
if (ticks % m_bitTicks > (m_bitTicks >> 1)) ++bits;
|
||||
while (bits > 0) {
|
||||
// start bit detection
|
||||
if (m_rxLastBit >= (m_pduBits - 1)) {
|
||||
// leading edge of start bit?
|
||||
if (level) break;
|
||||
m_rxLastBit = -1;
|
||||
--bits;
|
||||
continue;
|
||||
}
|
||||
// data bits
|
||||
if (m_rxLastBit < (m_dataBits - 1)) {
|
||||
uint8_t dataBits = min(bits, static_cast<uint32_t>(m_dataBits - 1 - m_rxLastBit));
|
||||
m_rxLastBit += dataBits;
|
||||
bits -= dataBits;
|
||||
m_rxCurByte >>= dataBits;
|
||||
if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); }
|
||||
continue;
|
||||
}
|
||||
// parity bit
|
||||
if (m_parityMode && m_rxLastBit == (m_dataBits - 1)) {
|
||||
++m_rxLastBit;
|
||||
--bits;
|
||||
m_rxCurParity = level;
|
||||
continue;
|
||||
}
|
||||
// stop bits
|
||||
// Store the received value in the buffer unless we have an overflow
|
||||
// if not high stop bit level, discard word
|
||||
if (bits >= static_cast<uint32_t>(m_pduBits - 1 - m_rxLastBit) && level) {
|
||||
m_rxCurByte >>= (sizeof(uint8_t) * 8 - m_dataBits);
|
||||
if (!m_buffer->push(m_rxCurByte)) {
|
||||
m_overflow = true;
|
||||
}
|
||||
else {
|
||||
if (m_parityBuffer)
|
||||
{
|
||||
if (m_rxCurParity) {
|
||||
m_parityBuffer->pushpeek() |= m_parityInPos;
|
||||
}
|
||||
else {
|
||||
m_parityBuffer->pushpeek() &= ~m_parityInPos;
|
||||
}
|
||||
m_parityInPos <<= 1;
|
||||
if (!m_parityInPos)
|
||||
{
|
||||
m_parityBuffer->push();
|
||||
m_parityInPos = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_rxLastBit = m_pduBits - 1;
|
||||
// reset to 0 is important for masked bit logic
|
||||
m_rxCurByte = 0;
|
||||
m_rxCurParity = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR UARTBase::rxBitISR(UARTBase* self) {
|
||||
const bool level = *self->m_rxReg & self->m_rxBitMask;
|
||||
const uint32_t curTick = microsToTicks(micros());
|
||||
const bool empty = !self->m_isrBuffer->available();
|
||||
|
||||
// Store level and tick in the buffer unless we have an overflow
|
||||
// tick's LSB is repurposed for the level bit
|
||||
if (!self->m_isrBuffer->push((curTick | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||
// Trigger rx callback only when receiver is starved
|
||||
if (empty) self->m_rxHandler();
|
||||
}
|
||||
|
||||
void IRAM_ATTR UARTBase::rxBitSyncISR(UARTBase* self) {
|
||||
bool level = self->m_invert;
|
||||
const uint32_t start = microsToTicks(micros());
|
||||
uint32_t wait = self->m_bitTicks;
|
||||
const bool empty = !self->m_isrBuffer->available();
|
||||
|
||||
// Store level and tick in the buffer unless we have an overflow
|
||||
// tick's LSB is repurposed for the level bit
|
||||
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||
|
||||
for (uint32_t i = 0; i < self->m_pduBits; ++i) {
|
||||
while (microsToTicks(micros()) - start < wait) {};
|
||||
wait += self->m_bitTicks;
|
||||
|
||||
// Store level and tick in the buffer unless we have an overflow
|
||||
// tick's LSB is repurposed for the level bit
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Trigger rx callback only when receiver is starved
|
||||
if (empty) self->m_rxHandler();
|
||||
}
|
||||
|
||||
void UARTBase::onReceive(const Delegate<void(), void*>& handler) {
|
||||
disableInterrupts();
|
||||
m_rxHandler = handler;
|
||||
restoreInterrupts();
|
||||
}
|
||||
|
||||
void UARTBase::onReceive(Delegate<void(), void*>&& handler) {
|
||||
disableInterrupts();
|
||||
m_rxHandler = std::move(handler);
|
||||
restoreInterrupts();
|
||||
}
|
||||
|
||||
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||
// these with the IRAM attribute:
|
||||
// Delegate<>::operator (), circular_queue<>::available,
|
||||
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||
|
||||
template void IRAM_ATTR delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||
template size_t IRAM_ATTR circular_queue<uint32_t, UARTBase*>::available() const;
|
||||
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(uint32_t&&);
|
||||
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(const uint32_t&);
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
/*
|
||||
SoftwareSerial.h - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||
Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __SoftwareSerial_h
|
||||
#define __SoftwareSerial_h
|
||||
|
||||
#include "circular_queue/circular_queue.h"
|
||||
#include <Stream.h>
|
||||
|
||||
namespace EspSoftwareSerial {
|
||||
|
||||
// Interface definition for template argument of BasicUART
|
||||
class IGpioCapabilities {
|
||||
public:
|
||||
static constexpr bool isValidPin(int8_t pin);
|
||||
static constexpr bool isValidInputPin(int8_t pin);
|
||||
static constexpr bool isValidOutputPin(int8_t pin);
|
||||
// result is only defined for a valid Rx pin
|
||||
static constexpr bool hasPullUp(int8_t pin);
|
||||
};
|
||||
|
||||
class GpioCapabilities : private IGpioCapabilities {
|
||||
public:
|
||||
static constexpr bool isValidPin(int8_t pin) {
|
||||
#if defined(ESP8266)
|
||||
return (pin >= 0 && pin <= 16) && !isFlashInterfacePin(pin);
|
||||
#elif defined(ESP32)
|
||||
// Remove the strapping pins as defined in the datasheets, they affect bootup and other critical operations
|
||||
// Remmove the flash memory pins on related devices, since using these causes memory access issues.
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf,
|
||||
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/esp32-devkitC-v4-pinout.jpg
|
||||
return (pin == 1) || (pin >= 3 && pin <= 5) ||
|
||||
(pin >= 12 && pin <= 15) ||
|
||||
(!psramFound() && pin >= 16 && pin <= 17) ||
|
||||
(pin >= 18 && pin <= 19) ||
|
||||
(pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 39);
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf,
|
||||
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg
|
||||
return (pin >= 1 && pin <= 21) || (pin >= 33 && pin <= 44);
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf,
|
||||
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/_images/esp32-c3-devkitm-1-v1-pinout.jpg
|
||||
return (pin >= 0 && pin <= 1) || (pin >= 3 && pin <= 7) || (pin >= 18 && pin <= 21);
|
||||
#else
|
||||
return pin >= 0;
|
||||
#endif
|
||||
#else
|
||||
return pin >= 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr bool isValidInputPin(int8_t pin) {
|
||||
return isValidPin(pin)
|
||||
#if defined(ESP8266)
|
||||
&& (pin != 16)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
static constexpr bool isValidOutputPin(int8_t pin) {
|
||||
return isValidPin(pin)
|
||||
#if defined(ESP32)
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||
&& (pin < 34)
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
&& (pin <= 45)
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
// no restrictions
|
||||
#endif
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
// result is only defined for a valid Rx pin
|
||||
static constexpr bool hasPullUp(int8_t pin) {
|
||||
#if defined(ESP32)
|
||||
return !(pin >= 34 && pin <= 39);
|
||||
#else
|
||||
(void)pin;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
enum Parity : uint8_t {
|
||||
PARITY_NONE = 000,
|
||||
PARITY_EVEN = 020,
|
||||
PARITY_ODD = 030,
|
||||
PARITY_MARK = 040,
|
||||
PARITY_SPACE = 070,
|
||||
};
|
||||
|
||||
enum Config {
|
||||
SWSERIAL_5N1 = PARITY_NONE,
|
||||
SWSERIAL_6N1,
|
||||
SWSERIAL_7N1,
|
||||
SWSERIAL_8N1,
|
||||
SWSERIAL_5E1 = PARITY_EVEN,
|
||||
SWSERIAL_6E1,
|
||||
SWSERIAL_7E1,
|
||||
SWSERIAL_8E1,
|
||||
SWSERIAL_5O1 = PARITY_ODD,
|
||||
SWSERIAL_6O1,
|
||||
SWSERIAL_7O1,
|
||||
SWSERIAL_8O1,
|
||||
SWSERIAL_5M1 = PARITY_MARK,
|
||||
SWSERIAL_6M1,
|
||||
SWSERIAL_7M1,
|
||||
SWSERIAL_8M1,
|
||||
SWSERIAL_5S1 = PARITY_SPACE,
|
||||
SWSERIAL_6S1,
|
||||
SWSERIAL_7S1,
|
||||
SWSERIAL_8S1,
|
||||
SWSERIAL_5N2 = 0200 | PARITY_NONE,
|
||||
SWSERIAL_6N2,
|
||||
SWSERIAL_7N2,
|
||||
SWSERIAL_8N2,
|
||||
SWSERIAL_5E2 = 0200 | PARITY_EVEN,
|
||||
SWSERIAL_6E2,
|
||||
SWSERIAL_7E2,
|
||||
SWSERIAL_8E2,
|
||||
SWSERIAL_5O2 = 0200 | PARITY_ODD,
|
||||
SWSERIAL_6O2,
|
||||
SWSERIAL_7O2,
|
||||
SWSERIAL_8O2,
|
||||
SWSERIAL_5M2 = 0200 | PARITY_MARK,
|
||||
SWSERIAL_6M2,
|
||||
SWSERIAL_7M2,
|
||||
SWSERIAL_8M2,
|
||||
SWSERIAL_5S2 = 0200 | PARITY_SPACE,
|
||||
SWSERIAL_6S2,
|
||||
SWSERIAL_7S2,
|
||||
SWSERIAL_8S2,
|
||||
};
|
||||
|
||||
/// This class is compatible with the corresponding AVR one, however,
|
||||
/// the constructor takes no arguments, for compatibility with the
|
||||
/// HardwareSerial class.
|
||||
/// Instead, the begin() function handles pin assignments and logic inversion.
|
||||
/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer.
|
||||
/// Bitrates up to at least 115200 can be used.
|
||||
class UARTBase : public Stream {
|
||||
public:
|
||||
UARTBase();
|
||||
/// Ctor to set defaults for pins.
|
||||
/// @param rxPin the GPIO pin used for RX
|
||||
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||
UARTBase(int8_t rxPin, int8_t txPin = -1, bool invert = false);
|
||||
UARTBase(const UARTBase&) = delete;
|
||||
UARTBase& operator= (const UARTBase&) = delete;
|
||||
virtual ~UARTBase();
|
||||
/// Configure the UARTBase object for use.
|
||||
/// @param baud the TX/RX bitrate
|
||||
/// @param config sets databits, parity, and stop bit count
|
||||
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||
/// @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.
|
||||
void begin(uint32_t baud, Config config,
|
||||
int8_t rxPin, int8_t txPin, bool invert);
|
||||
|
||||
uint32_t baudRate();
|
||||
/// Transmit control pin.
|
||||
void setTransmitEnablePin(int8_t txEnablePin);
|
||||
/// Enable (default) or disable interrupts during tx.
|
||||
void enableIntTx(bool on);
|
||||
/// Enable (default) or disable internal rx GPIO pull-up.
|
||||
void enableRxGPIOPullUp(bool on);
|
||||
/// Enable or disable (default) tx GPIO output mode.
|
||||
void enableTxGPIOOpenDrain(bool on);
|
||||
|
||||
bool overflow();
|
||||
|
||||
int available() override;
|
||||
#if defined(ESP8266)
|
||||
int availableForWrite() override {
|
||||
#else
|
||||
int availableForWrite() {
|
||||
#endif
|
||||
if (!m_txValid) return 0;
|
||||
return 1;
|
||||
}
|
||||
int peek() override;
|
||||
int read() override;
|
||||
/// @returns The verbatim parity bit associated with the last successful read() or peek() call
|
||||
bool readParity()
|
||||
{
|
||||
return m_lastReadParity;
|
||||
}
|
||||
/// @returns The calculated bit for even parity of the parameter byte
|
||||
static bool parityEven(uint8_t byte) {
|
||||
byte ^= byte >> 4;
|
||||
byte &= 0xf;
|
||||
return (0x6996 >> byte) & 1;
|
||||
}
|
||||
/// @returns The calculated bit for odd parity of the parameter byte
|
||||
static bool parityOdd(uint8_t byte) {
|
||||
byte ^= byte >> 4;
|
||||
byte &= 0xf;
|
||||
return (0x9669 >> byte) & 1;
|
||||
}
|
||||
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||
int read(uint8_t* buffer, size_t size)
|
||||
#if defined(ESP8266)
|
||||
override
|
||||
#endif
|
||||
;
|
||||
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||
int read(char* buffer, size_t size) {
|
||||
return read(reinterpret_cast<uint8_t*>(buffer), size);
|
||||
}
|
||||
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||
/// Stream::setTimeout() is reached.
|
||||
size_t readBytes(uint8_t* buffer, size_t size) override;
|
||||
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||
/// Stream::setTimeout() is reached.
|
||||
size_t readBytes(char* buffer, size_t size) override {
|
||||
return readBytes(reinterpret_cast<uint8_t*>(buffer), size);
|
||||
}
|
||||
void flush() override;
|
||||
size_t write(uint8_t byte) override;
|
||||
size_t write(uint8_t byte, Parity parity);
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
size_t write(const char* buffer, size_t size) {
|
||||
return write(reinterpret_cast<const uint8_t*>(buffer), size);
|
||||
}
|
||||
size_t write(const uint8_t* buffer, size_t size, Parity parity);
|
||||
size_t write(const char* buffer, size_t size, Parity parity) {
|
||||
return write(reinterpret_cast<const uint8_t*>(buffer), size, parity);
|
||||
}
|
||||
operator bool() const {
|
||||
return (-1 == m_rxPin || m_rxValid) && (-1 == m_txPin || m_txValid) && !(-1 == m_rxPin && m_oneWire);
|
||||
}
|
||||
|
||||
/// Disable or enable interrupts on the rx pin.
|
||||
void enableRx(bool on);
|
||||
/// One wire control.
|
||||
void enableTx(bool on);
|
||||
|
||||
// AVR compatibility methods.
|
||||
bool listen() { enableRx(true); return true; }
|
||||
void end();
|
||||
bool isListening() { return m_rxEnabled; }
|
||||
bool stopListening() { enableRx(false); return true; }
|
||||
|
||||
/// onReceive sets a callback that will be called in interrupt context
|
||||
/// when data is received.
|
||||
/// More precisely, the callback is triggered when UARTBase detects
|
||||
/// a new reception, which may not yet have completed on invocation.
|
||||
/// Reading - never from this interrupt context - should therefore be
|
||||
/// delayed at least for the duration of one incoming word.
|
||||
void onReceive(const Delegate<void(), void*>& handler);
|
||||
/// onReceive sets a callback that will be called in interrupt context
|
||||
/// when data is received.
|
||||
/// More precisely, the callback is triggered when UARTBase detects
|
||||
/// a new reception, which may not yet have completed on invocation.
|
||||
/// Reading - never from this interrupt context - should therefore be
|
||||
/// delayed at least for the duration of one incoming word.
|
||||
void onReceive(Delegate<void(), void*>&& handler);
|
||||
|
||||
[[deprecated("function removed; semantics of onReceive() changed; check the header file.")]]
|
||||
void perform_work();
|
||||
|
||||
using Print::write;
|
||||
|
||||
protected:
|
||||
void beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity);
|
||||
void beginTx();
|
||||
// Member variables
|
||||
int8_t m_rxPin = -1;
|
||||
int8_t m_txPin = -1;
|
||||
bool m_invert = false;
|
||||
|
||||
private:
|
||||
// It's legal to exceed the deadline, for instance,
|
||||
// by enabling interrupts.
|
||||
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.
|
||||
void writePeriod(
|
||||
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit);
|
||||
// safely set the pin mode for the Rx GPIO pin
|
||||
void setRxGPIOPinMode();
|
||||
// safely set the pin mode for the Tx GPIO pin
|
||||
void setTxGPIOPinMode();
|
||||
/* check m_rxValid that calling is safe */
|
||||
void rxBits();
|
||||
void rxBits(const uint32_t isrTick);
|
||||
static void disableInterrupts();
|
||||
static void restoreInterrupts();
|
||||
|
||||
static void rxBitISR(UARTBase* self);
|
||||
static void rxBitSyncISR(UARTBase* self);
|
||||
|
||||
static inline uint32_t IRAM_ATTR microsToTicks(uint32_t micros) __attribute__((always_inline)) {
|
||||
return micros << 1;
|
||||
}
|
||||
static inline uint32_t ticksToMicros(uint32_t ticks) __attribute__((always_inline)) {
|
||||
return ticks >> 1;
|
||||
}
|
||||
|
||||
// Member variables
|
||||
volatile uint32_t* m_rxReg;
|
||||
uint32_t m_rxBitMask;
|
||||
#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;
|
||||
bool m_rxValid = false;
|
||||
bool m_rxEnabled = false;
|
||||
bool m_txValid = false;
|
||||
bool m_txEnableValid = false;
|
||||
/// PDU bits include data, parity and stop bits; the start bit is not counted.
|
||||
uint8_t m_pduBits;
|
||||
bool m_intTxEnabled;
|
||||
bool m_rxGPIOHasPullUp = false;
|
||||
bool m_rxGPIOPullUpEnabled = true;
|
||||
bool m_txGPIOOpenDrain = false;
|
||||
Parity m_parityMode;
|
||||
uint8_t m_stopBits;
|
||||
bool m_lastReadParity;
|
||||
bool m_overflow = false;
|
||||
uint32_t m_bitTicks;
|
||||
uint8_t m_parityInPos;
|
||||
uint8_t m_parityOutPos;
|
||||
int8_t m_rxLastBit; // 0 thru (m_pduBits - m_stopBits - 1): data/parity bits. -1: start bit. (m_pduBits - 1): stop bit.
|
||||
uint8_t m_rxCurByte = 0;
|
||||
std::unique_ptr<circular_queue<uint8_t> > m_buffer;
|
||||
std::unique_ptr<circular_queue<uint8_t> > m_parityBuffer;
|
||||
uint32_t m_periodStart;
|
||||
uint32_t m_periodDuration;
|
||||
#ifndef ESP32
|
||||
static uint32_t m_savedPS;
|
||||
#else
|
||||
static portMUX_TYPE m_interruptsMux;
|
||||
#endif
|
||||
// the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement):
|
||||
// 1 = positive including 0, 0 = negative.
|
||||
std::unique_ptr<circular_queue<uint32_t, UARTBase*> > m_isrBuffer;
|
||||
const Delegate<void(uint32_t&&), UARTBase*> m_isrBufferForEachDel { [](UARTBase* self, uint32_t&& isrTick) { self->rxBits(isrTick); }, this };
|
||||
std::atomic<bool> m_isrOverflow { false };
|
||||
uint32_t m_isrLastTick;
|
||||
bool m_rxCurParity = false;
|
||||
Delegate<void(), void*> m_rxHandler;
|
||||
};
|
||||
|
||||
template< class GpioCapabilities > class BasicUART : public UARTBase {
|
||||
static_assert(std::is_base_of<IGpioCapabilities, GpioCapabilities>::value,
|
||||
"template argument is not derived from IGpioCapabilities");
|
||||
public:
|
||||
BasicUART() : UARTBase() {
|
||||
}
|
||||
/// Ctor to set defaults for pins.
|
||||
/// @param rxPin the GPIO pin used for RX
|
||||
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||
BasicUART(int8_t rxPin, int8_t txPin = -1, bool invert = false) :
|
||||
UARTBase(rxPin, txPin, invert) {
|
||||
}
|
||||
|
||||
/// Configure the BasicUART object for use.
|
||||
/// @param baud the TX/RX bitrate
|
||||
/// @param config sets databits, parity, and stop bit count
|
||||
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||
/// @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.
|
||||
void begin(uint32_t baud, Config config,
|
||||
int8_t rxPin, int8_t txPin, bool invert,
|
||||
int bufCapacity = 64, int isrBufCapacity = 0) {
|
||||
UARTBase::begin(baud, config, rxPin, txPin, invert);
|
||||
if (GpioCapabilities::isValidInputPin(rxPin)) {
|
||||
beginRx(GpioCapabilities:: hasPullUp(rxPin), bufCapacity, isrBufCapacity);
|
||||
}
|
||||
if (GpioCapabilities::isValidOutputPin(txPin)) {
|
||||
beginTx();
|
||||
}
|
||||
enableRx(true);
|
||||
}
|
||||
void begin(uint32_t baud, Config config,
|
||||
int8_t rxPin, int8_t txPin) {
|
||||
begin(baud, config, rxPin, txPin, m_invert);
|
||||
}
|
||||
void begin(uint32_t baud, Config config,
|
||||
int8_t rxPin) {
|
||||
begin(baud, config, rxPin, m_txPin, m_invert);
|
||||
}
|
||||
void begin(uint32_t baud, Config config = SWSERIAL_8N1) {
|
||||
begin(baud, config, m_rxPin, m_txPin, m_invert);
|
||||
}
|
||||
void setTransmitEnablePin(int8_t txEnablePin) {
|
||||
UARTBase::setTransmitEnablePin(
|
||||
GpioCapabilities::isValidOutputPin(txEnablePin) ? txEnablePin : -1);
|
||||
}
|
||||
};
|
||||
|
||||
using UART = BasicUART< GpioCapabilities >;
|
||||
|
||||
}; // namespace EspSoftwareSerial
|
||||
|
||||
using SoftwareSerial = EspSoftwareSerial::UART;
|
||||
using namespace EspSoftwareSerial;
|
||||
|
||||
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||
// these with the IRAM attribute:
|
||||
// Delegate<>::operator (), circular_queue<>::available,
|
||||
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||
|
||||
extern template void delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||
extern template size_t circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::available() const;
|
||||
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(uint32_t&&);
|
||||
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(const uint32_t&);
|
||||
|
||||
#endif // __SoftwareSerial_h
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,567 +0,0 @@
|
|||
/*
|
||||
MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate
|
||||
class
|
||||
Copyright (c) 2019-2020 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __MULTIDELEGATE_H
|
||||
#define __MULTIDELEGATE_H
|
||||
|
||||
#include <iterator>
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
#include <atomic>
|
||||
#else
|
||||
#include "circular_queue/ghostl.h"
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <interrupts.h>
|
||||
using esp8266::InterruptLock;
|
||||
#elif defined(ARDUINO)
|
||||
class InterruptLock {
|
||||
public:
|
||||
InterruptLock() {
|
||||
noInterrupts();
|
||||
}
|
||||
~InterruptLock() {
|
||||
interrupts();
|
||||
}
|
||||
};
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false, typename... P>
|
||||
struct CallP
|
||||
{
|
||||
static R execute(Delegate& del, P... args)
|
||||
{
|
||||
return del(std::forward<P...>(args...));
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, typename... P>
|
||||
struct CallP<Delegate, void, ISQUEUE, P...>
|
||||
{
|
||||
static bool execute(Delegate& del, P... args)
|
||||
{
|
||||
del(std::forward<P...>(args...));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false>
|
||||
struct Call
|
||||
{
|
||||
static R execute(Delegate& del)
|
||||
{
|
||||
return del();
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE>
|
||||
struct Call<Delegate, void, ISQUEUE>
|
||||
{
|
||||
static bool execute(Delegate& del)
|
||||
{
|
||||
del();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace delegate
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P>
|
||||
class MultiDelegatePImpl
|
||||
{
|
||||
public:
|
||||
MultiDelegatePImpl() = default;
|
||||
~MultiDelegatePImpl()
|
||||
{
|
||||
*this = nullptr;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(const MultiDelegatePImpl&) = delete;
|
||||
MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete;
|
||||
|
||||
MultiDelegatePImpl(MultiDelegatePImpl&& md)
|
||||
{
|
||||
first = md.first;
|
||||
last = md.last;
|
||||
unused = md.unused;
|
||||
nodeCount = md.nodeCount;
|
||||
md.first = nullptr;
|
||||
md.last = nullptr;
|
||||
md.unused = nullptr;
|
||||
md.nodeCount = 0;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(const Delegate& del)
|
||||
{
|
||||
add(del);
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(Delegate&& del)
|
||||
{
|
||||
add(std::move(del));
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md)
|
||||
{
|
||||
first = md.first;
|
||||
last = md.last;
|
||||
unused = md.unused;
|
||||
nodeCount = md.nodeCount;
|
||||
md.first = nullptr;
|
||||
md.last = nullptr;
|
||||
md.unused = nullptr;
|
||||
md.nodeCount = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator=(std::nullptr_t)
|
||||
{
|
||||
if (last)
|
||||
last->mNext = unused;
|
||||
if (first)
|
||||
unused = first;
|
||||
while (unused)
|
||||
{
|
||||
auto to_delete = unused;
|
||||
unused = unused->mNext;
|
||||
delete(to_delete);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator+=(const Delegate& del)
|
||||
{
|
||||
add(del);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator+=(Delegate&& del)
|
||||
{
|
||||
add(std::move(del));
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Node_t
|
||||
{
|
||||
~Node_t()
|
||||
{
|
||||
mDelegate = nullptr; // special overload in Delegate
|
||||
}
|
||||
Node_t* mNext = nullptr;
|
||||
Delegate mDelegate;
|
||||
};
|
||||
|
||||
Node_t* first = nullptr;
|
||||
Node_t* last = nullptr;
|
||||
Node_t* unused = nullptr;
|
||||
size_t nodeCount = 0;
|
||||
|
||||
// Returns a pointer to an unused Node_t,
|
||||
// or if none are available allocates a new one,
|
||||
// or nullptr if limit is reached
|
||||
Node_t* IRAM_ATTR get_node_unsafe()
|
||||
{
|
||||
Node_t* result = nullptr;
|
||||
// try to get an item from unused items list
|
||||
if (unused)
|
||||
{
|
||||
result = unused;
|
||||
unused = unused->mNext;
|
||||
}
|
||||
// if no unused items, and count not too high, allocate a new one
|
||||
else if (nodeCount < QUEUE_CAPACITY)
|
||||
{
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
result = new (std::nothrow) Node_t;
|
||||
#else
|
||||
result = new Node_t;
|
||||
#endif
|
||||
if (result)
|
||||
++nodeCount;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void recycle_node_unsafe(Node_t* node)
|
||||
{
|
||||
node->mDelegate = nullptr; // special overload in Delegate
|
||||
node->mNext = unused;
|
||||
unused = node;
|
||||
}
|
||||
|
||||
#ifndef ARDUINO
|
||||
std::mutex mutex_unused;
|
||||
#endif
|
||||
public:
|
||||
class iterator : public std::iterator<std::forward_iterator_tag, Delegate>
|
||||
{
|
||||
public:
|
||||
Node_t* current = nullptr;
|
||||
Node_t* prev = nullptr;
|
||||
const Node_t* stop = nullptr;
|
||||
|
||||
iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {}
|
||||
iterator() = default;
|
||||
iterator(const iterator&) = default;
|
||||
iterator& operator=(const iterator&) = default;
|
||||
iterator& operator=(iterator&&) = default;
|
||||
operator bool() const
|
||||
{
|
||||
return current && stop;
|
||||
}
|
||||
bool operator==(const iterator& rhs) const
|
||||
{
|
||||
return current == rhs.current;
|
||||
}
|
||||
bool operator!=(const iterator& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
Delegate& operator*() const
|
||||
{
|
||||
return current->mDelegate;
|
||||
}
|
||||
Delegate* operator->() const
|
||||
{
|
||||
return ¤t->mDelegate;
|
||||
}
|
||||
iterator& operator++() // prefix
|
||||
{
|
||||
if (current && stop != current)
|
||||
{
|
||||
prev = current;
|
||||
current = current->mNext;
|
||||
}
|
||||
else
|
||||
current = nullptr; // end
|
||||
return *this;
|
||||
}
|
||||
iterator& operator++(int) // postfix
|
||||
{
|
||||
iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return iterator(*this);
|
||||
}
|
||||
iterator end() const
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
|
||||
const Delegate* add(const Delegate& del)
|
||||
{
|
||||
return add(Delegate(del));
|
||||
}
|
||||
|
||||
const Delegate* add(Delegate&& del)
|
||||
{
|
||||
if (!del)
|
||||
return nullptr;
|
||||
|
||||
#ifdef ARDUINO
|
||||
InterruptLock lockAllInterruptsInThisScope;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||
#endif
|
||||
|
||||
Node_t* item = ISQUEUE ? get_node_unsafe() :
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
new (std::nothrow) Node_t;
|
||||
#else
|
||||
new Node_t;
|
||||
#endif
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
item->mDelegate = std::move(del);
|
||||
item->mNext = nullptr;
|
||||
|
||||
if (last)
|
||||
last->mNext = item;
|
||||
else
|
||||
first = item;
|
||||
last = item;
|
||||
|
||||
return &item->mDelegate;
|
||||
}
|
||||
|
||||
iterator erase(iterator it)
|
||||
{
|
||||
if (!it)
|
||||
return end();
|
||||
#ifdef ARDUINO
|
||||
InterruptLock lockAllInterruptsInThisScope;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||
#endif
|
||||
auto to_recycle = it.current;
|
||||
|
||||
if (last == it.current)
|
||||
last = it.prev;
|
||||
it.current = it.current->mNext;
|
||||
if (it.prev)
|
||||
{
|
||||
it.prev->mNext = it.current;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = it.current;
|
||||
}
|
||||
if (ISQUEUE)
|
||||
recycle_node_unsafe(to_recycle);
|
||||
else
|
||||
delete to_recycle;
|
||||
return it;
|
||||
}
|
||||
|
||||
bool erase(const Delegate* const del)
|
||||
{
|
||||
auto it = begin();
|
||||
while (it)
|
||||
{
|
||||
if (del == &(*it))
|
||||
{
|
||||
erase(it);
|
||||
return true;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
R operator()(P... args)
|
||||
{
|
||||
auto it = begin();
|
||||
if (!it)
|
||||
return {};
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return {};
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return {};
|
||||
#endif
|
||||
|
||||
R result;
|
||||
do
|
||||
{
|
||||
result = CallP<Delegate, R, ISQUEUE, P...>::execute(*it, args...);
|
||||
if (result && ISQUEUE)
|
||||
it = erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||
class MultiDelegateImpl : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegatePImpl;
|
||||
|
||||
R operator()()
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return {};
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return {};
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return {};
|
||||
#endif
|
||||
|
||||
R result;
|
||||
do
|
||||
{
|
||||
result = Call<Delegate, R, ISQUEUE>::execute(*it);
|
||||
if (result && ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate;
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||
class MultiDelegate<Delegate, R(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||
class MultiDelegate<Delegate, R(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||
class MultiDelegate<Delegate, void(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||
|
||||
void operator()(P... args)
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return;
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return;
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return;
|
||||
#endif
|
||||
|
||||
do
|
||||
{
|
||||
CallP<Delegate, void, ISQUEUE, P...>::execute(*it, args...);
|
||||
if (ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||
class MultiDelegate<Delegate, void(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||
|
||||
void operator()()
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return;
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return;
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return;
|
||||
#endif
|
||||
|
||||
do
|
||||
{
|
||||
Call<Delegate, void, ISQUEUE>::execute(*it);
|
||||
if (ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The MultiDelegate class template can be specialized to either a queue or an event multiplexer.
|
||||
It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function.
|
||||
@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on.
|
||||
@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true),
|
||||
the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain.
|
||||
This is exploited to minimize the use of new and delete by reusing already allocated items, thus
|
||||
reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are
|
||||
used for allocation of the event handler items.
|
||||
If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue
|
||||
removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until
|
||||
explicitly removed.
|
||||
If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue
|
||||
the type-conversion to bool of that result determines if the item is immediately removed or kept
|
||||
after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event
|
||||
handlers until they are explicitly removed.
|
||||
@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically
|
||||
allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate
|
||||
instance during its own lifetime for efficiency.
|
||||
*/
|
||||
template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||
class MultiDelegate : public delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>::MultiDelegate;
|
||||
};
|
||||
|
||||
#endif // __MULTIDELEGATE_H
|
|
@ -1,393 +0,0 @@
|
|||
/*
|
||||
circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __circular_queue_h
|
||||
#define __circular_queue_h
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include "Delegate.h"
|
||||
using std::min;
|
||||
#else
|
||||
#include "ghostl.h"
|
||||
#endif
|
||||
|
||||
#if !defined(ESP32) && !defined(ESP8266)
|
||||
#define IRAM_ATTR
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||
This implementation is lock-free between producer and consumer for the available(), peek(),
|
||||
pop(), and push() type functions.
|
||||
*/
|
||||
template< typename T, typename ForEachArg = void >
|
||||
class circular_queue
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
@brief Constructs a valid, but zero-capacity dummy queue.
|
||||
*/
|
||||
circular_queue() : m_bufSize(1)
|
||||
{
|
||||
m_inPos.store(0);
|
||||
m_outPos.store(0);
|
||||
}
|
||||
/*!
|
||||
@brief Constructs a queue of the given maximum capacity.
|
||||
*/
|
||||
circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize])
|
||||
{
|
||||
m_inPos.store(0);
|
||||
m_outPos.store(0);
|
||||
}
|
||||
circular_queue(circular_queue&& cq) :
|
||||
m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load())
|
||||
{}
|
||||
~circular_queue()
|
||||
{
|
||||
m_buffer.reset();
|
||||
}
|
||||
circular_queue(const circular_queue&) = delete;
|
||||
circular_queue& operator=(circular_queue&& cq)
|
||||
{
|
||||
m_bufSize = cq.m_bufSize;
|
||||
m_buffer = cq.m_buffer;
|
||||
m_inPos.store(cq.m_inPos.load());
|
||||
m_outPos.store(cq.m_outPos.load());
|
||||
}
|
||||
circular_queue& operator=(const circular_queue&) = delete;
|
||||
|
||||
/*!
|
||||
@brief Get the numer of elements the queue can hold at most.
|
||||
*/
|
||||
size_t capacity() const
|
||||
{
|
||||
return m_bufSize - 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Resize the queue. The available elements in the queue are preserved.
|
||||
This is not lock-free and concurrent producer or consumer access
|
||||
will lead to corruption.
|
||||
@return True if the new capacity could accommodate the present elements in
|
||||
the queue, otherwise nothing is done and false is returned.
|
||||
*/
|
||||
bool capacity(const size_t cap);
|
||||
|
||||
/*!
|
||||
@brief Discard all data in the queue.
|
||||
*/
|
||||
void flush()
|
||||
{
|
||||
m_outPos.store(m_inPos.load());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get a snapshot number of elements that can be retrieved by pop.
|
||||
*/
|
||||
size_t IRAM_ATTR available() const
|
||||
{
|
||||
int avail = static_cast<int>(m_inPos.load() - m_outPos.load());
|
||||
if (avail < 0) avail += m_bufSize;
|
||||
return avail;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get the remaining free elementes for pushing.
|
||||
*/
|
||||
size_t IRAM_ATTR available_for_push() const
|
||||
{
|
||||
int avail = static_cast<int>(m_outPos.load() - m_inPos.load()) - 1;
|
||||
if (avail < 0) avail += m_bufSize;
|
||||
return avail;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Peek at the next element pop will return without removing it from the queue.
|
||||
@return An rvalue copy of the next element that can be popped. If the queue is empty,
|
||||
return an rvalue copy of the element that is pending the next push.
|
||||
*/
|
||||
T peek() const
|
||||
{
|
||||
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return m_buffer[outPos];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Peek at the next pending input value.
|
||||
@return A reference to the next element that can be pushed.
|
||||
*/
|
||||
T& IRAM_ATTR pushpeek()
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return m_buffer[inPos];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Release the next pending input value, accessible by pushpeek(), into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push()
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const size_t next = (inPos + 1) % m_bufSize;
|
||||
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Move the rvalue parameter into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(T&& val)
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const size_t next = (inPos + 1) % m_bufSize;
|
||||
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
m_buffer[inPos] = std::move(val);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push a copy of the parameter into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(const T& val)
|
||||
{
|
||||
T v(val);
|
||||
return push(std::move(v));
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
/*!
|
||||
@brief Push copies of multiple elements from a buffer into the queue,
|
||||
in order, beginning at buffer's head.
|
||||
@return The number of elements actually copied into the queue, counted
|
||||
from the buffer head.
|
||||
*/
|
||||
size_t push_n(const T* buffer, size_t size);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Pop the next available element from the queue.
|
||||
@return An rvalue copy of the popped element, or a default
|
||||
value of type T if the queue is empty.
|
||||
*/
|
||||
T pop();
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
/*!
|
||||
@brief Pop multiple elements in ordered sequence from the queue to a buffer.
|
||||
If buffer is nullptr, simply discards up to size elements from the queue.
|
||||
@return The number of elements actually popped from the queue to
|
||||
buffer.
|
||||
*/
|
||||
size_t pop_n(T* buffer, size_t size);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Iterate over and remove each available element from queue,
|
||||
calling back fun with an rvalue reference of every single element.
|
||||
*/
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
void for_each(const Delegate<void(T&&), ForEachArg>& fun);
|
||||
#else
|
||||
void for_each(Delegate<void(T&&), ForEachArg> fun);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief In reverse order, iterate over, pop and optionally requeue each available element from the queue,
|
||||
calling back fun with a reference of every single element.
|
||||
Requeuing is dependent on the return boolean of the callback function. If it
|
||||
returns true, the requeue occurs.
|
||||
*/
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
bool for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||
#else
|
||||
bool for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
const T defaultValue {};
|
||||
size_t m_bufSize;
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
std::unique_ptr<T[]> m_buffer;
|
||||
#else
|
||||
std::unique_ptr<T> m_buffer;
|
||||
#endif
|
||||
std::atomic<size_t> m_inPos;
|
||||
std::atomic<size_t> m_outPos;
|
||||
};
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
bool circular_queue<T, ForEachArg>::capacity(const size_t cap)
|
||||
{
|
||||
if (cap + 1 == m_bufSize) return true;
|
||||
else if (available() > cap) return false;
|
||||
std::unique_ptr<T[] > buffer(new T[cap + 1]);
|
||||
const auto available = pop_n(buffer, cap);
|
||||
m_buffer.reset(buffer);
|
||||
m_bufSize = cap + 1;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
m_inPos.store(available, std::memory_order_relaxed);
|
||||
m_outPos.store(0, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
template< typename T, typename ForEachArg >
|
||||
size_t circular_queue<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||
|
||||
size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos;
|
||||
blockSize = min(size, blockSize);
|
||||
if (!blockSize) return 0;
|
||||
int next = (inPos + blockSize) % m_bufSize;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
auto dest = m_buffer.get() + inPos;
|
||||
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||
next += size;
|
||||
dest = m_buffer.get();
|
||||
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return blockSize + size;
|
||||
}
|
||||
#endif
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
T circular_queue<T, ForEachArg>::pop()
|
||||
{
|
||||
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
auto val = std::move(m_buffer[outPos]);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release);
|
||||
return val;
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
template< typename T, typename ForEachArg >
|
||||
size_t circular_queue<T, ForEachArg>::pop_n(T* buffer, size_t size) {
|
||||
size_t avail = size = min(size, available());
|
||||
if (!avail) return 0;
|
||||
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
size_t n = min(avail, static_cast<size_t>(m_bufSize - outPos));
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
if (buffer) {
|
||||
buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer);
|
||||
avail -= n;
|
||||
std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer);
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
void circular_queue<T, ForEachArg>::for_each(const Delegate<void(T&&), ForEachArg>& fun)
|
||||
#else
|
||||
void circular_queue<T, ForEachArg>::for_each(Delegate<void(T&&), ForEachArg> fun)
|
||||
#endif
|
||||
{
|
||||
auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
while (outPos != inPos)
|
||||
{
|
||||
fun(std::move(m_buffer[outPos]));
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
outPos = (outPos + 1) % m_bufSize;
|
||||
m_outPos.store(outPos, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||
#else
|
||||
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun)
|
||||
#endif
|
||||
{
|
||||
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (outPos == inPos0) return false;
|
||||
auto pos = inPos0;
|
||||
auto outPos1 = inPos0;
|
||||
const auto posDecr = circular_queue<T, ForEachArg>::m_bufSize - 1;
|
||||
do {
|
||||
pos = (pos + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[pos]);
|
||||
if (fun(val))
|
||||
{
|
||||
outPos1 = (outPos1 + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
if (outPos1 != pos) circular_queue<T, ForEachArg>::m_buffer[outPos1] = std::move(val);
|
||||
}
|
||||
} while (pos != outPos);
|
||||
circular_queue<T, ForEachArg>::m_outPos.store(outPos1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __circular_queue_h
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __circular_queue_mp_h
|
||||
#define __circular_queue_mp_h
|
||||
|
||||
#include "circular_queue.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "interrupts.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||
This implementation is lock-free between producers and consumer for the available(), peek(),
|
||||
pop(), and push() type functions, but is guarded to safely allow only a single producer
|
||||
at any instant.
|
||||
*/
|
||||
template< typename T, typename ForEachArg = void >
|
||||
class circular_queue_mp : protected circular_queue<T, ForEachArg>
|
||||
{
|
||||
public:
|
||||
circular_queue_mp() = default;
|
||||
circular_queue_mp(const size_t capacity) : circular_queue<T, ForEachArg>(capacity)
|
||||
{}
|
||||
circular_queue_mp(circular_queue<T, ForEachArg>&& cq) : circular_queue<T, ForEachArg>(std::move(cq))
|
||||
{}
|
||||
using circular_queue<T, ForEachArg>::operator=;
|
||||
using circular_queue<T, ForEachArg>::capacity;
|
||||
using circular_queue<T, ForEachArg>::flush;
|
||||
using circular_queue<T, ForEachArg>::available;
|
||||
using circular_queue<T, ForEachArg>::available_for_push;
|
||||
using circular_queue<T, ForEachArg>::peek;
|
||||
using circular_queue<T, ForEachArg>::pop;
|
||||
using circular_queue<T, ForEachArg>::pop_n;
|
||||
using circular_queue<T, ForEachArg>::for_each;
|
||||
using circular_queue<T, ForEachArg>::for_each_rev_requeue;
|
||||
|
||||
/*!
|
||||
@brief Resize the queue. The available elements in the queue are preserved.
|
||||
This is not lock-free, but safe, concurrent producer or consumer access
|
||||
is guarded.
|
||||
@return True if the new capacity could accommodate the present elements in
|
||||
the queue, otherwise nothing is done and false is returned.
|
||||
*/
|
||||
bool capacity(const size_t cap)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::capacity(cap);
|
||||
}
|
||||
|
||||
bool IRAM_ATTR push() = delete;
|
||||
|
||||
/*!
|
||||
@brief Move the rvalue parameter into the queue, guarded
|
||||
for multiple concurrent producers.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(T&& val)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push(std::move(val));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push a copy of the parameter into the queue, guarded
|
||||
for multiple concurrent producers.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(const T& val)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push(val);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push copies of multiple elements from a buffer into the queue,
|
||||
in order, beginning at buffer's head. This is guarded for
|
||||
multiple producers, push_n() is atomic.
|
||||
@return The number of elements actually copied into the queue, counted
|
||||
from the buffer head.
|
||||
*/
|
||||
size_t push_n(const T* buffer, size_t size)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push_n(buffer, size);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Pops the next available element from the queue, requeues
|
||||
it immediately.
|
||||
@return A reference to the just requeued element, or the default
|
||||
value of type T if the queue is empty.
|
||||
*/
|
||||
T& pop_requeue();
|
||||
|
||||
/*!
|
||||
@brief Iterate over, pop and optionally requeue each available element from the queue,
|
||||
calling back fun with a reference of every single element.
|
||||
Requeuing is dependent on the return boolean of the callback function. If it
|
||||
returns true, the requeue occurs.
|
||||
*/
|
||||
bool for_each_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||
|
||||
#ifndef ESP8266
|
||||
protected:
|
||||
std::mutex m_pushMtx;
|
||||
#endif
|
||||
};
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
T& circular_queue_mp<T, ForEachArg>::pop_requeue()
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
const auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_acquire);
|
||||
const auto inPos = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (inPos == outPos) return circular_queue<T, ForEachArg>::defaultValue;
|
||||
T& val = circular_queue<T, ForEachArg>::m_buffer[inPos] = std::move(circular_queue<T, ForEachArg>::m_buffer[outPos]);
|
||||
const auto bufSize = circular_queue<T, ForEachArg>::m_bufSize;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
circular_queue<T, ForEachArg>::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed);
|
||||
circular_queue<T, ForEachArg>::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release);
|
||||
return val;
|
||||
}
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
bool circular_queue_mp<T, ForEachArg>::for_each_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||
{
|
||||
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (outPos == inPos0) return false;
|
||||
do {
|
||||
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[outPos]);
|
||||
if (fun(val))
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
auto inPos = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
circular_queue<T, ForEachArg>::m_buffer[inPos] = std::move(val);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
circular_queue<T, ForEachArg>::m_inPos.store((inPos + 1) % circular_queue<T, ForEachArg>::m_bufSize, std::memory_order_release);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
}
|
||||
outPos = (outPos + 1) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
circular_queue<T, ForEachArg>::m_outPos.store(outPos, std::memory_order_release);
|
||||
} while (outPos != inPos0);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __circular_queue_mp_h
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell
|
||||
that allows building some Arduino ESP8266/ESP32
|
||||
libraries on Aruduino AVR.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __ghostl_h
|
||||
#define __ghostl_h
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
using size_t = decltype(sizeof(char));
|
||||
|
||||
namespace std
|
||||
{
|
||||
#if !defined(ARDUINO_ARCH_SAMD)
|
||||
typedef enum memory_order {
|
||||
memory_order_relaxed,
|
||||
memory_order_acquire,
|
||||
memory_order_release,
|
||||
memory_order_seq_cst
|
||||
} memory_order;
|
||||
template< typename T > class atomic {
|
||||
private:
|
||||
T value;
|
||||
public:
|
||||
atomic() {}
|
||||
atomic(T desired) { value = desired; }
|
||||
void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; }
|
||||
T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; }
|
||||
};
|
||||
inline void atomic_thread_fence(std::memory_order order) noexcept {}
|
||||
template< typename T > T&& move(T& t) noexcept { return static_cast<T&&>(t); }
|
||||
#endif
|
||||
|
||||
template< typename T, size_t long N > struct array
|
||||
{
|
||||
T _M_elems[N];
|
||||
decltype(sizeof(0)) size() const { return N; }
|
||||
T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; }
|
||||
const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; }
|
||||
};
|
||||
|
||||
template< typename T > class unique_ptr
|
||||
{
|
||||
public:
|
||||
using pointer = T*;
|
||||
unique_ptr() noexcept : ptr(nullptr) {}
|
||||
unique_ptr(pointer p) : ptr(p) {}
|
||||
pointer operator->() const noexcept { return ptr; }
|
||||
T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; }
|
||||
void reset(pointer p = pointer()) noexcept
|
||||
{
|
||||
delete ptr;
|
||||
ptr = p;
|
||||
}
|
||||
T& operator*() const { return *ptr; }
|
||||
private:
|
||||
pointer ptr;
|
||||
};
|
||||
|
||||
template< typename T > using function = T*;
|
||||
using nullptr_t = decltype(nullptr);
|
||||
|
||||
template<typename T>
|
||||
struct identity {
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline T&& forward(typename identity<T>::type& t) noexcept
|
||||
{
|
||||
return static_cast<typename identity<T>::type&&>(t);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __ghostl_h
|
|
@ -2,13 +2,14 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome import pins, automation
|
||||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ["preferences"]
|
||||
DEPENDENCIES = ["preferences", "uart"]
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
ratgdo_ns = cg.esphome_ns.namespace("ratgdo")
|
||||
RATGDO = ratgdo_ns.class_("RATGDOComponent", cg.Component)
|
||||
RATGDO = ratgdo_ns.class_("RATGDOComponent", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
CONF_OUTPUT_GDO = "output_gdo_pin"
|
||||
|
@ -104,13 +105,10 @@ async def to_code(config):
|
|||
pin = await cg.gpio_pin_expression(config[CONF_STATUS_OBST])
|
||||
cg.add(var.set_status_obst_pin(pin))
|
||||
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
cg.add_library(
|
||||
name="secplus",
|
||||
repository="https://github.com/bdraco/secplus",
|
||||
version="f98c3220356c27717a25102c0b35815ebbd26ccc",
|
||||
)
|
||||
cg.add_library(
|
||||
name="espsoftwareserial",
|
||||
repository="https://github.com/bdraco/espsoftwareserial",
|
||||
version="2f408224633316b997f82339e5b2731b1e561060",
|
||||
)
|
|
@ -126,7 +126,7 @@ namespace ratgdo {
|
|||
this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
|
||||
this->swSerial.begin(9600, SWSERIAL_8N1, this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin(), true);
|
||||
this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8);
|
||||
|
||||
this->trigger_open_pin_->attach_interrupt(RATGDOStore::isrDoorOpen, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
this->trigger_close_pin_->attach_interrupt(RATGDOStore::isrDoorClose, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
|
@ -345,11 +345,16 @@ namespace ratgdo {
|
|||
|
||||
void RATGDOComponent::gdoStateLoop()
|
||||
{
|
||||
if (!this->swSerial.available()) {
|
||||
if (!this->available()) {
|
||||
// ESP_LOGD(TAG, "No data available input:%d output:%d", this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin());
|
||||
return;
|
||||
}
|
||||
uint8_t serData = this->swSerial.read();
|
||||
uint8_t serData;
|
||||
if (!this->read_byte(&serData)) {
|
||||
ESP_LOGD(TAG, "Failed to read byte");
|
||||
return;
|
||||
}
|
||||
|
||||
static uint32_t msgStart;
|
||||
static bool reading = false;
|
||||
static uint16_t byteCount = 0;
|
||||
|
@ -481,7 +486,7 @@ namespace ratgdo {
|
|||
this->output_gdo_pin_->digital_write(false); // bring the line low
|
||||
|
||||
delayMicroseconds(1260); // "LOW" pulse duration before the message start
|
||||
this->swSerial.write(this->txRollingCode, CODE_LENGTH);
|
||||
this->write_array(this->txRollingCode, CODE_LENGTH);
|
||||
}
|
||||
|
||||
void RATGDOComponent::sync()
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
************************************/
|
||||
|
||||
#pragma once
|
||||
#include "SoftwareSerial.h" // Using espsoftwareserial https://github.com/plerup/espsoftwareserial
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
@ -79,8 +79,6 @@ namespace ratgdo {
|
|||
/********************************** GLOBAL VARS
|
||||
* *****************************************/
|
||||
uint32_t rollingCodeCounter;
|
||||
EspSoftwareSerial::UART swSerial;
|
||||
|
||||
uint8_t txRollingCode[CODE_LENGTH];
|
||||
uint8_t rxRollingCode[CODE_LENGTH];
|
||||
|
||||
|
|
Loading…
Reference in New Issue