/*
    si2c.c - Software I2C library for esp8266

    Copyright (c) 2015 Hristo Gochkov. All rights reserved.
    This file is part of the esp8266 core for Arduino environment.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    Modified January 2017 by Bjorn Hammarberg (bjoham@esp8266.com) - i2c slave support
*/
#include "twi.h"
#include "pins_arduino.h"
#include "wiring_private.h"
#include "PolledTimeout.h"



extern "C" {
#include "twi_util.h"
#include "ets_sys.h"
};

// Inline helpers
static inline __attribute__((always_inline)) void SDA_LOW(const int twi_sda)
{
    GPES = (1 << twi_sda);
}
static inline __attribute__((always_inline)) void SDA_HIGH(const int twi_sda)
{
    GPEC = (1 << twi_sda);
}
static inline __attribute__((always_inline)) bool SDA_READ(const int twi_sda)
{
    return (GPI & (1 << twi_sda)) != 0;
}
static inline __attribute__((always_inline)) void SCL_LOW(const int twi_scl)
{
    GPES = (1 << twi_scl);
}
static inline __attribute__((always_inline)) void SCL_HIGH(const int twi_scl)
{
    GPEC = (1 << twi_scl);
}
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
{
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
    // byte-wide ones, and since these emums are used everywhere, the difference adds up fast.  There is only a single
    // instance of the class, though, so the extra 12 bytes of RAM used here saves a lot more IRAM.
    volatile enum { TWIPM_UNKNOWN = 0, TWIPM_IDLE, TWIPM_ADDRESSED, TWIPM_WAIT} twip_mode = TWIPM_IDLE;
    volatile enum { TWIP_UNKNOWN = 0, TWIP_IDLE, TWIP_START, TWIP_SEND_ACK, TWIP_WAIT_ACK, TWIP_WAIT_STOP, TWIP_SLA_W, TWIP_SLA_R, TWIP_REP_START, TWIP_READ, TWIP_STOP, TWIP_REC_ACK, TWIP_READ_ACK, TWIP_RWAIT_ACK, TWIP_WRITE, TWIP_BUS_ERR } twip_state = TWIP_IDLE;
    volatile int twip_status = TW_NO_INFO;
    volatile int bitCount = 0;

    volatile uint8_t twi_data = 0x00;
    volatile int twi_ack = 0;
    volatile int twi_ack_rec = 0;
    volatile int twi_timeout_ms = 10;

    volatile enum { TWI_READY = 0, TWI_MRX, TWI_MTX, TWI_SRX, TWI_STX } twi_state = TWI_READY;
    volatile uint8_t twi_error = 0xFF;

    uint8_t twi_txBuffer[TWI_BUFFER_LENGTH];
    volatile int twi_txBufferIndex = 0;
    volatile int twi_txBufferLength = 0;

    uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
    volatile int twi_rxBufferIndex = 0;

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

    // ETS queue/timer interfaces
    enum { EVENTTASK_QUEUE_SIZE = 1, EVENTTASK_QUEUE_PRIO = 2 };
    enum { TWI_SIG_RANGE = 0x00000100, TWI_SIG_RX = 0x00000101, TWI_SIG_TX = 0x00000102 };
    ETSEvent eventTaskQueue[EVENTTASK_QUEUE_SIZE];
    ETSTimer timer;

    // Event/IRQ callbacks, so they can't use "this" and need to be static
    static void IRAM_ATTR onSclChange(void);
    static void IRAM_ATTR onSdaChange(void);
    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
    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);
    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 IRAM_ATTR reply(uint8_t ack);
    void IRAM_ATTR releaseBus(void);
    void enableSlave();
};

static Twi twi;

#ifndef FCPU80
#define FCPU80 80000000L
#endif

