From 64385453bb549b6d2f868658119259e605aca74d Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Wed, 24 Jun 2020 10:21:55 +1000 Subject: [PATCH 1/8] Fix infinite recursion issue in SERCOM::startTransmissionWIRE * If a significant hardware issue is struck startTransmissionWIRE calls itself to restart sending the address+r/w byte. The stack can be blown very quickly in the event of a prolonged hardware issue that warrants transmission restarts. A restart limit has been added (currently set at 5) to fix this issue. * This fixes issue #476 and it builds on work by @jrowberg in which a timeout was added addressing issue #439. * Some code simplification and tidying has followed from #439 and #476 being resolved. --- cores/arduino/SERCOM.cpp | 127 ++++++++++++++++++++++----------------- cores/arduino/SERCOM.h | 24 ++++++-- libraries/Wire/Wire.cpp | 93 +++++++++++++++++++--------- libraries/Wire/Wire.h | 9 +++ 4 files changed, 164 insertions(+), 89 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 8bf5a4521..26bd1bbd4 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -29,6 +29,8 @@ SERCOM::SERCOM(Sercom* s) { sercom = s; + timeoutOccurred = false; + timeoutInterval = SERCOM_DEFAULT_I2C_OPERATION_TIMEOUT_MS; } /* ========================= @@ -393,11 +395,8 @@ void SERCOM::enableWIRE() // Setting bus idle mode sercom->I2CM.STATUS.bit.BUSSTATE = 1 ; + waitSyncWIRE(); - while ( sercom->I2CM.SYNCBUSY.bit.SYSOP != 0 ) - { - // Wait the SYSOP bit from SYNCBUSY coming back to 0 - } } void SERCOM::disableWIRE() @@ -433,10 +432,8 @@ void SERCOM::initSlaveWIRE( uint8_t ucAddress, bool enableGeneralCall ) SERCOM_I2CS_INTENSET_AMATCH | // Address Match SERCOM_I2CS_INTENSET_DRDY ; // Data Ready - while ( sercom->I2CM.SYNCBUSY.bit.SYSOP != 0 ) - { - // Wait the SYSOP bit from SYNCBUSY to come back to 0 - } + waitSyncWIRE(); + } void SERCOM::initMasterWIRE( uint32_t baudrate ) @@ -453,12 +450,12 @@ void SERCOM::initMasterWIRE( uint32_t baudrate ) // Enable Smart mode and Quick Command //sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN /*| SERCOM_I2CM_CTRLB_QCEN*/ ; - // Enable all interrupts // sercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR ; // Synchronous arithmetic baudrate sercom->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); + } void SERCOM::prepareNackBitWIRE( void ) @@ -485,14 +482,17 @@ void SERCOM::prepareCommandBitsWire(uint8_t cmd) { if(isMasterWIRE()) { sercom->I2CM.CTRLB.bit.CMD = cmd; + waitSyncWIRE(); + } else { + sercom->I2CS.CTRLB.bit.CMD = cmd; + } +} +void SERCOM::waitSyncWIRE() { while(sercom->I2CM.SYNCBUSY.bit.SYSOP) { // Waiting for synchronization } - } else { - sercom->I2CS.CTRLB.bit.CMD = cmd; - } } bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) @@ -500,53 +500,47 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag // 7-bits address + 1-bits R/W address = (address << 0x1ul) | flag; - // If another master owns the bus or the last bus owner has not properly - // sent a stop, return failure early. This will prevent some misbehaved - // devices from deadlocking here at the cost of the caller being responsible - // for retrying the failed transmission. See SercomWireBusState for the - // possible bus states. - if(!isBusOwnerWIRE()) - { - if( isBusBusyWIRE() || (isArbLostWIRE() && !isBusIdleWIRE()) ) - { - return false; - } - } - // Send start and address sercom->I2CM.ADDR.bit.ADDR = address; + waitSyncWIRE(); + + // wait Address Transmitted + initTimeout(); - // Address Transmitted if ( flag == WIRE_WRITE_FLAG ) // Write mode { - while( !sercom->I2CM.INTFLAG.bit.MB ) - { - // Wait transmission complete - } - // Check for loss of arbitration (multiple masters starting communication at the same time) - if(!isBusOwnerWIRE()) + while (!sercom->I2CM.INTFLAG.bit.MB && !testTimeout()) { - // Restart communication - startTransmissionWIRE(address >> 1, flag); + // wait transmission complete } } else // Read mode { - while( !sercom->I2CM.INTFLAG.bit.SB ) + while (!sercom->I2CM.INTFLAG.bit.SB && !testTimeout()) { - // If the slave NACKS the address, the MB bit will be set. - // In that case, send a stop condition and return false. - if (sercom->I2CM.INTFLAG.bit.MB) { - sercom->I2CM.CTRLB.bit.CMD = 3; // Stop condition - return false; - } - // Wait transmission complete + // wait transmission complete } - - // Clean the 'Slave on Bus' flag, for further usage. - //sercom->I2CM.INTFLAG.bit.SB = 0x1ul; } + // Check for loss of arbitration (multiple masters starting communication at the same time) + // or timeout + if(!isBusOwnerWIRE() || didTimeout()) { + + restartTX_cnt++; // increment restart counter + + if (restartTX_cnt >= restartTX_limit) { + // only allow limited restarts + restartTX_cnt = 0; + return false; + } else { + // Restart communication after small delay + if (!didTimeout()) delayMicroseconds(1000); // delay if not already timed out + + // Restart + startTransmissionWIRE(address >> 1, flag); + } + + } else restartTX_cnt = 0; //ACK received (0: ACK, 1: NACK) if(sercom->I2CM.STATUS.bit.RXNACK) @@ -563,17 +557,18 @@ bool SERCOM::sendDataMasterWIRE(uint8_t data) { //Send data sercom->I2CM.DATA.bit.DATA = data; + waitSyncWIRE(); //Wait transmission successful - while(!sercom->I2CM.INTFLAG.bit.MB) { - - // If a bus error occurs, the MB bit may never be set. - // Check the bus error bit and bail if it's set. - if (sercom->I2CM.STATUS.bit.BUSERR) { - return false; - } + initTimeout(); + while (!sercom->I2CM.INTFLAG.bit.MB && !testTimeout()) + { + // Wait transmission complete } + // check for timeout condition + if ( didTimeout() ) return false; + //Problems on line? nack received? if(sercom->I2CM.STATUS.bit.RXNACK) return false; @@ -665,9 +660,10 @@ uint8_t SERCOM::readDataWIRE( void ) { if(isMasterWIRE()) { - while( sercom->I2CM.INTFLAG.bit.SB == 0 && sercom->I2CM.INTFLAG.bit.MB == 0 ) + initTimeout(); + while (!sercom->I2CM.INTFLAG.bit.SB && !testTimeout()) { - // Waiting complete receive + // wait receive } return sercom->I2CM.DATA.bit.DATA ; @@ -739,3 +735,26 @@ void SERCOM::initClockNVIC( void ) /* Wait for synchronization */ } } + +void SERCOM::setTimeout( uint16_t ms ) +{ + timeoutInterval = ms; +} + +bool SERCOM::didTimeout( void ) +{ + return timeoutOccurred; +} + +void SERCOM::initTimeout( void ) +{ + timeoutOccurred = false; + timeoutRef = millis(); +} + +bool SERCOM::testTimeout( void ) +{ + if (!timeoutInterval) return false; + timeoutOccurred = (millis() - timeoutRef) > timeoutInterval; + return timeoutOccurred; +} diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 6f855af19..56cda1642 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -24,6 +24,9 @@ #define SERCOM_FREQ_REF 48000000 #define SERCOM_NVIC_PRIORITY ((1<<__NVIC_PRIO_BITS) - 1) +// timeout detection default length (zero is disabled) +#define SERCOM_DEFAULT_I2C_OPERATION_TIMEOUT_MS 1000 + typedef enum { UART_EXT_CLOCK = 0, @@ -191,10 +194,10 @@ class SERCOM void resetWIRE( void ) ; void enableWIRE( void ) ; - void disableWIRE( void ); - void prepareNackBitWIRE( void ) ; - void prepareAckBitWIRE( void ) ; - void prepareCommandBitsWire(uint8_t cmd); + void disableWIRE( void ); + void prepareNackBitWIRE( void ) ; + void prepareAckBitWIRE( void ) ; + void prepareCommandBitsWire(uint8_t cmd); bool startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) ; bool sendDataMasterWIRE(uint8_t data) ; bool sendDataSlaveWIRE(uint8_t data) ; @@ -209,15 +212,26 @@ class SERCOM bool isRestartDetectedWIRE( void ) ; bool isAddressMatch( void ) ; bool isMasterReadOperationWIRE( void ) ; - bool isRXNackReceivedWIRE( void ) ; + bool isRXNackReceivedWIRE( void ) ; int availableWIRE( void ) ; uint8_t readDataWIRE( void ) ; + void setTimeout( uint16_t ms ); + bool didTimeout( void ); private: Sercom* sercom; uint8_t calculateBaudrateSynchronous(uint32_t baudrate) ; uint32_t division(uint32_t dividend, uint32_t divisor) ; void initClockNVIC( void ) ; + + void waitSyncWIRE( void ) ; + + // timeout detection and tx restart for I2C operations + void initTimeout( void ); + bool testTimeout( void ); + uint16_t timeoutInterval; + uint32_t timeoutRef, restartTX_cnt, restartTX_limit = 5; + bool timeoutOccurred; }; #endif diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index c56381911..4a99bdaf0 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -35,6 +35,9 @@ TwoWire::TwoWire(SERCOM * s, uint8_t pinSDA, uint8_t pinSCL) } void TwoWire::begin(void) { + // track baud clock for auto-restarting bus in timeout condition + activeBaudrate = TWI_CLOCK; + //Master Mode sercom->initMasterWIRE(TWI_CLOCK); sercom->enableWIRE(); @@ -53,6 +56,9 @@ void TwoWire::begin(uint8_t address, bool enableGeneralCall) { } void TwoWire::setClock(uint32_t baudrate) { + // track baud clock for auto-restarting bus in timeout condition + activeBaudrate = baudrate; + sercom->disableWIRE(); sercom->initMasterWIRE(baudrate); sercom->enableWIRE(); @@ -70,6 +76,7 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) } size_t byteRead = 0; + bool busOwner; rxBuffer.clear(); @@ -78,26 +85,38 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) // Read first data rxBuffer.store_char(sercom->readDataWIRE()); - bool busOwner; // Connected to slave - for (byteRead = 1; byteRead < quantity && (busOwner = sercom->isBusOwnerWIRE()); ++byteRead) + for (byteRead = 1; byteRead < quantity && !sercom->didTimeout() && (busOwner = sercom->isBusOwnerWIRE()); ++byteRead) { - sercom->prepareAckBitWIRE(); // Prepare Acknowledge - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_READ); // Prepare the ACK command for the slave - rxBuffer.store_char(sercom->readDataWIRE()); // Read data and send the ACK - } - sercom->prepareNackBitWIRE(); // Prepare NACK to stop slave transmission - //sercom->readDataWIRE(); // Clear data register to send NACK + // prepare and send ACK for the slave + sercom->prepareAckBitWIRE(); + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_READ); - if (stopBit && busOwner) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); // Send Stop unless arbitration was lost + if (quantity > 1) rxBuffer.store_char(sercom->readDataWIRE()); // Read next byte } - if (!busOwner) + sercom->prepareNackBitWIRE(); // prepare NACK for slave + + if (!busOwner || sercom->didTimeout()) { byteRead--; // because last read byte was garbage/invalid } + + } + + // Send Stop if we still have control of the bus, or hit a timeout + if ((stopBit && busOwner) || sercom->didTimeout()) + { + sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + } + + // catch and handle timeout condition + if (sercom->didTimeout()) + { + // reset the bus + setClock(activeBaudrate); + transmissionBegun = false; + return 0; } return byteRead; @@ -121,35 +140,50 @@ void TwoWire::beginTransmission(uint8_t address) { // 1 : Data too long // 2 : NACK on transmit of address // 3 : NACK on transmit of data -// 4 : Other error +// 4 : Timeout +// 5 : Other error uint8_t TwoWire::endTransmission(bool stopBit) { + uint8_t errCode = 0; + bool busOwner; + transmissionBegun = false ; // Start I2C transmission if ( !sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - return 2 ; // Address error + errCode = 2; // Address error } // Send all buffer - while( txBuffer.available() ) - { - // Trying to send data - if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) + if (!errCode) { + while ( txBuffer.available() && (busOwner = sercom->isBusOwnerWIRE()) ) { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - return 3 ; // Nack or error + // Trying to send data + if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) + { + errCode = 3; // Nack or error + txBuffer.clear(); + break; + } } } - - if (stopBit) + + // Send Stop if we still have control of the bus, or hit an error + if ((stopBit && busOwner) || errCode) { sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - } + } + + // catch timeout condition + if (sercom->didTimeout()) { + // reset the bus + setClock(activeBaudrate); + transmissionBegun = false; + errCode = 4; + } - return 0; + return errCode; } uint8_t TwoWire::endTransmission() @@ -219,7 +253,7 @@ void TwoWire::onService(void) { if ( sercom->isSlaveWIRE() ) { - if(sercom->isStopDetectedWIRE() || + if(sercom->isStopDetectedWIRE() || (sercom->isAddressMatch() && sercom->isRestartDetectedWIRE() && !sercom->isMasterReadOperationWIRE())) //Stop or Restart detected { sercom->prepareAckBitWIRE(); @@ -230,7 +264,7 @@ void TwoWire::onService(void) { onReceiveCallback(available()); } - + rxBuffer.clear(); } else if(sercom->isAddressMatch()) //Address Match @@ -264,12 +298,12 @@ void TwoWire::onService(void) transmissionBegun = sercom->sendDataSlaveWIRE(c); } else { //Received data if (rxBuffer.isFull()) { - sercom->prepareNackBitWIRE(); + sercom->prepareNackBitWIRE(); } else { //Store data rxBuffer.store_char(sercom->readDataWIRE()); - sercom->prepareAckBitWIRE(); + sercom->prepareAckBitWIRE(); } sercom->prepareCommandBitsWire(0x03); @@ -334,4 +368,3 @@ void TwoWire::onService(void) Wire5.onService(); } #endif - diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index db2ae646e..71da0f13b 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -29,6 +29,9 @@ // WIRE_HAS_END means Wire has end() #define WIRE_HAS_END 1 + // WIRE_HAS_TIMEOUT means Wire implements timeout detection +#define WIRE_HAS_TIMEOUT 1 + class TwoWire : public Stream { public: @@ -44,6 +47,9 @@ class TwoWire : public Stream uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit); uint8_t requestFrom(uint8_t address, size_t quantity); + + bool didTimeout() { return sercom->didTimeout(); } + bool setTimeout(uint16_t ms) { sercom->setTimeout(ms); } size_t write(uint8_t data); size_t write(const uint8_t * data, size_t quantity); @@ -70,6 +76,9 @@ class TwoWire : public Stream bool transmissionBegun; + // Used to re-initialize the clock rate after a timeout + uint32_t activeBaudrate; + // RX Buffer RingBufferN<256> rxBuffer; From e576493ef2eb9549c6e454d97e12232241a52fc3 Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Wed, 8 Jul 2020 09:57:09 +1000 Subject: [PATCH 2/8] some small formatting changes and location of stop condition * TwoWire::endTransmission layout changed to be more inline with TwoWire::requestFrom because they are essentially functioning in a similar way. * Sending of stop condition moved to within block of successful start. Apon successful start but unsuccessful read/write, stop condition is also sent - regardless of whether stop has been requested at that time. --- libraries/Wire/Wire.cpp | 45 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index 4a99bdaf0..c9c18f4d7 100644 --- a/libraries/Wire/Wire.cpp +++ b/libraries/Wire/Wire.cpp @@ -97,6 +97,8 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) sercom->prepareNackBitWIRE(); // prepare NACK for slave + if (stopBit || didTimeout() || !busOwner) sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); // Send Stop + if (!busOwner || sercom->didTimeout()) { byteRead--; // because last read byte was garbage/invalid @@ -104,18 +106,11 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) } - // Send Stop if we still have control of the bus, or hit a timeout - if ((stopBit && busOwner) || sercom->didTimeout()) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - } - // catch and handle timeout condition if (sercom->didTimeout()) { // reset the bus setClock(activeBaudrate); - transmissionBegun = false; return 0; } @@ -147,42 +142,34 @@ uint8_t TwoWire::endTransmission(bool stopBit) uint8_t errCode = 0; bool busOwner; - transmissionBegun = false ; - // Start I2C transmission - if ( !sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) + if ( sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) { - errCode = 2; // Address error - } - - // Send all buffer - if (!errCode) { + // successful start so transmit data while ( txBuffer.available() && (busOwner = sercom->isBusOwnerWIRE()) ) { - // Trying to send data - if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) - { - errCode = 3; // Nack or error - txBuffer.clear(); - break; - } + // Trying to send data + if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) + { + errCode = 3; // Nack or error + txBuffer.clear(); + break; + } } - } - // Send Stop if we still have control of the bus, or hit an error - if ((stopBit && busOwner) || errCode) - { - sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); - } + if (stopBit || errCode || !busOwner) sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); // Send Stop + + } else errCode = 2; // Address error // catch timeout condition if (sercom->didTimeout()) { // reset the bus setClock(activeBaudrate); - transmissionBegun = false; errCode = 4; } + transmissionBegun = false ; + return errCode; } From a744583bd5c55141939409e8327eba771c531f1f Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Thu, 15 Apr 2021 14:51:45 +1000 Subject: [PATCH 3/8] fix Serial.flush() blocks forever #597 * The aynchronous nature of the DRE and TXC interrupt flags causes issues (lockups) when the TX DATA register is empty on start and a flush is issued. Simply looking at the DRE prior to waiting for TXC is insufficient because the data register may well be empty but the shift register could still contain data, in this case SERCOM::flushUART() would return before TXC has been raised thus before flushing is complete. * bool added to SERCOM.h to indicate when it is ok for SERCOM::flushUART() to wait for the TXC flag. This flag is set when any data is written to the data register via SERCOM::writeDataUART(). It is cleared when a flush is done. --- cores/arduino/SERCOM.cpp | 12 +++++++----- cores/arduino/SERCOM.h | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 26bd1bbd4..54c66c1d5 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -113,12 +113,10 @@ void SERCOM::enableUART() void SERCOM::flushUART() { - // Skip checking transmission completion if data register is empty - if(isDataRegisterEmptyUART()) - return; + // Wait for transmission to complete, if ok to do so. + while(!sercom->USART.INTFLAG.bit.TXC && onFlushWaitUartTXC); - // Wait for transmission to complete - while(!sercom->USART.INTFLAG.bit.TXC); + onFlushWaitUartTXC = false; } void SERCOM::clearStatusUART() @@ -185,6 +183,10 @@ int SERCOM::writeDataUART(uint8_t data) //Put data into DATA register sercom->USART.DATA.reg = (uint16_t)data; + + // indicate it's ok to wait for TXC flag when flushing + onFlushWaitUartTXC = true; + return 1; } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 56cda1642..32ffb5c4d 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -230,8 +230,13 @@ class SERCOM void initTimeout( void ); bool testTimeout( void ); uint16_t timeoutInterval; - uint32_t timeoutRef, restartTX_cnt, restartTX_limit = 5; + uint32_t timeoutRef, restartTX_cnt, restartTX_limit = 10; bool timeoutOccurred; + + // Flag set when data is loaded into sercom->USART.DATA.reg. + // Helps with preventing UART lockups when flushing on startup + // and the asyncronous nature of the DRE and TXC interrupt flags. + bool onFlushWaitUartTXC = false; }; #endif From cbcb17a79d8238966124cede9a3c77b04f888f1e Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Fri, 16 Apr 2021 11:02:04 +1000 Subject: [PATCH 4/8] fixed indent format in SERCOM.h, spaces to tabs --- cores/arduino/SERCOM.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 32ffb5c4d..71bcfaa4e 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -233,10 +233,10 @@ class SERCOM uint32_t timeoutRef, restartTX_cnt, restartTX_limit = 10; bool timeoutOccurred; - // Flag set when data is loaded into sercom->USART.DATA.reg. - // Helps with preventing UART lockups when flushing on startup - // and the asyncronous nature of the DRE and TXC interrupt flags. - bool onFlushWaitUartTXC = false; + // Flag set when data is loaded into sercom->USART.DATA.reg. + // Helps with preventing UART lockups when flushing on startup + // and the asyncronous nature of the DRE and TXC interrupt flags. + bool onFlushWaitUartTXC = false; }; #endif From eca25817ab5f592740b06e71ad65c5c91e583cd2 Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Thu, 30 Sep 2021 20:10:31 +1000 Subject: [PATCH 5/8] bad assignment of setTimeout fixed (bool -> void) --- libraries/Wire/Wire.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Wire/Wire.h b/libraries/Wire/Wire.h index 71da0f13b..9c2e7c416 100644 --- a/libraries/Wire/Wire.h +++ b/libraries/Wire/Wire.h @@ -47,9 +47,9 @@ class TwoWire : public Stream uint8_t requestFrom(uint8_t address, size_t quantity, bool stopBit); uint8_t requestFrom(uint8_t address, size_t quantity); - + bool didTimeout() { return sercom->didTimeout(); } - bool setTimeout(uint16_t ms) { sercom->setTimeout(ms); } + void setTimeout(uint16_t ms) { sercom->setTimeout(ms); } size_t write(uint8_t data); size_t write(const uint8_t * data, size_t quantity); From 05222495952550cfc97741eeb2d56ce2bfb808ae Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Wed, 27 Oct 2021 11:32:57 +1100 Subject: [PATCH 6/8] change default wire timeout to 25ms (was 1000ms) --- cores/arduino/SERCOM.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 71bcfaa4e..6332b6c25 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -25,7 +25,7 @@ #define SERCOM_NVIC_PRIORITY ((1<<__NVIC_PRIO_BITS) - 1) // timeout detection default length (zero is disabled) -#define SERCOM_DEFAULT_I2C_OPERATION_TIMEOUT_MS 1000 +#define SERCOM_DEFAULT_I2C_OPERATION_TIMEOUT_MS 25 typedef enum { From b30bf1cbe83e0499986c7a463cafd513a8119131 Mon Sep 17 00:00:00 2001 From: Andrew Cunningham Date: Thu, 4 Nov 2021 19:35:46 +1100 Subject: [PATCH 7/8] take 2 on infinite recurssion issue in SERCOM::startTransmissionWIRE #535 * Although the previous solution works, it doesn't respect the timeout limit imposed by @jrowberg setTimeout, and the restart limit was somewhat arbitrary. This solution does respect this hard limit and removes the self recursive nature (that tends to blow the stack) of the restarts implemented in the original source. * Tested using Arduino Zero as Master and 128x64 OLED, BM280 and ATTiny85 as slaves. SCL -> VCC, SCL -> GND, SDA -> VCC, SDA -> GND all recoverable. Multi-master tested in a similar way using 2x Arduino Zero's and 128x64 OLED, same results. * Relates to issues #476, #439 and PR #535. --- cores/arduino/SERCOM.cpp | 67 ++++++++++++++++++++-------------------- cores/arduino/SERCOM.h | 2 +- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 54c66c1d5..3e1474afc 100644 --- a/cores/arduino/SERCOM.cpp +++ b/cores/arduino/SERCOM.cpp @@ -499,53 +499,52 @@ void SERCOM::waitSyncWIRE() { bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag) { + bool restart = false; // transmission restart flag, is set to true on loss of arbitration + // 7-bits address + 1-bits R/W address = (address << 0x1ul) | flag; - // Send start and address - sercom->I2CM.ADDR.bit.ADDR = address; - waitSyncWIRE(); - - // wait Address Transmitted + // mark timeout timer start initTimeout(); - if ( flag == WIRE_WRITE_FLAG ) // Write mode + do { - while (!sercom->I2CM.INTFLAG.bit.MB && !testTimeout()) + // Send start and address + sercom->I2CM.ADDR.bit.ADDR = address; + waitSyncWIRE(); + + // wait Address Transmitted + if ( flag == WIRE_WRITE_FLAG ) // Write mode { - // wait transmission complete + while (!sercom->I2CM.INTFLAG.bit.MB && !testTimeout()) + { + // wait transmission complete + } } - } - else // Read mode - { - while (!sercom->I2CM.INTFLAG.bit.SB && !testTimeout()) + else // Read mode { - // wait transmission complete + while (!sercom->I2CM.INTFLAG.bit.SB && !testTimeout()) + { + // wait transmission complete + } } - } - - // Check for loss of arbitration (multiple masters starting communication at the same time) - // or timeout - if(!isBusOwnerWIRE() || didTimeout()) { - - restartTX_cnt++; // increment restart counter - if (restartTX_cnt >= restartTX_limit) { - // only allow limited restarts - restartTX_cnt = 0; - return false; - } else { - // Restart communication after small delay - if (!didTimeout()) delayMicroseconds(1000); // delay if not already timed out - - // Restart - startTransmissionWIRE(address >> 1, flag); - } + // Check for loss of arbitration (multiple masters starting communication at the same time) + if (!isBusOwnerWIRE() && !didTimeout()) + { + // small 1ms delay before restart + uint32_t start = micros(); + while ((micros() - start) < 1000) yield(); + restart = true; + } else + { + restart = false; + } - } else restartTX_cnt = 0; + } while (restart && !didTimeout()); - //ACK received (0: ACK, 1: NACK) - if(sercom->I2CM.STATUS.bit.RXNACK) + //ACK received (0: ACK, 1: NACK) or timeout + if(sercom->I2CM.STATUS.bit.RXNACK || didTimeout()) { return false; } diff --git a/cores/arduino/SERCOM.h b/cores/arduino/SERCOM.h index 6332b6c25..9f2d4b6ef 100644 --- a/cores/arduino/SERCOM.h +++ b/cores/arduino/SERCOM.h @@ -230,7 +230,7 @@ class SERCOM void initTimeout( void ); bool testTimeout( void ); uint16_t timeoutInterval; - uint32_t timeoutRef, restartTX_cnt, restartTX_limit = 10; + uint32_t timeoutRef; bool timeoutOccurred; // Flag set when data is loaded into sercom->USART.DATA.reg. From cc2883f3e4ab419d5e48133c747f8fcb684c6c33 Mon Sep 17 00:00:00 2001 From: acicuc <66670219+acicuc@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:10:15 +1100 Subject: [PATCH 8/8] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fca8a216d..f2f72ee1b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,16 @@ This repository contains the source code and configuration files of the Arduino Core for Atmel's SAMD21 processor (used on the Arduino/Genuino Zero, MKR1000 and MKRZero boards). +## My mods contained in 'wire_timeout' branch + +This version of the core has the following changes: + +1. A modified version of jrowberg's excellent i2c wire timeout implementation. See pull request [#439](https://github.com/arduino/ArduinoCore-samd/pull/439). By the way I've made the default timeout 25ms. An example modification of Adafruits SSD1306 library for a 128x64 OLED is [here](https://github.com/acicuc/Adafruit_SSD1306/tree/wire_timeout). Albeit old, it still demonstrates one use for the i2c timeout implementation. +2. A fix for the infamous infinite recursion SERCOM issue, see pull request [#535](https://github.com/arduino/ArduinoCore-samd/pull/535). +3. The Serial.flush() blocks forever fix, see issue [#597](https://github.com/arduino/ArduinoCore-samd/issues/597). + +These are currently the main issues I've struck while working with the SAMD21 in the 'real' world. In particular 1&2 above have allowed for hot-swap and plug&play for devices on the i2c bus, and allows for complete device failure without locking up your entire system or blowing the stack! I'll add others as they raise their ugly heads within my projects. + ## Installation on Arduino IDE This core is available as a package in the Arduino IDE cores manager.