Commit deea9293 authored by Greyson Christoforo's avatar Greyson Christoforo Committed by Martino Facchin

Introduce non compulsory Wire timeout

move timout handling into its own function

change timeout from milliseconds to microseconds

don't forget operating slave address or the bitrate when we reset because of a timeout
Co-Authored-By: default avatarWitold Markowski <witold.a.markowski@gmail.com>

fix delay datatype uint16_t --> uint32_t

Update libraries/Wire/src/utility/twi.c

fix mix up using TWBR instea of TWAR!
Co-Authored-By: default avatarMatthijs Kooijman <matthijs@stdin.nl>

Update libraries/Wire/src/utility/twi.c

fix 2nd TWBR/TWAR mixup
Co-Authored-By: default avatarMatthijs Kooijman <matthijs@stdin.nl>

twi_stop() should use the same timeout as everywhere else

all while loops are now protected by timeouts

Revert "twi_stop() should use the same timeout as everywhere else"

This reverts commit 68fe5f1dae1bb41183bb37eeda3fb453394a580c.

make timeout counter volatile

rename timeout function for improved clarity

- resetting the twi interface on timeouts is now optional
- timeouts in the ISR are no longer hardcoded and now obey the set timeout value
- a user-readable flag is now set whenever a timeout occurs
  - the user can clear this flag whenever they like
