Skip to content

Split software I2C code into Master-only and Master-or-Slave parts, enabling multiple instances of TwoWire in master-only role #7911

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 11 commits into
base: master
Choose a base branch
from
Open
147 changes: 71 additions & 76 deletions cores/esp8266/core_esp8266_si2c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,12 @@ static inline __attribute__((always_inline)) bool SCL_READ(const int twi_scl)
return (GPI & (1 << twi_scl)) != 0;
}


// Implement as a class to reduce code size by allowing access to many global variables with a single base pointer
class Twi
// Derived from TwiMaster which can be instantied multiple times
class TwiMasterOrSlave : public TwiMaster
{
private:
unsigned int preferred_si2c_clock = 100000;
uint32_t twi_dcount = 18;
unsigned char twi_sda = 0;
unsigned char twi_scl = 0;
unsigned char twi_addr = 0;
uint32_t twi_clockStretchLimit = 0;

// These are int-wide, even though they could all fit in a byte, to reduce code size and avoid any potential
// issues about RmW on packed bytes. The int-wide variations of asm instructions are smaller than the equivalent
Expand All @@ -93,8 +88,9 @@ class Twi
uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
volatile int twi_rxBufferIndex = 0;

void (*twi_onSlaveTransmit)(void);
void (*twi_onSlaveReceive)(uint8_t*, size_t);
void* twi_SlaveTargetObject;
void (*twi_onSlaveTransmit)(void*);
void (*twi_onSlaveReceive)(uint8_t*, size_t, void*);

// ETS queue/timer interfaces
enum { EVENTTASK_QUEUE_SIZE = 1, EVENTTASK_QUEUE_PRIO = 2 };
Expand All @@ -108,59 +104,46 @@ class Twi
static void eventTask(ETSEvent *e);
static void IRAM_ATTR onTimer(void *unused);

// Allow not linking in the slave code if there is no call to setAddress
// Allow not linking in the slave code if there is no call to enableSlave
bool _slaveEnabled = false;

// Internal use functions
void IRAM_ATTR busywait(unsigned int v);
bool write_start(void);
bool write_stop(void);
bool write_bit(bool bit);
bool read_bit(void);
bool write_byte(unsigned char byte);
unsigned char read_byte(bool nack);
void IRAM_ATTR onTwipEvent(uint8_t status);

// Handle the case where a slave needs to stretch the clock with a time-limited busy wait
inline void WAIT_CLOCK_STRETCH()
{
esp8266::polledTimeout::oneShotFastUs timeout(twi_clockStretchLimit);
esp8266::polledTimeout::periodicFastUs yieldTimeout(5000);
while (!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit
{
if (yieldTimeout) // inner loop yields every 5ms
{
yield();
}
}
}

// Generate a clock "valley" (at the end of a segment, just before a repeated start)
void twi_scl_valley(void);

public:
void setClock(unsigned int freq);
void setClockStretchLimit(uint32_t limit);
// custom version
void init(unsigned char sda, unsigned char scl);

void setAddress(uint8_t address);
unsigned char writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop);
unsigned char readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop);
uint8_t status();
uint8_t transmit(const uint8_t* data, uint8_t length);
void attachSlaveRxEvent(void (*function)(uint8_t*, size_t));
void attachSlaveTxEvent(void (*function)(void));
void attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*));
void attachSlaveTxEvent(void (*function)(void*));
void IRAM_ATTR reply(uint8_t ack);
void IRAM_ATTR releaseBus(void);
void enableSlave();
void enableSlave(void* targetObject);
};

static Twi twi;
static TwiMasterOrSlave twi;
TwiMaster& twiMasterSingleton = twi;

#ifndef FCPU80
#define FCPU80 80000000L
#endif

