New I2C library for Teensy3

Thank you very much for supporting this. Outstanding, really!

Sure, hope it works for you.

Also, for everyone, I've added a function list on the top post, which details all the new functions and which ones have been added. That can be used as a guide if you need portable code which has to work on AVR. The green functions will only work in this library, which only works on Teensy3.
 
Hi,

I'm trying to get the debug feature of the examples working, but I keep failing so far.

Adding "#define I2C_DEBUG" to the top of 'i2c_slave'_t3 produces the following compiling error:
Code:
Arduino: 1.0.5 (Windows NT (unknown)), Board: "Teensy 3.0"
IntervalTimer.cpp.o: In function `IntervalTimer::end()':
L:\Program Files (x86)\Arduino\hardware\teensy\cores\teensy3/IntervalTimer.cpp:39: multiple definition of `pit0_isr'
i2c_slave_t3.cpp.o:L:\Program Files (x86)\Arduino/i2c_slave_t3.ino:144: first defined here
i2c_slave_t3.cpp.o: In function `pit0_isr':
i2c_slave_t3.cpp:(.text.pit0_isr+0xa): undefined reference to `printI2CDebug()'
i2c_slave_t3.cpp:(.text.pit0_isr+0x18): undefined reference to `i2cDebug'
collect2.exe: error: ld returned 1 exit status

What I'm doing wrong?
 
A couple things I can think of. The top error appears to be due to the new IntervalTimer library which is for the PIT timer. It appears to be conflicting with my explicit use of the PIT timer. I haven't got my build on the latest Teensyduino yet. I'll need to study and rework it.

The second thing (last line) looks like a link error, which might be because a lib is not getting included in the build process. What is your build enviro, are you using Arduino to build?

Give me some time, I'll try to look into it.
 
What I'm doing wrong?

I'm going to guess you defined I2C_DEBUG in your own code. The library appears to intend for you to uncomment this in i2c_t3.h, so additional code inside the library (specifically those undefined functions) gets compiled. If you define it only within your program, it isn't defined with i2c_t3.cpp is compiled.

Regarding the PIT troubles, yes, it looks like this code was made before IntervalTimer became part of the core library.

In the code, try this:

Code:
    #ifdef I2C_DEBUG
        Serial.begin(115200);
        IntervalTimer *t = new IntervalTimer;
        t->begin(debugPrint, 1000);
    #endif

and thisL:

Code:
void debugPrint(void)
{
    if(i2cDebug.len())
        printI2CDebug();
}

That should fix the PIT issue. If you also edit i2c_t3.h to define I2C_DEBUG, I believe it should compile without error.
 
@nox771 - if you publish version 5, could I talk you into rearranging the files to the convention most Arduino libraries use?

Normally the zip file extracts to a directory with the same name as the library, which contains all the .cpp and .h files the library needs. Normally all the examples are in an "examples" subdirectory (where "examples" is all lowercase which matters on Linux). Following that convention, the zip file might extract like this:

i2c_t3/i2c_t3.cpp
i2c_t3/i2c_t3.h
i2c_t3/rbuf.cpp
i2c_t3/rbuf.h
i2c_t3/examples/i2c_slave_t3/i2c_slave_t3.ino
i2c_t3/examples/i2c_multi_master_t3/i2c_multi_master_t3.ino
i2c_t3/examples/i2c_timeout_t3/i2c_timeout_t3.ino
i2c_t3/examples/i2c_master_t3/i2c_master_t3.ino

Normally the examples would not need the "i2c_" prefix and "_t3" suffix, since they'll appear in the Arduino's menu under File > Examples > i2c_t3.

The one other strong convention for libraries is a keywords.txt file, which lets the Arduino editor highlight your library's keywords nicely. Here's a quick first attempt I made, which gets most of your library's keywords.
 

Attachments

  • keywords.txt
    819 bytes · Views: 677
Thanks Paul, I'll take your suggestions and repackage/rebuild everything clean against the latest Teensyduino.

Question for you though. I don't want to drag this thread too far off topic, but one thing I was wondering is regarding things like the PIT ISR being defined as an integral part of the Teensyduino environment. Were you ever planning on wrapping defines around those types of things so they could be modularly included/excluded from an end-user program?

I'm not sure the best way to do that, as it would perhaps require a default include or such in order to build normally. It would seem to me that having ISRs (or any resource like that) already "taken" by the libraries sort of makes it difficult if anyone wants to override it in an end-user program. It requires going down into the Teensyduino library code and explicitly removing sections (which while it is do-able, makes things a pain across a set of end-user programs if only a few need it). It also, for instance in this I2C case, creates dependencies, in that the I2C won't build properly unless IntervalTimer is there. I haven't delved into the DMA stuff yet, but I kind of wonder if it is the same type of situation.
 
IntervalTimer vs direct PIT access is a trade-off, having a built-in library "consumes" the 4 ISR functions with some extra overhead, but it offers a standard API that many different libraries can use to effectively share the hardware resources. It was discussed quite a bit in another thread here, and I actually had some reservations about this for a while.

In the long term, facilitating sharing of the resources offers a lot of benefit. Originally one of the PIT interrupts was tied up by the tone() function, whether you were using tone or not. Now all 4 PIT timers are available, but through this API. Already 3 libraries (SoftPWM, VirtualWire and DmxSimple) are ported to use IntervalTimer. I recently worked on one of Adafruit's RGB matrix libraries, which also needed a timer for running a function (but no actual hardware needs). A couple other libraries will probably be ported to use it too. IntervalTimer really opens up the possibility for all these libraries to coexist without hard-wired dependencies. The ideal situation would be all combinations of libraries "just work" without editing any library code.

The one other resource where this sort of API probably makes a lot of sense is DMA. So far, very little work as happened on an API for dynamically allocating DMA channels. OctoWS2811 is probably the only widely used library using DMA at this point... so when/if this happens, I'll be the first one to suffer having to rewrite a library!

One idea that's come up many times on the Arduino developer mail list is allowing sketches to #define symbols that affect how the core library is built. There are a lot of really compelling features that could offer. But the general consensus, which I must admit I believe is wise, is opening that up will create chaos in the Arduino world. Already there are lots of widely used libraries (including at least one I wrote) that depend heavily on quirks of Arduino's build process. If the build process changed so sketches could reconfigure the core, it's felt the long-term effect would be many example programs that are very incompatible with each other. A lot of the value of the Arduino platform is the ability to copy and paste bits and pieces of code and include libraries from different sources and generally have things work as expected. If programs and libraries could reconfigure the core library, it would become a very different platform. Still, I've considered doing this sort of thing on a limited scale with Teensyduino, but I generally do try to follow what the Arduino folks are doing too. It's a very difficult decision, to balance powerful features with the desire for a base platform that's a dependable known quantity.

This ended up rambling without any good answers, because quite honestly these are very difficult questions. Specifically on IntervalTimer, I do lament the loss of direct access to the PIT interrupts, but already it's allowing all 4 timers to be used and several libraries to be used together in any combination without needing to worry about which one needs to allocate with interrupt. I think that's worth the small overhead, and unfortunately the headache of reworking all the earlier code which directly used the PIT interrupts.
 
Many thanks to the both of you!

@PaulStoffregen: Your assessment of the problem was 'uncannily' accurate. :)

I got the slave device program working now, at least in combination with an Arduino as a master.
Sadly, the problem which led me to this point did not evaporate magically: I’m trying to swap an I²C controlled LCD-display with a Teensy 3, in order to read the data and display it elsewhere. I don’t know for sure, which driver is built in the LCD, but I know the address (0x38) and the communication rate (about 350 kHz according to logic analysis). Also the driver as I²C slave only receives data and gets no requests. So I thought, that activating the debug mode and changing the address in your example would yield me the stream of bytes intended for the display (because the I²C master should not be able to tell the difference between the display driver and the Teensy).

But the only thing I get via serial is (with I2C_DEBUG_VERBOSE2):

I A1:70 F:85 C1:C0 S:E2 C2:20 FLT:04 ASR
I A1:70 F:85 C1:C0 S:E2 C2:20 FLT:04 RSTART ASR
And so on….

Can you tell me, what the information means? Additionally I would be very grateful for any clue to the source of the problem, of course. I have only a not so educated guess: Between master (5V) and Teensy is a logic level shifter, may this be in some way the cause?
 
I don't think the debug mode is what you want. It is dumping the low-level I2C registers from the I2C module, specifically these (you can get more information on these in the datasheet):
Code:
    // Chapter 44: Inter-Integrated Circuit (I2C) - Page 1012
    //  I2C0_A1      // I2C Address Register 1
    //  I2C0_F       // I2C Frequency Divider register
    //  I2C0_C1      // I2C Control Register 1
    //  I2C0_S       // I2C Status register
    //  I2C0_D       // I2C Data I/O register
    //  I2C0_C2      // I2C Control Register 2
    //  I2C0_FLT     // I2C Programmable Input Glitch Filter register

What you really want is the data received, not all those registers. However this Slave setup is actually a useful test case for many things. When I rework the library today I think I'll build in a new Slave demo app, which just dumps receive data to Serial.

Regardless the information is a bit useful, as I can see the below stream looks a little odd:

I A1:70 F:85 C1:C0 S:E2 C2:20 FLT:04 ASR
I A1:70 F:85 C1:C0 S:E2 C2:20 FLT:04 RSTART ASR
And so on….

The A1 register is the address, which is 7-bit Addr + R/W, so 70h = 38h + WRITE (write is '0'). So that part is correct, and the Slave ACKs. ASR = Addressed Slave Receive, meaning the Slave understands that it was addressed, and it changes into a receive mode.

Odd part is that the next thing the Master sent is a RSTART = Repeated-START, so the Master sent the address byte then for some reason sent it again - meaning it never sent any data. It looks like it did not see the Slave ACK, are your pullups too low resistance?

Since you have an analyzer you should be able to put the SDA/SCL on it and see if the ACK registers from the Slave device at the end of the address byte transmission (the last bit should pull low).

EDIT: Sorry I missed the last line in your post - yes if you have a one-directional level shifter between Master and Slave, then the Master won't be able to see the ACK from the Slave. Slave needs to signal that it recv'd the byte by pulling SDA low, a one-directional level shifter will block the signal getting back to the Master.

Another thing - a logical level-shift should be unnecessary - I2C uses resistive pullups, just change the pullup voltage to effect a level shift.
 
Last edited:
I just revised the logic level converter. Even the old design has been bidirectional, but now it is working better and I get one or two bytes before RSTART and sometimes a STOP. It’s still gibberish, but at least the first byte seems to be a correct control byte for LCD-Drivers from NXP.
It seems very plausible, that the problem is with the SCL. I can’t look up the pullups right now since they are on the back of the circuit board (which is the instrument panel of a car) and the whole experiment set-up is very delicate – and I’d like to try another few things first. For example: I tried ‘I2C_PULLUP_INT‘, that yielded no results at all.
Concerning your edit:

Another thing - a logical level-shift should be unnecessary - I2C uses resistive pullups, just change the pullup voltage to effect a level shift.

So you say that the 5V SDA/SCL (presumably) won’t harm the Teensy 3? I’m sorry for being daft, but what exactly do you mean by “just change the pullup voltage to effect a level shift”?
 
Well if you had control over the pullups you could connect them to whatever voltage you thought was appropriate (eg. dropping the voltage to 3.3V on the pullups), but from your description it sounds like an embedded system and the pullups are inaccessible.

I would not try a 5V pullup connection directly to the Teensy.

(As an ASIC designer if I were going to try such a thing I would first attempt to figure out the ESD structure of the I/O pins. If they have reverse diodes to supply then a resistive connection to 5V would be fine, but they may be using grounded-gate NMOS or similar for ESD, in which case a sustained high voltage could conceivably blow an input gate.)

Take a look at this app note from NXP, if you have some discrete NMOS you can try this:
http://www.nxp.com/documents/application_note/AN10441.pdf
 
Thanks for the link! I copied this design https://www.sparkfun.com/datasheets/BreakoutBoards/Level-Converter-v10.pdf and I used the same MOSFETs, so I suppose the thing should be working correctly.

Well if you had control over the pullups you could connect them to whatever voltage you thought was appropriate (eg. dropping the voltage to 3.3V on the pullups), but from your description it sounds like an embedded system and the pullups are inaccessible.

Meddling with the SMD resistors on the circuit board would not be my first choice, but if nothing else works - why not give it a try :)
 
I examined the circuit board again: Only the SDA line has an external pullup (4,7k), the SCA line is probably pulled up internally by the controller. Seems a little curious to me…
Furthermore did a little testing with the logic analyser, but I can’t figure why the acknowledging isn’t working.

Schema.png

Especially this example (Controller & Teensy) confuses me:

cor.png

The terminal read out is:

Code:
I RSTART ASR
I SR D:00
^S:20
^S:20 –x-
The first ACK after the address is seemingly working, so the Controller sends the next byte. But this byte should be 10000000 and not 00000000. And after that I can’t interpret what’s happening.
 
I examined the circuit board again: Only the SDA line has an external pullup (4,7k), the SCA line is probably pulled up internally by the controller.

It may be that the SDA is driven as normal via resistive pullup but the SCL is driven as a CMOS signal. In a single-master setup with no clock stretching by the slave I suppose that could work, but it probably isn't a good way to do it.

One thing that I notice in the 3rd line in your 1st plot, is the fractional send by the master, it only sent 5 out of 9 clocks. At that same point of time I also notice that the SDA and SCL both going high at the same time. What might be happening is that the SDA is slower than SCL and if the Master sees SDA rising-edge while SCL is already high (because SDA is slightly lagging) it might interpret it as a STOP. Really SDA and SCL should not be changing at the same time.

Your 2nd plot is similar, only 7 out of 9 clocks on the last byte.

It could be that the capacitive loading is different than with the LCD so the edge behavior is different. You might try slowing SCL down a little bit (add some cap) to see if the problem goes away.

The terminal read out is:

Code:
I RSTART ASR
I SR D:00
^S:20
^S:20 –x-
The first ACK after the address is seemingly working, so the Controller sends the next byte. But this byte should be 10000000 and not 00000000. And after that I can’t interpret what’s happening.
This isn't enough diagnostics to tell what is going on. The lines beginning with '^' are the SDA rising-edge ISR attempting to detect STOP conditions (for reasons too long to describe, the Slave mode needs help in this regard, so there is a separate ISR).

What is shown doesn't indicate a problem, did it output anything else? It may not have as this situation (partial byte received) is a bit unusual (it is actually difficult to generate this kind of error without some kind of bit-banging I2C wherein one can create malformed signals on purpose).
 
Thanks for that idea! I'll try it tomorrow; what scale would you suggest, some pF?

I would try something like that. If you had access to a scope you might be able to take a look at the problem edges and see what is going on a little better, but if not then iterating through some small cap values like that might work. It might be tricky - if you slow SCL too much it might fix the rising-edge SDA/SCL and give you a problem on the falling-edge SDA/SCL (if SDA falling-edge occurs when SCL is high it would create a START signal).

I'm not sure exactly if that is what is going on, but I can't think of other things that would kill a transmission in the middle of a byte.
 
I've uploaded a new version (v5) of the I2C library, it has the following changes:

  1. The zip file structure has been changed to a more standard format. Just drop the zip contents into your Sketchbook/libraries folder. Examples sketches are now under the library path, and can be found from Arduino menus under: File->Examples->i2c_t3

  2. The debug routines now use the IntervalTimer library instead of using the PIT interrupts directly.

  3. Debug routines are now completely defined in the library code, no end-user sketch code is required (other than the linker fix #include for rbuf).

  4. Added a new Slave mode whereby Slaves can operate over a range of Addresses. This allows a device to act multiple targets on the bus, or as a general catch-all for any unmapped address. This is done via the following:
    • A new begin() function: begin(mode, address1, address2, pins, pullup, rate);
      whereby the Slave will respond to the range given by address1 to address2 (high/low addr order can be either way).
    • Also: getRxAddr() This function is for use in the Slave callback functions. It returns the target address of the incoming I2C command. This is so devices which are acting as multiple targets can decide what to do based on the incoming target address.
  5. I've included two new example sketches:
    • slave_range - this Slave-mode sketch uses the above Slave address range method to capture all incoming traffic on the bus. It will output any receive data on Serial, and will respond to Read requests from the Master by sending zeroes. (note: this is not a sniffer since it will respond to the traffic, I haven't figured out a way to make the hardware behave as a sniffer)
    • scanner - this is a Master-mode sketch. It will scan the entire 7-bit I2C address range and report all devices which ACK. It will output the results to Serial.
  6. Fixed a bug in the ISR timeout code, which in certain conditions the timeout would not properly reset on the next command.

  7. Fixed a bug in Slave mode in sda_rising_isr attach, whereby it was not getting attached on the Addr byte.

Also, just a note for anyone using the Slave address range method above - I would avoid addresses from 0x00-0x07, and 0x78-0x7F. Those are special/reserved according to the I2C spec (things like General Call, High-speed mode, 10-bit mode, etc - refer to the spec for details).
 
Last edited:
I think I figured it out … sort of:

teensy_clock_stretch.png

The Teensy seems to stretch the clock after the acknowledgment of the address byte, while the master is trying to give the first two clock signals for the second byte anyway (A). Master and slave are then asynchronous, i.e. at point B the master is awaiting acknowledgment for byte 2, but the Teensy has missed two clock pulses and instead keep SDA low for acknowledgment a point C, while the master is applying its internal pull up in order to transmit 1 (the second bit of the third byte).
So far, so bad (for my project). What puzzles me is that the master seems to support clock stretching when communicating with the original LCD (I’ve seen it not right after the address byte, but later during the data bytes).
Furthermore the first bit of the second byte should be 1, but SDA isn’t high during the clock pulse. Is it kept low like SCL during clock stretching? Unfortunately I can’t understand the inner workings of the library concerning this aspect.
 
It's a little odd that the Master doesn't recognize the stretching. The voltage seems adequately low. First thing I would do, if you haven't already, is recompile with the debug routines turned OFF. The debug adds latency into the ISR, and while stretching is not explicitly done, my suspicion is that the I2C hardware will keep the ACK low until the data is read, so that latency could be the cause of the problem.

Overall the latency is very high (about ~10us), so something seems a bit wrong here. What version of the lib are you running, and what is your slave begin() command?

I'll have to hook up the logic analyzer and see if I can benchmark some different setup conditions. I'm pretty sure when I was running my back-to-back setup at the highest rate I wasn't getting 10us between data bytes. A longer term goal I had was to try to speed optimize the ISR (eventually using DMA) but I haven't had a chance to get to that. Let me know your setup command and I'll see if I can do some testing.
 
Deactivating the debug routine didn't change much. I then scraped most of the program, that brought a little improvement, but still at least one clock pulse is being suppressed.
The command is: Wire.begin(I2C_SLAVE, 0x38, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_400);
Thanks for the continued help!
 
I just wanted to say a big thank you to Brian and to Paul of course, for all your hard work. The tools and information you've provided here are extremely useful and it's great to see people still identifying shortcomings and working to fix them. In case the previous posts haven't made it abundantly obvious, I would like to highlight to people still using the older Wire library that they should download and use the i2c_t3 library if they are having trouble with I2C communication using the “Read word protocol” (see figure 5-13 of SMBus specifications etc.). This library helped me immensely in this area!
 
Glad it worked for you. And I wanted to mention to Natri that I haven't forgotten about your problem. I'm still working to analyze and/or optimize the latency between bytes. I know it's been weeks, but I've been absolutely crushed under work from my day job, so it's going to take me a bit more time.
 
I think I figured it out … sort of:

View attachment 575

The Teensy seems to stretch the clock after the acknowledgment of the address byte, while the master is trying to give the first two clock signals for the second byte anyway (A). Master and slave are then asynchronous, i.e. at point B the master is awaiting acknowledgment for byte 2, but the Teensy has missed two clock pulses and instead keep SDA low for acknowledgment a point C, while the master is applying its internal pull up in order to transmit 1 (the second bit of the third byte).
So far, so bad (for my project). What puzzles me is that the master seems to support clock stretching when communicating with the original LCD (I’ve seen it not right after the address byte, but later during the data bytes).
Furthermore the first bit of the second byte should be 1, but SDA isn’t high during the clock pulse. Is it kept low like SCL during clock stretching? Unfortunately I can’t understand the inner workings of the library concerning this aspect.

I finally got a chance to look at this a little bit. Indeed the clock stretching after the received byte is due to the ISR interrupt time. I typically see ISR times of about 3.5us with some a bit lower down to 2us. For a Slave this can also be highly variable depending on what is in the onReceive/onRequest callbacks.

I coded up a small bit-banged Master which uses the SCL in a forced mode (CMOS output, no pullups, and no clock stretching), and I can see a similar type of response as you show above. It was very easy to corrupt or drop clock bits which messed up the whole state of the Slave. You mention that the LCD was clock stretching on data, but you also mention that only SDA had a pullup. Given what I've seen, I'm inclined to think the Master in your setup might be intentionally putting out stretched clocks (perhaps to meet some LCD timing criteria), and that it doesn't actually support true clock stretching. I'm thinking this because I see the same thing as you when I use a forced SCL.

Unfortunately what this all comes down to is I don't think there is a fix for you using the code as-is right now. The operations in the ISR are necessary and in a particular order for proper functioning, and I don't see a lot of optimization potential. However that said, there might be some benefit from a DMA approach, but I'll have to spend some time studying that.

----------

One other thing I did notice - the small delay between bytes is due to the ISR, but the Slave also seems to enforce a particular bit timing given whatever baud rate it is initialized with. So if a Slave is initialized at 100kHz and a Master tries to talk to it at 400kHz it will stretch every bit. Therefore it is important to have a Slave match or exceed the Master rate.
 
Last edited:
Hi nox, Paul, et. al,

Thanks, nox, very much for this library!

I'm currently trying to get a MLX90620 16x4 IR sensor talking with a Teensy 3.0.
The old code for this used what looks to be an outdated i2c lib, so I'm trying
to get it up to date. I've gotten some of the stuff to work, but am flummoxed
by various attempts at reading the IR sensor array from RAM. The thing that has
me hung up is a command to the sensor at Slave Address 0x60. The command format is

0x60; //slave address
0x02; //read RAM command
0xYY; // start address of ram to read
0xZZ; // byte step to make- this seems to be 1 for more than one address to be read, there's an MSB and LSB
0xWW; // number or addresses to read (two byte chunks, it seems, highest value shown is 0x40, 64,(16x4)

Sending it a bunch of writes of the appropriate values doesn't work, even for
the one value read that the old code uses.

I was able to get a similar command (read bytes in eeprom) to work by doing thusly:

____________
Wire.beginTransmission(0x50); // slave address of eeprom chip on IR reader
Wire.write(0x00); //dump all eeprom command
Wire.endTransmission();

//this was put inside a read loop, and I read everything just fine.
Wire.requestFrom(0x40,1,I2C_NOSTOP);
theDatum = Wire.readByte();
________

One other caveat I just noticed, these guys say to specify the Slave address (0x60)
plus a write bit (0xC0)- looks like it needs another zero bit before continuing? If
I put in C0 for the address I get an "slave address not acknowledged", so it does look like it's
talking to 0x60 when it's set to such...any way to slip in another bit at the end of that
before the next (command)byte?

They say:

1. Start condition (Falling edge of SDA while SCL is high)
2. Slave address (SA=0x60) plus write bit = 0xC0
3. Command = 0x02
...


The datasheet for the sensor is
http://www.melexis.com/Assets/Datasheet-IR-thermometer-16X4-sensor-array-MLX90620-6099.aspx
if any kind soul wants to get a better look at the commands.

Any and all help, greatly appreciated. Beer is offered. More details if you need them, just ask.

Thanks.
 
Oh, in case it isn't clear

Wire.beginTransmission(0x60); // slave address (sensor)
Wire.write(0x02); //command
...more relevant command bytes

Wire.endTransmission();

this will be at least recognized as a valid slave address. If I try to sneak in an 0xCO after the beginTransmission, it gives me a slave address error.

thx
 
Back
Top