#include <i2c_t3.h> vs #include <Wire.h>?

paynterf

Well-known member
I'm confused about the differences between the Teensy version of Wire.h and the Teensy-specific (I think)? i2c_t3.h. I have found through trial and error that when using #include <Wire.h> with a Teensy 3.x target, somehow magically the TwoWire objects 'Wire1' & 'Wire2' (& 'Wire3'???) get declared, and they can be used just like Wire.

However, i2c_t3.h also defines the i2c_t3 objects Wire1, Wire2, and Wire3 as appropriate to the target processor


Code:
extern i2c_t3 Wire;
#if I2C_BUS_NUM >= 2
    extern i2c_t3 Wire1;
#endif
#if I2C_BUS_NUM >= 3
    extern i2c_t3 Wire2;
#endif
#if I2C_BUS_NUM >= 4
    extern i2c_t3 Wire3;
#endif

So it seems we have the option of choosing either Wire.h (i.e. C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\Wire\Wire.h on my Win 10 box), or i2c_t3.h (C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\i2c_t3\i2c_t3.h on my Win 10 box), and they both support multiple I2C buses. Sometimes it seems easier to use one over the other, depending on specific library usage, but I don't have any feel for why/how to choose one over the other. Originally I thought that i2c_t3.h was the only way to get multiple I2C bus support, but that clearly is not the case (although for the life of me I can't find the WireX declarations in Wire.h)

Anyone out there have some guidance for a poor dim ex-engineer?

TIA,

Frank
 
Wire.h is the base i2c supplied and supported by PJRC. It covers all WIRE#'s on various Teensy models: See {local install}\hardware\teensy\avr\libraries\Wire\WireKinetis.h

i2c_t3.h is an alternative library that can be used to instead of WIRE.h when it works or offers some added feature or alternate method.
 
Wire.h is the base i2c supplied and supported by PJRC. It covers all WIRE#'s on various Teensy models: See {local install}\hardware\teensy\avr\libraries\Wire\WireKinetis.h

i2c_t3.h is an alternative library that can be used to instead of WIRE.h when it works or offers some added feature or alternate method.


Thanks defragster; that's pretty much the same conclusion I was (slowly) coming to. Also thanks for the pointer to WireKinetis.h - at least now I have a bit of a hint how 'the magic' happens when using '#include <Wire.h>' :p
 
As you probably already know, your sketch can use one or the other, but not both. And that includes all of the libraries that your sketch may include.

So for example if you are using some sensor that communicates with I2C and it's library includes <Wire.h> and you choose to include I2c_t3.h, The sketch will not build.

Why? Because they both define some of the same objects like Wire, plus they both define their own versions of things like Interrupt handlers, using the same default names.

Also they are not 100% source level compatible. Especially the defining of the objects and how or if you can use some pin combinations.
Like on T3.5/6 I believe you can use I2C_PINS_18_19 or I2C_PINS_16_17
But you can not use 17,19 or 18,16 which would work using Wire.h ...
Why - because he chose to do it that way... versus using Wire.setSCL(16); Wire.setSDA(18)....

But I2C_t3 has other capabilities not in Wire... Like DMA transfers or the like.
 
After switching from #include <i2c_t3.h> to #include <Wire.h> for my robot project using I2C to communicate between two different Teensy 3.5's, I couldn't get the connection to work at all, even though I had it working before with #include <i2c_t3.h> (and some custom modifications to the i2cdevlib and MPU6050 libraries to work with i2c_t3.h).

So, I went back to basics and set up an experiment with two Teensy 3.5's using slightly modified versions the 'Basic Master Callback' and 'Basic Slave' examples, shown below:

Master_Slave_Test_Setup.JPG

Code:
/*
    Name:       I2CT3H_Master_Reader.ino
    Created:	1/22/2022 8:04:37 PM
    Author:     NEWXPS15\paynt
*/
// -------------------------------------------------------------------------------------------
// Basic Master Callback
// -------------------------------------------------------------------------------------------
//
// This creates a simple I2C Master device which when triggered will send/receive a text
// string to/from a Slave device.  It is intended to pair with a Slave device running the
// basic_slave sketch.  
//
// Functionally this sketch is similar to the basic_master sketch, except for three things:
// 1) It uses callbacks to handle the results and errors, instead of explicitly coding such
//    routines after each send/receive call.
// 2) It can optionally demonstrate background transfers by setting the NONBLOCKING flag (from
//    external observation these transfers will appear similar, but the LED duration will
//    be shorter as they exit immediately after initiating the transfer).
// 3) There is an added test to dump error diagnostics from the error counting system.
//
// Pull pin12 input low to send.
// Pull pin11 input low to receive.
// Pull pin10 input low to dump error diagnostics.
//
// This example code is in the public domain.
//
// -------------------------------------------------------------------------------------------

