diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index e4bb1c2b07..e9ea848809 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 @@ -93,8 +88,9 @@ class Twi 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 }; @@ -108,59 +104,46 @@ class Twi static void eventTask(ETSEvent *e); static void IRAM_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 IRAM_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 IRAM_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)); + void attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*)); + void attachSlaveTxEvent(void (*function)(void*)); void IRAM_ATTR reply(uint8_t ack); void IRAM_ATTR releaseBus(void); - void enableSlave(); + void enableSlave(void* targetObject); }; -static Twi twi; +static TwiMasterOrSlave twi; +TwiMaster& twiMasterSingleton = 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 +173,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,32 +206,33 @@ 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(void* targetObject) { if (!_slaveEnabled) { + twi_SlaveTargetObject = targetObject; attachInterrupt(twi_scl, onSclChange, CHANGE); attachInterrupt(twi_sda, onSdaChange, CHANGE); _slaveEnabled = true; } } -void IRAM_ATTR Twi::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 { - __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 +246,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 +259,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 +277,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 +289,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 +300,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 +312,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 +357,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 +396,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 +404,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 +429,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 +455,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, void*)) { twi_onSlaveReceive = function; } -void Twi::attachSlaveTxEvent(void (*function)(void)) +void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void*)) { twi_onSlaveTransmit = function; } @@ -474,7 +468,7 @@ void Twi::attachSlaveTxEvent(void (*function)(void)) // DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into // 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 IRAM_ATTR Twi::reply(uint8_t ack) +void IRAM_ATTR TwiMasterOrSlave::reply(uint8_t ack) { // transmit master read ready signal, with or without ack if (ack) @@ -492,7 +486,7 @@ void IRAM_ATTR Twi::reply(uint8_t ack) } -void IRAM_ATTR Twi::releaseBus(void) +void IRAM_ATTR TwiMasterOrSlave::releaseBus(void) { // release bus //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT); @@ -505,7 +499,7 @@ void IRAM_ATTR Twi::releaseBus(void) } -void IRAM_ATTR Twi::onTwipEvent(uint8_t status) +void IRAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status) { twip_status = status; switch (status) @@ -612,7 +606,7 @@ void IRAM_ATTR Twi::onTwipEvent(uint8_t status) } } -void IRAM_ATTR Twi::onTimer(void *unused) +void IRAM_ATTR TwiMasterOrSlave::onTimer(void *unused) { (void)unused; twi.releaseBus(); @@ -621,7 +615,7 @@ void IRAM_ATTR Twi::onTimer(void *unused) twi.twip_state = TWIP_BUS_ERR; } -void Twi::eventTask(ETSEvent *e) +void TwiMasterOrSlave::eventTask(ETSEvent *e) { if (e == NULL) @@ -632,7 +626,7 @@ void Twi::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) @@ -649,7 +643,7 @@ void Twi::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; } } @@ -662,7 +656,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 IRAM_ATTR Twi::onSclChange(void) +void IRAM_ATTR TwiMasterOrSlave::onSclChange(void) { unsigned int sda; unsigned int scl; @@ -860,7 +854,7 @@ void IRAM_ATTR Twi::onSclChange(void) } } -void IRAM_ATTR Twi::onSdaChange(void) +void IRAM_ATTR TwiMasterOrSlave::onSdaChange(void) { unsigned int sda; unsigned int scl; @@ -998,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); @@ -1018,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 27aaaff640..99043ab362 100644 --- a/cores/esp8266/twi.h +++ b/cores/esp8266/twi.h @@ -48,16 +48,76 @@ 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 } #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 IRAM_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(); +}; + +extern TwiMaster& twiMasterSingleton; + +#endif + #endif diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 3aa3604ab0..5babb91f76 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" @@ -38,121 +39,107 @@ 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; - +// Private Methods ///////////////////////////////////////////////////////////// // Constructors //////////////////////////////////////////////////////////////// -TwoWire::TwoWire() {} - -// Public Methods ////////////////////////////////////////////////////////////// - -void TwoWire::begin(int sda, int scl) +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]) { - default_sda_pin = sda; - default_scl_pin = scl; - twi_init(sda, scl); - flush(); } -void TwoWire::begin(int sda, int scl, uint8_t address) +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} { - default_sda_pin = sda; - default_scl_pin = scl; - twi_setAddress(address); - twi_init(sda, scl); - twi_attachSlaveTxEvent(onRequestService); - twi_attachSlaveRxEvent(onReceiveService); - flush(); } -void TwoWire::pins(int sda, int scl) +inline TwiMaster& TwoWireBase::getTwiMaster() +{ + return *twiMaster; +} +inline void TwoWireBase::releaseTwiMaster() { - default_sda_pin = sda; - default_scl_pin = scl; + twiMaster.release(); } -void TwoWire::begin(void) +// Public Methods ////////////////////////////////////////////////////////////// + +void TwoWireBase::begin(int sda, int scl) { - begin(default_sda_pin, default_scl_pin); + lastSdaPin = sda; + lastSclPin = scl; + getTwiMaster().init(sda, scl); + flush(); } -void TwoWire::begin(uint8_t address) +void TwoWireBase::begin(void) { - twi_setAddress(address); - twi_attachSlaveTxEvent(onRequestService); - twi_attachSlaveRxEvent(onReceiveService); - begin(); + 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 > 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 = (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; @@ -160,30 +147,30 @@ 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, 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) { - if (txBufferLength >= BUFFER_LENGTH) + if (txBufferLength >= txBufferSize) { setWriteError(); return 0; @@ -194,12 +181,13 @@ size_t TwoWire::write(uint8_t data) } else { - twi_transmit(&data, 1); + setWriteError(); + return 0; } 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) { @@ -213,12 +201,13 @@ size_t TwoWire::write(const uint8_t *data, size_t quantity) } else { - twi_transmit(data, quantity); + setWriteError(); + return 0; } return quantity; } -int TwoWire::available(void) +int TwoWireBase::available(void) { int result = rxBufferLength - rxBufferIndex; @@ -232,7 +221,7 @@ int TwoWire::available(void) return result; } -int TwoWire::read(void) +int TwoWireBase::read(void) { int value = -1; if (rxBufferIndex < rxBufferLength) @@ -243,7 +232,7 @@ int TwoWire::read(void) return value; } -int TwoWire::peek(void) +int TwoWireBase::peek(void) { int value = -1; if (rxBufferIndex < rxBufferLength) @@ -253,7 +242,7 @@ int TwoWire::peek(void) return value; } -void TwoWire::flush(void) +void TwoWireBase::flush(void) { rxBufferIndex = 0; rxBufferLength = 0; @@ -261,10 +250,61 @@ void TwoWire::flush(void) txBufferLength = 0; } -void TwoWire::onReceiveService(uint8_t* inBytes, size_t numBytes) +// 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) +{} + +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) + : TwoWireMaster(&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 (!user_onReceive) + if (targetObject == nullptr || !instance.user_onReceive) { return; } @@ -279,35 +319,38 @@ void TwoWire::onReceiveService(uint8_t* inBytes, size_t numBytes) // this enables new reads to happen in parallel for (uint8_t i = 0; i < numBytes; ++i) { - rxBuffer[i] = inBytes[i]; + instance.rxBuffer[i] = inBytes[i]; } // set rx iterator vars - rxBufferIndex = 0; - rxBufferLength = numBytes; + instance.rxBufferIndex = 0; + instance.rxBufferLength = numBytes; // alert user program - user_onReceive(numBytes); + instance.user_onReceive(numBytes); } -void TwoWire::onRequestService(void) +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 (!user_onRequest) + if (targetObject == nullptr || !instance.user_onRequest) { return; } // reset tx buffer iterator vars // !!! this will kill any pending pre-master sendTo() activity - txBufferIndex = 0; - txBufferLength = 0; + instance.txBufferIndex = 0; + instance.txBufferLength = 0; // alert user program - user_onRequest(); + instance.user_onRequest(); } -void TwoWire::onReceive(void (*function)(int)) +void TwoWireMasterOrSlave::onReceive(void (*function)(int)) { // arduino api compatibility fixer: // really hope size parameter will not exceed 2^31 :) @@ -315,20 +358,33 @@ void TwoWire::onReceive(void (*function)(int)) user_onReceive = reinterpret_cast(function); } -void TwoWire::onReceive(void (*function)(size_t)) +void TwoWireMasterOrSlave::onReceive(void (*function)(size_t)) { user_onReceive = function; - twi_enableSlaveMode(); + twi_enableSlaveModeWithTarget(this); } -void TwoWire::onRequest(void (*function)(void)) +void TwoWireMasterOrSlave::onRequest(void (*function)(void)) { user_onRequest = function; - twi_enableSlaveMode(); + 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() + : 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 5d6b36457e..4d12ea3ab4 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -24,38 +24,69 @@ #ifndef TwoWire_h #define TwoWire_h +/* + 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 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 + 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 #include "Stream.h" +#include +// forward declaration, no need to load twi.h here! +class TwiMaster; -#define BUFFER_LENGTH 128 +#ifndef I2C_BUFFER_LENGTH +#define I2C_BUFFER_LENGTH 128 +#endif -class TwoWire : public Stream +class TwoWireBase : 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); + 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; + std::unique_ptr txBuffer; + uint8_t txBufferIndex; + uint8_t txBufferLength; + + uint8_t transmitting; + public: - TwoWire(); 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 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); @@ -76,16 +107,75 @@ class TwoWire : public Stream virtual int read(void); virtual int peek(void); virtual void flush(void); + + 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 +{ +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 + 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 TwoWireMaster +{ +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)); - - using Print::write; }; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_TWOWIRE) 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 +