void Twi::setClock(unsigned int freq)
{
    if (freq < 1000)  // minimum freq 1000Hz to minimize slave timeouts and WDT resets
    {
        freq = 1000;
    }

    preferred_si2c_clock = freq;

#if F_CPU == FCPU80

    if (freq > 400000)
    {
        freq = 400000;
    }
    twi_dcount = (500000000 / freq);  // half-cycle period in ns
    twi_dcount = (1000 * (twi_dcount - 1120)) / 62500; // (half cycle - overhead) / busywait loop time

#else

    if (freq > 800000)
    {
        freq = 800000;
    }
    twi_dcount = (500000000 / freq);  // half-cycle period in ns
    twi_dcount = (1000 * (twi_dcount - 560)) / 31250; // (half cycle - overhead) / busywait loop time

#endif
}

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



void Twi::init(unsigned char sda, unsigned char scl)
{
    // set timer function
    ets_timer_setfn(&timer, onTimer, NULL);

    // create event task
    ets_task(eventTask, EVENTTASK_QUEUE_PRIO, eventTaskQueue, EVENTTASK_QUEUE_SIZE);

    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 Twi::setAddress(uint8_t address)
{
    // set twi slave address (skip over R/W bit)
    twi_addr = address << 1;
}

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

void IRAM_ATTR Twi::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.
    }
}

bool Twi::write_start(void)
{
    SCL_HIGH(twi_scl);
    SDA_HIGH(twi_sda);
    if (!SDA_READ(twi_sda))
    {
        return false;
    }
    busywait(twi_dcount);
    SDA_LOW(twi_sda);
    busywait(twi_dcount);
    return true;
}

bool Twi::write_stop(void)
{
    SCL_LOW(twi_scl);
    SDA_LOW(twi_sda);
    busywait(twi_dcount);
    SCL_HIGH(twi_scl);
    WAIT_CLOCK_STRETCH();
    busywait(twi_dcount);
    SDA_HIGH(twi_sda);
    busywait(twi_dcount);
    return true;
}

bool Twi::write_bit(bool bit)
{
    SCL_LOW(twi_scl);
    if (bit)
    {
        SDA_HIGH(twi_sda);
    }
    else
    {
        SDA_LOW(twi_sda);
    }
    busywait(twi_dcount + 1);
    SCL_HIGH(twi_scl);
    WAIT_CLOCK_STRETCH();
    busywait(twi_dcount);
    return true;
}

bool Twi::read_bit(void)
{
    SCL_LOW(twi_scl);
    SDA_HIGH(twi_sda);
    busywait(twi_dcount + 2);
    SCL_HIGH(twi_scl);
    WAIT_CLOCK_STRETCH();
    bool bit = SDA_READ(twi_sda);
    busywait(twi_dcount);
    return bit;
}

bool Twi::write_byte(unsigned char byte)
{
    unsigned char bit;
    for (bit = 0; bit < 8; bit++)
    {
        write_bit(byte & 0x80);
        byte <<= 1;
    }
    return !read_bit();//NACK/ACK
}

unsigned char Twi::read_byte(bool nack)
{
    unsigned char byte = 0;
    unsigned char bit;
    for (bit = 0; bit < 8; bit++)
    {
        byte = (byte << 1) | read_bit();
    }
    write_bit(nack);
    return byte;
}

unsigned char Twi::writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
{
    unsigned int i;
    if (!write_start())
    {
        return 4;  //line busy
    }
    if (!write_byte(((address << 1) | 0) & 0xFF))
    {
        if (sendStop)
        {
            write_stop();
        }
        return 2; //received NACK on transmit of address
    }
    for (i = 0; i < len; i++)
    {
        if (!write_byte(buf[i]))
        {
            if (sendStop)
            {
                write_stop();
            }
            return 3;//received NACK on transmit of data
        }
    }
    if (sendStop)
    {
        write_stop();
    }
    else
    {
        twi_scl_valley();
        // TD-er: Also busywait(twi_dcount) here?
        // busywait(twi_dcount);
    }
    i = 0;
    while (!SDA_READ(twi_sda) && (i++) < 10)
    {
        twi_scl_valley();
        busywait(twi_dcount);
    }
    return 0;
}