#include <i2c_t3.h>

// Memory
#define MEM_LEN 256
char databuf[MEM_LEN];
int count;

#define NONBLOCKING

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);    // LED
  digitalWrite(LED_BUILTIN, LOW);  // LED off
  pinMode(12, INPUT_PULLUP);       // Control for Send
  pinMode(11, INPUT_PULLUP);       // Control for Receive
  pinMode(10, INPUT_PULLUP);       // Control for Diagnostics

  // Setup for Master mode, pins 18/19, external pullups, 400kHz, 50ms default timeout
  //Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 400000);
  Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_INT, 400000);
  //Wire.begin(I2C_MASTER, 0x00, I2C_PINS_37_38, I2C_PULLUP_INT, 400000);
  Wire.setDefaultTimeout(50000); // 50ms

  // Data init
  memset(databuf, 0, sizeof(databuf));
  count = 0;

  Serial.begin(115200);
  delay(2000);

  Wire.onTransmitDone(transmitDone);
  Wire.onReqFromDone(requestDone);
  Wire.onError(errorEvent);
}

void loop()
{
  uint8_t target = 0x66; // target Slave address

  // Send string to Slave
  //
  if (digitalRead(12) == LOW)
  {
    digitalWrite(LED_BUILTIN, HIGH);   // LED on

    // Construct data message
    sprintf(databuf, "Data Message #%d", count++);

    // Print message
    Serial.printf("Sending to Slave: '%s' ", databuf);

    // Transmit to Slave
    Wire.beginTransmission(target);   // Slave address
    Wire.write(databuf, strlen(databuf) + 1); // Write string to I2C Tx buffer (incl. string null at end)
#if defined(NONBLOCKING)
    Wire.sendTransmission();          // Transmit to Slave, non-blocking
#else
    Wire.endTransmission();           // Transmit to Slave, blocking
#endif

// After send complete, callback will print result

    digitalWrite(LED_BUILTIN, LOW);    // LED off
    delay(100);                       // 100ms delay
  }

  // Read string from Slave
  //
  if (digitalRead(11) == LOW)
  {
    digitalWrite(LED_BUILTIN, HIGH);   // LED on

    // Print message
    Serial.print("Reading from Slave: ");

    // Read from Slave
#if defined(NONBLOCKING)
    Wire.sendRequest(target, (size_t)MEM_LEN); // Read from Slave (string len unknown, request full buffer), non-blocking
#else
    Wire.requestFrom(target, (size_t)MEM_LEN); // Read from Slave (string len unknown, request full buffer), blocking
#endif

// After request complete, callback will print result

    digitalWrite(LED_BUILTIN, LOW);    // LED off
    delay(100);                       // 100ms delay
  }

  // Diagnostics - print error count summary
  //
  if (digitalRead(10) == LOW)
  {
    digitalWrite(LED_BUILTIN, HIGH);   // LED on

    // Print errors
    Serial.print("\n");
    Serial.printf("I2C_ERRCNT_RESET_BUS: %d\n", Wire.getErrorCount(I2C_ERRCNT_RESET_BUS));
    Serial.printf("I2C_ERRCNT_TIMEOUT:   %d\n", Wire.getErrorCount(I2C_ERRCNT_TIMEOUT));
    Serial.printf("I2C_ERRCNT_ADDR_NAK:  %d\n", Wire.getErrorCount(I2C_ERRCNT_ADDR_NAK));
    Serial.printf("I2C_ERRCNT_DATA_NAK:  %d\n", Wire.getErrorCount(I2C_ERRCNT_DATA_NAK));
    Serial.printf("I2C_ERRCNT_ARBL:      %d\n", Wire.getErrorCount(I2C_ERRCNT_ARBL));
    Serial.printf("I2C_ERRCNT_NOT_ACQ:   %d\n", Wire.getErrorCount(I2C_ERRCNT_NOT_ACQ));
    Serial.printf("I2C_ERRCNT_DMA_ERR:   %d\n", Wire.getErrorCount(I2C_ERRCNT_DMA_ERR));

    // uncomment to zero all errors after dumping
    //for(uint8_t i=0; i < I2C_ERRCNT_DMA_ERR; i++) Wire.zeroErrorCount((i2c_err_count)i);

    digitalWrite(LED_BUILTIN, LOW);    // LED off
    delay(100);                       // 100ms delay
  }
}

