// -------------------------------------------------------------------------------------------
// Loopback
// -------------------------------------------------------------------------------------------
//
// This creates a device using one I2C bus as a Master and one or more I2C buses as Slaves.
// When the buses are connected together in closed loopback, then it creates a closed test
// environment which allows Master/Slave development on a single device.  
//
// Wire will act as the Master device, and Wire1/2/3 will act as the Slave device(s).
//
// Pulling pin12 low will initiate a WRITE then READ transfer between Master and Slave(s).
//
// This example code is in the public domain.
//
// -------------------------------------------------------------------------------------------

#include <i2c_t3.h>


// -------------------------------------------------------------------------------------------
// Defines - modify as needed for pin config
//
#define WIRE_PINS   I2C_PINS_18_19
#if defined(__MKL26Z64__)               // LC
#define WIRE1_PINS   I2C_PINS_22_23
#endif
#if defined(__MK20DX256__)              // 3.1-3.2
#define WIRE1_PINS   I2C_PINS_29_30
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)  // 3.5/3.6
#define WIRE1_PINS   I2C_PINS_37_38
#define WIRE2_PINS   I2C_PINS_3_4
#endif
#if defined(__MK66FX1M0__)              // 3.6
#define WIRE3_PINS   I2C_PINS_56_57
#endif


// Function prototypes and variables
//
void rwSlave(uint8_t target);
void printWireStatus(void);
void printStatus(i2c_status status);
#define MEM_LEN 16
uint8_t mem[MEM_LEN];

#if I2C_BUS_NUM >= 2
void receiveEvent1(size_t len);
void requestEvent1(void);
void printWire1Status(void);
uint8_t mem1[MEM_LEN];
#define TARGET1 0x1A
#endif

#if I2C_BUS_NUM >= 3
void receiveEvent2(size_t len);
void requestEvent2(void);
void printWire2Status(void);
uint8_t mem2[MEM_LEN];
#define TARGET2 0x2B
#endif

#if I2C_BUS_NUM >= 4
void receiveEvent3(size_t len);
void requestEvent3(void);
void printWire3Status(void);
uint8_t mem3[MEM_LEN];
#define TARGET3 0x3C
#endif


void setup()
{
    pinMode(LED_BUILTIN,OUTPUT);    // LED
    pinMode(12,INPUT_PULLUP);       // Control for Test1

    // Wire - Setup for Master mode, external pullup, 400kHz, 200ms default timeout
    Wire.begin(I2C_MASTER, 0x00, WIRE_PINS, I2C_PULLUP_EXT, 400000);
    Wire.setDefaultTimeout(200000);
    memset(mem, 0, sizeof(mem));

    #if I2C_BUS_NUM >= 2
    // Wire1 - Setup for Slave mode, external pullup, 400kHz
    Wire1.begin(I2C_SLAVE, TARGET1, WIRE1_PINS, I2C_PULLUP_EXT, 400000);
    Wire1.onReceive(receiveEvent1);
    Wire1.onRequest(requestEvent1);
    memset(mem1, 0, sizeof(mem1));
    #endif
    
    #if I2C_BUS_NUM >= 3
    // Wire2 - Setup for Slave mode, external pullup, 400kHz
    Wire2.begin(I2C_SLAVE, TARGET2, WIRE2_PINS, I2C_PULLUP_EXT, 400000);
    Wire2.onReceive(receiveEvent2);
    Wire2.onRequest(requestEvent2);
    memset(mem2, 0, sizeof(mem2));
    #endif
    
    #if I2C_BUS_NUM >= 4
    // Wire3 - Setup for Slave mode, external pullup, 400kHz
    Wire3.begin(I2C_SLAVE, TARGET3, WIRE3_PINS, I2C_PULLUP_EXT, 400000);
    Wire3.onReceive(receiveEvent3);
    Wire3.onRequest(requestEvent3);
    memset(mem3, 0, sizeof(mem3));
    #endif

    Serial.begin(115200);
}


