From 9dd52f1ca76f200de326b77f4dabe7b9b93ff891 Mon Sep 17 00:00:00 2001 From: mkotoun Date: Sun, 7 Mar 2021 18:13:06 +0100 Subject: [PATCH 01/10] Enable use of multiple software I2C master interfaces in core --- cores/esp8266/core_esp8266_si2c.cpp | 121 +++++++++++++--------------- cores/esp8266/twi.h | 39 +++++++++ 2 files changed, 95 insertions(+), 65 deletions(-) diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index fa6d164342..0d6cb7223a 100644 --- a/cores/esp8266/core_esp8266_si2c.cpp +++ b/cores/esp8266/core_esp8266_si2c.cpp @@ -57,17 +57,12 @@ static inline __attribute__((always_inline)) bool SCL_READ(const int twi_scl) return (GPI & (1 << twi_scl)) != 0; } - // Implement as a class to reduce code size by allowing access to many global variables with a single base pointer -class Twi +// Derived from TwiMaster which can be instantied multiple times +class TwiMasterOrSlave : public TwiMaster { private: - unsigned int preferred_si2c_clock = 100000; - uint32_t twi_dcount = 18; - unsigned char twi_sda = 0; - unsigned char twi_scl = 0; unsigned char twi_addr = 0; - uint32_t twi_clockStretchLimit = 0; // These are int-wide, even though they could all fit in a byte, to reduce code size and avoid any potential // issues about RmW on packed bytes. The int-wide variations of asm instructions are smaller than the equivalent @@ -110,42 +105,15 @@ class Twi // Allow not linking in the slave code if there is no call to setAddress bool _slaveEnabled = false; - + // Internal use functions - void ICACHE_RAM_ATTR busywait(unsigned int v); - bool write_start(void); - bool write_stop(void); - bool write_bit(bool bit); - bool read_bit(void); - bool write_byte(unsigned char byte); - unsigned char read_byte(bool nack); void ICACHE_RAM_ATTR onTwipEvent(uint8_t status); - // Handle the case where a slave needs to stretch the clock with a time-limited busy wait - inline void WAIT_CLOCK_STRETCH() - { - esp8266::polledTimeout::oneShotFastUs timeout(twi_clockStretchLimit); - esp8266::polledTimeout::periodicFastUs yieldTimeout(5000); - while (!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit - { - if (yieldTimeout) // inner loop yields every 5ms - { - yield(); - } - } - } - - // Generate a clock "valley" (at the end of a segment, just before a repeated start) - void twi_scl_valley(void); - public: - void setClock(unsigned int freq); - void setClockStretchLimit(uint32_t limit); + // custom version void init(unsigned char sda, unsigned char scl); + void setAddress(uint8_t address); - unsigned char writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop); - unsigned char readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop); - uint8_t status(); uint8_t transmit(const uint8_t* data, uint8_t length); void attachSlaveRxEvent(void (*function)(uint8_t*, size_t)); void attachSlaveTxEvent(void (*function)(void)); @@ -154,13 +122,26 @@ class Twi void enableSlave(); }; -static Twi twi; +static TwiMasterOrSlave twi; #ifndef FCPU80 #define FCPU80 80000000L #endif -void Twi::setClock(unsigned int freq) +inline void TwiMaster::WAIT_CLOCK_STRETCH() +{ + esp8266::polledTimeout::oneShotFastUs timeout(twi_clockStretchLimit); + esp8266::polledTimeout::periodicFastUs yieldTimeout(5000); + while (!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit + { + if (yieldTimeout) // inner loop yields every 5ms + { + yield(); + } + } +} + +void TwiMaster::setClock(unsigned int freq) { if (freq < 1000) // minimum freq 1000Hz to minimize slave timeouts and WDT resets { @@ -190,14 +171,24 @@ void Twi::setClock(unsigned int freq) #endif } -void Twi::setClockStretchLimit(uint32_t limit) +void TwiMaster::setClockStretchLimit(uint32_t limit) { twi_clockStretchLimit = limit; } -void Twi::init(unsigned char sda, unsigned char scl) +void TwiMaster::init(unsigned char sda, unsigned char scl) +{ + twi_sda = sda; + twi_scl = scl; + pinMode(twi_sda, INPUT_PULLUP); + pinMode(twi_scl, INPUT_PULLUP); + twi_setClock(preferred_si2c_clock); + twi_setClockStretchLimit(150000L); // default value is 150 mS +} + +void TwiMasterOrSlave::init(unsigned char sda, unsigned char scl) { // set timer function ets_timer_setfn(&timer, onTimer, NULL); @@ -213,13 +204,13 @@ void Twi::init(unsigned char sda, unsigned char scl) twi_setClockStretchLimit(150000L); // default value is 150 mS } -void Twi::setAddress(uint8_t address) +void TwiMasterOrSlave::setAddress(uint8_t address) { // set twi slave address (skip over R/W bit) twi_addr = address << 1; } -void Twi::enableSlave() +void TwiMasterOrSlave::enableSlave() { if (!_slaveEnabled) { @@ -229,16 +220,16 @@ void Twi::enableSlave() } } -void ICACHE_RAM_ATTR Twi::busywait(unsigned int v) +void ICACHE_RAM_ATTR TwiMaster::busywait(unsigned int v) { unsigned int i; for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz { - __asm__ __volatile__("nop"); // minimum element to keep GCC from optimizing this function out. + asm("nop"); // minimum element to keep GCC from optimizing this function out. } } -bool Twi::write_start(void) +bool TwiMaster::write_start(void) { SCL_HIGH(twi_scl); SDA_HIGH(twi_sda); @@ -252,7 +243,7 @@ bool Twi::write_start(void) return true; } -bool Twi::write_stop(void) +bool TwiMaster::write_stop(void) { SCL_LOW(twi_scl); SDA_LOW(twi_sda); @@ -265,7 +256,7 @@ bool Twi::write_stop(void) return true; } -bool Twi::write_bit(bool bit) +bool TwiMaster::write_bit(bool bit) { SCL_LOW(twi_scl); if (bit) @@ -283,7 +274,7 @@ bool Twi::write_bit(bool bit) return true; } -bool Twi::read_bit(void) +bool TwiMaster::read_bit(void) { SCL_LOW(twi_scl); SDA_HIGH(twi_sda); @@ -295,7 +286,7 @@ bool Twi::read_bit(void) return bit; } -bool Twi::write_byte(unsigned char byte) +bool TwiMaster::write_byte(unsigned char byte) { unsigned char bit; for (bit = 0; bit < 8; bit++) @@ -306,7 +297,7 @@ bool Twi::write_byte(unsigned char byte) return !read_bit();//NACK/ACK } -unsigned char Twi::read_byte(bool nack) +unsigned char TwiMaster::read_byte(bool nack) { unsigned char byte = 0; unsigned char bit; @@ -318,7 +309,7 @@ unsigned char Twi::read_byte(bool nack) return byte; } -unsigned char Twi::writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop) +unsigned char TwiMaster::writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop) { unsigned int i; if (!write_start()) @@ -363,7 +354,7 @@ unsigned char Twi::writeTo(unsigned char address, unsigned char * buf, unsigned return 0; } -unsigned char Twi::readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop) +unsigned char TwiMaster::readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop) { unsigned int i; if (!write_start()) @@ -402,7 +393,7 @@ unsigned char Twi::readFrom(unsigned char address, unsigned char* buf, unsigned return 0; } -void Twi::twi_scl_valley(void) +void TwiMaster::twi_scl_valley(void) { SCL_LOW(twi_scl); busywait(twi_dcount); @@ -410,7 +401,7 @@ void Twi::twi_scl_valley(void) WAIT_CLOCK_STRETCH(); } -uint8_t Twi::status() +uint8_t TwiMaster::status() { WAIT_CLOCK_STRETCH(); // wait for a slow slave to finish if (!SCL_READ(twi_scl)) @@ -435,7 +426,7 @@ uint8_t Twi::status() return I2C_OK; } -uint8_t Twi::transmit(const uint8_t* data, uint8_t length) +uint8_t TwiMasterOrSlave::transmit(const uint8_t* data, uint8_t length) { uint8_t i; @@ -461,12 +452,12 @@ uint8_t Twi::transmit(const uint8_t* data, uint8_t length) return 0; } -void Twi::attachSlaveRxEvent(void (*function)(uint8_t*, size_t)) +void TwiMasterOrSlave::attachSlaveRxEvent(void (*function)(uint8_t*, size_t)) { twi_onSlaveReceive = function; } -void Twi::attachSlaveTxEvent(void (*function)(void)) +void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void)) { twi_onSlaveTransmit = function; } @@ -474,7 +465,7 @@ void Twi::attachSlaveTxEvent(void (*function)(void)) // DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into // parts and the ICACHE_RAM_ATTR isn't propagated correctly to all parts, which of course causes crashes. // TODO: test with gcc 9.x and if it still fails, disable optimization with -fdisable-ipa-fnsplit -void ICACHE_RAM_ATTR Twi::reply(uint8_t ack) +void ICACHE_RAM_ATTR TwiMasterOrSlave::reply(uint8_t ack) { // transmit master read ready signal, with or without ack if (ack) @@ -492,7 +483,7 @@ void ICACHE_RAM_ATTR Twi::reply(uint8_t ack) } -void ICACHE_RAM_ATTR Twi::releaseBus(void) +void ICACHE_RAM_ATTR TwiMasterOrSlave::releaseBus(void) { // release bus //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT); @@ -505,7 +496,7 @@ void ICACHE_RAM_ATTR Twi::releaseBus(void) } -void ICACHE_RAM_ATTR Twi::onTwipEvent(uint8_t status) +void ICACHE_RAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status) { twip_status = status; switch (status) @@ -612,7 +603,7 @@ void ICACHE_RAM_ATTR Twi::onTwipEvent(uint8_t status) } } -void ICACHE_RAM_ATTR Twi::onTimer(void *unused) +void ICACHE_RAM_ATTR TwiMasterOrSlave::onTimer(void *unused) { (void)unused; twi.releaseBus(); @@ -621,7 +612,7 @@ void ICACHE_RAM_ATTR Twi::onTimer(void *unused) twi.twip_state = TWIP_BUS_ERR; } -void Twi::eventTask(ETSEvent *e) +void TwiMasterOrSlave::eventTask(ETSEvent *e) { if (e == NULL) @@ -662,7 +653,7 @@ void Twi::eventTask(ETSEvent *e) // Shorthand for if the state is any of the or'd bitmask x #define IFSTATE(x) if (twip_state_mask & (x)) -void ICACHE_RAM_ATTR Twi::onSclChange(void) +void ICACHE_RAM_ATTR TwiMasterOrSlave::onSclChange(void) { unsigned int sda; unsigned int scl; @@ -860,7 +851,7 @@ void ICACHE_RAM_ATTR Twi::onSclChange(void) } } -void ICACHE_RAM_ATTR Twi::onSdaChange(void) +void ICACHE_RAM_ATTR TwiMasterOrSlave::onSdaChange(void) { unsigned int sda; unsigned int scl; diff --git a/cores/esp8266/twi.h b/cores/esp8266/twi.h index 27aaaff640..a32e0f361d 100644 --- a/cores/esp8266/twi.h +++ b/cores/esp8266/twi.h @@ -60,4 +60,43 @@ void twi_enableSlaveMode(void); } #endif +#ifdef __cplusplus + +// this is a C++ class, so declare it only in C++ context + +class TwiMaster +{ +protected: + unsigned int preferred_si2c_clock = 100000; + uint32_t twi_dcount = 18; + unsigned char twi_sda = 0; + unsigned char twi_scl = 0; + uint32_t twi_clockStretchLimit = 0; + + // Internal use functions + void ICACHE_RAM_ATTR busywait(unsigned int v); + bool write_start(void); + bool write_stop(void); + bool write_bit(bool bit); + bool read_bit(void); + bool write_byte(unsigned char byte); + unsigned char read_byte(bool nack); + + // Handle the case where a slave needs to stretch the clock with a time-limited busy wait + inline void WAIT_CLOCK_STRETCH(); + + // Generate a clock "valley" (at the end of a segment, just before a repeated start) + void twi_scl_valley(void); + +public: + void setClock(unsigned int freq); + void setClockStretchLimit(uint32_t limit); + void init(unsigned char sda, unsigned char scl); + unsigned char writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop); + unsigned char readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop); + uint8_t status(); +}; + +#endif + #endif From cbb5a59db4e857db0585b4897409435b9382c02e Mon Sep 17 00:00:00 2001 From: michkot Date: Sun, 7 Mar 2021 18:13:33 +0100 Subject: [PATCH 02/10] Change Wire to Master-only I2C interface --- libraries/Wire/Wire.cpp | 141 ++++++---------------------------------- libraries/Wire/Wire.h | 42 +++++------- 2 files changed, 38 insertions(+), 145 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 3aa3604ab0..33421bd699 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -38,71 +38,36 @@ extern "C" { #error Wire library is not supported on this board #endif -// Initialize Class Variables ////////////////////////////////////////////////// - -uint8_t TwoWire::rxBuffer[BUFFER_LENGTH]; -uint8_t TwoWire::rxBufferIndex = 0; -uint8_t TwoWire::rxBufferLength = 0; - -uint8_t TwoWire::txAddress = 0; -uint8_t TwoWire::txBuffer[BUFFER_LENGTH]; -uint8_t TwoWire::txBufferIndex = 0; -uint8_t TwoWire::txBufferLength = 0; - -uint8_t TwoWire::transmitting = 0; -void (*TwoWire::user_onRequest)(void); -void (*TwoWire::user_onReceive)(size_t); - -static int default_sda_pin = SDA; -static int default_scl_pin = SCL; - // Constructors //////////////////////////////////////////////////////////////// -TwoWire::TwoWire() {} +TwoWire::TwoWire(uint8_t rxBufferSize, uint8_t txBufferSize) : + rxBuffer{new uint8_t[rxBufferSize]}, + txBuffer{new uint8_t[txBufferSize]} +{ + this->txBufferSize = txBufferSize; + this->rxBufferSize = rxBufferSize; +} // Public Methods ////////////////////////////////////////////////////////////// void TwoWire::begin(int sda, int scl) { - default_sda_pin = sda; - default_scl_pin = scl; - twi_init(sda, scl); - flush(); -} - -void TwoWire::begin(int sda, int scl, uint8_t address) -{ - default_sda_pin = sda; - default_scl_pin = scl; - twi_setAddress(address); - twi_init(sda, scl); - twi_attachSlaveTxEvent(onRequestService); - twi_attachSlaveRxEvent(onReceiveService); + twi.init(sda, scl); flush(); } void TwoWire::pins(int sda, int scl) { - default_sda_pin = sda; - default_scl_pin = scl; } void TwoWire::begin(void) { - begin(default_sda_pin, default_scl_pin); -} - -void TwoWire::begin(uint8_t address) -{ - twi_setAddress(address); - twi_attachSlaveTxEvent(onRequestService); - twi_attachSlaveRxEvent(onReceiveService); - begin(); + begin(SDA, SCL); } uint8_t TwoWire::status() { - return twi_status(); + return twi.status(); } void TwoWire::begin(int address) @@ -112,21 +77,21 @@ void TwoWire::begin(int address) void TwoWire::setClock(uint32_t frequency) { - twi_setClock(frequency); + twi.setClock(frequency); } void TwoWire::setClockStretchLimit(uint32_t limit) { - twi_setClockStretchLimit(limit); + twi.setClockStretchLimit(limit); } size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) { - if (size > BUFFER_LENGTH) + if (size > rxBufferSize) { - size = BUFFER_LENGTH; + size = rxBufferSize; } - size_t read = (twi_readFrom(address, rxBuffer, size, sendStop) == 0) ? size : 0; + size_t read = (twi.readFrom(address, rxBuffer.get(), size, sendStop) == 0) ? size : 0; rxBufferIndex = 0; rxBufferLength = read; return read; @@ -167,7 +132,7 @@ void TwoWire::beginTransmission(int address) uint8_t TwoWire::endTransmission(uint8_t sendStop) { - int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, sendStop); + int8_t ret = twi.writeTo(txAddress, txBuffer.get(), txBufferLength, sendStop); txBufferIndex = 0; txBufferLength = 0; transmitting = 0; @@ -183,7 +148,7 @@ size_t TwoWire::write(uint8_t data) { if (transmitting) { - if (txBufferLength >= BUFFER_LENGTH) + if (txBufferLength >= txBufferSize) { setWriteError(); return 0; @@ -194,7 +159,8 @@ size_t TwoWire::write(uint8_t data) } else { - twi_transmit(&data, 1); + setWriteError(); + return 0; } return 1; } @@ -213,7 +179,8 @@ size_t TwoWire::write(const uint8_t *data, size_t quantity) } else { - twi_transmit(data, quantity); + setWriteError(); + return 0; } return quantity; } @@ -261,72 +228,6 @@ void TwoWire::flush(void) txBufferLength = 0; } -void TwoWire::onReceiveService(uint8_t* inBytes, size_t numBytes) -{ - // don't bother if user hasn't registered a callback - if (!user_onReceive) - { - return; - } - // // don't bother if rx buffer is in use by a master requestFrom() op - // // i know this drops data, but it allows for slight stupidity - // // meaning, they may not have read all the master requestFrom() data yet - // if(rxBufferIndex < rxBufferLength){ - // return; - // } - - // copy twi rx buffer into local read buffer - // this enables new reads to happen in parallel - for (uint8_t i = 0; i < numBytes; ++i) - { - rxBuffer[i] = inBytes[i]; - } - - // set rx iterator vars - rxBufferIndex = 0; - rxBufferLength = numBytes; - - // alert user program - user_onReceive(numBytes); -} - -void TwoWire::onRequestService(void) -{ - // don't bother if user hasn't registered a callback - if (!user_onRequest) - { - return; - } - - // reset tx buffer iterator vars - // !!! this will kill any pending pre-master sendTo() activity - txBufferIndex = 0; - txBufferLength = 0; - - // alert user program - user_onRequest(); -} - -void TwoWire::onReceive(void (*function)(int)) -{ - // arduino api compatibility fixer: - // really hope size parameter will not exceed 2^31 :) - static_assert(sizeof(int) == sizeof(size_t), "something is wrong in Arduino kingdom"); - user_onReceive = reinterpret_cast(function); -} - -void TwoWire::onReceive(void (*function)(size_t)) -{ - user_onReceive = function; - twi_enableSlaveMode(); -} - -void TwoWire::onRequest(void (*function)(void)) -{ - user_onRequest = function; - twi_enableSlaveMode(); -} - // Preinstantiate Objects ////////////////////////////////////////////////////// #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_TWOWIRE) diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 5d6b36457e..8f61fa3ad8 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -26,36 +26,31 @@ #include #include "Stream.h" - - - -#define BUFFER_LENGTH 128 +#include +#include class TwoWire : public Stream { private: - static uint8_t rxBuffer[]; - static uint8_t rxBufferIndex; - static uint8_t rxBufferLength; - - static uint8_t txAddress; - static uint8_t txBuffer[]; - static uint8_t txBufferIndex; - static uint8_t txBufferLength; - - static uint8_t transmitting; - static void (*user_onRequest)(void); - static void (*user_onReceive)(size_t); - static void onRequestService(void); - static void onReceiveService(uint8_t*, size_t); + TwiMaster twi; + + uint8_t rxBufferSize; + std::unique_ptr rxBuffer; + uint8_t rxBufferIndex; + uint8_t rxBufferLength; + + uint8_t txAddress; + uint8_t txBufferSize; + std::unique_ptr txBuffer; + uint8_t txBufferIndex; + uint8_t txBufferLength; + + uint8_t transmitting; public: - TwoWire(); + TwoWire(uint8_t rxBufferSize = 128, uint8_t txBufferSize = 128); void begin(int sda, int scl); - void begin(int sda, int scl, uint8_t address); void pins(int sda, int scl) __attribute__((deprecated)); // use begin(sda, scl) in new code void begin(); - void begin(uint8_t); - void begin(int); void setClock(uint32_t); void setClockStretchLimit(uint32_t); void beginTransmission(uint8_t); @@ -76,9 +71,6 @@ class TwoWire : public Stream virtual int read(void); virtual int peek(void); virtual void flush(void); - void onReceive(void (*)(int)); // arduino api - void onReceive(void (*)(size_t)); // legacy esp8266 backward compatibility - void onRequest(void (*)(void)); using Print::write; }; From 7d9b474f7db14a753370ab19f0a6c742c18f9838 Mon Sep 17 00:00:00 2001 From: michkot Date: Mon, 8 Mar 2021 00:43:41 +0100 Subject: [PATCH 03/10] Make TwiMaster-portion of TwiMasterOrSlave singletion available for TwoWire --- cores/esp8266/core_esp8266_si2c.cpp | 1 + cores/esp8266/twi.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index 0d6cb7223a..c97da35df8 100644 --- a/cores/esp8266/core_esp8266_si2c.cpp +++ b/cores/esp8266/core_esp8266_si2c.cpp @@ -123,6 +123,7 @@ class TwiMasterOrSlave : public TwiMaster }; static TwiMasterOrSlave twi; +TwiMaster& twiMasterSingleton = twi; #ifndef FCPU80 #define FCPU80 80000000L diff --git a/cores/esp8266/twi.h b/cores/esp8266/twi.h index a32e0f361d..625ee24c5a 100644 --- a/cores/esp8266/twi.h +++ b/cores/esp8266/twi.h @@ -97,6 +97,8 @@ class TwiMaster uint8_t status(); }; +extern TwiMaster& twiMasterSingleton; + #endif #endif From 79532bab8862eff9ec9b0948023794e68535e7bb Mon Sep 17 00:00:00 2001 From: michkot Date: Mon, 8 Mar 2021 00:53:12 +0100 Subject: [PATCH 04/10] TWI slave callbacks parameter for target object (needed for Wire library) --- cores/esp8266/core_esp8266_si2c.cpp | 35 ++++++++++++++++------------- cores/esp8266/twi.h | 25 ++++++++++++++++++--- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index c97da35df8..390d4190ab 100644 --- a/cores/esp8266/core_esp8266_si2c.cpp +++ b/cores/esp8266/core_esp8266_si2c.cpp @@ -88,8 +88,9 @@ class TwiMasterOrSlave : public TwiMaster uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH]; volatile int twi_rxBufferIndex = 0; - void (*twi_onSlaveTransmit)(void); - void (*twi_onSlaveReceive)(uint8_t*, size_t); + void* twi_SlaveTargetObject; + void (*twi_onSlaveTransmit)(void*); + void (*twi_onSlaveReceive)(uint8_t*, size_t, void*); // ETS queue/timer interfaces enum { EVENTTASK_QUEUE_SIZE = 1, EVENTTASK_QUEUE_PRIO = 2 }; @@ -103,9 +104,9 @@ class TwiMasterOrSlave : public TwiMaster static void eventTask(ETSEvent *e); static void ICACHE_RAM_ATTR onTimer(void *unused); - // Allow not linking in the slave code if there is no call to setAddress + // Allow not linking in the slave code if there is no call to enableSlave bool _slaveEnabled = false; - + // Internal use functions void ICACHE_RAM_ATTR onTwipEvent(uint8_t status); @@ -115,11 +116,11 @@ class TwiMasterOrSlave : public TwiMaster void setAddress(uint8_t address); uint8_t transmit(const uint8_t* data, uint8_t length); - void attachSlaveRxEvent(void (*function)(uint8_t*, size_t)); - void attachSlaveTxEvent(void (*function)(void)); + void attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*)); + void attachSlaveTxEvent(void (*function)(void*)); void ICACHE_RAM_ATTR reply(uint8_t ack); void ICACHE_RAM_ATTR releaseBus(void); - void enableSlave(); + void enableSlave(void* targetObject); }; static TwiMasterOrSlave twi; @@ -211,10 +212,11 @@ void TwiMasterOrSlave::setAddress(uint8_t address) twi_addr = address << 1; } -void TwiMasterOrSlave::enableSlave() +void TwiMasterOrSlave::enableSlave(void* targetObject) { if (!_slaveEnabled) { + twi_SlaveTargetObject = targetObject; attachInterrupt(twi_scl, onSclChange, CHANGE); attachInterrupt(twi_sda, onSdaChange, CHANGE); _slaveEnabled = true; @@ -453,12 +455,12 @@ uint8_t TwiMasterOrSlave::transmit(const uint8_t* data, uint8_t length) return 0; } -void TwiMasterOrSlave::attachSlaveRxEvent(void (*function)(uint8_t*, size_t)) +void TwiMasterOrSlave::attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*)) { twi_onSlaveReceive = function; } -void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void)) +void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void*)) { twi_onSlaveTransmit = function; } @@ -624,7 +626,7 @@ void TwiMasterOrSlave::eventTask(ETSEvent *e) switch (e->sig) { case TWI_SIG_TX: - twi.twi_onSlaveTransmit(); + twi.twi_onSlaveTransmit(twi.twi_SlaveTargetObject); // if they didn't change buffer & length, initialize it if (twi.twi_txBufferLength == 0) @@ -641,7 +643,7 @@ void TwiMasterOrSlave::eventTask(ETSEvent *e) case TWI_SIG_RX: // ack future responses and leave slave receiver state twi.releaseBus(); - twi.twi_onSlaveReceive(twi.twi_rxBuffer, e->par); + twi.twi_onSlaveReceive(twi.twi_rxBuffer, e->par, twi.twi_SlaveTargetObject); break; } } @@ -990,16 +992,17 @@ extern "C" { return twi.transmit(buf, len); } - void twi_attachSlaveRxEvent(void (*cb)(uint8_t*, size_t)) + void twi_attachSlaveRxEventWithTarget(void (*cb)(uint8_t*, size_t, void*)) { twi.attachSlaveRxEvent(cb); } - void twi_attachSlaveTxEvent(void (*cb)(void)) + void twi_attachSlaveTxEventWithTarget(void (*cb)(void*)) { twi.attachSlaveTxEvent(cb); } + void twi_reply(uint8_t r) { twi.reply(r); @@ -1010,9 +1013,9 @@ extern "C" { twi.releaseBus(); } - void twi_enableSlaveMode(void) + void twi_enableSlaveModeWithTarget(void* targetObject) { - twi.enableSlave(); + twi.enableSlave(targetObject); } }; diff --git a/cores/esp8266/twi.h b/cores/esp8266/twi.h index 625ee24c5a..62658325a2 100644 --- a/cores/esp8266/twi.h +++ b/cores/esp8266/twi.h @@ -48,13 +48,32 @@ uint8_t twi_status(); uint8_t twi_transmit(const uint8_t*, uint8_t); -void twi_attachSlaveRxEvent(void (*)(uint8_t*, size_t)); -void twi_attachSlaveTxEvent(void (*)(void)); +void twi_attachSlaveRxEventWithTarget(void (*)(uint8_t*, size_t, void*)); +void twi_attachSlaveTxEventWithTarget(void (*)(void*)); void twi_reply(uint8_t); //void twi_stop(void); void twi_releaseBus(void); -void twi_enableSlaveMode(void); +void twi_enableSlaveModeWithTarget(void* targetObject); + +inline void twi_attachSlaveRxEvent(void (*cb)(uint8_t*, size_t)) +{ // force cast to the previous version of the callback + // the ESP8266 convention should be fine with that: + // http://naberius.de/2015/05/14/esp8266-gpio-output-performance/ + // https://boredpentester.com/reversing-esp8266-firmware-part-5/ + twi_attachSlaveRxEventWithTarget((void (*)(uint8_t*, size_t, void*))(void*)cb); +} +inline void twi_attachSlaveTxEvent(void (*cb)(void)) +{ // force cast to the previous version of the callback + // the ESP8266 convention should be fine with that: + // http://naberius.de/2015/05/14/esp8266-gpio-output-performance/ + // https://boredpentester.com/reversing-esp8266-firmware-part-5/ + twi_attachSlaveTxEventWithTarget((void (*)(void*))(void*)cb); +} +inline void twi_enableSlaveMode(void) +{ + twi_enableSlaveModeWithTarget(NULL); +} #ifdef __cplusplus } From 887fe875fb075c5ff3e52f3336e49c15490eb959 Mon Sep 17 00:00:00 2001 From: michkot Date: Mon, 8 Mar 2021 01:04:12 +0100 Subject: [PATCH 05/10] Reworked Wire library - implemented two classes, one support only I2C master role, fully non-static with independent instances, second implementing both master and slave roles, always using the same static TWI singleton class from core - classes implementing both mods can be always used directly via names TwoWireMaster and TwoWireMasterOrSlave, however that's not a recommand use for portable code/libraries! - mode (the behaviour or TwoWire class) can be switched from default/compatible master-or-slave to master-only via TWOWIRE_MASTER_ONLY define - support for optional global Wire instance in both modes - master-only and master-or-slave modes can used together in some project by splitting code into separate compilation units --- libraries/Wire/Wire.cpp | 225 +++++++++++++++++++++++++++++++++------- libraries/Wire/Wire.h | 92 ++++++++++++++-- 2 files changed, 271 insertions(+), 46 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 33421bd699..58d61f4e34 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -38,86 +38,105 @@ extern "C" { #error Wire library is not supported on this board #endif +// Private Methods ///////////////////////////////////////////////////////////// // Constructors //////////////////////////////////////////////////////////////// -TwoWire::TwoWire(uint8_t rxBufferSize, uint8_t txBufferSize) : - rxBuffer{new uint8_t[rxBufferSize]}, - txBuffer{new uint8_t[txBufferSize]} +TwoWireBase::TwoWireBase(uint8_t rxBufferSize, uint8_t txBufferSize) : + twiMaster{new TwiMaster}, + rxBufferSize{rxBufferSize}, + rxBuffer{new uint8_t[rxBufferSize]}, + txBufferSize{txBufferSize}, + txBuffer{new uint8_t[txBufferSize]} { - this->txBufferSize = txBufferSize; - this->rxBufferSize = rxBufferSize; } -// Public Methods ////////////////////////////////////////////////////////////// +TwoWireBase::TwoWireBase(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) : + twiMaster{twiPtr}, + rxBufferSize{rxBufferSize}, + rxBuffer{rxBuffer}, + txBufferSize{txBufferSize}, + txBuffer{txBuffer} +{ +} -void TwoWire::begin(int sda, int scl) +inline TwiMaster& TwoWireBase::getTwiMaster() { - twi.init(sda, scl); - flush(); + return *twiMaster; } +inline void TwoWireBase::releaseTwiMaster() +{ + twiMaster.release(); +} + +// Public Methods ////////////////////////////////////////////////////////////// -void TwoWire::pins(int sda, int scl) +void TwoWireBase::begin(int sda, int scl) { + lastSdaPin = sda; + lastSclPin = scl; + getTwiMaster().init(sda, scl); + flush(); } -void TwoWire::begin(void) +void TwoWireBase::begin(void) { - begin(SDA, SCL); + begin(lastSdaPin, lastSclPin); } -uint8_t TwoWire::status() +void TwoWireBase::pins(int sda, int scl) { - return twi.status(); + lastSdaPin = sda; + lastSclPin = scl; } -void TwoWire::begin(int address) +uint8_t TwoWireBase::status() { - begin((uint8_t)address); + return getTwiMaster().status(); } -void TwoWire::setClock(uint32_t frequency) +void TwoWireBase::setClock(uint32_t frequency) { - twi.setClock(frequency); + getTwiMaster().setClock(frequency); } -void TwoWire::setClockStretchLimit(uint32_t limit) +void TwoWireBase::setClockStretchLimit(uint32_t limit) { - twi.setClockStretchLimit(limit); + getTwiMaster().setClockStretchLimit(limit); } -size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) +size_t TwoWireBase::requestFrom(uint8_t address, size_t size, bool sendStop) { if (size > rxBufferSize) { size = rxBufferSize; } - size_t read = (twi.readFrom(address, rxBuffer.get(), size, sendStop) == 0) ? size : 0; + size_t read = (getTwiMaster().readFrom(address, rxBuffer.get(), size, sendStop) == 0) ? size : 0; rxBufferIndex = 0; rxBufferLength = read; return read; } -uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) +uint8_t TwoWireBase::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) { return requestFrom(address, static_cast(quantity), static_cast(sendStop)); } -uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity) +uint8_t TwoWireBase::requestFrom(uint8_t address, uint8_t quantity) { return requestFrom(address, static_cast(quantity), true); } -uint8_t TwoWire::requestFrom(int address, int quantity) +uint8_t TwoWireBase::requestFrom(int address, int quantity) { return requestFrom(static_cast(address), static_cast(quantity), true); } -uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop) +uint8_t TwoWireBase::requestFrom(int address, int quantity, int sendStop) { return requestFrom(static_cast(address), static_cast(quantity), static_cast(sendStop)); } -void TwoWire::beginTransmission(uint8_t address) +void TwoWireBase::beginTransmission(uint8_t address) { transmitting = 1; txAddress = address; @@ -125,26 +144,26 @@ void TwoWire::beginTransmission(uint8_t address) txBufferLength = 0; } -void TwoWire::beginTransmission(int address) +void TwoWireBase::beginTransmission(int address) { beginTransmission((uint8_t)address); } -uint8_t TwoWire::endTransmission(uint8_t sendStop) +uint8_t TwoWireBase::endTransmission(uint8_t sendStop) { - int8_t ret = twi.writeTo(txAddress, txBuffer.get(), txBufferLength, sendStop); + int8_t ret = getTwiMaster().writeTo(txAddress, txBuffer.get(), txBufferLength, sendStop); txBufferIndex = 0; txBufferLength = 0; transmitting = 0; return ret; } -uint8_t TwoWire::endTransmission(void) +uint8_t TwoWireBase::endTransmission(void) { return endTransmission(true); } -size_t TwoWire::write(uint8_t data) +size_t TwoWireBase::write(uint8_t data) { if (transmitting) { @@ -165,7 +184,7 @@ size_t TwoWire::write(uint8_t data) return 1; } -size_t TwoWire::write(const uint8_t *data, size_t quantity) +size_t TwoWireBase::write(const uint8_t *data, size_t quantity) { if (transmitting) { @@ -185,7 +204,7 @@ size_t TwoWire::write(const uint8_t *data, size_t quantity) return quantity; } -int TwoWire::available(void) +int TwoWireBase::available(void) { int result = rxBufferLength - rxBufferIndex; @@ -199,7 +218,7 @@ int TwoWire::available(void) return result; } -int TwoWire::read(void) +int TwoWireBase::read(void) { int value = -1; if (rxBufferIndex < rxBufferLength) @@ -210,7 +229,7 @@ int TwoWire::read(void) return value; } -int TwoWire::peek(void) +int TwoWireBase::peek(void) { int value = -1; if (rxBufferIndex < rxBufferLength) @@ -220,7 +239,7 @@ int TwoWire::peek(void) return value; } -void TwoWire::flush(void) +void TwoWireBase::flush(void) { rxBufferIndex = 0; rxBufferLength = 0; @@ -228,8 +247,136 @@ void TwoWire::flush(void) txBufferLength = 0; } -// Preinstantiate Objects ////////////////////////////////////////////////////// +// Master-only Constructors //////////////////////////////////////////////////// + +TwoWireMaster::TwoWireMaster(uint8_t rxBufferSize, uint8_t txBufferSize) + : TwoWireBase(rxBufferSize, txBufferSize) +{} +TwoWireMaster::TwoWireMaster(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) + : TwoWireBase(new TwiMaster{}, rxBufferSize, txBufferSize, rxBuffer, txBuffer) +{} + +// Master-or-Slave Constructors //////////////////////////////////////////////// + +TwoWireMasterOrSlave::TwoWireMasterOrSlave(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) + : TwoWireBase(&twiMasterSingleton, rxBufferSize, txBufferSize, rxBuffer, txBuffer) +{} + +TwoWireMasterOrSlave::~TwoWireMasterOrSlave() +{ + releaseTwiMaster(); +} + +// Master-or-Slave Public Methods ////////////////////////////////////////////// + +void TwoWireMasterOrSlave::begin(int sda, int scl, uint8_t address) +{ + twi_setAddress(address); + twi_attachSlaveTxEventWithTarget(onRequestService); + twi_attachSlaveRxEventWithTarget(onReceiveService); + begin(sda, scl); +} + +void TwoWireMasterOrSlave::begin(uint8_t address) +{ + twi_setAddress(address); + twi_attachSlaveTxEventWithTarget(onRequestService); + twi_attachSlaveRxEventWithTarget(onReceiveService); + begin(); +} +void TwoWireMasterOrSlave::begin(int address) +{ + begin((uint8_t)address); +} + +void TwoWireMasterOrSlave::onReceiveService(uint8_t* inBytes, size_t numBytes, void* targetObject) +{ + auto& instance = *(TwoWireMasterOrSlave*)targetObject; + + // return if targetObject (an instance of TwoWireMasterOrSlave) was not set/received correctly + // don't bother if user hasn't registered a callback + if (targetObject == nullptr || !instance.user_onReceive) + { + return; + } + // // don't bother if rx buffer is in use by a master requestFrom() op + // // i know this drops data, but it allows for slight stupidity + // // meaning, they may not have read all the master requestFrom() data yet + // if(rxBufferIndex < rxBufferLength){ + // return; + // } + + // copy twi rx buffer into local read buffer + // this enables new reads to happen in parallel + for (uint8_t i = 0; i < numBytes; ++i) + { + instance.rxBuffer[i] = inBytes[i]; + } + + // set rx iterator vars + instance.rxBufferIndex = 0; + instance.rxBufferLength = numBytes; + + // alert user program + instance.user_onReceive(numBytes); +} + +void TwoWireMasterOrSlave::onRequestService(void* targetObject) +{ + auto& instance = *(TwoWireMasterOrSlave*)targetObject; + + // return if targetObject (an instance of TwoWireMasterOrSlave) was not set/received correctly + // don't bother if user hasn't registered a callback + if (targetObject == nullptr || !instance.user_onRequest) + { + return; + } + + // reset tx buffer iterator vars + // !!! this will kill any pending pre-master sendTo() activity + instance.txBufferIndex = 0; + instance.txBufferLength = 0; + + // alert user program + instance.user_onRequest(); +} + +void TwoWireMasterOrSlave::onReceive(void (*function)(int)) +{ + // arduino api compatibility fixer: + // really hope size parameter will not exceed 2^31 :) + static_assert(sizeof(int) == sizeof(size_t), "something is wrong in Arduino kingdom"); + user_onReceive = reinterpret_cast(function); +} + +void TwoWireMasterOrSlave::onReceive(void (*function)(size_t)) +{ + user_onReceive = function; + twi_enableSlaveModeWithTarget(this); +} + +void TwoWireMasterOrSlave::onRequest(void (*function)(void)) +{ + user_onRequest = function; + twi_enableSlaveModeWithTarget(this); +} + +// Preinstantiate Objects ////////////////////////////////////////////////////// #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_TWOWIRE) -TwoWire Wire; +static uint8_t _rxBuffer[I2C_BUFFER_LENGTH]; +static uint8_t _txBuffer[I2C_BUFFER_LENGTH]; + +TwoWire Wire{I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH, _rxBuffer, _txBuffer}; + +TwoWireMasterOrSlave::TwoWireMasterOrSlave() + : TwoWireBase(&twiMasterSingleton, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH, _txBuffer, _rxBuffer) +{} + +#else + +TwoWireMasterOrSlave::TwoWireMasterOrSlave() + : TwoWireBase(&twiMasterSingleton, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH, new uint8_t[I2C_BUFFER_LENGTH], new uint8_t[I2C_BUFFER_LENGTH]) +{} + #endif diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 8f61fa3ad8..29aaa82443 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -24,33 +24,65 @@ #ifndef TwoWire_h #define TwoWire_h +/* + If you need multiple TwoWire instnaces for multiple I2C master interfaces, + define TWOWIRE_MASTER_ONLY (and optionally NO_GLOBAL_TWOWIRE). Then, you can + freely instantiate the TwoWire class. If you do NOT define TWOWIRE_MASTER_ONLY, + all TwoWire instances will share the same Master-or-Slave core TWI + implementation -> any call to begin(...) will change pins used for all instances. + + You can use different configuration in separate compilation units, e.g., use + TWOWIRE_MASTER_ONLY in most of the code, and don't use it only in the + compilation unit where you need I2C slave capabilities. +*/ + #include #include "Stream.h" #include -#include -class TwoWire : public Stream +// forward declaration, no need to load twi.h here! + +class TwiMaster; + +#ifndef I2C_BUFFER_LENGTH +#define I2C_BUFFER_LENGTH 128 +#endif + +class TwoWireBase : public Stream { private: - TwiMaster twi; - + std::unique_ptr twiMaster; +protected: + // creates new TwiMaster in heap + TwoWireBase(uint8_t rxBufferSize, uint8_t txBufferSize); + // uses existing TwiMaster ptr (usefull for MasterOrSlave implementaion) and existing buffers + TwoWireBase(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer); + + inline TwiMaster& getTwiMaster(); + // release the TwiMaster ptr from the unique_ptr, usefull when a global variable is used + inline void releaseTwiMaster(); + + // cashing of pins - needed for libraries that call begin() which might reset the non-default PIN settings + uint8_t lastSdaPin = SDA; + uint8_t lastSclPin = SCL; + uint8_t rxBufferSize; std::unique_ptr rxBuffer; uint8_t rxBufferIndex; uint8_t rxBufferLength; uint8_t txAddress; - uint8_t txBufferSize; + uint8_t txBufferSize; std::unique_ptr txBuffer; uint8_t txBufferIndex; uint8_t txBufferLength; uint8_t transmitting; + public: - TwoWire(uint8_t rxBufferSize = 128, uint8_t txBufferSize = 128); void begin(int sda, int scl); - void pins(int sda, int scl) __attribute__((deprecated)); // use begin(sda, scl) in new code void begin(); + void pins(int sda, int scl) __attribute__((deprecated)); // use begin(sda, scl) in new code void setClock(uint32_t); void setClockStretchLimit(uint32_t); void beginTransmission(uint8_t); @@ -75,6 +107,52 @@ class TwoWire : public Stream using Print::write; }; +// we could use typedefs the other way, and also do it the other way arround (using TwoWire = TwoWireMaster / TwoWireMasterOrSlave) +// but some libraries expecting TwoWire to be a class and do forward declarations of it (which are incompatible with typedefs) +// which breaks if TwoWire is only a typedef! So we need to create the following hack: +#ifdef TWOWIRE_MASTER_ONLY +#define TwoWireMaster TwoWire +#else +#define TwoWireMasterOrSlave TwoWire +#endif + +// this is expected to be used with multiple instances for different pins +class TwoWireMaster : public TwoWireBase +{ +public: + TwoWireMaster(uint8_t rxBufferSize = I2C_BUFFER_LENGTH, uint8_t txBufferSize = I2C_BUFFER_LENGTH); + // singleton global instance constructor + TwoWireMaster(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer); +}; + +// this is expected to be used a singleton, it uses the singleton instance of TwiMaster, twiMasterSingleton, from twi.h +class TwoWireMasterOrSlave : public TwoWireBase +{ +private: + void (*user_onRequest)(void); + void (*user_onReceive)(size_t); + + static void onRequestService(void*); + static void onReceiveService(uint8_t*, size_t, void*); + +public: + // singleton global instance constructor + TwoWireMasterOrSlave(); + // singleton global instance constructor + TwoWireMasterOrSlave(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer); + // releases the pointers to TwiMaster + ~TwoWireMasterOrSlave(); + + using TwoWireBase::begin; + void begin(int sda, int scl, uint8_t address); + void begin(uint8_t address); + void begin(int address); + + void onReceive(void (*)(int)); // arduino api + void onReceive(void (*)(size_t)); // legacy esp8266 backward compatibility + void onRequest(void (*)(void)); +}; + #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_TWOWIRE) extern TwoWire Wire; #endif From 3b8bd99c40250ab86a7da89751fadef8605fdd2b Mon Sep 17 00:00:00 2001 From: michkot Date: Tue, 9 Mar 2021 00:28:31 +0100 Subject: [PATCH 06/10] Prevent #define leak outside of Wire.h and Wire.cpp, provide public typedef --- libraries/Wire/Wire.cpp | 17 ++++++++++------- libraries/Wire/Wire.h | 12 ++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 58d61f4e34..7b6b2378a9 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -29,6 +29,7 @@ extern "C" { } #include "twi.h" +#define TwoWire_h_IMPLEMENTATION #include "Wire.h" @@ -41,16 +42,18 @@ extern "C" { // Private Methods ///////////////////////////////////////////////////////////// // Constructors //////////////////////////////////////////////////////////////// -TwoWireBase::TwoWireBase(uint8_t rxBufferSize, uint8_t txBufferSize) : - twiMaster{new TwiMaster}, - rxBufferSize{rxBufferSize}, - rxBuffer{new uint8_t[rxBufferSize]}, - txBufferSize{txBufferSize}, - txBuffer{new uint8_t[txBufferSize]} +TwoWireBase::TwoWireBase(uint8_t rxBufferSize, uint8_t txBufferSize) + : + twiMaster(new TwiMaster), + rxBufferSize(rxBufferSize), + rxBuffer(new uint8_t[rxBufferSize]), + txBufferSize(txBufferSize), + txBuffer(new uint8_t[txBufferSize]) { } -TwoWireBase::TwoWireBase(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) : +TwoWireBase::TwoWireBase(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) + : twiMaster{twiPtr}, rxBufferSize{rxBufferSize}, rxBuffer{rxBuffer}, diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 29aaa82443..20d20f5250 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -157,5 +157,17 @@ class TwoWireMasterOrSlave : public TwoWireBase extern TwoWire Wire; #endif +#ifndef TwoWire_h_IMPLEMENTATION + +#ifdef TWOWIRE_MASTER_ONLY +#undef TwoWireMaster +using TwoWireMaster = TwoWire; +#else +#undef TwoWireMasterOrSlave +using TwoWireMasterOrSlave = TwoWire; #endif +#endif // TwoWire_h_IMPLEMENTATION + +#endif // TwoWire_h + From 57af720fe4f86bf02c4e9fb80d81bcdf48e68609 Mon Sep 17 00:00:00 2001 From: michkot Date: Sat, 27 Nov 2021 02:28:27 +0100 Subject: [PATCH 07/10] fix comments --- libraries/Wire/Wire.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 20d20f5250..437364dd73 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -25,11 +25,12 @@ #define TwoWire_h /* - If you need multiple TwoWire instnaces for multiple I2C master interfaces, + If you need multiple TwoWire instances for multiple I2C master interfaces, define TWOWIRE_MASTER_ONLY (and optionally NO_GLOBAL_TWOWIRE). Then, you can freely instantiate the TwoWire class. If you do NOT define TWOWIRE_MASTER_ONLY, - all TwoWire instances will share the same Master-or-Slave core TWI - implementation -> any call to begin(...) will change pins used for all instances. + all the class called TwoWire will behave like singleton-like (existing behaviour) + and all instances will share the same Master-or-Slave core TWI implementation + -> any call to begin(...) will change pins used for all instances. You can use different configuration in separate compilation units, e.g., use TWOWIRE_MASTER_ONLY in most of the code, and don't use it only in the From d9554607b54ea4bae9ea34605faf39e81aa4f48c Mon Sep 17 00:00:00 2001 From: michkot Date: Sat, 27 Nov 2021 02:29:30 +0100 Subject: [PATCH 08/10] Hot-fix for making MasterOrSlave derivate of Master for easy downcasting --- libraries/Wire/Wire.cpp | 4 ++-- libraries/Wire/Wire.h | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 7b6b2378a9..ff3e33d298 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -263,7 +263,7 @@ TwoWireMaster::TwoWireMaster(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t // Master-or-Slave Constructors //////////////////////////////////////////////// TwoWireMasterOrSlave::TwoWireMasterOrSlave(uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) - : TwoWireBase(&twiMasterSingleton, rxBufferSize, txBufferSize, rxBuffer, txBuffer) + : TwoWireMaster(&twiMasterSingleton, rxBufferSize, txBufferSize, rxBuffer, txBuffer) {} TwoWireMasterOrSlave::~TwoWireMasterOrSlave() @@ -379,7 +379,7 @@ TwoWireMasterOrSlave::TwoWireMasterOrSlave() #else TwoWireMasterOrSlave::TwoWireMasterOrSlave() - : TwoWireBase(&twiMasterSingleton, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH, new uint8_t[I2C_BUFFER_LENGTH], new uint8_t[I2C_BUFFER_LENGTH]) + : TwoWireMaster(&twiMasterSingleton, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH, new uint8_t[I2C_BUFFER_LENGTH], new uint8_t[I2C_BUFFER_LENGTH]) {} #endif diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 437364dd73..4d12ea3ab4 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -35,6 +35,9 @@ You can use different configuration in separate compilation units, e.g., use TWOWIRE_MASTER_ONLY in most of the code, and don't use it only in the compilation unit where you need I2C slave capabilities. + + You can also explicitely define TwoWireMaster instances and cast them to TwoWire + when e.g. passing them to libarires using only master features. */ #include @@ -120,6 +123,10 @@ class TwoWireBase : public Stream // this is expected to be used with multiple instances for different pins class TwoWireMaster : public TwoWireBase { +protected: + // forwards to base + // uses existing TwiMaster ptr (usefull for MasterOrSlave implementaion) and existing buffers + TwoWireMaster(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer); public: TwoWireMaster(uint8_t rxBufferSize = I2C_BUFFER_LENGTH, uint8_t txBufferSize = I2C_BUFFER_LENGTH); // singleton global instance constructor @@ -127,7 +134,7 @@ class TwoWireMaster : public TwoWireBase }; // this is expected to be used a singleton, it uses the singleton instance of TwiMaster, twiMasterSingleton, from twi.h -class TwoWireMasterOrSlave : public TwoWireBase +class TwoWireMasterOrSlave : public TwoWireMaster { private: void (*user_onRequest)(void); From f08470c7b9cd6144e60911f96f889f47e537b6d9 Mon Sep 17 00:00:00 2001 From: michkot Date: Sat, 27 Nov 2021 02:52:13 +0100 Subject: [PATCH 09/10] Fix ICACHE_RAM_ATTR to IRAM_ATTR for latest master --- cores/esp8266/core_esp8266_si2c.cpp | 28 ++++++++++++++-------------- cores/esp8266/twi.h | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index 390d4190ab..e9ea848809 100644 --- a/cores/esp8266/core_esp8266_si2c.cpp +++ b/cores/esp8266/core_esp8266_si2c.cpp @@ -99,16 +99,16 @@ class TwiMasterOrSlave : public TwiMaster ETSTimer timer; // Event/IRQ callbacks, so they can't use "this" and need to be static - static void ICACHE_RAM_ATTR onSclChange(void); - static void ICACHE_RAM_ATTR onSdaChange(void); + static void IRAM_ATTR onSclChange(void); + static void IRAM_ATTR onSdaChange(void); static void eventTask(ETSEvent *e); - static void ICACHE_RAM_ATTR onTimer(void *unused); + static void IRAM_ATTR onTimer(void *unused); // Allow not linking in the slave code if there is no call to enableSlave bool _slaveEnabled = false; // Internal use functions - void ICACHE_RAM_ATTR onTwipEvent(uint8_t status); + void IRAM_ATTR onTwipEvent(uint8_t status); public: // custom version @@ -118,8 +118,8 @@ class TwiMasterOrSlave : public TwiMaster uint8_t transmit(const uint8_t* data, uint8_t length); void attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*)); void attachSlaveTxEvent(void (*function)(void*)); - void ICACHE_RAM_ATTR reply(uint8_t ack); - void ICACHE_RAM_ATTR releaseBus(void); + void IRAM_ATTR reply(uint8_t ack); + void IRAM_ATTR releaseBus(void); void enableSlave(void* targetObject); }; @@ -223,7 +223,7 @@ void TwiMasterOrSlave::enableSlave(void* targetObject) } } -void ICACHE_RAM_ATTR TwiMaster::busywait(unsigned int v) +void IRAM_ATTR TwiMaster::busywait(unsigned int v) { unsigned int i; for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz @@ -466,9 +466,9 @@ void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void*)) } // DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into -// parts and the ICACHE_RAM_ATTR isn't propagated correctly to all parts, which of course causes crashes. +// parts and the IRAM_ATTR isn't propagated correctly to all parts, which of course causes crashes. // TODO: test with gcc 9.x and if it still fails, disable optimization with -fdisable-ipa-fnsplit -void ICACHE_RAM_ATTR TwiMasterOrSlave::reply(uint8_t ack) +void IRAM_ATTR TwiMasterOrSlave::reply(uint8_t ack) { // transmit master read ready signal, with or without ack if (ack) @@ -486,7 +486,7 @@ void ICACHE_RAM_ATTR TwiMasterOrSlave::reply(uint8_t ack) } -void ICACHE_RAM_ATTR TwiMasterOrSlave::releaseBus(void) +void IRAM_ATTR TwiMasterOrSlave::releaseBus(void) { // release bus //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT); @@ -499,7 +499,7 @@ void ICACHE_RAM_ATTR TwiMasterOrSlave::releaseBus(void) } -void ICACHE_RAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status) +void IRAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status) { twip_status = status; switch (status) @@ -606,7 +606,7 @@ void ICACHE_RAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status) } } -void ICACHE_RAM_ATTR TwiMasterOrSlave::onTimer(void *unused) +void IRAM_ATTR TwiMasterOrSlave::onTimer(void *unused) { (void)unused; twi.releaseBus(); @@ -656,7 +656,7 @@ void TwiMasterOrSlave::eventTask(ETSEvent *e) // Shorthand for if the state is any of the or'd bitmask x #define IFSTATE(x) if (twip_state_mask & (x)) -void ICACHE_RAM_ATTR TwiMasterOrSlave::onSclChange(void) +void IRAM_ATTR TwiMasterOrSlave::onSclChange(void) { unsigned int sda; unsigned int scl; @@ -854,7 +854,7 @@ void ICACHE_RAM_ATTR TwiMasterOrSlave::onSclChange(void) } } -void ICACHE_RAM_ATTR TwiMasterOrSlave::onSdaChange(void) +void IRAM_ATTR TwiMasterOrSlave::onSdaChange(void) { unsigned int sda; unsigned int scl; diff --git a/cores/esp8266/twi.h b/cores/esp8266/twi.h index 62658325a2..99043ab362 100644 --- a/cores/esp8266/twi.h +++ b/cores/esp8266/twi.h @@ -93,7 +93,7 @@ class TwiMaster uint32_t twi_clockStretchLimit = 0; // Internal use functions - void ICACHE_RAM_ATTR busywait(unsigned int v); + void IRAM_ATTR busywait(unsigned int v); bool write_start(void); bool write_stop(void); bool write_bit(bool bit); From 685cbd59d41c6c8e405f721eb3168d9149df5c52 Mon Sep 17 00:00:00 2001 From: michkot Date: Sun, 16 Jan 2022 14:38:55 +0100 Subject: [PATCH 10/10] Fix add missing ctor --- libraries/Wire/Wire.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index ff3e33d298..5babb91f76 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -252,6 +252,11 @@ void TwoWireBase::flush(void) // Master-only Constructors //////////////////////////////////////////////////// +// protected, for use with TwoWireMasterOrSlave +TwoWireMaster::TwoWireMaster(TwiMaster* twiPtr, uint8_t rxBufferSize, uint8_t txBufferSize, uint8_t* rxBuffer, uint8_t* txBuffer) + : TwoWireBase(twiPtr, rxBufferSize, txBufferSize, rxBuffer, txBuffer) +{} + TwoWireMaster::TwoWireMaster(uint8_t rxBufferSize, uint8_t txBufferSize) : TwoWireBase(rxBufferSize, txBufferSize) {}