unsigned char Twi::readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop)
{
    unsigned int i;
    if (!write_start())
    {
        return 4;  //line busy
    }
    if (!write_byte(((address << 1) | 1) & 0xFF))
    {
        if (sendStop)
        {
            write_stop();
        }
        return 2;//received NACK on transmit of address
    }
    for (i = 0; i < (len - 1); i++)
    {
        buf[i] = read_byte(false);
    }
    buf[len - 1] = read_byte(true);
    if (sendStop)
    {
        write_stop();
    }
    else
    {
        twi_scl_valley();
        // TD-er: Also busywait(twi_dcount) here?
        // busywait(twi_dcount);
    }
    i = 0;
    while (!SDA_READ(twi_sda) && (i++) < 10)
    {
        twi_scl_valley();
        busywait(twi_dcount);
    }
    return 0;
}

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

uint8_t Twi::status()
{
    WAIT_CLOCK_STRETCH();  // wait for a slow slave to finish
    if (!SCL_READ(twi_scl))
    {
        return I2C_SCL_HELD_LOW;  // SCL held low by another device, no procedure available to recover
    }

    int clockCount = 20;
    while (!SDA_READ(twi_sda) && clockCount-- > 0)   // if SDA low, read the bits slaves have to sent to a max
    {
        read_bit();
        if (!SCL_READ(twi_scl))
        {
            return I2C_SCL_HELD_LOW_AFTER_READ; // I2C bus error. SCL held low beyond slave clock stretch time
        }
    }
    if (!SDA_READ(twi_sda))
    {
        return I2C_SDA_HELD_LOW;  // I2C bus error. SDA line held low by slave/another_master after n bits.
    }

    return I2C_OK;
}

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

    // ensure data will fit into buffer
    if (length > TWI_BUFFER_LENGTH)
    {
        return 1;
    }

    // ensure we are currently a slave transmitter
    if (twi_state != TWI_STX)
    {
        return 2;
    }

    // set length and copy data into tx buffer
    twi_txBufferLength = length;
    for (i = 0; i < length; ++i)
    {
        twi_txBuffer[i] = data[i];
    }

    return 0;
}

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

void Twi::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)
{
    // transmit master read ready signal, with or without ack
    if (ack)
    {
        //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
        SCL_HIGH(twi.twi_scl);      // _BV(TWINT)
        twi_ack = 1;    // _BV(TWEA)
    }
    else
    {
        //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
        SCL_HIGH(twi.twi_scl);      // _BV(TWINT)
        twi_ack = 0;    // ~_BV(TWEA)
    }
}


void IRAM_ATTR Twi::releaseBus(void)
{
    // release bus
    //TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
    SCL_HIGH(twi.twi_scl);      // _BV(TWINT)
    twi_ack = 1;    // _BV(TWEA)
    SDA_HIGH(twi.twi_sda);

    // update twi state
    twi_state = TWI_READY;
}


