Skip to content

Implement SERCOM/Wire I2C timeout detection #439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions cores/arduino/SERCOM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
SERCOM::SERCOM(Sercom* s)
{
sercom = s;
timeoutOccurred = false;
timeoutInterval = SERCOM_DEFAULT_I2C_OPERATION_TIMEOUT_MS;
}

/* =========================
Expand Down Expand Up @@ -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
}
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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;
}
12 changes: 12 additions & 0 deletions cores/arduino/SERCOM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
22 changes: 20 additions & 2 deletions libraries/Wire/Wire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand All @@ -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 ;
Expand All @@ -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
}

Expand All @@ -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
}
}
Expand Down
9 changes: 9 additions & 0 deletions libraries/Wire/Wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
Expand All @@ -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;

Expand Down