Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Summed up, made the Wire library more resistant to erroneous beahiour of the physical bus in Client/Slave mode
  • Loading branch information
MX682X committed May 25, 2024
1 parent 949491f commit bed90a3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 42 deletions.
86 changes: 47 additions & 39 deletions megaavr/libraries/Wire/src/Wire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ extern "C" { // compiler was complaining when I put twi.h into the upper C in
#include "twi_pins.h"
}

static uint8_t sleepStack = 0;
volatile uint8_t sleepStack = 0;

void pushSleep(void);
void popSleep(void);
void pauseDeepSleep(uint8_t moduelAddr);
void restoreSleep(uint8_t moduelAddr);

TwoWire* twi0_wire;
TwoWire* twi1_wire;
Expand Down Expand Up @@ -1288,7 +1288,6 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) {
return;
}


uint8_t *address, *buffer;
twi_buf_index_t *head, *tail;
#if defined(TWI_MANDS)
Expand All @@ -1312,22 +1311,21 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) {


if (clientStatus & TWI_APIF_bm) { // Address/Stop Bit set

if ((*head) > 0) { // At this point, we have either a START, REPSTART or a STOP
if (wire_s->user_onReceive != NULL) { // at START, head should be 0, as it was set so on the last STOP
wire_s->user_onReceive((*head)); // otherwise, we notify the use sketch, as we have an REPSTART/STOP
if (wire_s->_bools._hostDataSent != 0) { // At this point, we have either a START, REPSTART or a STOP
wire_s->_bools._hostDataSent = 0x00;
if (wire_s->user_onReceive != NULL) { // only if the last APIF was a Master Write,
wire_s->user_onReceive((*head)); // we notify the sketch about new Data
}
}

if (clientStatus & TWI_AP_bm) { // Address bit set
if ((*head) == 0) { // only if there was no data (START)
pushSleep(); // push the sleep
pauseDeepSleep((uint8_t)((uint16_t)wire_s->_module)); // Only START can wake from deep sleep, change to IDLE
}
(*head) = 0;
(*tail) = 0;
(*address) = wire_s->_module->SDATA; // read address from data register
if (clientStatus & TWI_DIR_bm) { // Master is reading
(*head) = 0; // reset buffer positions for user sketch
(*tail) = 0;

if (wire_s->user_onRequest != NULL) {
wire_s->user_onRequest();
}
Expand All @@ -1337,31 +1335,30 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) {
action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte"
}
} else { // Master is writing
(*head) = 0; // reset buffer positions
(*tail) = 0;
action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte"
wire_s->_bools._hostDataSent = 0x01;
action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by slave data interrupt"
}
} else { // Stop bit set
popSleep();

(*head) = 0; // clear whatever might be left due errors
restoreSleep((uint8_t)((uint16_t)wire_s->_module));
(*head) = 0;
(*tail) = 0;
action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition"
}
} else if (clientStatus & TWI_DIF_bm) { // Data bit set
if (clientStatus & TWI_DIR_bm) { // Master is reading
if (clientStatus & wire_s->client_irq_mask) { // If a collision was detected, or RXACK bit is set (when it matters)
(*head) = 0; // Abort further data writes
wire_s->client_irq_mask = TWI_COLL_bm; // stop checking for NACK
(*head) = 0; // Abort further data writes
action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition"
} else { // RXACK bit not set, no COLL
wire_s->_bytesTransmittedS++; // increment bytes transmitted counter (for register model)
wire_s->_bytesTransmittedS++; // increment bytes transmitted counter (for register model)
wire_s->client_irq_mask = TWI_COLL_bm | TWI_RXACK_bm; // start checking for NACK
if ((*tail) < (*head)) { // Data is available
wire_s->_module->SDATA = buffer[(*tail)]; // Writing to the register to send data
(*tail)++; // Increment counter for sent bytes
action = TWI_SCMD_RESPONSE_gc; // "Execute a byte read operation followed by Acknowledge Action"
action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte"
} else { // No more data available
(*head) = 0; // Avoid triggering REPSTART handler
action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition"
}
}
Expand Down Expand Up @@ -1433,50 +1430,61 @@ ISR(TWI0_TWIS_vect) {


/**
*@brief pushSleep and popSleep handle the sleep guard
*@brief pauseDeepSleep and restoreSleep handle the sleep guard
*
* When used only by one peripheral, just saving the sleep register is plenty,
* But when used by more then one, special care must be taken to restore the
* sleep settings only at the end.
* sleep settings only at the end by using a bitmask on the upper nibble.
* e.g. when TWI0 - START, TWI1 - START, TWI0 - STOP, TWI1 - STOP
* so, there is a counter that counts up to 15 when pushing and down to 0 when
* popping. Only at 0, the actual push and pop happen. An overflow will lead to
* unpredictable results.
*
*@param none
*@param uint8_t module_lower_Addr - lower byte of the TWI Module address
(used only with two Wire objects)
*
*@return void
*/
void pushSleep() {
void pauseDeepSleep(uint8_t module_lower_Addr) {
#if defined(TWI_USING_WIRE1)
uint8_t bit_mask = 0x10;
if (module_lower_Addr == (uint8_t)((uint16_t)&TWI1)){
bit_mask = 0x20;
}
uint8_t sleepStackLoc = sleepStack;
if (sleepStackLoc > 0) { // Increment only if sleep was enabled
sleepStackLoc = (sleepStackLoc + 0x10); // use upper nibble to count - max 15 pushes
} else {
if (sleepStackLoc == 0) { // Save sleep state only if stack empty
sleepStackLoc = SLPCTRL.CTRLA; // save sleep settings to sleepStack
SLPCTRL.CTRLA = sleepStackLoc & 0x01; // Set to IDLE if sleep was enabled
SLPCTRL.CTRLA = sleepStackLoc & 0x01; // only leave the SEN bit, if it was set
}
sleepStackLoc |= bit_mask; // Remember which module is busy
sleepStack = sleepStackLoc;
#else
sleepStack = SLPCTRL.CTRLA; // save old sleep State
SLPCTRL.CTRLA = sleepStack & 0x01; // only leave the SEN bit, if it was set
(void) module_lower_Addr;

if (sleepStack == 0x00) {
uint8_t slp = SLPCTRL.CTRLA; // save current sleep State
sleepStack = slp; // using local variable for less store/load
SLPCTRL.CTRLA = slp & 0x01; // only leave the SEN bit, if it was set
}
#endif
}

void popSleep() {
void restoreSleep(uint8_t module_lower_Addr) {
#if defined(TWI_USING_WIRE1)
uint8_t bit_mask = ~0x10;
if (module_lower_Addr == (uint8_t)((uint16_t)&TWI1)){
bit_mask = ~0x20;
}
uint8_t sleepStackLoc = sleepStack;
sleepStackLoc &= bit_mask;
if (sleepStackLoc > 0) { // only do something if sleep was enabled
if (sleepStackLoc > 0x10) { // only decrement if pushed once before
sleepStackLoc = (sleepStackLoc - 0x10); // upper nibble
} else { // at 0 we are about to put sleep back
if (sleepStackLoc < 0x10) { // if upper nibble is clear
SLPCTRL.CTRLA = sleepStackLoc; // restore sleep
sleepStackLoc = 0; // reset everything
}
sleepStack = sleepStackLoc;
}
sleepStack = sleepStackLoc;
#else
(void) module_lower_Addr;
SLPCTRL.CTRLA = sleepStack;
sleepStack = 0;
#endif
}

Expand Down
5 changes: 2 additions & 3 deletions megaavr/libraries/Wire/src/Wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,12 @@ struct twiDataBools { // using a struct so the compiler can use skip if
bool _toggleStreamFn: 1; // used to toggle between Slave and Master elements when TWI_MANDS defined
bool _hostEnabled: 1;
bool _clientEnabled: 1;
uint8_t _reserved: 5;
bool _hostDataSent: 1;
uint8_t _reserved: 4;
};





class TwoWire: public Stream {
private:
TWI_t *_module;
Expand Down

0 comments on commit bed90a3

Please sign in to comment.