New I2C library for Teensy3

nox771. Just want to confirm that I can i2c_t3.h for wire.h and still use the typical Wire calls as if I was using Wire.h? I am familiar with I2Cdevlib and a myriad of other libs for different sensors and I2Cdevlib only uses wire.function calls. If you want to use wire1 you have to manually change wire to wire1 but then it might affect any other libs that use I2Cdevlib. There is a way to use both libs that I used and that is to test for teensy board or not, but then that adds another layer of complication.

Yes the typical Wire commands should work if you swap Wire.h to i2c_t3.h. The library is designed specifically for that to work. It will also support having both Wire and Wire1 (as well as Wire2 and Wire3 if you have a T3.6) being used in the same sketch, there are examples included which show that.

That said, you cannot have a mix of Wire.h and i2c_t3.h includes in a single project. If you have 3rd-party libraries which use Wire.h they must also change to i2c_t3.h. If all libs do that it should compile fine. There was a case of a project (https://forum.pjrc.com/threads/41959-i2c_t3-h-versus-Wire-h-with-LCD-w-i2c-backpack-and-teensy-3-2) which actually had a I2C port expander in it, and was using Liquidcrystal. For that to work it required setting immediate mode as shown below. In that mode the library will operate in a mode similar to stock Wire.h. This is a rare requirement, normally 3rd-party libs work fine in ISR (interrupt) mode which is the default.

To set immediate mode (only if needed):
Code:
Wire.begin(...);
Wire.setOpMode(I2C_OP_MODE_IMM);
 
In a single master setup it has no practical effect if you don't send a stop. The effect of not sending a stop is that the bus is still "busy" (SCL remains low), but there is no problem leaving it in that state for an indefinite period of time (since the single master is the only one which can initiate a new transfer). From a slave perspective it may keep a slave which uses repeated-starts in a sort of intermediate state, whereby it is perhaps expecting more data, but usually that is ok too. Addressing a different slave isn't a problem either, since the next start will send out a new slave address.
Even if it works, is it recommended practice? Also, doesn't keeping SCL low for long periods waste power?
 
Even if it works, is it recommended practice? Also, doesn't keeping SCL low for long periods waste power?

Yes keeping SCL low will drain power through the pullup resistor, on the order of ~mA for typical component values. Recommended operation is to pair every START/repSTART with a STOP (eg. complete command sequence). But people can run things any way they want, that's the freedom of DIY. If someone wants to put a 100ohm pullup and run the bus at 10Hz, well have fun.
 
Yes keeping SCL low will drain power through the pullup resistor, on the order of ~mA for typical component values. Recommended operation is to pair every START/repSTART with a STOP (eg. complete command sequence). But people can run things any way they want, that's the freedom of DIY. If someone wants to put a 100ohm pullup and run the bus at 10Hz, well have fun.
The code I'm writing is actually for more than just a one-off project, so I would like to adhere to recommended practice as much as possible. I'm going to see if I can write my own TWI driver. In your experience, has there ever been a case where it was necessary to use two or more repeated STARTs in a transfer to complete a task?
 
The code I'm writing is actually for more than just a one-off project, so I would like to adhere to recommended practice as much as possible. I'm going to see if I can write my own TWI driver. In your experience, has there ever been a case where it was necessary to use two or more repeated STARTs in a transfer to complete a task?

Multiple repeated starts can occur if there was a chain of commands.

Typical case for RepSTART might be some device with memory, whereby to read an address you first send the memory address then do a read:
START-SlaveWr-MemAddr-RepSTART-SlaveRd-Read1-Read2...STOP

Note in this case RepSTART is mandatory, because if there were a STOP in the middle the bus would be freed, and a 2nd master could step in the middle and retarget the slave mem address. By using a RepSTART the bus is locked busy by the 1st master so such a thing cannot happen.

But it's also possible to just chain write commands:
START-SlaveWr-Data1-Data2-RepSTART-SlaveWr-Data3-Data4...STOP

In this case RepSTART is just a convenience to save time. But notice also the slave addresses don't have to be the same, RepSTART is a way for the master to keep the bus busy, but the slave it talks to could be any on the bus. So an example of this might be if you are writing the same command to 10 different slaves on the bus. Or it could be 10 commands to the same slave. Or it could be a chain of writes and reads. There is no set protocol, it is all application dependent.
 
Multiple repeated starts can occur if there was a chain of commands.

Typical case for RepSTART might be some device with memory, whereby to read an address you first send the memory address then do a read:
START-SlaveWr-MemAddr-RepSTART-SlaveRd-Read1-Read2...STOP

Note in this case RepSTART is mandatory, because if there were a STOP in the middle the bus would be freed, and a 2nd master could step in the middle and retarget the slave mem address. By using a RepSTART the bus is locked busy by the 1st master so such a thing cannot happen.

But it's also possible to just chain write commands:
START-SlaveWr-Data1-Data2-RepSTART-SlaveWr-Data3-Data4...STOP

In this case RepSTART is just a convenience to save time. But notice also the slave addresses don't have to be the same, RepSTART is a way for the master to keep the bus busy, but the slave it talks to could be any on the bus. So an example of this might be if you are writing the same command to 10 different slaves on the bus. Or it could be 10 commands to the same slave. Or it could be a chain of writes and reads. There is no set protocol, it is all application dependent.
So then would you say that the most flexible way of implementing repeated starts in a driver is the one you chose?
 
nox771. Thanks for the info. I just had to be sure I am thinking of simplifying I2Cdevlib and expanding it a bit. Getting tired of every library having its own set of i2c commands. Also, looking at making it work for teensy as well as non-teensy boards.
 
So then would you say that the most flexible way of implementing repeated starts in a driver is the one you chose?

I'm not sure what you are referring to. The i2c_t3 library itself handles RepSTARTs as given by the i2c spec.

If you are designing a master-side device, the most common thing is to send commands one at a time (not to chain them). Everything understands that. If a master-side device needs to talk to something like a memory device, then it will likely need to send a single RepSTART as part of the command in order to do the direction change (sending to receiving).
 
I'm not sure what you are referring to. The i2c_t3 library itself handles RepSTARTs as given by the i2c spec.

If you are designing a master-side device, the most common thing is to send commands one at a time (not to chain them). Everything understands that. If a master-side device needs to talk to something like a memory device, then it will likely need to send a single RepSTART as part of the command in order to do the direction change (sending to receiving).
I'm just trying to make my driver future-proof so that I don't ever have to find myself making substantial changes to it. I'm thinking of a case where not all of what's going to be sent during a transfer is known at the beginning of the transfer, i.e. the application decides what to do based on what is received from the slave as the transfer is in progress. In this case, it would not be possible to queue up a huge transfer complete with repeated STARTs, and it would be necessary to use the technique of leaving STOPs off of the ends of messages.
 
I'm just trying to make my driver future-proof so that I don't ever have to find myself making substantial changes to it. I'm thinking of a case where not all of what's going to be sent during a transfer is known at the beginning of the transfer, i.e. the application decides what to do based on what is received from the slave as the transfer is in progress. In this case, it would not be possible to queue up a huge transfer complete with repeated STARTs, and it would be necessary to use the technique of leaving STOPs off of the ends of messages.

The main purpose of not sending a STOP is to keep the bus locked. A secondary purpose is to accelerate a long chain of commands. So if your application has reason to keep the bus locked to a single master, then it makes sense to do what you are saying. If you are doing some long drawn out communication and that is not a requirement, then what reason is there to not send a STOP? It sounds like there is no speed advantage.

The overall scenario you are describing is very common. Lots of devices communicate in an iterative method using just simple short command sequences.
 
The main purpose of not sending a STOP is to keep the bus locked. A secondary purpose is to accelerate a long chain of commands. So if your application has reason to keep the bus locked to a single master, then it makes sense to do what you are saying. If you are doing some long drawn out communication and that is not a requirement, then what reason is there to not send a STOP? It sounds like there is no speed advantage.

The overall scenario you are describing is very common. Lots of devices communicate in an iterative method using just simple short command sequences.
I want to make a general-purpose driver, one that will fit the needs of all my current and future projects. In my current project there is a single master, so no, it is not a hard requirement that that the driver can maintain control of the bus indefinitely. But it is certainly possible that this will not be the case in a future project. So if I'm understanding correctly, it would be better if my driver allowed an application to skip sending STOPs provides more flexibility than allowing it to queue up multiple transactions?
 
I want to make a general-purpose driver, one that will fit the needs of all my current and future projects. In my current project there is a single master, so no, it is not a hard requirement that that the driver can maintain control of the bus indefinitely. But it is certainly possible that this will not be the case in a future project. So if I'm understanding correctly, it would be better if my driver allowed an application to skip sending STOPs provides more flexibility than allowing it to queue up multiple transactions?

I would think controlling the STOP behavior via some option setting is probably best, as it can be set depending on application.
 
I would think controlling the STOP behavior via some option setting is probably best, as it can be set depending on application.

It seems that writing a TWI driver is harder than I thought. Do you have plans to port this library to the AVR Teensies?
 
It seems that writing a TWI driver is harder than I thought. Do you have plans to port this library to the AVR Teensies?

Sorry but no. The original purpose of the library was to support the advanced features and speed of the Teensy 3.x I2C hardware. Much of that simply doesn't exist on the AVR hardware. I would agree the AVR devices could use something better than what they have, but it will have to be someone else (good luck to them though!).
 
Sorry but no. The original purpose of the library was to support the advanced features and speed of the Teensy 3.x I2C hardware. Much of that simply doesn't exist on the AVR hardware. I would agree the AVR devices could use something better than what they have, but it will have to be someone else (good luck to them though!).

Well, given your experience and their relative simplicity, I can't imagine that it'd be much of a challenge for you if you did decide to do it.
 
Well, given your experience and their relative simplicity, I can't imagine that it'd be much of a challenge for you if you did decide to do it.

Nice of you to say, but the challenge I can't ever get around is time. Like many here, my project stack is already much higher than available time (and unfortunately building a new AVR lib isn't even on the stack...). Aside from Arduino/Wire there are a number of 3rd-party I2C/TWI libs around for AVR, perhaps you can find a solution in those?
 