void IRAM_ATTR Twi::onTwipEvent(uint8_t status)
{
    twip_status = status;
    switch (status)
    {
    // Slave Receiver
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
        // enter slave receiver mode
        twi_state = TWI_SRX;
        // indicate that rx buffer can be overwritten and ack
        twi_rxBufferIndex = 0;
        reply(1);
        break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
        // if there is still room in the rx buffer
        if (twi_rxBufferIndex < TWI_BUFFER_LENGTH)
        {
            // put byte in buffer and ack
            twi_rxBuffer[twi_rxBufferIndex++] = twi_data;
            reply(1);
        }
        else
        {
            // otherwise nack
            reply(0);
        }
        break;
    case TW_SR_STOP: // stop or repeated start condition received
        // put a null char after data if there's room
        if (twi_rxBufferIndex < TWI_BUFFER_LENGTH)
        {
            twi_rxBuffer[twi_rxBufferIndex] = '\0';
        }
        // callback to user-defined callback over event task to allow for non-RAM-residing code
        //twi_rxBufferLock = true; // This may be necessary
        ets_post(EVENTTASK_QUEUE_PRIO, TWI_SIG_RX, twi_rxBufferIndex);

        // since we submit rx buffer to "wire" library, we can reset it
        twi_rxBufferIndex = 0;
        break;

    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
        // nack back at master
        reply(0);
        break;

    // Slave Transmitter
    case TW_ST_SLA_ACK:          // addressed, returned ack
    case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
        // enter slave transmitter mode
        twi_state = TWI_STX;
        // ready the tx buffer index for iteration
        twi_txBufferIndex = 0;
        // set tx buffer length to be zero, to verify if user changes it
        twi_txBufferLength = 0;
        // callback to user-defined callback over event task to allow for non-RAM-residing code
        // request for txBuffer to be filled and length to be set
        // note: user must call twi_transmit(bytes, length) to do this
        ets_post(EVENTTASK_QUEUE_PRIO, TWI_SIG_TX, 0);
        break;

    case TW_ST_DATA_ACK: // byte sent, ack returned
        // copy data to output register
        twi_data = twi_txBuffer[twi_txBufferIndex++];

        bitCount = 8;
        bitCount--;
        if (twi_data & 0x80)
        {
            SDA_HIGH(twi.twi_sda);
        }
        else
        {
            SDA_LOW(twi.twi_sda);
        }
        twi_data <<= 1;

        // if there is more to send, ack, otherwise nack
        if (twi_txBufferIndex < twi_txBufferLength)
        {
            reply(1);
        }
        else
        {
            reply(0);
        }
        break;
    case TW_ST_DATA_NACK: // received nack, we are done
    case TW_ST_LAST_DATA: // received ack, but we are done already!
        // leave slave receiver state
        releaseBus();
        break;

    // All
    case TW_NO_INFO:   // no state information
        break;
    case TW_BUS_ERROR: // bus error, illegal stop/start
        twi_error = TW_BUS_ERROR;
        break;
    }
}

void IRAM_ATTR Twi::onTimer(void *unused)
{
    (void)unused;
    twi.releaseBus();
    twi.onTwipEvent(TW_BUS_ERROR);
    twi.twip_mode = TWIPM_WAIT;
    twi.twip_state = TWIP_BUS_ERR;
}

void Twi::eventTask(ETSEvent *e)
{

    if (e == NULL)
    {
        return;
    }

    switch (e->sig)
    {
    case TWI_SIG_TX:
        twi.twi_onSlaveTransmit();

        // if they didn't change buffer & length, initialize it
        if (twi.twi_txBufferLength == 0)
        {
            twi.twi_txBufferLength = 1;
            twi.twi_txBuffer[0] = 0x00;
        }

        // Initiate transmission
        twi.onTwipEvent(TW_ST_DATA_ACK);

        break;

    case TWI_SIG_RX:
        // ack future responses and leave slave receiver state
        twi.releaseBus();
        twi.twi_onSlaveReceive(twi.twi_rxBuffer, e->par);
        break;
    }
}