//
// Trigger after Tx complete (outgoing I2C data)
//
void transmitDone(void)
{
  Serial.print("OK\n");
}

//
// Trigger after Rx complete (incoming I2C data)
//
void requestDone(void)
{
  Wire.read(databuf, Wire.available());
  Serial.printf("'%s' OK\n", databuf);
}

//
// Trigger on I2C Error
//
void errorEvent(void)
{
  Serial.print("FAIL - ");
  switch (Wire.status())
  {
  case I2C_TIMEOUT:  Serial.print("I2C timeout\n"); Wire.resetBus(); 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("Arbitration Lost, possible pullup problem\n"); Wire.resetBus(); break;
  case I2C_BUF_OVF:  Serial.print("I2C buffer overflow\n"); break;
  case I2C_NOT_ACQ:  Serial.print("Cannot acquire bus, possible stuck SDA/SCL\n"); Wire.resetBus(); break;
  case I2C_DMA_ERR:  Serial.print("DMA Error\n"); break;
  default:           break;
  }
}

Code:
/*
    Name:       I2CT3H_Slave_Sender.ino
    Created:	1/22/2022 8:12:20 PM
    Author:     NEWXPS15\paynt
*/


// -------------------------------------------------------------------------------------------
// Basic Slave
// -------------------------------------------------------------------------------------------
//
// This creates a simple I2C Slave device which will print whatever text string is sent to it.
// It will retain the text string in memory and will send it back to a Master device if 
// requested.  It is intended to pair with a Master device running the basic_master sketch.
//
// This example code is in the public domain.
//
// -------------------------------------------------------------------------------------------

#include <i2c_t3.h>
#include <elapsedMillis.h>

// Function prototypes
void receiveEvent(size_t count);
void requestEvent(void);

// Memory
#define MEM_LEN 256
char databuf[MEM_LEN];
volatile uint8_t received;
elapsedMillis mSecSinceLastPrint;
int maxnumdots = 60;
int dotnum = 0;

//
// Setup
//
void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // LED

  // Setup for Slave mode, address 0x66, pins 18/19, external pullups, 400kHz
  //Wire.begin(I2C_SLAVE, 0x66, I2C_PINS_18_19, I2C_PULLUP_EXT, 400000);
  Wire.begin(I2C_SLAVE, 0x66, I2C_PINS_18_19, I2C_PULLUP_INT, 400000);

  // Data init
  received = 0;
  memset(databuf, 0, sizeof(databuf));

  // register events
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  Serial.begin(115200);
  delay(2000);

  Serial.printf("Slave Program Started\n");
  mSecSinceLastPrint = 0;

}

void loop()
{
  // print received data - this is done in main loop to keep time spent in I2C ISR to minimum
  if (received)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.printf("Slave received: '%s'\n", databuf);
    received = 0;
    digitalWrite(LED_BUILTIN, LOW);
  }

  if (mSecSinceLastPrint >= 200)
  {
    mSecSinceLastPrint -= 200;
    Serial.printf(".");
    dotnum++;
    if (dotnum > maxnumdots)
    {
      Serial.printf("\n");
      dotnum = 0;
    }
  }
}

//
// handle Rx Event (incoming I2C data)
//
void receiveEvent(size_t count)
{
  Wire.read(databuf, count);  // copy Rx data to databuf
  received = count;           // set received flag to count, this triggers print in main loop
}

//
// handle Tx Event (outgoing I2C data)
//
void requestEvent(void)
{
  Wire.write(databuf, MEM_LEN); // fill Tx buffer (send full mem)
}


After getting everything working, I noticed that if the I2C_PULLUP_INT option is used for both master and slave, external pullup resistors are not required, and O'scope monitoring of the waveforms showed no discernable changes with or without external 2.2K Ohm resistors. The first image below is 'with' 2.2K pullups, and the second one is 'without'

2022-01-22 i2c with pullups.jpg
2022-01-22 i2c without pullups.jpg