Nice of you to say, but the challenge I can't ever get around is time. Like many here, my project stack is already much higher than available time (and unfortunately building a new AVR lib isn't even on the stack...). Aside from Arduino/Wire there are a number of 3rd-party I2C/TWI libs around for AVR, perhaps you can find a solution in those?

I did investigate alternatives to Wire, but like you said they're all lacking in one way or another.
 
What's the best hardware and ecosystem for the job?

It seems that writing a TWI driver is harder than I thought. Do you have plans to port this library to the AVR Teensies?
Given that there is a huge movement to ARM based systems by semiconductor manufacturers and users - e.g. Teensy 3.2 is less expensive and waaaay more capable (memory, execution speed, peripherals) than say AVR Arduino Uno, and not much more than Teensy 2.X - why not use the hardware that has the ability to do what you need? This includes available libraries. Even if your volumes are high and you are trying to shave "product cost", it must really consider "lifecycle cost", not just the component hardware cost. A modern platform with up-to-date tools and libraries, and room to grow is usually less expensive overall. AVRs are still fine when the task is super simple, but otherwise a Cortex M0, M3, M4 may well be the better choice. I suspect that's the call a lot of us here on this forum have made. Like nox771 says, time is the biggest constraint.
 
i2c_t3 compatability with 3rd party I2C library