// The state machine is converted from a 0...15 state to a 1-hot encoded state, and then
// compared to the logical-or of all states with the same branch.  This removes the need
// for a large series of straight-line compares.  The biggest win is when multiple states
// all have the same branch (onSdaChange), but for others there is some benefit, still.
#define S2M(x) (1<<(x))
// 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)
{
    unsigned int sda;
    unsigned int scl;

    // Store bool return in int to reduce final code size.

    sda = SDA_READ(twi.twi_sda);
    scl = SCL_READ(twi.twi_scl);

    twi.twip_status = 0xF8;     // reset TWI status

    int twip_state_mask = S2M(twi.twip_state);
    IFSTATE(S2M(TWIP_START) | S2M(TWIP_REP_START) | S2M(TWIP_SLA_W) | S2M(TWIP_READ))
    {
        if (!scl)
        {
            // ignore
        }
        else
        {
            twi.bitCount--;
            twi.twi_data <<= 1;
            twi.twi_data |= sda;

            if (twi.bitCount != 0)
            {
                // continue
            }
            else
            {
                twi.twip_state = TWIP_SEND_ACK;
            }
        }
    }
    else IFSTATE(S2M(TWIP_SEND_ACK))
    {
        if (scl)
        {
            // ignore
        }
        else
        {
            if (twi.twip_mode == TWIPM_IDLE)
            {
                if ((twi.twi_data & 0xFE) != twi.twi_addr)
                {
                    // ignore
                }
                else
                {
                    SDA_LOW(twi.twi_sda);
                }
            }
            else
            {
                if (!twi.twi_ack)
                {
                    // ignore
                }
                else
                {
                    SDA_LOW(twi.twi_sda);
                }
            }
            twi.twip_state = TWIP_WAIT_ACK;
        }
    }
    else IFSTATE(S2M(TWIP_WAIT_ACK))
    {
        if (scl)
        {
            // ignore
        }
        else
        {
            if (twi.twip_mode == TWIPM_IDLE)
            {
                if ((twi.twi_data & 0xFE) != twi.twi_addr)
                {
                    SDA_HIGH(twi.twi_sda);
                    twi.twip_state = TWIP_WAIT_STOP;
                }
                else
                {
                    SCL_LOW(twi.twi_scl);   // clock stretching
                    SDA_HIGH(twi.twi_sda);
                    twi.twip_mode = TWIPM_ADDRESSED;
                    if (!(twi.twi_data & 0x01))
                    {
                        twi.onTwipEvent(TW_SR_SLA_ACK);
                        twi.bitCount = 8;
                        twi.twip_state = TWIP_SLA_W;
                    }
                    else
                    {
                        twi.onTwipEvent(TW_ST_SLA_ACK);
                        twi.twip_state = TWIP_SLA_R;
                    }
                }
            }
            else
            {
                SCL_LOW(twi.twi_scl);   // clock stretching
                SDA_HIGH(twi.twi_sda);
                if (!twi.twi_ack)
                {
                    twi.onTwipEvent(TW_SR_DATA_NACK);
                    twi.twip_mode = TWIPM_WAIT;
                    twi.twip_state = TWIP_WAIT_STOP;
                }
                else
                {
                    twi.onTwipEvent(TW_SR_DATA_ACK);
                    twi.bitCount = 8;
                    twi.twip_state = TWIP_READ;
                }
            }
        }
    }
    else IFSTATE(S2M(TWIP_SLA_R) | S2M(TWIP_WRITE))
    {
        if (scl)
        {
            // ignore
        }
        else
        {
            twi.bitCount--;
            if (twi.twi_data & 0x80)
            {
                SDA_HIGH(twi.twi_sda);
            }
            else
            {
                SDA_LOW(twi.twi_sda);
            }
            twi.twi_data <<= 1;

            if (twi.bitCount != 0)
            {
                // continue
            }
            else
            {
                twi.twip_state = TWIP_REC_ACK;
            }
        }
    }
    else IFSTATE(S2M(TWIP_REC_ACK))
    {
        if (scl)
        {
            // ignore
        }
        else
        {
            SDA_HIGH(twi.twi_sda);
            twi.twip_state = TWIP_READ_ACK;
        }
    }
    else IFSTATE(S2M(TWIP_READ_ACK))
    {
        if (!scl)
        {
            // ignore
        }
        else
        {
            twi.twi_ack_rec = !sda;
            twi.twip_state = TWIP_RWAIT_ACK;
        }
    }
    else IFSTATE(S2M(TWIP_RWAIT_ACK))
    {
        if (scl)
        {
            // ignore
        }
        else
        {
            SCL_LOW(twi.twi_scl);   // clock stretching
            if (twi.twi_ack && twi.twi_ack_rec)
            {
                twi.onTwipEvent(TW_ST_DATA_ACK);
                twi.twip_state = TWIP_WRITE;
            }
            else
            {
                // we have no more data to send and/or the master doesn't want anymore
                twi.onTwipEvent(twi.twi_ack_rec ? TW_ST_LAST_DATA : TW_ST_DATA_NACK);
                twi.twip_mode = TWIPM_WAIT;
                twi.twip_state = TWIP_WAIT_STOP;
            }
        }
    }
}

