Skip to content

Commit

Permalink
Merge pull request #618 from Uberi/uberi-timeout-support
Browse files Browse the repository at this point in the history
Add support for timeouts to improve hard realtime operation
  • Loading branch information
SpenceKonde authored Dec 21, 2021
2 parents e6477c9 + 986cded commit bd2fff4
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 47 deletions.
144 changes: 119 additions & 25 deletions avr/libraries/Wire/src/USI_TWI_Master/USI_TWI_Master.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,26 @@
* returns a status byte, which can be used to evaluate the
* success of the transmission.
*
* Modified 2021 by Anthony Zhang (me@anthonyz.ca) to implement timeouts
*
****************************************************************************/


#include <avr/io.h>
#include "Arduino.h" // for micros

#ifdef USIDR
#include "USI_TWI_Master.h"

unsigned char USI_TWI_Master_Transfer(unsigned char);
unsigned char USI_TWI_Master_Transfer(unsigned char, unsigned char *);
unsigned char USI_TWI_Master_Stop(void);
void USI_Master_Handle_Timeout(unsigned char);
static unsigned char USI_TWI_MASTER_SPEED=0;

static volatile uint32_t timeout_us = 0ul;
static volatile unsigned char timed_out_flag = FALSE; // a timeout has been seen
static volatile unsigned char do_reset_on_timeout = TRUE; // reset the USI registers on timeout

union USI_TWI_state {
unsigned char errorState; // Can reuse the TWI_state for error states due to that it will not be need if there
// exists an error.
Expand Down Expand Up @@ -73,6 +81,15 @@ void USI_TWI_Master_Initialise(void)
(0x0 << USICNT0); // and reset counter.
}

void USI_TWI_Master_Disable(void) {
USICR = 0x00; // Disable USI
USISR = 0xF0; // Clear all flags and reset counter
DDR_USI_CL &= ~(1 << PIN_USI_SCL); // Enable SCL as input.
DDR_USI &= ~(1 << PIN_USI_SDA); // Enable SDA as input.
PORT_USI &= ~(1 << PIN_USI_SDA); // Disable pullup on SDA.
PORT_USI_CL &= ~(1 << PIN_USI_SCL); // Disable pullup on SCL.
}

