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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
from esphome import pins, automation
|
from esphome import pins, automation
|
||||||
|
from esphome.components import uart
|
||||||
|
|
||||||
DEPENDENCIES = ["preferences"]
|
DEPENDENCIES = ["preferences", "uart"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
|
||||||
ratgdo_ns = cg.esphome_ns.namespace("ratgdo")
|
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"
|
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])
|
pin = await cg.gpio_pin_expression(config[CONF_STATUS_OBST])
|
||||||
cg.add(var.set_status_obst_pin(pin))
|
cg.add(var.set_status_obst_pin(pin))
|
||||||
|
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
cg.add_library(
|
cg.add_library(
|
||||||
name="secplus",
|
name="secplus",
|
||||||
repository="https://github.com/bdraco/secplus",
|
repository="https://github.com/bdraco/secplus",
|
||||||
version="f98c3220356c27717a25102c0b35815ebbd26ccc",
|
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_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT);
|
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_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);
|
this->trigger_close_pin_->attach_interrupt(RATGDOStore::isrDoorClose, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||||
|
@ -345,11 +345,16 @@ namespace ratgdo {
|
||||||
|
|
||||||
void RATGDOComponent::gdoStateLoop()
|
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());
|
// ESP_LOGD(TAG, "No data available input:%d output:%d", this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin());
|
||||||
return;
|
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 uint32_t msgStart;
|
||||||
static bool reading = false;
|
static bool reading = false;
|
||||||
static uint16_t byteCount = 0;
|
static uint16_t byteCount = 0;
|
||||||
|
@ -481,7 +486,7 @@ namespace ratgdo {
|
||||||
this->output_gdo_pin_->digital_write(false); // bring the line low
|
this->output_gdo_pin_->digital_write(false); // bring the line low
|
||||||
|
|
||||||
delayMicroseconds(1260); // "LOW" pulse duration before the message start
|
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()
|
void RATGDOComponent::sync()
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
************************************/
|
************************************/
|
||||||
|
|
||||||
#pragma once
|
#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/component.h"
|
||||||
#include "esphome/core/gpio.h"
|
#include "esphome/core/gpio.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
@ -79,8 +79,6 @@ namespace ratgdo {
|
||||||
/********************************** GLOBAL VARS
|
/********************************** GLOBAL VARS
|
||||||
* *****************************************/
|
* *****************************************/
|
||||||
uint32_t rollingCodeCounter;
|
uint32_t rollingCodeCounter;
|
||||||
EspSoftwareSerial::UART swSerial;
|
|
||||||
|
|
||||||
uint8_t txRollingCode[CODE_LENGTH];
|
uint8_t txRollingCode[CODE_LENGTH];
|
||||||
uint8_t rxRollingCode[CODE_LENGTH];
|
uint8_t rxRollingCode[CODE_LENGTH];
|
||||||
|
|
||||||
|
@ -152,4 +150,4 @@ namespace ratgdo {
|
||||||
}; // RATGDOComponent
|
}; // RATGDOComponent
|
||||||
|
|
||||||
} // namespace ratgdo
|
} // namespace ratgdo
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
Loading…
Reference in New Issue