//
// Master Loop
//
void loop()
{
    if(digitalRead(12) == LOW)
    {      
        Serial.print("------------------------------------------------------------\n");
        Serial.printf("Writing then reading a %d byte block from Slave device(s)\n", MEM_LEN);
        Serial.print("------------------------------------------------------------\n");

        digitalWrite(LED_BUILTIN,HIGH);         // LED on

        #if I2C_BUS_NUM >= 2
        rwSlave(TARGET1);
        #endif
        #if I2C_BUS_NUM >= 3
        rwSlave(TARGET2);
        #endif
        #if I2C_BUS_NUM >= 4
        rwSlave(TARGET3);
        #endif
        
        delay(100);                             // delay to space out tests
        digitalWrite(LED_BUILTIN,LOW);          // LED off
    }
}


//
// Write/Read to Slave
//
void rwSlave(uint8_t target)
{
    uint8_t fail=0, count, data;
    
    // Writing to Slave ------------------------------------------------------
    Wire.beginTransmission(target);         // slave addr
    Serial.printf("I2C WRITE %d bytes   to Slave 0x%0X : ", MEM_LEN, target);
    for(count = 0; count < MEM_LEN; count++) // prepare data to send
    {
        mem[count] = random(0xFF);          // set random data
        Serial.printf("%02X ",mem[count]);
        Wire.write(mem[count]);
    }
    fail = Wire.endTransmission();          // blocking write
    Serial.print("       ");
    printWireStatus();                      // print I2C final status

    // Reading from Slave ------------------------------------------------------
    if(!fail)
    {
        Wire.requestFrom(target,(size_t)MEM_LEN);  // blocking read
        fail = Wire.getError();

        Serial.printf("I2C  READ %d bytes from Slave 0x%0X : ", Wire.available(), target);
        for(count = 0; count < MEM_LEN && Wire.available(); count++) // verify block
        {
            data = Wire.readByte();
            Serial.printf("%02X ", data);
            if(mem[count] != data) fail++;
        }
        if(!fail)
            Serial.print("  OK   ");
        else
            Serial.print(" FAIL  ");
        printWireStatus();                  // print I2C final status
    }
}


//
// Slave ISR handlers
//
#if I2C_BUS_NUM >= 2
void receiveEvent1(size_t count)  // Handle Wire1 Slave Rx Event (incoming I2C request/data)
{
    Wire1.read(mem1, count);      // copy Rx data to databuf
}
void requestEvent1(void)          // Handle Wire1 Tx Event (outgoing I2C data)
{
    Wire1.write(mem1, MEM_LEN);   // fill Tx buffer (send full mem)
}
#endif

#if I2C_BUS_NUM >= 3
void receiveEvent2(size_t count)  // Handle Wire2 Slave Rx Event (incoming I2C request/data)
{
    Wire2.read(mem2, count);      // copy Rx data to databuf
}
void requestEvent2(void)          // Handle Wire2 Tx Event (outgoing I2C data)
{
    Wire2.write(mem2, MEM_LEN);   // fill Tx buffer (send full mem)
}
#endif

#if I2C_BUS_NUM >= 4
void receiveEvent3(size_t count)  // Handle Wire3 Slave Rx Event (incoming I2C request/data)
{
    Wire3.read(mem3, count);      // copy Rx data to databuf
}
void requestEvent3(void)          // Handle Wire3 Tx Event (outgoing I2C data)
{
    Wire3.write(mem3, MEM_LEN);   // fill Tx buffer (send full mem)
}
#endif


//
// print I2C status
//
void printWireStatus(void) { printStatus(Wire.status()); }
#if I2C_BUS_NUM >= 2
void printWire1Status(void) { printStatus(Wire1.status()); }
#endif
#if I2C_BUS_NUM >= 3
void printWire2Status(void) { printStatus(Wire2.status()); }
#endif
#if I2C_BUS_NUM >= 4
void printWire3Status(void) { printStatus(Wire3.status()); }
#endif
void printStatus(i2c_status status)
{
    switch(status)
    {
    case I2C_WAITING:  Serial.print("I2C waiting, no errors\n"); break;
    case I2C_ADDR_NAK: Serial.print("Slave addr not acknowledged\n"); break;
    case I2C_DATA_NAK: Serial.print("Slave data not acknowledged\n"); break;
    case I2C_ARB_LOST: Serial.print("Bus Error: Arbitration Lost\n"); break;
    case I2C_TIMEOUT:  Serial.print("I2C timeout\n"); break;
    case I2C_BUF_OVF:  Serial.print("I2C buffer overflow\n"); break;
    default:           Serial.print("I2C busy\n"); break;
    }
}