/*---------------------------------------------------------------
Use this function to get hold of the error message from the last transmission
---------------------------------------------------------------*/
Expand Down Expand Up @@ -150,8 +167,14 @@ unsigned char USI_TWI_Start_Transceiver_With_Data_Stop(unsigned char *msg, unsig

/* Release SCL to ensure that (repeated) Start can be performed */
PORT_USI_CL |= (1 << PIN_USI_SCL); // Release SCL.
while (!(PIN_USI_CL & (1 << PIN_USI_SCL)))
; // Verify that SCL becomes high.
uint32_t startMicros = micros();
while (!(PIN_USI_CL & (1 << PIN_USI_SCL))) { // Wait for SCL to go high.
if (timeout_us > 0ul && (micros() - startMicros) > timeout_us) {
USI_Master_Handle_Timeout(do_reset_on_timeout);
USI_TWI_state.errorState = USI_TWI_TIMEOUT;
return (FALSE);
}
}
if (USI_TWI_MASTER_SPEED) DELAY_T4TWI_FM; // Delay for T4TWI if TWI_FAST_MODE
else DELAY_T2TWI; // Delay for T2TWI if TWI_STANDARD_MODE

Expand All @@ -175,26 +198,35 @@ unsigned char USI_TWI_Start_Transceiver_With_Data_Stop(unsigned char *msg, unsig
/* If masterWrite cycle (or initial address transmission)*/
if (USI_TWI_state.addressMode || USI_TWI_state.masterWriteDataMode) {
/* Write a byte */
PORT_USI_CL &= ~(1 << PIN_USI_SCL); // Pull SCL LOW.
PORT_USI_CL &= ~(1 << PIN_USI_SCL); // Pull SCL LOW.
USIDR = *(msg++); // Setup data.
USI_TWI_Master_Transfer(tempUSISR_8bit); // Send 8 bits on bus.
if (!USI_TWI_Master_Transfer(tempUSISR_8bit, NULL)) { // Send 8 bits on bus.
return FALSE;
}

/* Clock and verify (N)ACK from slave */
DDR_USI &= ~(1 << PIN_USI_SDA); // Enable SDA as input.
if (USI_TWI_Master_Transfer(tempUSISR_1bit) & (1 << TWI_NACK_BIT)) {
unsigned char dataOut;
if (!USI_TWI_Master_Transfer(tempUSISR_1bit, &dataOut)) {
return FALSE;
}
if (dataOut & (1 << TWI_NACK_BIT)) {
if (USI_TWI_state.addressMode)
USI_TWI_state.errorState = USI_TWI_NO_ACK_ON_ADDRESS;
else
USI_TWI_state.errorState = USI_TWI_NO_ACK_ON_DATA;
return (FALSE);
return FALSE;
}
USI_TWI_state.addressMode = FALSE; // Only perform address transmission once.
}
/* Else masterRead cycle*/
else {
/* Read a data byte */
DDR_USI &= ~(1 << PIN_USI_SDA); // Enable SDA as input.
*(msg++) = USI_TWI_Master_Transfer(tempUSISR_8bit);
if (!USI_TWI_Master_Transfer(tempUSISR_8bit, msg)) {
return FALSE;
}
msg ++;

/* Prepare to generate ACK (or NACK in case of End Of Transmission) */
if (msgSize == 1) // If transmission of last byte was performed.
Expand All @@ -203,7 +235,9 @@ unsigned char USI_TWI_Start_Transceiver_With_Data_Stop(unsigned char *msg, unsig
} else {
USIDR = 0x00; // Load ACK. Set data register bit 7 (output for SDA) low.
}
USI_TWI_Master_Transfer(tempUSISR_1bit); // Generate ACK/NACK.
if (!USI_TWI_Master_Transfer(tempUSISR_1bit, NULL)) { // Generate ACK/NACK.
return FALSE;
}
}
} while (--msgSize); // Until all data sent/received.

Expand All @@ -220,29 +254,44 @@ unsigned char USI_TWI_Start_Transceiver_With_Data_Stop(unsigned char *msg, unsig
Data to be sent has to be placed into the USIDR prior to calling
this function. Data read, will be return'ed from the function.
---------------------------------------------------------------*/
unsigned char USI_TWI_Master_Transfer(unsigned char temp)
unsigned char USI_TWI_Master_Transfer(unsigned char usiStatus, unsigned char *dataOut)
{
USISR = temp; // Set USISR according to temp.
// Prepare clocking.
temp = (0 << USISIE) | (0 << USIOIE) | // Interrupts disabled
(1 << USIWM1) | (0 << USIWM0) | // Set USI in Two-wire mode.
(1 << USICS1) | (0 << USICS0) | (1 << USICLK) | // Software clock strobe as source.
(1 << USITC); // Toggle Clock Port.
USISR = usiStatus; // Set USISR according to usiStatus.
// Prepare clocking.
uint8_t usiControl = (0 << USISIE) | (0 << USIOIE) | // Interrupts disabled
(1 << USIWM1) | (0 << USIWM0) | // Set USI in Two-wire mode.
(1 << USICS1) | (0 << USICS0) | (1 << USICLK) | // Software clock strobe as source.
(1 << USITC); // Toggle Clock Port.
uint32_t startMicros = micros();
do {
if (USI_TWI_MASTER_SPEED) DELAY_T2TWI_FM; else DELAY_T2TWI;
USICR = temp; // Generate positive SCL edge.
while (!(PIN_USI_CL & (1 << PIN_USI_SCL)))
; // Wait for SCL to go high.
USICR = usiControl; // Generate positive SCL edge.
while (!(PIN_USI_CL & (1 << PIN_USI_SCL))) { // Wait for SCL to go high.
if (timeout_us > 0ul && (micros() - startMicros) > timeout_us) {
USI_Master_Handle_Timeout(do_reset_on_timeout);
USI_TWI_state.errorState = USI_TWI_TIMEOUT;
return FALSE;
}
}
if (USI_TWI_MASTER_SPEED) DELAY_T4TWI_FM; else DELAY_T4TWI;
USICR = temp; // Generate negative SCL edge.
USICR = usiControl; // Generate negative SCL edge.

// Handle case where SCL is always high but transfer never completes
if (timeout_us > 0ul && (micros() - startMicros) > timeout_us) {
USI_Master_Handle_Timeout(do_reset_on_timeout);
USI_TWI_state.errorState = USI_TWI_TIMEOUT;
return FALSE;
}
} while (!(USISR & (1 << USIOIF))); // Check for transfer complete.

if (USI_TWI_MASTER_SPEED) DELAY_T2TWI_FM; else DELAY_T2TWI;
temp = USIDR; // Read out data.
USIDR = 0xFF; // Release SDA.
if (dataOut != NULL) {
*dataOut = USIDR; // Read out data.
}
USIDR = 0xFF; // Release SDA.
DDR_USI |= (1 << PIN_USI_SDA); // Enable SDA as output.

return temp; // Return the data from the USIDR
return TRUE;
}

/*---------------------------------------------------------------
Expand All @@ -253,8 +302,14 @@ unsigned char USI_TWI_Master_Stop(void)
{
PORT_USI &= ~(1 << PIN_USI_SDA); // Pull SDA low.
PORT_USI_CL |= (1 << PIN_USI_SCL); // Release SCL.
while (!(PIN_USI_CL & (1 << PIN_USI_SCL)))
; // Wait for SCL to go high.
uint32_t startMicros = micros();
while (!(PIN_USI_CL & (1 << PIN_USI_SCL))) { // Wait for SCL to go high.
if (timeout_us > 0ul && (micros() - startMicros) > timeout_us) {
USI_Master_Handle_Timeout(do_reset_on_timeout);
USI_TWI_state.errorState = USI_TWI_TIMEOUT;
return (FALSE);
}
}
if (USI_TWI_MASTER_SPEED) DELAY_T4TWI_FM; else DELAY_T4TWI;
PORT_USI |= (1 << PIN_USI_SDA); // Release SDA.
if (USI_TWI_MASTER_SPEED) DELAY_T2TWI_FM; else DELAY_T2TWI;
Expand All @@ -268,4 +323,43 @@ unsigned char USI_TWI_Master_Stop(void)

return (TRUE);
}

/*---------------------------------------------------------------
Function for configuring USI timeout settings.
---------------------------------------------------------------*/
void USI_TWI_Master_Timeout(uint32_t timeout, unsigned char reset_with_timeout) {
timed_out_flag = FALSE;
timeout_us = timeout;
do_reset_on_timeout = reset_with_timeout;
}

/*---------------------------------------------------------------
Function for reading and optionally clearing the timeout flag.
---------------------------------------------------------------*/
unsigned char USI_TWI_Master_Manage_Timeout_Flag(unsigned char clear_flag) {
unsigned char flag = timed_out_flag;
if (clear_flag) {
timed_out_flag = FALSE;
}
return flag;
}

void USI_Master_Handle_Timeout(unsigned char reset) {
timed_out_flag = TRUE;

if (reset) {
// remember bitrate and address settings
uint8_t previous_USICR = USICR;
uint8_t previous_USISR = USISR;

// reset the interface
USI_TWI_Master_Disable();
USI_TWI_Master_Initialise();

// reapply the previous register values
USICR = previous_USICR;
USISR = previous_USISR;
}
}

#endif
6 changes: 6 additions & 0 deletions avr/libraries/Wire/src/USI_TWI_Master/USI_TWI_Master.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* returns with a fail, then use USI_TWI_Get_Status_Info to evaluate the
* cause of the failure.
*
* Modified 2021 by Anthony Zhang (me@anthonyz.ca) to implement timeouts
****************************************************************************/

#include <avr/io.h>
Expand Down Expand Up @@ -64,6 +66,7 @@
#define USI_TWI_NO_ACK_ON_ADDRESS 0x06 // The slave did not acknowledge the address
#define USI_TWI_MISSING_START_CON 0x07 // Generated Start Condition not detected on bus
#define USI_TWI_MISSING_STOP_CON 0x08 // Generated Stop Condition not detected on bus
#define USI_TWI_TIMEOUT 0x09 // Timeout occurred during bus operation, hard reset recommended


#include "pins_arduino.h"
Expand All @@ -79,8 +82,11 @@
//********** Prototypes **********//

void USI_TWI_Master_Initialise(void);
void USI_TWI_Master_Disable(void);
void USI_TWI_Master_Speed(uint8_t);
unsigned char USI_TWI_Start_Transceiver_With_Data_Stop(unsigned char *, unsigned char, unsigned char);
unsigned char USI_TWI_Start_Transceiver_With_Data(unsigned char *, unsigned char);
unsigned char USI_TWI_Get_State_Info(void);
void USI_TWI_Master_Timeout(uint32_t, unsigned char);
unsigned char USI_TWI_Master_Manage_Timeout_Flag(unsigned char);
#endif
Loading

0 comments on commit bd2fff4

Please sign in to comment.