parent 71c3f992
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts
Modified 2017 by Chuck Todd (ctodd@cableone.net) to correct Unconfigured Slave Mode reboot Modified 2017 by Chuck Todd (ctodd@cableone.net) to correct Unconfigured Slave Mode reboot
Modified 2020 by Greyson Christoforo (grey@christoforo.net) to implement timeouts
*/ */
extern "C" { extern "C" {
...@@ -86,6 +87,33 @@ void TwoWire::setClock(uint32_t clock) ...@@ -86,6 +87,33 @@ void TwoWire::setClock(uint32_t clock)
twi_setFrequency(clock); twi_setFrequency(clock);
} }
/***
* Sets the TWI timeout.
*
* @param timeout a timeout value in microseconds, if zero then timeout checking is disabled
* @param reset_with_timeout if true then TWI interface will be automatically reset on timeout
* if false then TWI interface will not be reset on timeout
*/
void TwoWire::setWireTimeoutUs(uint32_t timeout, bool reset_with_timeout){
twi_setTimeoutInMicros(timeout, reset_with_timeout);
}
/***
* Returns the TWI timeout flag.
*
* @return true if timeout has occured
*/
bool TwoWire::getWireTimeoutFlag(void){
return(twi_manageTimeoutFlag(false));
}
/***
* Clears the TWI timeout flag.
*/
void TwoWire::clearWireTimeoutFlag(void){
twi_manageTimeoutFlag(true);
}
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop) uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop)
{ {
if (isize > 0) { if (isize > 0) {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts
Modified 2020 by Greyson Christoforo (grey@christoforo.net) to implement timeouts
*/ */
#ifndef TwoWire_h #ifndef TwoWire_h
...@@ -54,6 +55,9 @@ class TwoWire : public Stream ...@@ -54,6 +55,9 @@ class TwoWire : public Stream
void begin(int); void begin(int);
void end(); void end();
void setClock(uint32_t); void setClock(uint32_t);
void setWireTimeoutUs(uint32_t, bool);
bool getWireTimeoutFlag(void);
void clearWireTimeoutFlag(void);
void beginTransmission(uint8_t); void beginTransmission(uint8_t);
void beginTransmission(int); void beginTransmission(int);
uint8_t endTransmission(void); uint8_t endTransmission(void);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts
Modified 2020 by Greyson Christoforo (grey@christoforo.net) to implement timeouts
*/ */
#include <math.h> #include <math.h>
...@@ -24,8 +25,9 @@ ...@@ -24,8 +25,9 @@
#include <inttypes.h> #include <inttypes.h>
#include <avr/io.h> #include <avr/io.h>
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include <util/delay.h>
#include <compat/twi.h> #include <compat/twi.h>
#include "Arduino.h" // for digitalWrite #include "Arduino.h" // for digitalWrite and micros
#ifndef cbi #ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
...@@ -43,6 +45,16 @@ static volatile uint8_t twi_slarw; ...@@ -43,6 +45,16 @@ static volatile uint8_t twi_slarw;
static volatile uint8_t twi_sendStop; // should the transaction end with a stop static volatile uint8_t twi_sendStop; // should the transaction end with a stop
static volatile uint8_t twi_inRepStart; // in the middle of a repeated start static volatile uint8_t twi_inRepStart; // in the middle of a repeated start
// twi_timeout_us > 0 prevents the code from getting stuck in various while loops here
// if twi_timeout_us == 0 then timeout checking is disabled (the previous Wire lib behavior)
// at some point in the future, the default twi_timeout_us value could become 25000
// and twi_do_reset_on_timeout could become true
// to conform to the SMBus standard
// http://smbus.org/specs/SMBus_3_1_20180319.pdf
static volatile uint32_t twi_timeout_us = 0ul;
static volatile bool twi_timed_out_flag = false; // a timeout has been seen
static volatile bool twi_do_reset_on_timeout = false; // reset the TWI registers on timeout
static void (*twi_onSlaveTransmit)(void); static void (*twi_onSlaveTransmit)(void);
static void (*twi_onSlaveReceive)(uint8_t*, int); static void (*twi_onSlaveReceive)(uint8_t*, int);
...@@ -154,8 +166,12 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen ...@@ -154,8 +166,12 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
} }
// wait until twi is ready, become master receiver // wait until twi is ready, become master receiver
uint32_t startMicros = micros();
while(TWI_READY != twi_state){ while(TWI_READY != twi_state){
continue; if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return 0;
}
} }
twi_state = TWI_MRX; twi_state = TWI_MRX;
twi_sendStop = sendStop; twi_sendStop = sendStop;
...@@ -183,22 +199,32 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen ...@@ -183,22 +199,32 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
// up. Also, don't enable the START interrupt. There may be one pending from the // up. Also, don't enable the START interrupt. There may be one pending from the
// repeated start that we sent ourselves, and that would really confuse things. // repeated start that we sent ourselves, and that would really confuse things.
twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR
startMicros = micros();
do { do {
TWDR = twi_slarw; TWDR = twi_slarw;
if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return 0;
}
} while(TWCR & _BV(TWWC)); } while(TWCR & _BV(TWWC));
TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START
} } else {
else
// send start condition // send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA); TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
}
// wait for read operation to complete // wait for read operation to complete
startMicros = micros();
while(TWI_MRX == twi_state){ while(TWI_MRX == twi_state){
continue; if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return 0;
}
} }
if (twi_masterBufferIndex < length) if (twi_masterBufferIndex < length) {
length = twi_masterBufferIndex; length = twi_masterBufferIndex;
}
// copy twi buffer to data // copy twi buffer to data
for(i = 0; i < length; ++i){ for(i = 0; i < length; ++i){
...@@ -222,6 +248,7 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen ...@@ -222,6 +248,7 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
* 2 .. address send, NACK received * 2 .. address send, NACK received
* 3 .. data send, NACK received * 3 .. data send, NACK received
* 4 .. other twi error (lost bus arbitration, bus error, ..) * 4 .. other twi error (lost bus arbitration, bus error, ..)
* 5 .. timeout
*/ */
uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop) uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop)
{ {
...@@ -233,8 +260,12 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait ...@@ -233,8 +260,12 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait
} }
// wait until twi is ready, become master transmitter // wait until twi is ready, become master transmitter
uint32_t startMicros = micros();
while(TWI_READY != twi_state){ while(TWI_READY != twi_state){
continue; if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return (5);
}
} }
twi_state = TWI_MTX; twi_state = TWI_MTX;
twi_sendStop = sendStop; twi_sendStop = sendStop;
...@@ -265,18 +296,27 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait ...@@ -265,18 +296,27 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait
// up. Also, don't enable the START interrupt. There may be one pending from the // up. Also, don't enable the START interrupt. There may be one pending from the
// repeated start that we sent outselves, and that would really confuse things. // repeated start that we sent outselves, and that would really confuse things.
twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR
startMicros = micros();
do { do {
TWDR = twi_slarw; TWDR = twi_slarw;
if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return (5);
}
} while(TWCR & _BV(TWWC)); } while(TWCR & _BV(TWWC));
TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START
} } else {
else
// send start condition // send start condition
TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); // enable INTs TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); // enable INTs
}
// wait for write operation to complete // wait for write operation to complete
startMicros = micros();
while(wait && (TWI_MTX == twi_state)){ while(wait && (TWI_MTX == twi_state)){
continue; if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
twi_handleTimeout(twi_do_reset_on_timeout);
return (5);
}
} }
if (twi_error == 0xFF) if (twi_error == 0xFF)
...@@ -373,8 +413,17 @@ void twi_stop(void) ...@@ -373,8 +413,17 @@ void twi_stop(void)
// wait for stop condition to be exectued on bus // wait for stop condition to be exectued on bus
// TWINT is not set after a stop condition! // TWINT is not set after a stop condition!
volatile uint32_t counter = twi_timeout_us/10ul; // approximate the timeout
while(TWCR & _BV(TWSTO)){ while(TWCR & _BV(TWSTO)){
continue; if(twi_timeout_us > 0ul){
if (counter > 0ul){
_delay_us(10);
counter--;
} else {
twi_handleTimeout(twi_do_reset_on_timeout);
return;
}
}
} }
// update twi state // update twi state
...@@ -396,6 +445,59 @@ void twi_releaseBus(void) ...@@ -396,6 +445,59 @@ void twi_releaseBus(void)
twi_state = TWI_READY; twi_state = TWI_READY;
} }
/*
* Function twi_setTimeoutInMicros
* Desc set a timeout for while loops that twi might get stuck in
* Input timeout value in microseconds (0 means never time out)
* Input reset_with_timeout: true causes timeout events to reset twi
* Output none
*/
void twi_setTimeoutInMicros(uint32_t timeout, bool reset_with_timeout){
twi_timed_out_flag = false;
twi_timeout_us = timeout;
twi_do_reset_on_timeout = reset_with_timeout;
}
/*
* Function twi_handleTimeout
* Desc this gets called whenever a while loop here has lasted longer than
* twi_timeout_us microseconds. always sets twi_timed_out_flag
* Input reset: true causes this function to reset the twi hardware interface
* Output none
*/
void twi_handleTimeout(bool reset){
twi_timed_out_flag = true;
if (reset) {
// remember bitrate and address settings
uint8_t previous_TWBR = TWBR;
uint8_t previous_TWAR = TWAR;
// reset the interface
twi_disable();
twi_init();
// reapply the previous register values
TWAR = previous_TWAR;
TWBR = previous_TWBR;
}
}
/*
* Function twi_manageTimeoutFlag
* Desc returns true if twi has seen a timeout
* optionally clears the timeout flag
* Input clear_flag: true if we should reset the hardware
* Output none
*/
bool twi_manageTimeoutFlag(bool clear_flag){
bool flag = twi_timed_out_flag;
if (clear_flag){
twi_timed_out_flag = false;
}
return(flag);
}
ISR(TWI_vect) ISR(TWI_vect)
{ {
switch(TW_STATUS){ switch(TW_STATUS){
...@@ -416,9 +518,9 @@ ISR(TWI_vect) ...@@ -416,9 +518,9 @@ ISR(TWI_vect)
TWDR = twi_masterBuffer[twi_masterBufferIndex++]; TWDR = twi_masterBuffer[twi_masterBufferIndex++];
twi_reply(1); twi_reply(1);
}else{ }else{
if (twi_sendStop) if (twi_sendStop){
twi_stop(); twi_stop();
else { } else {
twi_inRepStart = true; // we're gonna send the START twi_inRepStart = true; // we're gonna send the START
// don't enable the interrupt. We'll generate the start, but we // don't enable the interrupt. We'll generate the start, but we
// avoid handling the interrupt until we're in the next transaction, // avoid handling the interrupt until we're in the next transaction,
...@@ -457,9 +559,9 @@ ISR(TWI_vect) ...@@ -457,9 +559,9 @@ ISR(TWI_vect)
case TW_MR_DATA_NACK: // data received, nack sent case TW_MR_DATA_NACK: // data received, nack sent
// put final byte into buffer // put final byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR; twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
if (twi_sendStop) if (twi_sendStop){
twi_stop(); twi_stop();
else { } else {
twi_inRepStart = true; // we're gonna send the START twi_inRepStart = true; // we're gonna send the START
// don't enable the interrupt. We'll generate the start, but we // don't enable the interrupt. We'll generate the start, but we
// avoid handling the interrupt until we're in the next transaction, // avoid handling the interrupt until we're in the next transaction,
...@@ -560,4 +662,3 @@ ISR(TWI_vect) ...@@ -560,4 +662,3 @@ ISR(TWI_vect)
break; break;
} }
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 2020 by Greyson Christoforo (grey@christoforo.net) to implement timeouts
*/ */
#ifndef twi_h #ifndef twi_h
...@@ -50,6 +52,8 @@ ...@@ -50,6 +52,8 @@
void twi_reply(uint8_t); void twi_reply(uint8_t);
void twi_stop(void); void twi_stop(void);
void twi_releaseBus(void); void twi_releaseBus(void);
void twi_setTimeoutInMicros(uint32_t, bool);
void twi_handleTimeout(bool);
bool twi_manageTimeoutFlag(bool);
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment