1717 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1818
1919 Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts
20+ Modified 2020 by Greyson Christoforo (grey@christoforo.net) to implement timeouts
2021*/
2122
2223#include <math.h>
2324#include <stdlib.h>
2425#include <inttypes.h>
2526#include <avr/io.h>
2627#include <avr/interrupt.h>
28+ #include <util/delay.h>
2729#include <compat/twi.h>
28- #include "Arduino.h" // for digitalWrite
30+ #include "Arduino.h" // for digitalWrite and micros
2931
3032#ifndef cbi
3133#define cbi (sfr , bit ) (_SFR_BYTE(sfr) &= ~_BV(bit))
@@ -43,6 +45,16 @@ static volatile uint8_t twi_slarw;
4345static volatile uint8_t twi_sendStop ; // should the transaction end with a stop
4446static volatile uint8_t twi_inRepStart ; // in the middle of a repeated start
4547
48+ // twi_timeout_us > 0 prevents the code from getting stuck in various while loops here
49+ // if twi_timeout_us == 0 then timeout checking is disabled (the previous Wire lib behavior)
50+ // at some point in the future, the default twi_timeout_us value could become 25000
51+ // and twi_do_reset_on_timeout could become true
52+ // to conform to the SMBus standard
53+ // http://smbus.org/specs/SMBus_3_1_20180319.pdf
54+ static volatile uint32_t twi_timeout_us = 0ul ;
55+ static volatile bool twi_timed_out_flag = false; // a timeout has been seen
56+ static volatile bool twi_do_reset_on_timeout = false; // reset the TWI registers on timeout
57+
4658static void (* twi_onSlaveTransmit )(void );
4759static void (* twi_onSlaveReceive )(uint8_t * , int );
4860
@@ -154,8 +166,12 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
154166 }
155167
156168 // wait until twi is ready, become master receiver
169+ uint32_t startMicros = micros ();
157170 while (TWI_READY != twi_state ){
158- continue ;
171+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
172+ twi_handleTimeout (twi_do_reset_on_timeout );
173+ return 0 ;
174+ }
159175 }
160176 twi_state = TWI_MRX ;
161177 twi_sendStop = sendStop ;
@@ -183,28 +199,38 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
183199 // up. Also, don't enable the START interrupt. There may be one pending from the
184200 // repeated start that we sent ourselves, and that would really confuse things.
185201 twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR
202+ startMicros = micros ();
186203 do {
187204 TWDR = twi_slarw ;
205+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
206+ twi_handleTimeout (twi_do_reset_on_timeout );
207+ return 0 ;
208+ }
188209 } while (TWCR & _BV (TWWC ));
189210 TWCR = _BV (TWINT ) | _BV (TWEA ) | _BV (TWEN ) | _BV (TWIE ); // enable INTs, but not START
190- }
191- else
211+ } else {
192212 // send start condition
193213 TWCR = _BV (TWEN ) | _BV (TWIE ) | _BV (TWEA ) | _BV (TWINT ) | _BV (TWSTA );
214+ }
194215
195216 // wait for read operation to complete
217+ startMicros = micros ();
196218 while (TWI_MRX == twi_state ){
197- continue ;
219+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
220+ twi_handleTimeout (twi_do_reset_on_timeout );
221+ return 0 ;
222+ }
198223 }
199224
200- if (twi_masterBufferIndex < length )
225+ if (twi_masterBufferIndex < length ) {
201226 length = twi_masterBufferIndex ;
227+ }
202228
203229 // copy twi buffer to data
204230 for (i = 0 ; i < length ; ++ i ){
205231 data [i ] = twi_masterBuffer [i ];
206232 }
207-
233+
208234 return length ;
209235}
210236
@@ -222,6 +248,7 @@ uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sen
222248 * 2 .. address send, NACK received
223249 * 3 .. data send, NACK received
224250 * 4 .. other twi error (lost bus arbitration, bus error, ..)
251+ * 5 .. timeout
225252 */
226253uint8_t twi_writeTo (uint8_t address , uint8_t * data , uint8_t length , uint8_t wait , uint8_t sendStop )
227254{
@@ -233,8 +260,12 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait
233260 }
234261
235262 // wait until twi is ready, become master transmitter
263+ uint32_t startMicros = micros ();
236264 while (TWI_READY != twi_state ){
237- continue ;
265+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
266+ twi_handleTimeout (twi_do_reset_on_timeout );
267+ return (5 );
268+ }
238269 }
239270 twi_state = TWI_MTX ;
240271 twi_sendStop = sendStop ;
@@ -265,18 +296,27 @@ uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait
265296 // up. Also, don't enable the START interrupt. There may be one pending from the
266297 // repeated start that we sent outselves, and that would really confuse things.
267298 twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR
299+ startMicros = micros ();
268300 do {
269- TWDR = twi_slarw ;
301+ TWDR = twi_slarw ;
302+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
303+ twi_handleTimeout (twi_do_reset_on_timeout );
304+ return (5 );
305+ }
270306 } while (TWCR & _BV (TWWC ));
271307 TWCR = _BV (TWINT ) | _BV (TWEA ) | _BV (TWEN ) | _BV (TWIE ); // enable INTs, but not START
272- }
273- else
308+ } else {
274309 // send start condition
275310 TWCR = _BV (TWINT ) | _BV (TWEA ) | _BV (TWEN ) | _BV (TWIE ) | _BV (TWSTA ); // enable INTs
311+ }
276312
277313 // wait for write operation to complete
314+ startMicros = micros ();
278315 while (wait && (TWI_MTX == twi_state )){
279- continue ;
316+ if ((twi_timeout_us > 0ul ) && ((micros () - startMicros ) > twi_timeout_us )) {
317+ twi_handleTimeout (twi_do_reset_on_timeout );
318+ return (5 );
319+ }
280320 }
281321
282322 if (twi_error == 0xFF )
@@ -356,7 +396,7 @@ void twi_reply(uint8_t ack)
356396 if (ack ){
357397 TWCR = _BV (TWEN ) | _BV (TWIE ) | _BV (TWINT ) | _BV (TWEA );
358398 }else {
359- TWCR = _BV (TWEN ) | _BV (TWIE ) | _BV (TWINT );
399+ TWCR = _BV (TWEN ) | _BV (TWIE ) | _BV (TWINT );
360400 }
361401}
362402
@@ -373,8 +413,17 @@ void twi_stop(void)
373413
374414 // wait for stop condition to be exectued on bus
375415 // TWINT is not set after a stop condition!
416+ volatile uint32_t counter = twi_timeout_us /10ul ; // approximate the timeout
376417 while (TWCR & _BV (TWSTO )){
377- continue ;
418+ if (twi_timeout_us > 0ul ){
419+ if (counter > 0ul ){
420+ _delay_us (10 );
421+ counter -- ;
422+ } else {
423+ twi_handleTimeout (twi_do_reset_on_timeout );
424+ return ;
425+ }
426+ }
378427 }
379428
380429 // update twi state
@@ -396,6 +445,59 @@ void twi_releaseBus(void)
396445 twi_state = TWI_READY ;
397446}
398447
448+ /*
449+ * Function twi_setTimeoutInMicros
450+ * Desc set a timeout for while loops that twi might get stuck in
451+ * Input timeout value in microseconds (0 means never time out)
452+ * Input reset_with_timeout: true causes timeout events to reset twi
453+ * Output none
454+ */
455+ void twi_setTimeoutInMicros (uint32_t timeout , bool reset_with_timeout ){
456+ twi_timed_out_flag = false;
457+ twi_timeout_us = timeout ;
458+ twi_do_reset_on_timeout = reset_with_timeout ;
459+ }
460+
461+ /*
462+ * Function twi_handleTimeout
463+ * Desc this gets called whenever a while loop here has lasted longer than
464+ * twi_timeout_us microseconds. always sets twi_timed_out_flag
465+ * Input reset: true causes this function to reset the twi hardware interface
466+ * Output none
467+ */
468+ void twi_handleTimeout (bool reset ){
469+ twi_timed_out_flag = true;
470+
471+ if (reset ) {
472+ // remember bitrate and address settings
473+ uint8_t previous_TWBR = TWBR ;
474+ uint8_t previous_TWAR = TWAR ;
475+
476+ // reset the interface
477+ twi_disable ();
478+ twi_init ();
479+
480+ // reapply the previous register values
481+ TWAR = previous_TWAR ;
482+ TWBR = previous_TWBR ;
483+ }
484+ }
485+
486+ /*
487+ * Function twi_manageTimeoutFlag
488+ * Desc returns true if twi has seen a timeout
489+ * optionally clears the timeout flag
490+ * Input clear_flag: true if we should reset the hardware
491+ * Output none
492+ */
493+ bool twi_manageTimeoutFlag (bool clear_flag ){
494+ bool flag = twi_timed_out_flag ;
495+ if (clear_flag ){
496+ twi_timed_out_flag = false;
497+ }
498+ return (flag );
499+ }
500+
399501ISR (TWI_vect )
400502{
401503 switch (TW_STATUS ){
@@ -416,16 +518,16 @@ ISR(TWI_vect)
416518 TWDR = twi_masterBuffer [twi_masterBufferIndex ++ ];
417519 twi_reply (1 );
418520 }else {
419- if (twi_sendStop )
521+ if (twi_sendStop ){
420522 twi_stop ();
421- else {
422- twi_inRepStart = true; // we're gonna send the START
423- // don't enable the interrupt. We'll generate the start, but we
424- // avoid handling the interrupt until we're in the next transaction,
425- // at the point where we would normally issue the start.
426- TWCR = _BV (TWINT ) | _BV (TWSTA )| _BV (TWEN ) ;
427- twi_state = TWI_READY ;
428- }
523+ } else {
524+ twi_inRepStart = true; // we're gonna send the START
525+ // don't enable the interrupt. We'll generate the start, but we
526+ // avoid handling the interrupt until we're in the next transaction,
527+ // at the point where we would normally issue the start.
528+ TWCR = _BV (TWINT ) | _BV (TWSTA )| _BV (TWEN ) ;
529+ twi_state = TWI_READY ;
530+ }
429531 }
430532 break ;
431533 case TW_MT_SLA_NACK : // address sent, nack received
@@ -457,17 +559,17 @@ ISR(TWI_vect)
457559 case TW_MR_DATA_NACK : // data received, nack sent
458560 // put final byte into buffer
459561 twi_masterBuffer [twi_masterBufferIndex ++ ] = TWDR ;
460- if (twi_sendStop )
461- twi_stop ();
462- else {
463- twi_inRepStart = true; // we're gonna send the START
464- // don't enable the interrupt. We'll generate the start, but we
465- // avoid handling the interrupt until we're in the next transaction,
466- // at the point where we would normally issue the start.
467- TWCR = _BV (TWINT ) | _BV (TWSTA )| _BV (TWEN ) ;
468- twi_state = TWI_READY ;
469- }
470- break ;
562+ if (twi_sendStop ){
563+ twi_stop ();
564+ } else {
565+ twi_inRepStart = true; // we're gonna send the START
566+ // don't enable the interrupt. We'll generate the start, but we
567+ // avoid handling the interrupt until we're in the next transaction,
568+ // at the point where we would normally issue the start.
569+ TWCR = _BV (TWINT ) | _BV (TWSTA )| _BV (TWEN ) ;
570+ twi_state = TWI_READY ;
571+ }
572+ break ;
471573 case TW_MR_SLA_NACK : // address sent, nack received
472574 twi_stop ();
473575 break ;
@@ -560,4 +662,3 @@ ISR(TWI_vect)
560662 break ;
561663 }
562664}
563-
0 commit comments