Sorry for the delay in response, I have only been able to work on this project sporadically.

The general method of using i2c_t3 in your code with a 3rd-party library that has a I2C dependency, is to modify the 3rd-party library to include i2c_t3.h instead of Wire.h.

Thanks for the suggestion, this removed the library compile-time conflict.

If your 3rd-party lib is using Wire, then you should probably be using a different bus in your code, such as Wire1, Wire2, etc. That is done by substituting Wire1.<function> instead of Wire.<function> in your code. Of course the other buses map to different pins and such, so you need to modify your wiring as well. You could also share a bus with the 3rd-party lib if you have good control over when it communicates.

Currently I'm using Wire for both and this is causing it to hang when I try to pull data from the sensor and send to another Teensy at the same time. I can implement using Wire1 instead of Wire for transmitting data. Any other suggestions on how to de-conflict the transmissions so I can use the same bus?

Thanks
pzschulte
 
Currently I'm using Wire for both and this is causing it to hang when I try to pull data from the sensor and send to another Teensy at the same time. I can implement using Wire1 instead of Wire for transmitting data. Any other suggestions on how to de-conflict the transmissions so I can use the same bus?

To share the bus with a library you need to coordinate the transfers so they do not interfere with each other. That is purely application level code, i2c_t3 only manages single buffer transfers at a time. It will be difficult if one or more of the transfers are generated via interrupt-triggered events. In that case you need to build some kind of message queue.