Also, for some reason I was unable to switch the Master T3.5 from pins 18 & 19 to pins 37/38 using 'Wire.begin(I2C_MASTER, 0x00, I2C_PINS_37_38, I2C_PULLUP_INT, 400000);' The program would compile and run, but the I2C I/O waveforms still appeared on pins 18/19, and not 37/38. Anyone have an idea what I'm doing wrong here?

Then I did a similar test with Wire.h, using slightly modified versions of 'Wire_Slave_Sender.ino' and 'Wire_Master_Reader.ino', as shown below:

Code:
/*
 Name:		Wire_Slave_Sender.ino
 Created:	1/18/2022 9:08:53 PM
 Author:	paynt
*/
// Wire Slave Sender
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Sends data as an I2C/TWI slave device
// Refer to the "Wire Master Reader" example for use with this

// Created 29 March 2006

// This example code is in the public domain.
// 01/20/22 modified to use I2C_Anything to send a float value


#include <Wire.h>
#include "I2C_Anything.h"

int led = LED_BUILTIN;

void setup()
{
  pinMode(led, OUTPUT);
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
}

void loop()
{
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  digitalWrite(led, HIGH);  // briefly flash the LED

  // 01/20/22 rev to use I2C_Anything to write a double val
  double val = 3.14159;
  Serial.printf("sending %2.4f to master\n", val);
  I2C_writeAnything(val);//2nd arg missing, so &Wire selected by default
  digitalWrite(led, LOW);
}

Code:
/*
    Name:       Wire_Master_Reader.ino
    Created:	1/18/2022 9:11:05 PM
    Author:     NEWXPS15\paynt

    First trial was with all default settings, and 
    connected to Wire_Slave_Sender.ino via pins 18,19
    on both ends. This worked as expected

    Second trial was to change all 'Wire.*' to 'Wire1.*'
    in this program, and physically move the SCL & SDA
    wires from 19,18 to 37,38 on a T3.5.  This also worked
*/

// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this

// Created 29 March 2006

// This example code is in the public domain.
// 01/20/22 modified to use I2C_Anything to ask for a 'double' (4 byte) value


#include <Wire.h>
#include "I2C_Anything.h"


const int led = LED_BUILTIN;
const int SLAVE_ADDR = 0x8;
  double val = 0;

void setup()
{
  pinMode(led, OUTPUT);
  Wire.begin();             // pins 18/19
  //Wire1.begin();             // pins 37/38
  Serial.begin(9600);       // start serial for output
  delay(2000);
}

void loop()
{
  Serial.print("read: ");

  digitalWrite(led, HIGH);  // briefly flash the LED
  Wire.requestFrom(8, sizeof(val));   // request 6 bytes from slave device #8
  //Wire1.requestFrom(8, sizeof(val));   // request 6 bytes from slave device #8

  //01/20/22 now read double value using I2C_Anything
  I2C_readAnything(val);
  //I2C_readAnything(val, &Wire1);
  Serial.printf("read %2.4f from Slave\n", val);

  digitalWrite(led, LOW);
  delay(500);
}

After getting everything to work on 'Wire', I changed the 'master_reader' program to use Wire1. I verified that I2C activity changed from pins 18/19 to pins 37/38 and everything worked fine just by moving the I2C wires from 18/19 to 37/38. This is what I expected (but didn't get) in the i2c_t3.h configuration).

The following images show the data lines with and without external 2.2K pullups, showing that external pullups are definitely required. This is obviously the reason nothing worked when I switched from a 'i2c_t3.h' based configuration to a 'Wire.h' one

Wire_with_pullup.jpg
Wire_without_pullup.jpg



So it seems that the i2c_t3.h library's 'begin()' function must be somehow enabling internal pullups on the i2c pins when the I2C_PULLUP_INT option is selected, but Wire.begin() does not. Is there a way to manually enable the internal pullups on T3.5 after Wire.begin()? I would rather not be forced to add external pullup resistors (which will be a real PITA in my setup) if I can avoid them by achieving the same pin setup as is obviously do-able using i2c_t3.h.
 
Looking through other posts and the 'K20 Sub-Family Reference Manual' I thought the following should work to enable internal pullups on pins 37/38 (Wire1) when placed just before 'Wire1.begin()'.


for (uint8_t pin = 37; pin <= 38; pin++)
{
* portConfigRegister(pin) |= PORT_PCR_PE; //pull enable
*portConfigRegister(pin) |= PORT_PCR_PS; //pull up
}

but AFACT this does nothing; external pullups are still required. What am I doing wrong here?

TIA,

Frank
 
Back
Top