void IRAM_ATTR Twi::onSdaChange(void)
{
    unsigned int sda;
    unsigned int scl;

    // Store bool return in int to reduce final code size.
    sda = SDA_READ(twi.twi_sda);
    scl = SCL_READ(twi.twi_scl);

    int twip_state_mask = S2M(twi.twip_state);
    if (scl)   /* !DATA */
    {
        IFSTATE(S2M(TWIP_IDLE))
        {
            if (sda)
            {
                // STOP - ignore
            }
            else
            {
                // START
                twi.bitCount = 8;
                twi.twip_state = TWIP_START;
                ets_timer_arm_new(&twi.timer, twi.twi_timeout_ms, false, true); // Once, ms
            }
        }
        else IFSTATE(S2M(TWIP_START) | S2M(TWIP_REP_START) | S2M(TWIP_SEND_ACK) | S2M(TWIP_WAIT_ACK) | S2M(TWIP_SLA_R) | S2M(TWIP_REC_ACK) | S2M(TWIP_READ_ACK) | S2M(TWIP_RWAIT_ACK) | S2M(TWIP_WRITE))
        {
            // START or STOP
            SDA_HIGH(twi.twi_sda);   // Should not be necessary
            twi.onTwipEvent(TW_BUS_ERROR);
            twi.twip_mode = TWIPM_WAIT;
            twi.twip_state = TWIP_BUS_ERR;
        }
        else IFSTATE(S2M(TWIP_WAIT_STOP) | S2M(TWIP_BUS_ERR))
        {
            if (sda)
            {
                // STOP
                SCL_LOW(twi.twi_scl);   // generates a low SCL pulse after STOP
                ets_timer_disarm(&twi.timer);
                twi.twip_state = TWIP_IDLE;
                twi.twip_mode = TWIPM_IDLE;
                SCL_HIGH(twi.twi_scl);
            }
            else
            {
                // START
                if (twi.twip_state == TWIP_BUS_ERR)
                {
                    // ignore
                }
                else
                {
                    twi.bitCount = 8;
                    twi.twip_state = TWIP_REP_START;
                    ets_timer_arm_new(&twi.timer, twi.twi_timeout_ms, false, true); // Once, ms
                }
            }
        }
        else IFSTATE(S2M(TWIP_SLA_W) | S2M(TWIP_READ))
        {
            // START or STOP
            if (twi.bitCount != 7)
            {
                // inside byte transfer - error
                twi.onTwipEvent(TW_BUS_ERROR);
                twi.twip_mode = TWIPM_WAIT;
                twi.twip_state = TWIP_BUS_ERR;
            }
            else
            {
                // during first bit in byte transfer - ok
                SCL_LOW(twi.twi_scl);   // clock stretching
                twi.onTwipEvent(TW_SR_STOP);
                if (sda)
                {
                    // STOP
                    ets_timer_disarm(&twi.timer);
                    twi.twip_state = TWIP_IDLE;
                    twi.twip_mode = TWIPM_IDLE;
                }
                else
                {
                    // START
                    twi.bitCount = 8;
                    ets_timer_arm_new(&twi.timer, twi.twi_timeout_ms, false, true); // Once, ms
                    twi.twip_state = TWIP_REP_START;
                    twi.twip_mode = TWIPM_IDLE;
                }
            }
        }
    }
}

// C wrappers for the object, since API is exposed only as C
extern "C" {

    void twi_init(unsigned char sda, unsigned char scl)
    {
        return twi.init(sda, scl);
    }

    void twi_setAddress(uint8_t a)
    {
        return twi.setAddress(a);
    }

    void twi_setClock(unsigned int freq)
    {
        twi.setClock(freq);
    }

    void twi_setClockStretchLimit(uint32_t limit)
    {
        twi.setClockStretchLimit(limit);
    }

    uint8_t twi_writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
    {
        return twi.writeTo(address, buf, len, sendStop);
    }

    uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
    {
        return twi.readFrom(address, buf, len, sendStop);
    }

    uint8_t twi_status()
    {
        return twi.status();
    }

    uint8_t twi_transmit(const uint8_t * buf, uint8_t len)
    {
        return twi.transmit(buf, len);
    }

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

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

    void twi_reply(uint8_t r)
    {
        twi.reply(r);
    }

    void twi_releaseBus(void)
    {
        twi.releaseBus();
    }

    void twi_enableSlaveMode(void)
    {
        twi.enableSlave();
    }

};
