Skip to content
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