That said, the simple solution if the other bus is available is to move either your foreground communication, or the library to a different bus. In some cases the libraries have very limited send/receive routines and it is easier to move them to Wire1/Wire2/etc. since the code requires less changes. If they operate on different buses then i2c_t3 already provides different Tx/Rx buffers and collisions are not an issue.
 
Switching i2c_t3 to Wire1

it is easier to move them to Wire1/Wire2/etc. since the code requires less changes. If they operate on different buses then i2c_t3 already provides different Tx/Rx buffers and collisions are not an issue.

Ok, I have attempted to move to Wire1 (pins 29/30 on Teensy 3.2) but haven't been able to establish communication. Are there any code changes I need to make besides switching Wire-->Wire1 and I2C_PINS_18_19 --> I2C_PINS_29_30 ?

Here are my slightly modified basic_master and basic_slave code:

Code:
// -------------------------------------------------------------------------------------------
// Basic Master
// -------------------------------------------------------------------------------------------
//
// 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.
//
// Pull pin12 input low to send.
// Pull pin11 input low to receive.
//
// This example code is in the public domain.
//
// -------------------------------------------------------------------------------------------

#include <i2c_t3.h>

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

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(12,INPUT);       // Control for Send
    pinMode(11,INPUT);       // Control for Receive

    // Setup for Master mode, pins 18/19, external pullups, 400kHz, 200ms default timeout
    Wire1.begin(I2C_MASTER, 0x00, I2C_PINS_29_30, I2C_PULLUP_EXT, 400000);
    Wire1.setDefaultTimeout(200000); // 200ms

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

    Serial.begin(115200);
}

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

    //Set pin 12 LOW to send
    delay(1000);
    digitalWrite(12,LOW);
//    if(digitalRead(12) == LOW)
//    {
//      Serial.printf("\nSet pin 12 LOW to send\n");    
//    }
    
    // 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
        Wire1.beginTransmission(target);   // Slave address
        for(idx = 0; idx <= strlen(databuf); idx++) // Write string to I2C Tx buffer (incl. string null at end)
            Wire1.write(databuf[idx]);
        Wire1.endTransmission();           // Transmit to Slave

        // Check if error occured
        if(Wire1.getError())
            Serial.print("FAIL\n");
        else
            Serial.print("OK\n");

        digitalWrite(LED_BUILTIN,LOW);    // LED off
        delay(100);                       // Delay to space out tests
    }
    
    //Set pin 12 HIGH to stop sending
    //digitalWrite(12,HIGH);
//    if(digitalRead(12) == HIGH)
//    {
//      Serial.printf("Set pin 12 HIGH to stop sending\n\n");    
//    }
    
    //Set pin 11 LOW to receive
    delay(1000);
    digitalWrite(11,LOW);
