From 0b0006bb90fd826495bab721a5984083dc8ba8ff Mon Sep 17 00:00:00 2001 From: Jeff Rowberg Date: Sun, 11 Aug 2019 15:48:48 -0400 Subject: [PATCH] Implement SERCOM/Wire I2C timeout detection --- cores/arduino/SERCOM.cpp | 41 ++++++++++++++++++++++++++++++++++++---- cores/arduino/SERCOM.h | 12 ++++++++++++ libraries/Wire/Wire.cpp | 22 +++++++++++++++++++-- libraries/Wire/Wire.h | 9 +++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/cores/arduino/SERCOM.cpp b/cores/arduino/SERCOM.cpp index 8bf5a4521..a73125cfe 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; } /* ========================= @@ -517,9 +519,10 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag sercom->I2CM.ADDR.bit.ADDR = address; // Address Transmitted + initTimeout(); if ( flag == WIRE_WRITE_FLAG ) // Write mode { - while( !sercom->I2CM.INTFLAG.bit.MB ) + while( !sercom->I2CM.INTFLAG.bit.MB && !testTimeout() ) { // Wait transmission complete } @@ -532,7 +535,7 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag } 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. @@ -547,6 +550,8 @@ bool SERCOM::startTransmissionWIRE(uint8_t address, SercomWireReadWriteFlag flag //sercom->I2CM.INTFLAG.bit.SB = 0x1ul; } + // check for timeout condition + if ( didTimeout() ) return false; //ACK received (0: ACK, 1: NACK) if(sercom->I2CM.STATUS.bit.RXNACK) @@ -565,7 +570,8 @@ bool SERCOM::sendDataMasterWIRE(uint8_t data) sercom->I2CM.DATA.bit.DATA = data; //Wait transmission successful - while(!sercom->I2CM.INTFLAG.bit.MB) { + initTimeout(); + while(!sercom->I2CM.INTFLAG.bit.MB && !testTimeout()) { // If a bus error occurs, the MB bit may never be set. // Check the bus error bit and bail if it's set. @@ -574,6 +580,9 @@ bool SERCOM::sendDataMasterWIRE(uint8_t data) } } + // check for timeout condition + if ( didTimeout() ) return false; + //Problems on line? nack received? if(sercom->I2CM.STATUS.bit.RXNACK) return false; @@ -665,7 +674,8 @@ 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 == 0 && sercom->I2CM.INTFLAG.bit.MB == 0 && !testTimeout() ) { // Waiting complete receive } @@ -739,3 +749,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..2451b24c1 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, @@ -212,12 +215,21 @@ class SERCOM 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 ) ; + + // timeout detection for I2C operations + void initTimeout( void ); + bool testTimeout( void ); + uint16_t timeoutInterval; + uint32_t timeoutRef; + bool timeoutOccurred; }; #endif diff --git a/libraries/Wire/Wire.cpp b/libraries/Wire/Wire.cpp index c56381911..158d7ab37 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(); @@ -80,7 +86,7 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) 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 @@ -100,6 +106,15 @@ uint8_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) } } + // catch timeout condition + if (sercom->didTimeout()) + { + // reset the bus + setClock(activeBaudrate); + transmissionBegun = false; + return 0; + } + return byteRead; } @@ -121,7 +136,8 @@ 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) { transmissionBegun = false ; @@ -130,6 +146,7 @@ uint8_t TwoWire::endTransmission(bool stopBit) if ( !sercom->startTransmissionWIRE( txAddress, WIRE_WRITE_FLAG ) ) { sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + if (sercom->didTimeout()) return 4; // Timeout return 2 ; // Address error } @@ -140,6 +157,7 @@ uint8_t TwoWire::endTransmission(bool stopBit) if ( !sercom->sendDataMasterWIRE( txBuffer.read_char() ) ) { sercom->prepareCommandBitsWire(WIRE_MASTER_ACT_STOP); + if (sercom->didTimeout()) return 4; // Timeout return 3 ; // Nack or error } } 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;