void Twi::setClock(unsigned int freq)
inline void TwiMaster::WAIT_CLOCK_STRETCH()
{
esp8266::polledTimeout::oneShotFastUs timeout(twi_clockStretchLimit);
esp8266::polledTimeout::periodicFastUs yieldTimeout(5000);
while (!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit
{
if (yieldTimeout) // inner loop yields every 5ms
{
yield();
}
}
}

void TwiMaster::setClock(unsigned int freq)
{
if (freq < 1000) // minimum freq 1000Hz to minimize slave timeouts and WDT resets
{
Expand Down Expand Up @@ -190,14 +173,24 @@ void Twi::setClock(unsigned int freq)
#endif
}

void Twi::setClockStretchLimit(uint32_t limit)
void TwiMaster::setClockStretchLimit(uint32_t limit)
{
twi_clockStretchLimit = limit;
}



void Twi::init(unsigned char sda, unsigned char scl)
void TwiMaster::init(unsigned char sda, unsigned char scl)
{
twi_sda = sda;
twi_scl = scl;
pinMode(twi_sda, INPUT_PULLUP);
pinMode(twi_scl, INPUT_PULLUP);
twi_setClock(preferred_si2c_clock);
twi_setClockStretchLimit(150000L); // default value is 150 mS
}

void TwiMasterOrSlave::init(unsigned char sda, unsigned char scl)
{
// set timer function
ets_timer_setfn(&timer, onTimer, NULL);
Expand All @@ -213,32 +206,33 @@ void Twi::init(unsigned char sda, unsigned char scl)
twi_setClockStretchLimit(150000L); // default value is 150 mS
}

void Twi::setAddress(uint8_t address)
void TwiMasterOrSlave::setAddress(uint8_t address)
{
// set twi slave address (skip over R/W bit)
twi_addr = address << 1;
}

void Twi::enableSlave()
void TwiMasterOrSlave::enableSlave(void* targetObject)
{
if (!_slaveEnabled)
{
twi_SlaveTargetObject = targetObject;
attachInterrupt(twi_scl, onSclChange, CHANGE);
attachInterrupt(twi_sda, onSdaChange, CHANGE);
_slaveEnabled = true;
}
}

void IRAM_ATTR Twi::busywait(unsigned int v)
void IRAM_ATTR TwiMaster::busywait(unsigned int v)
{
unsigned int i;
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
{
__asm__ __volatile__("nop"); // minimum element to keep GCC from optimizing this function out.
asm("nop"); // minimum element to keep GCC from optimizing this function out.
}
}

bool Twi::write_start(void)
bool TwiMaster::write_start(void)
{
SCL_HIGH(twi_scl);
SDA_HIGH(twi_sda);
Expand All @@ -252,7 +246,7 @@ bool Twi::write_start(void)
return true;
}

bool Twi::write_stop(void)
bool TwiMaster::write_stop(void)
{
SCL_LOW(twi_scl);
SDA_LOW(twi_sda);
Expand All @@ -265,7 +259,7 @@ bool Twi::write_stop(void)
return true;
}

bool Twi::write_bit(bool bit)
bool TwiMaster::write_bit(bool bit)
{
SCL_LOW(twi_scl);
if (bit)
Expand All @@ -283,7 +277,7 @@ bool Twi::write_bit(bool bit)
return true;
}

bool Twi::read_bit(void)
bool TwiMaster::read_bit(void)
{
SCL_LOW(twi_scl);
SDA_HIGH(twi_sda);
Expand All @@ -295,7 +289,7 @@ bool Twi::read_bit(void)
return bit;
}

bool Twi::write_byte(unsigned char byte)
bool TwiMaster::write_byte(unsigned char byte)
{
unsigned char bit;
for (bit = 0; bit < 8; bit++)
Expand All @@ -306,7 +300,7 @@ bool Twi::write_byte(unsigned char byte)
return !read_bit();//NACK/ACK
}

unsigned char Twi::read_byte(bool nack)
unsigned char TwiMaster::read_byte(bool nack)
{
unsigned char byte = 0;
unsigned char bit;
Expand All @@ -318,7 +312,7 @@ unsigned char Twi::read_byte(bool nack)
return byte;
}

unsigned char Twi::writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
unsigned char TwiMaster::writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
{
unsigned int i;
if (!write_start())
Expand Down Expand Up @@ -363,7 +357,7 @@ unsigned char Twi::writeTo(unsigned char address, unsigned char * buf, unsigned
return 0;
}

unsigned char Twi::readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop)
unsigned char TwiMaster::readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop)
{
unsigned int i;
if (!write_start())
Expand Down Expand Up @@ -402,15 +396,15 @@ unsigned char Twi::readFrom(unsigned char address, unsigned char* buf, unsigned
return 0;
}

void Twi::twi_scl_valley(void)
void TwiMaster::twi_scl_valley(void)
{
SCL_LOW(twi_scl);
busywait(twi_dcount);
SCL_HIGH(twi_scl);
WAIT_CLOCK_STRETCH();
}

uint8_t Twi::status()
uint8_t TwiMaster::status()
{
WAIT_CLOCK_STRETCH(); // wait for a slow slave to finish
if (!SCL_READ(twi_scl))
Expand All @@ -435,7 +429,7 @@ uint8_t Twi::status()
return I2C_OK;
}

uint8_t Twi::transmit(const uint8_t* data, uint8_t length)
uint8_t TwiMasterOrSlave::transmit(const uint8_t* data, uint8_t length)
{
uint8_t i;

Expand All @@ -461,20 +455,20 @@ uint8_t Twi::transmit(const uint8_t* data, uint8_t length)
return 0;
}

void Twi::attachSlaveRxEvent(void (*function)(uint8_t*, size_t))
void TwiMasterOrSlave::attachSlaveRxEvent(void (*function)(uint8_t*, size_t, void*))
{
twi_onSlaveReceive = function;
}

void Twi::attachSlaveTxEvent(void (*function)(void))
void TwiMasterOrSlave::attachSlaveTxEvent(void (*function)(void*))
{
twi_onSlaveTransmit = function;
}

// DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into
// parts and the IRAM_ATTR isn't propagated correctly to all parts, which of course causes crashes.
// TODO: test with gcc 9.x and if it still fails, disable optimization with -fdisable-ipa-fnsplit
void IRAM_ATTR Twi::reply(uint8_t ack)
void IRAM_ATTR TwiMasterOrSlave::reply(uint8_t ack)
{
// transmit master read ready signal, with or without ack
if (ack)
Expand All @@ -492,7 +486,7 @@ void IRAM_ATTR Twi::reply(uint8_t ack)
}


void IRAM_ATTR Twi::releaseBus(void)
void IRAM_ATTR TwiMasterOrSlave::releaseBus(void)
{
// release bus
//TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
Expand All @@ -505,7 +499,7 @@ void IRAM_ATTR Twi::releaseBus(void)
}


void IRAM_ATTR Twi::onTwipEvent(uint8_t status)
void IRAM_ATTR TwiMasterOrSlave::onTwipEvent(uint8_t status)
{
twip_status = status;
switch (status)
Expand Down Expand Up @@ -612,7 +606,7 @@ void IRAM_ATTR Twi::onTwipEvent(uint8_t status)
}
}

void IRAM_ATTR Twi::onTimer(void *unused)
void IRAM_ATTR TwiMasterOrSlave::onTimer(void *unused)
{
(void)unused;
twi.releaseBus();
Expand All @@ -621,7 +615,7 @@ void IRAM_ATTR Twi::onTimer(void *unused)
twi.twip_state = TWIP_BUS_ERR;
}

void Twi::eventTask(ETSEvent *e)
void TwiMasterOrSlave::eventTask(ETSEvent *e)
{

if (e == NULL)
Expand All @@ -632,7 +626,7 @@ void Twi::eventTask(ETSEvent *e)
switch (e->sig)
{
case TWI_SIG_TX:
twi.twi_onSlaveTransmit();
twi.twi_onSlaveTransmit(twi.twi_SlaveTargetObject);

// if they didn't change buffer & length, initialize it
if (twi.twi_txBufferLength == 0)
Expand All @@ -649,7 +643,7 @@ void Twi::eventTask(ETSEvent *e)
case TWI_SIG_RX:
// ack future responses and leave slave receiver state
twi.releaseBus();
twi.twi_onSlaveReceive(twi.twi_rxBuffer, e->par);
twi.twi_onSlaveReceive(twi.twi_rxBuffer, e->par, twi.twi_SlaveTargetObject);
break;
}
}
Expand All @@ -662,7 +656,7 @@ void Twi::eventTask(ETSEvent *e)
// Shorthand for if the state is any of the or'd bitmask x
#define IFSTATE(x) if (twip_state_mask & (x))

void IRAM_ATTR Twi::onSclChange(void)
void IRAM_ATTR TwiMasterOrSlave::onSclChange(void)
{
unsigned int sda;
unsigned int scl;
Expand Down Expand Up @@ -860,7 +854,7 @@ void IRAM_ATTR Twi::onSclChange(void)
}
}

void IRAM_ATTR Twi::onSdaChange(void)
void IRAM_ATTR TwiMasterOrSlave::onSdaChange(void)
{
unsigned int sda;
unsigned int scl;
Expand Down Expand Up @@ -998,16 +992,17 @@ extern "C" {
return twi.transmit(buf, len);
}

void twi_attachSlaveRxEvent(void (*cb)(uint8_t*, size_t))
void twi_attachSlaveRxEventWithTarget(void (*cb)(uint8_t*, size_t, void*))
{
twi.attachSlaveRxEvent(cb);
}

void twi_attachSlaveTxEvent(void (*cb)(void))
void twi_attachSlaveTxEventWithTarget(void (*cb)(void*))
{
twi.attachSlaveTxEvent(cb);
}


void twi_reply(uint8_t r)
{
twi.reply(r);
Expand All @@ -1018,9 +1013,9 @@ extern "C" {
twi.releaseBus();
}

void twi_enableSlaveMode(void)
void twi_enableSlaveModeWithTarget(void* targetObject)
{
twi.enableSlave();
twi.enableSlave(targetObject);
}

};
Loading