diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index 686a3903ee0..0bab0fd58fe 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -38,8 +38,11 @@ TwoWire::TwoWire(uint8_t bus_num) :num(bus_num & 1) ,sda(-1) ,scl(-1) + ,bufferSize(I2C_BUFFER_LENGTH) // default Wire Buffer Size + ,rxBuffer(NULL) ,rxIndex(0) ,rxLength(0) + ,txBuffer(NULL) ,txLength(0) ,txAddress(0) ,_timeOutMillis(50) @@ -132,6 +135,87 @@ bool TwoWire::setPins(int sdaPin, int sclPin) return !i2cIsInit(num); } +bool TwoWire::allocateWireBuffer(void) +{ + // or both buffer can be allocated or none will be + if (rxBuffer == NULL) { + rxBuffer = (uint8_t *)malloc(bufferSize); + if (rxBuffer == NULL) { + log_e("Can't allocate memory for I2C_%d rxBuffer", num); + return false; + } + } + if (txBuffer == NULL) { + txBuffer = (uint8_t *)malloc(bufferSize); + if (txBuffer == NULL) { + log_e("Can't allocate memory for I2C_%d txBuffer", num); + freeWireBuffer(); // free rxBuffer for safety! + return false; + } + } + // in case both were allocated before, they must have the same size. All good. + return true; +} + +void TwoWire::freeWireBuffer(void) +{ + if (rxBuffer != NULL) { + free(rxBuffer); + rxBuffer = NULL; + } + if (txBuffer != NULL) { + free(txBuffer); + txBuffer = NULL; + } +} + +size_t TwoWire::setBufferSize(size_t bSize) +{ + // Maximum size .... HEAP limited ;-) + if (bSize < 32) { // 32 bytes is the I2C FIFO Len for ESP32/S2/S3/C3 + log_e("Minimum Wire Buffer size is 32 bytes"); + return 0; + } + +#if !CONFIG_DISABLE_HAL_LOCKS + if(lock == NULL){ + lock = xSemaphoreCreateMutex(); + if(lock == NULL){ + log_e("xSemaphoreCreateMutex failed"); + return 0; + } + } + //acquire lock + if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){ + log_e("could not acquire lock"); + return 0; + } +#endif + // allocateWireBuffer allocates memory for both pointers or just free them + if (rxBuffer != NULL || txBuffer != NULL) { + // if begin() has been already executed, memory size changes... data may be lost. We don't care! :^) + if (bSize != bufferSize) { + // we want a new buffer size ... just reset buffer pointers and allocate new ones + freeWireBuffer(); + bufferSize = bSize; + if (!allocateWireBuffer()) { + // failed! Error message already issued + bSize = 0; // returns error + log_e("Buffer allocation failed"); + } + } // else nothing changes, all set! + } else { + // no memory allocated yet, just change the size value - allocation in begin() + bufferSize = bSize; + } +#if !CONFIG_DISABLE_HAL_LOCKS + //release lock + xSemaphoreGive(lock); + +#endif + return bSize; +} + // Slave Begin bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) { @@ -159,17 +243,22 @@ bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) log_e("Bus already started in Master Mode."); goto end; } + if (!allocateWireBuffer()) { + // failed! Error Message already issued + goto end; + } if(!initPins(sdaPin, sclPin)){ goto end; } i2cSlaveAttachCallbacks(num, onRequestService, onReceiveService, this); - if(i2cSlaveInit(num, sda, scl, addr, frequency, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH) != ESP_OK){ + if(i2cSlaveInit(num, sda, scl, addr, frequency, bufferSize, bufferSize) != ESP_OK){ log_e("Slave Init ERROR"); goto end; } is_slave = true; started = true; end: + if (!started) freeWireBuffer(); #if !CONFIG_DISABLE_HAL_LOCKS //release lock xSemaphoreGive(lock); @@ -205,6 +294,10 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) started = true; goto end; } + if (!allocateWireBuffer()) { + // failed! Error Message already issued + goto end; + } if(!initPins(sdaPin, sclPin)){ goto end; } @@ -212,6 +305,7 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) started = (err == ESP_OK); end: + if (!started) freeWireBuffer(); #if !CONFIG_DISABLE_HAL_LOCKS //release lock xSemaphoreGive(lock); @@ -239,6 +333,7 @@ bool TwoWire::end() } else if(i2cIsInit(num)){ err = i2cDeinit(num); } + freeWireBuffer(); #if !CONFIG_DISABLE_HAL_LOCKS //release lock xSemaphoreGive(lock); @@ -325,12 +420,26 @@ void TwoWire::beginTransmission(uint16_t address) txLength = 0; } +/* +https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/ +endTransmission() returns: +0: success. +1: data too long to fit in transmit buffer. +2: received NACK on transmit of address. +3: received NACK on transmit of data. +4: other error. +5: timeout +*/ uint8_t TwoWire::endTransmission(bool sendStop) { if(is_slave){ log_e("Bus is in Slave Mode"); return 4; } + if (txBuffer == NULL){ + log_e("NULL TX buffer pointer"); + return 4; + } esp_err_t err = ESP_OK; if(sendStop){ err = i2cWrite(num, txAddress, txBuffer, txLength, _timeOutMillis); @@ -360,6 +469,10 @@ size_t TwoWire::requestFrom(uint16_t address, size_t size, bool sendStop) log_e("Bus is in Slave Mode"); return 0; } + if (rxBuffer == NULL || txBuffer == NULL){ + log_e("NULL buffer pointer"); + return 0; + } esp_err_t err = ESP_OK; if(nonStop #if !CONFIG_DISABLE_HAL_LOCKS @@ -401,7 +514,11 @@ size_t TwoWire::requestFrom(uint16_t address, size_t size, bool sendStop) size_t TwoWire::write(uint8_t data) { - if(txLength >= I2C_BUFFER_LENGTH) { + if (txBuffer == NULL){ + log_e("NULL TX buffer pointer"); + return 0; + } + if(txLength >= bufferSize) { return 0; } txBuffer[txLength++] = data; @@ -428,6 +545,10 @@ int TwoWire::available(void) int TwoWire::read(void) { int value = -1; + if (rxBuffer == NULL){ + log_e("NULL RX buffer pointer"); + return value; + } if(rxIndex < rxLength) { value = rxBuffer[rxIndex++]; } @@ -437,6 +558,10 @@ int TwoWire::read(void) int TwoWire::peek(void) { int value = -1; + if (rxBuffer == NULL){ + log_e("NULL RX buffer pointer"); + return value; + } if(rxIndex < rxLength) { value = rxBuffer[rxIndex]; } @@ -520,6 +645,10 @@ void TwoWire::onReceiveService(uint8_t num, uint8_t* inBytes, size_t numBytes, b if(!wire->user_onReceive){ return; } + if (wire->rxBuffer == NULL){ + log_e("NULL RX buffer pointer"); + return; + } for(uint8_t i = 0; i < numBytes; ++i){ wire->rxBuffer[i] = inBytes[i]; } @@ -534,6 +663,10 @@ void TwoWire::onRequestService(uint8_t num, void * arg) if(!wire->user_onRequest){ return; } + if (wire->txBuffer == NULL){ + log_e("NULL TX buffer pointer"); + return; + } wire->txLength = 0; wire->user_onRequest(); if(wire->txLength){ diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index 3d028f3c3d5..339b0e2866f 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -34,8 +34,13 @@ #endif #include "Stream.h" +// WIRE_HAS_BUFFER_SIZE means Wire has setBufferSize() +#define WIRE_HAS_BUFFER_SIZE 1 +// WIRE_HAS_END means Wire has end() +#define WIRE_HAS_END 1 + #ifndef I2C_BUFFER_LENGTH - #define I2C_BUFFER_LENGTH 128 + #define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t) #endif typedef void(*user_onRequest)(void); typedef void(*user_onReceive)(uint8_t*, int); @@ -47,11 +52,12 @@ class TwoWire: public Stream int8_t sda; int8_t scl; - uint8_t rxBuffer[I2C_BUFFER_LENGTH]; + size_t bufferSize; + uint8_t *rxBuffer; size_t rxIndex; size_t rxLength; - uint8_t txBuffer[I2C_BUFFER_LENGTH]; + uint8_t *txBuffer; size_t txLength; uint16_t txAddress; @@ -68,6 +74,8 @@ class TwoWire: public Stream static void onRequestService(uint8_t, void *); static void onReceiveService(uint8_t, uint8_t*, size_t, bool, void *); bool initPins(int sdaPin, int sclPin); + bool allocateWireBuffer(void); + void freeWireBuffer(void); public: TwoWire(uint8_t bus_num); @@ -93,6 +101,8 @@ class TwoWire: public Stream } bool end(); + size_t setBufferSize(size_t bSize); + void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50ms uint16_t getTimeOut();