//    if(digitalRead(11) == LOW)
//    {
//      Serial.printf("Set pin 11 LOW to receive\n");    
//    }
//    
    // Read string from Slave
    //
    if(digitalRead(11) == LOW)
    {
        digitalWrite(LED_BUILTIN,HIGH);   // LED on

        // Print message
        Serial.print("Reading from Slave: ");
        
        // Read from Slave
        Wire1.requestFrom(target, (size_t)MEM_LEN); // Read from Slave (string len unknown, request full buffer)

        // Check if error occured
        if(Wire1.getError())
            Serial.print("FAIL\n");
        else
        {
            // If no error then read Rx data into buffer and print
            idx = 0;
            while(Wire1.available())
                databuf[idx++] = Wire1.readByte();
            Serial.printf("'%s' OK\n",databuf);
        }

        digitalWrite(LED_BUILTIN,LOW);    // LED off
        delay(100);                       // Delay to space out tests
    }
    
    //Set pin 11 HIGH to stop recieving
    //digitalWrite(11,HIGH);
//    if(digitalRead(11) == HIGH)
//    {
//      Serial.printf("Set pin 11 HIGH to stop receiving\n\n");    
//    }
}

Code:
// -------------------------------------------------------------------------------------------
// 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>

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

// Memory
#define MEM_LEN 256
uint8_t databuf[MEM_LEN];
volatile uint8_t received;

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

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

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

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

    Serial.begin(115200);
}

void loop()
{
    delay(500);
    //Serial.printf("I'm alive!\n");
    
    // 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", (char*)databuf);
        received = 0;
        digitalWrite(LED_BUILTIN,LOW);
    }
}

//
// handle Rx Event (incoming I2C data)
//
void receiveEvent(size_t count)
{
    size_t idx=0;
    
    while(idx < count)
    {
        if(idx < MEM_LEN)                     // drop data beyond mem boundary
            databuf[idx++] = Wire1.readByte(); // copy data to mem
        else
            Wire1.readByte();                  // drop data if mem full
    }
    
    received = count; // set received flag to count, this triggers print in main loop
}

//
// handle Tx Event (outgoing I2C data)
//
void requestEvent(void)
{
    Wire1.write(databuf, MEM_LEN); // fill Tx buffer (send full mem)
}
 
Seems there was a multi-bus version of SCAN example that might allow diagnostic check of the connection for device to be present
 
Ok, I have attempted to move to Wire1 (pins 29/30 on Teensy 3.2) but haven't been able to establish communication. Are there any code changes I need to make besides switching Wire-->Wire1 and I2C_PINS_18_19 --> I2C_PINS_29_30 ?

I'll take a closer look tomorrow. However with just a cursory look, one thing is a little odd. Presumably you are using your library that needs Wire on the Master device only, is that right? If so, then there is no need to adjust the bus that the Slave device uses (the Slave can continue to use Wire). That said there should be no problem having the Slave use Wire1 if that's what you want.

You are using two T3.2 devices is that right? One for Master device and one for Slave device? If so make sure you are hitting the right pins (w/pullups) and SDA/SCL are not cross-wired.
 
Presumably you are using your library that needs Wire on the Master device only, is that right? If so, then there is no need to adjust the bus that the Slave device uses (the Slave can continue to use Wire). That said there should be no problem having the Slave use Wire1 if that's what you want.

You are using two T3.2 devices is that right? One for Master device and one for Slave device? If so make sure you are hitting the right pins (w/pullups) and SDA/SCL are not cross-wired.

Yes, I have two T3.2 devices. The first (my slave) has the other library that is using Wire (I2Cdev library for an MPU6050 accelerometer) on pins 18/19. I connected the master and slave to use Wire1 on pins 29/30 (tripled checked that they are not cross-wired). What do you mean by "w/pullups"? When I was using Wire (pins 18/19) I had the pullup set us "I2C_PULLUP_EXT". Does that not apply when using Wire1 (pins 29/30)?

Seems there was a multi-bus version of SCAN example that might allow diagnostic check of the connection for device to be present

I tried both the basic_scanner and advanced_scanner examples, but neither one gave any output.
 
Back
Top