New I2C library for Teensy3

All, I've uploaded a new version of the library (v6) which has the following changes:

  • It now includes Teensy 3.1 support, including the 2nd I2C interface (I2C1) which works the same as Wire but is called Wire1.
  • The Wire1 interface can be configured to use pins 29/30 or pins 26/31. Unfortunately both sets of these are on the backside surface mount pads. Refer to the top post and code examples for information on setup.
  • Added a new header define to allow the library to be compiled as a single interface (I2C0-only) for cases where code/ram space is tight.
  • Also added some interrupt "flag" defines, which when set will toggle pins high when the I2C interrupts occur. This can be useful as a logic-analyzer trigger, or in determining interrupt timing.
  • Added two new examples, specifically for Teensy 3.1: quad_master and dual_bus_master_slave.
Great work on this library! I have created a slightly renamed version of it, called FastWire (from a suggestion somebody else made). The naming is the only difference. Everything else is as you wrote it so far. :) I hope to try it out soon.

Have you considered getting a GitHub.com or Bitbucket.org account and using source control for your stuff?

8-Dale
 
Have you considered getting a GitHub.com or Bitbucket.org account and using source control for your stuff?

I do not currently have a github or otherwise similar setup yet. The reason is more historical and has to do with my development setup. Most of my heavy-use programs are "portablized", in that I run almost everything off a USB flash drive. This allows me to plug into almost any computer and do work without the hassle of installing things. It includes the bulk of my devel apps (including big stuff like Eclipse, WinPython, Code::Blocks, wxFormBuilder), with the exception of a few big ones which just can't be setup that way (Visual Studio, Atmel Studio, CodeWarrior, DesignSpark, etc...). I also run a Virtualbox Linux install off the drive also, but I mostly use that for Verilog work, not this stuff.

Anyway point being, until within probably the last year, there wasn't any good portable SCM that fit into my flow. I did have a beta Mercurial portable, but I never got going with it. Now there appears to be a portable Git: http://code.google.com/p/msysgit/downloads/detail?name=PortableGit-1.8.5.2-preview20131230.7z
I might try utilizing that on future releases if I get it all figured out.

Regarding the name, it was chosen because Wire is really a rather bad name for the library (even the Arduino guys think so). When I first heard it I thought it was referring to 1-Wire, obviously a very different thing. A more accurate name would either be I2C or TWI. The suffix is just because it is highly specific to the Teensy3 (well actually the Freescale K20-series, there is no AVR code) so a generic name like just "i2c" could be confusing to someone with say a Teensy2 (these are after all shared forums).

I may in the future rename the lib, whenever a Teensy4 or Teensy3++ or whatever comes out, and at that time I might ask for names. Not sure if it would be FastWire though as it is still very generic. In fact there is already a Fastwire on github, which predictably is AVR code. One might be tempted to stick ARM in the name somewhere, but on github that would probably be confusing to users of other similar parts (the I2C registers in the K20 are nothing like in say the Atmel SAM or NXP LPC parts even though they are all ARM Cortex-M3/4 parts).
 
I ran into a tiny bug in V6 i2c_t3.h:

Code:
// Interrupt flag - uncomment and set below to make the specified pin high whenever the
//                  I2C interrupt occurs.  This is useful as a trigger signal when using a logic analyzer.
//
#define I2C0_INTR_FLAG_PIN 8
#define I2C1_INTR_FLAG_PIN 9

Took me a bit of time to find out why I was having trouble using pin 8 for something else. Looks like these two #defines were intended to be commented out.

Thanks for the great work nox771.
 
I ran into a tiny bug in V6 i2c_t3.h:

Code:
// Interrupt flag - uncomment and set below to make the specified pin high whenever the
//                  I2C interrupt occurs.  This is useful as a trigger signal when using a logic analyzer.
//
#define I2C0_INTR_FLAG_PIN 8
#define I2C1_INTR_FLAG_PIN 9

Took me a bit of time to find out why I was having trouble using pin 8 for something else. Looks like these two #defines were intended to be commented out.

Doh! Sorry, you are right, it was supposed to be commented out. Thanks for pointing it out. I've removed the previous v6 rev and uploaded a fixed version.
 
Problems with an LSM303D

Hi,

I'm having serious problems with an LSM303D. In the first version of my code I didn't have error checking, and the bus seemed to hang from time to time. So I added error checking. This is what I'm doing now:

Code:
static const uint8_t SLA = 0x1D;
static const uint16_t timeout = 100000; // this was 100, but I wanted 100 ms (!)
bool LSM303D::begin()
{
  // configure
  Wire.beginTransmission(SLA);       // slave addr
  Wire.write(0x20);               // memory address (CTRL1)
  Wire.write((0b0101 << 4) | (1 << 3) | 0b111) ; // accel normal mode, 50 Hz, block data update, all axes enabled
  if (Wire.endTransmission(I2C_STOP, timeout) != 0)
  {
    return false;
  }

  Wire.beginTransmission(SLA);       // slave addr
  Wire.write(0x24);               // memory address (CTRL5)
  Wire.write((0b11 << 5) | (0b100 << 2) | 0b111) ; // magn high resolution, 50 Hz
  if (Wire.endTransmission(I2C_STOP, timeout) != 0)
  {
    return false;
  }

  Wire.beginTransmission(SLA);       // slave addr
  Wire.write(0x25);               // memory address (CTRL5)
  Wire.write(0b00 << 5); // magn full scale: +-2 gauss
  if (Wire.endTransmission(I2C_STOP, timeout) != 0)
  {
    return false;
  }

  Wire.beginTransmission(SLA);       // slave addr
  Wire.write(0x26);               // memory address (CTRL5)
  Wire.write(0); // magn enable
  if (Wire.endTransmission(I2C_STOP, timeout) != 0)
  {
    return false;
  }
  return true;
}
I also have code for reading values from the chip, but begin() already fails with a timeout error. The wiring is correct, the pullups are correct as well - otherwise the previous code without error checking wouldn't have worked. My first question is: am I using the i2c_t3 methods correctly in the first place?

Here is my reading code:
Code:
bool LSM303D::read()
{
  // read accel
  Wire.beginTransmission(SLA);
  Wire.write(0x28 | 0x80);           // read accel, continuous
  Wire.endTransmission(I2C_NOSTOP, timeout);
  Wire.requestFrom(SLA, 6, I2C_STOP, timeout);
  uint8_t i = Wire.available();
  std::array<uint8_t, 6> buf;
  if(i == 6)
  {
    for(uint8_t i = 0; i < 6; i++)
    {
      buf[i] = Wire.readByte();
    }

    ... process data in buf ...

  }
  else
  {
    for (uint8_t j = 0; j < i; j++)
    {
      // empty buffer and discard data
      Wire.readByte();
    }
    return false;
  }

  ... more read operations ...
}

To be honest, I'm not sure which version of the library I'm using. The last modification note in i2c_t3.h is
- Modified 09Jun13 by Brian
EDIT: I have downloaded the latest Version and it works slightly better, with occasional lockups lasting until I cycle power.

Summed up questions:
  • Am I using the i2c_t3 methods correctly?
  • Has anyone used the LSM303D? What might cause it to hang?
  • What can I do to "unhang" the bus again? I know that is a complex issue (it already was on AVRs), but there might be readily available code that attempts to it on a Teensy 3.0
Regards

Christoph
 
Last edited:
Summed up questions:
  • Am I using the i2c_t3 methods correctly?
  • Has anyone used the LSM303D? What might cause it to hang?
  • What can I do to "unhang" the bus again? I know that is a complex issue (it already was on AVRs), but there might be readily available code that attempts to it on a Teensy 3.0
I have an LSM303DLHC that is part of the 10DOF IMU Breakout Board from Adafruit. It seems to work fine with the standard Wire library for both regular Arduino and TeensyDuino, although I am still testing this to be sure everything is right. Some of my orientation readings seem to be off with the Teensy 3.1. I have not tried using this new I2C library for the Teensy 3.1 yet so don't know if there are any differences.

8-Dale
 
I'm not sure how different they are, but the LSM303DLHC is different from the LSM303D. They definitely have different interfaces. I can also try a different library altogether, does the Wire library work with the Teensy 3.0? I've never tried it.
 
I'm not sure how different they are, but the LSM303DLHC is different from the LSM303D. They definitely have different interfaces. I can also try a different library altogether, does the Wire library work with the Teensy 3.0? I've never tried it.
Try the LSM303 library from Adafruit. That's what I have been using for both Arduino and Teensy 3.1, with the standard Wire libraries for both. However, I am not sure it is returning correct results for the Teensy 3.1. My orientation readings (pitch, roll, and heading) aren't correct for the Teensy 3.1. Maybe you can get better results using this new I2C library. I would definitely like to hear about your results with the Adafruit LSM303 library using the new I2C stuff.

8-Dale
 
Last edited:
There are several ways to hang the bus, one is to hang a slave device, another is to get the hardware to think it has sent an I2C STOP when in fact it did not (this case would not be something that is your fault, it would be the ISR code).

I have personally seen the I2C interface mangle the STOP when doing a receive using max baud rates (2400 setting), in fact I had to insert a delay in the latest rev (v6) to help with this. It is possible your problems are related to the STOP that occurs at the end of a Tx or Rx, although I have never seen this occur on a Tx, and your begin() function is only Tx. There have been other people complaining about sporadic hangs, so if this could be identified as the cause I would be interested to know.

For Rx problem I have seen, what is happening is that it is trying to change from a Rx mode back to Tx (as happens at the end of a receive) too quickly, which confuses the I2C hardware. I had a test case at the 2400 rate that would cause this fairly reliably which is what I used to figure out my delay patch. However if this is happening at lower rates also then it will have to be fixed.

Some questions:
  • What baud rate are you using?
  • Can you be more specific about the hang conditions:
  • When it hangs, is it exiting your begin() with a false, or it is hanging inside the begin() function? If it is STOP related it will hang inside the begin(), specifically on an endTransmmission() call, because it will think the bus is busy and will be waiting inside that call.
  • If it does exit begin() with a false, which Tx command does it hang on (first, second, third, fourth?) and what is the I2C error return value?
  • Do you have any kind of a logic analyzer or scope dump of the I2C traffic? Your commands are short, so if you trigger on the START (SDA falling-edge while SCL high) then you should be able to capture the entire command.

One thing I notice right off:
static const uint16_t timeout = 100000; // this was 100, but I wanted 100 ms (!)
You have over-ranged this. The largest delay you can get with a uint16 will be 65535us. If you really want a timeout that long I can try to rework the routines to maybe support milliseconds. I did not expect people would use timeouts that long.

I'm having serious problems with an LSM303D. In the first version of my code I didn't have error checking, and the bus seemed to hang from time to time. So I added error checking. This is what I'm doing now:
...
I also have code for reading values from the chip, but begin() already fails with a timeout error. The wiring is correct, the pullups are correct as well - otherwise the previous code without error checking wouldn't have worked. My first question is: am I using the i2c_t3 methods correctly in the first place?
The code looks ok at first glance. If you can provide more information I'll think about it some more. I am not familiar with a LSM303D, I'll need to pull a datasheet on it. If we can't identify a solution then I can probably acquire one and experiment on it. What specifically are you using?

Has anyone used the LSM303D? What might cause it to hang?
Slaves can hang if the clocking gets somehow confused between Master and Slave. For instance on a bit-banged Master (not what we are using here, just an example), if the Master drives the SCL line as a CMOS line, not a open-drain, then it will not recognize Slave clock-stretching. So on a Rx command (Slave sending data to Master) the Slave thinks it is stretching the clock and the Master keeps banging away, they become completely out-of-sync and eventually the Slave ends up getting a partial clock and hangs in the middle of transmitting a byte. At that point you are done, the bus is hung. There are other similar ways, basically anything that ends up holding SCL or SDA low indefinitely will hang the bus.

The problem I described at the top is an internal problem due to the I2C hardware. Basically the ISR tells it to send a STOP, it doesn't do it (but it thinks it did), and on the next command it hangs because SCL/SDA is low, it thinks its no longer the Master, and it hangs waiting for the "other" (non-existant) Master to release the bus.

What can I do to "unhang" the bus again? I know that is a complex issue (it already was on AVRs), but there might be readily available code that attempts to it on a Teensy 3.0
Well the real solution is to not hang the bus in the first place. The less correct (but probably equally workable) solution is for single-Master setups. In the i2c_t3.cpp file look for lines like this (I think there are two spots):
Code:
// we are not currently the bus master, so wait for bus ready
while(*(i2c->S) & I2C_S_BUSY);
Comment it out, it will no longer hang waiting for the bus to release. I would be interested to know if that works, I haven't tried it. If it does, it implies there is some condition where the STOP isn't properly being sent.
 
  • What baud rate are you using?
I'm running the bus at 400 kHz, this is my call to begin():
Code:
Wire.begin(I2C_MASTER, 0x00, I2C_PINS_16_17, I2C_PULLUP_EXT, I2C_RATE_400);
  • When it hangs, is it exiting your begin() with a false, or it is hanging inside the begin() function?
begin() was hanging in the first call to endTransmission(), which I didn't expect at all, because I thought that the timeout would make endTransmission() return after the given time.
  • Do you have any kind of a logic analyzer or scope dump of the I2C traffic?
Unfortunately, I don't.

Well the real solution is to not hang the bus in the first place. The less correct (but probably equally workable) solution is for single-Master setups. In the i2c_t3.cpp file look for lines like this (I think there are two spots):
I'll try that, thanks for that! It's a single-Master setup and it will stay that way. I might more slaves, though, but that shouldn't make a difference (my experience is that more slaves *will* make a difference).

The board I'm using is the LSM303D board from pololu

I'll do some more testing and will come back when I can tell what call exactly is hanging during reading, because begin() is quite reliable now.
 
begin() was hanging in the first call to endTransmission(), which I didn't expect at all, because I thought that the timeout would make endTransmission() return after the given time.
This is very suspicious. I'm assuming that you start with a power-cycle on the Teensy3. If so, the hardware cannot think that it is a busy bus, so it should never hang on the first Tx call. There is no prior I2C activity to it hanging on the first call to endTransmission() is that right?

If it is truly hanging on the first call, and the first call occurs right after a power-cycle then this implies to me that a slave device is hung and is holding the bus low. If that is the case there is no fix other than to reset the slave device via power-cycle or whatever.

I have had this situation occur myself when I get a slave hung and my normal upload/power-cycle procedure does not include the slave device (eg. it stays hung).

Looking at the schematic I can see that the board has integrated 4.7k pullups (plus a level-shift system). Out of curiosity, do you have many other things connected on the same bus? Are there other pullup devices in parallel, and if so are the voltages matched?

If the bus capacitance is too high (or pullups too large) it could cause the pulses to be truncated (shark fins vs square waves). That could possibly cause problems also. One thing to test this - if you change the bus speed to 100kHz does anything about the behavior change? Does it still get stuck?

The board I'm using is the LSM303D board from pololu
Ok, well if we can't get your system stable I'll look into getting one of these. I use this library in my own stuff, so I'm definitely interested in making it reliable.

I'll do some more testing and will come back when I can tell what call exactly is hanging during reading, because begin() is quite reliable now.
Ok, more information is better. Another thing to try - if you can isolate the problem to a repeatable case, say the first Tx to occur after a Rx, then just after the Rx (prior to the Tx), you can have the system pause, and then probe SCL/SDA with a voltmeter. They should both be high. In the general case, if either line is low after a STOP has been issued (at the end of any Tx or Rx command, except when I2C_NOSTOP is used), then something is wrong.
 
OK, some more information about my system:
  • Currently the LSM303D is the only slave device on the bus.
  • Power cycling means disconnecting the Teensy's USB cable and reconnecting it. There are no huge caps or batteries in my circuit, USB is the only way to supply my circuit with power.
  • The pololu board is powered with 3.3V via VDD, not VIN. That means that the pullups added by pololu shouldn't be pulling the bus high on the Teensy side.
  • I have external 4k7 pullups on the Teensy side of the board.

With the newest version of the library the hang during begin() disappeared. Now it hangs occasionally during read() calls, which I tracked with debug messages over Serial. This is my current read() code, where I marked the last debug message that is printed (100% reproducible with 100 kHz and 400 kHz bus speed):
Code:
bool LSM303D::read()
{
  // read accel
  Serial.println("LSM303::read() 1"); Serial.flush();
  Wire.beginTransmission(SLA);
  Wire.write(0x28 | 0x80);           // read accel, continuous

  Serial.print("LSM303::read() 2"); Serial.flush();
  if (Wire.endTransmission(I2C_NOSTOP, timeout) != 0)
  {
    Serial.println("error"); Serial.flush();
    return false;
  }

  Serial.println("\nLSM303::read() 3"); Serial.flush(); // <========== When it hangs, this is the last message
  if (Wire.requestFrom(SLA, 6, I2C_STOP, timeout) == 0) // <========== So this must be the bad one that doesn't return
  {
    Serial.println("error"); Serial.flush();
    return false;
  }
  uint8_t i = Wire.available();
  std::array<uint8_t, 6> buf;
  if(i == 6)
  {
    for(uint8_t i = 0; i < 6; i++)
    {
      buf[i] = Wire.readByte();
    }
    ... data processing ... 
  }
  else
  {
    for (uint8_t j = 0; j < i; j++)
    {
      // empty buffer and discard data
      Wire.readByte();
    }
    return false;
  }

  // read magn
  Serial.println("LSM303::read() 4"); Serial.flush();
  Wire.beginTransmission(SLA);
  Wire.write(0x08 | 0x80);           // read magn, continuous

  Serial.println("LSM303::read() 5"); Serial.flush();
  if (Wire.endTransmission(I2C_NOSTOP, timeout) != 0)
  {
    Serial.println("error"); Serial.flush();
    return false;
  }

  Serial.println("LSM303::read() 6"); Serial.flush();
  if (Wire.requestFrom(SLA, 6, I2C_STOP, timeout) == 0)
  {
    Serial.println("error"); Serial.flush();
    return false;
  }
  i = Wire.available();
  if(i == 6)
  {
    for(uint8_t i = 0; i < 6; i++)
    {
      buf[i] = Wire.readByte();
    }
    ... data processing ...
  }
  else
  {
    for (uint8_t j = 0; j < i; j++)
    {
      // empty buffer and discard data
      Wire.readByte();
    }
    return false;
  }
  return true;
}

I couldn't probe SCL and SDA prior to the call that hangs, because I only have my breadboard here and literally no tools at all. This would be very tedious work, as the board happily runs in an endless loop (about 30 loops per second) for a few minutes before hanging. Probing the lines in every loop would drive me insane, and I'm sure the results wouldn't be correct either.

Regards

Christoph
 
Ok, no tools will make it more challenging, so it might take some additional trial and error.

These kinds of intermittent bugs are really the worst kind. The fact that there is apparently no dependence on baud rate makes me wonder if there is some subtle timing problem occurring during the C1 mode changes (specifically if the ISR running at 96MHz is changing the I2C peripheral registers too quickly). For it to hang on a requestFrom() when using a timeout, there is only one possibility - I2C_C1_MST must be not set (which should be impossible, since just prior you did a Tx with a NOSTOP), and it must be hanging waiting for the I2C_S_BUSY flag to clear. Ah, wouldn't JTAG be nice right about now :p

Can you comment out this line in the sendRequest_() function, and tell me if anything changes:
Code:
void i2c_t3::sendRequest_(struct i2cStruct* i2c, uint8_t addr, size_t len, i2c_stop sendStop)
{
    // exit immediately if request for 0 bytes
    if(len == 0) return;

    i2c->reqCount=len; // store request length
    i2c->rxBufferIndex = 0; // reset buffer
    i2c->rxBufferLength = 0;

    // clear the status flags
    *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL;

    // now take control of the bus...
    if(*(i2c->C1) & I2C_C1_MST)
    {
        // we are already the bus master, so send a repeated start
        I2C_DEBUG_STR("RSTART"); // Repeated START
        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX;
    }
    else
    {
        // we are not currently the bus master, so wait for bus ready
        [COLOR="#FF0000"][B]//while(*(i2c->S) & I2C_S_BUSY);[/B][/COLOR]
        // become the bus master in transmit mode (send start)
        I2C_DEBUG_STR("START"); // START
        i2c->currentMode = I2C_MASTER;
        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
    }

    // send 1st data and enable interrupts
    i2c->currentStatus = I2C_SEND_ADDR;
    i2c->currentStop = sendStop;
    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr
    uint8_t target = (addr << 1) | 1; // address + READ
    I2C_DEBUG_STR("T:"); I2C_DEBUG_HEX(target); I2C_DEBUG_STRB("\n"); // target addr
    *(i2c->D) = target;
}

If this can be narrowed to the cause, I2C_C1_MST being wrong, then I can work backwards and try to find out how it is getting cleared.

You mention you have no test equipment available at the moment, do you happen to have a spare LED and resistor? Might be able to use that in combination with digitalWrite() to a pin as a signal probe.

Also regarding the timeout, I rechecked it, the i2c code is already using a uint32_t type on the timeout delays, so it can handle 100ms already. Just alter your timeout definition to use uint32_t:
Code:
static const uint32_t timeout = 100000; // this was 100, but I wanted 100 ms (!)
 
Nox, have a break!

I have a different suspect that might be related to the LSM303, but not to your code: I have timed code that only runs if the acceleration derivative exceeds a certain threshold. This code is in an ISR that apparently is always entered between messages 3 and 4 (where the I2C code seems to hang) and I will check if this might cause the hang. Your code is more mature than mine, so chances are that the bug is in mine.

Commenting out this/these (I found 2) loops didn't change the behavior:
Code:
//while(*(i2c->S) & I2C_S_BUSY);

I've already fixed the uint16_t to be a uint32_t, this was a copy/paste error. I got acompilerwarning as soon as I changed the constant to a value bigger than 65535.

Regards

Christoph
 
Nox, your code works perfectly. Thank you for your support!

This is what happened: When my device is moved, the LSM values are filltered differently than while it is more or less still. This special filter had an integer overflow after 32768 samples, which caused a division by zero, causing my device to "die". The timing was such that this always occured between my debug messages 3 and 4. The threshold for triggering this special filtering was a bit too low, so that it kicked in even when I was typing on my table with the breadboard next to me. This is why it occured after a seemingly random time. What a debugging adventure...

Regards

Christoph
 
Hi guys, i'm facing problems using the i2c_t3 code from within an IntervallTimer callback (need that for sampling).

The following part of code causes the application to hang:
i2c_t3.cpp:
Code:
uint8_t i2c_t3::finish_(struct i2cStruct* i2c, uint32_t timeout)
{
	deltaT = elapsedMicros();
	int i =0;
   [B] while(!done_(i2c) && (timeout == 0 || deltaT < timeout)) ;[/B]
 	
...

}

done_(i2c) never gets true because ic2-->status is WAITING.
This is NOT happening when calling the same functions from outside the IntervallTimer callback.

I called endTransmission without a timeout, if called with, the following beginTransmission fails because
of the ic2-->status WAITING.

Any ideas, suggestion what i code to to narrow it furhter down? Maybe a known issue?

Thanks in advance, Andi

By the way: really great work nox :)

I2C Bus init:
Code:
   Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_800);

Request device:
Code:
 Wire.beginTransmission(devAddr);
 Wire.write(regAddr);
 Wire.endTransmission();
 Wire.beginTransmission(devAddr);
 Wire.requestFrom((int) devAddr, (int)min
 
Last edited:
done_(i2c) never gets true because ic2-->status is WAITING.

Well, something here is off, if status is WAITING then done_() will return true (it is one of the true conditions).

This is NOT happening when calling the same functions from outside the IntervallTimer callback.

My first thought is that IntervalTimer is interrupt based, and so is i2c_t3. I'm not totally clear on how ARM ISRs handle interrupting each other, but on many micros this would be a blocking problem (IntervalTimer ISR blocking the i2c_t3 ISR). I don't know what timing tolerances you need, but one way to unblock it is to move the waiting part out of the ISR, and instead use the ISR to set flags.

Something like this:
Code:
volatile uint8_t flag=0;

void timerISR()  // <-- IntervalTimer ISR
{
    flag=1;  // set flag based on IntervalTimer timing
}

void loop()  // <-- main loop
{
    if(flag)
    {
        // do I2C stuff here
        flag=0;
    }
}

This unblocks the i2c_t3 ISR, and will function largely the same as putting the code in the ISR, except with somewhat more timing variability depending on what else is in the main loop. However if you put wait states into ISRs, you are in a sense already introducing a lot of timing variability.


Code:
 Wire.beginTransmission(devAddr);
 Wire.write(regAddr);
 Wire.endTransmission();
 [COLOR=#ff0000]Wire.beginTransmission(devAddr);  // <-- this line is unnecessary, it is only for Transmit, not Receive[/COLOR]
 Wire.requestFrom((int) devAddr, (int)min

FYI - You don't need the line above. A lot of people do this, but beginTransmission is actually just a Tx buffer setup routine. This is really the fault of bad function naming on the original Arduino API.
 
Thanks for your answer!

Well, something here is off, if status is WAITING then done_() will return true (it is one of the true conditions).
I will double check this, maybe as was just a bit confused because of to less coffee and to much debugging ;)

My first thought is that IntervalTimer is interrupt based, and so is i2c_t3. I'm not totally clear on how ARM ISRs handle interrupting each other, but on many micros this would be a blocking problem (IntervalTimer ISR blocking the i2c_t3 ISR). I don't know what timing tolerances you need, but one way to unblock it is to move the waiting part out of the ISR, and instead use the ISR to set flags.
Timing is rather critical because the code collects samples to run a FFT on it. Allready tried your suggestion before but (without further optimization) results are not very promising.



FYI - You don't need the line above. A lot of people do this, but beginTransmission is actually just a Tx buffer setup routine. This is really the fault of bad function naming on the original Arduino API.
Good point. The line was not in my original version of code by searching around the web i found this line (or similar) in a lot of code so i tried it out: same result.

I'm not sure but i think i read a posting of you where you wrote that you moved some routines to be handled by interrupt routines, is there a version of ic2_3t without ISR?
Apart from that i will go a bit deeper into ARM ISR handling, maybe i could work out a solution :confused:
 
I'm not sure but i think i read a posting of you where you wrote that you moved some routines to be handled by interrupt routines, is there a version of ic2_3t without ISR?

For Master mode I think the original Wire library does not use ISR, however currently i2c_t3 always uses ISR.

Now that you bring this up though I'm thinking I'll go back and perhaps add a compile flag which would allow optional compile of Master mode as a non-ISR method. Actually I could do it that way or I could just add on another function (or overload), to indicate Tx/Rx without ISR (eg. immediate and blocking). It will possibly take some time before I can do this.

As an immediate fix, you could take the original Wire library, and graft on some of the setup code from i2c_t3, as far as setting the rate. Regarding your earlier begin() line, ext pullup and pins 18/19 are already the defaults IIRC, so you only need to patch in the rate adjustment to get it to more/less match what you are doing.
 
Another option, you can leave the code as-is, and try altering the interrupt priority levels. I haven't tried this before in my code, but perhaps it is an easy fix.

From this post, I saw the following:
The NVIC implements interrupt priority levels, from 0 to 255 (however, only the upper 4 bits are implemented for 16 different levels, so 0-15 are the same, 16-31 are the same, and so on). The lower the number, the higher the priority. By default, all interrupts are priority 0, which means no other interrupt is allowed until it returns. The NVIC keeps track of the current priority level as interrupts are run and when they return. So it doesn't matter if you use sei() inside your interrupt routine (or an ISR macro which puts a SEI instruction early in the function epilogue). The NVIC doesn't allow another interrupt of the same or lower priority until your function returns. If you want to use this feature to allow some interrupts to interrupt others, instead of fiddling with state at run-time, you just assign each interrupt a priority ahead of time. The NVIC and ARM hardware automatically manages everything.

So, it looks like the original thought is probably right, the ISRs are blocking each other. Now since the IntervalTimer is PIT, and PIT has 4 interrupts, you can try altering the priority on those. In the mk20dx128.h file you will see this near the bottom:
Code:
// 0 = highest priority
// Cortex-M4: 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224,240
// Cortex-M0: 0,64,128,192
#define NVIC_SET_PRIORITY(irqnum, priority)  (*((volatile uint8_t *)0xE000E400 + (irqnum)) = (uint8_t)(priority))
#define NVIC_GET_PRIORITY(irqnum) (*((uint8_t *)0xE000E400 + (irqnum)))

Also defined in there are the IRQ number definitions. So perhaps something like the following in your setup() routine:
Code:
void setup()
{
    ...
    // probably do this after Wire and IntervalTimer setup (just so nothing resets to defaults)
    NVIC_SET_PRIORITY(IRQ_PIT_CH0,16);
    NVIC_SET_PRIORITY(IRQ_PIT_CH1,16);
    NVIC_SET_PRIORITY(IRQ_PIT_CH2,16);
    NVIC_SET_PRIORITY(IRQ_PIT_CH3,16);
    // and just for completeness
    NVIC_SET_PRIORITY(IRQ_I2C0,0);
    NVIC_SET_PRIORITY(IRQ_I2C1,0);
    ...
}
I would be interested to know if something like that works.
 
It works :D

By setting the I2C IRQ Priority the code works without issues.
Code:
NVIC_SET_PRIORITY(IRQ_I2C0,0);

It doesn't matter if you change the NVIC Priority before or after the start of the intervall timer but i think set the priority before is more safe.

I have no success setting the Priority of the PIT IRQ below (which means higher value) the I2C IRQ priority...

Best Regards, Andi
 
Last edited:
Recently I added code to the hardware serial objects write() functions, to detect if their main program code is running at the same or higher priority than their interrupt code. I must admit, this started as something of an experiment. So far, the results seem very positive. Perhaps someday the various USB interfaces, Wire library and this improved I2C library might someday do the same?

The idea is to automatically fall back to automatically fall back to polling the hardware, because you know the interrupt you'd normally depend upon can't happen. It's tricky to get right, and certainly not an optimal way to do things. But if the user has structured their program in such a way that they're calling functions to transmit data while running within an interrupt, hogging the CPU to successfully complete the transmission is FAR better than a deadlock.

I put a nvic_execution_priority() function into the core library some time ago. It does the thorny work of figuring out the current priority level. It's not perfect, attempting a balance between speed and accuracy, but I'd highly recommend using this function if you try to implement this feature. In the future, as Teensy3 develops as a platform, my goal is to keep that function as fast as it can be, but if more complexities of deducing the ARM NVIC priority level are needed to make Arduino programs "just work" even in the cases where users do lots of I/O inside interrupts, that nvic_execution_priority() function will grow to accommodate the wider platform needs.

Using it just involves comparing the return value from nvic_execution_priority() with the priority your own interrupt uses. If it's less or equal, you can know your interrupt can't happen. Figuring out what to do in that case is tricky. For example, here's Serial3:

Code:
void serial3_putchar(uint32_t c)
{
        uint32_t head;

        if (!(SIM_SCGC4 & SIM_SCGC4_UART2)) return;
        head = tx_buffer_head;
        if (++head >= TX_BUFFER_SIZE) head = 0;
        while (tx_buffer_tail == head) {
                int priority = nvic_execution_priority();
                if (priority <= IRQ_PRIORITY) {
                        if ((UART2_S1 & UART_S1_TDRE)) {
                                uint32_t tail = tx_buffer_tail;
                                if (++tail >= TX_BUFFER_SIZE) tail = 0;
                                UART2_D = tx_buffer[tail];
                                tx_buffer_tail = tail;
                        }
                } else if (priority >= 256) {
                        yield(); // wait
                }
        }
        tx_buffer[head] = c;
        transmitting = 1;
        tx_buffer_head = head;
        UART2_C2 = C2_TX_ACTIVE;
}

You can see it falls back to manually checking the TDRE bit in the status register and manually takes a byte out of the full buffer and shoves it into the UART data register if the transmitter is ready. Normally you'd carefully disable interrupts when doing this sort of thing, but obviously in this case you know the interrupt can't occur.

Eventually I'm going to try doing something similar in the Wire library, the many USB interfaces, and other code that transmits data using interrupts. In fact the main point of this message is to serve as a reminder to myself to someday work on this (when I have lots of extra time.....)

I have no idea if this would be possible for this improved I2C library, but perhaps it's something to think about?
 
Paul, I have to admit, I am now thoroughly confused.

Starting with Codefella's comment, changing the I2C priority to 0 fixes things for him (in which it appeared the PIT and I2C priorities were equal, so PIT was blocking I2C), but according to your earlier post here, the default priorities are already 0, so this by itself should not have changed anything. Are the default priorities set to something else? Could you perhaps direct me to where in the main code priorities are originally getting set so I can try to understand it? I think I'll have to study that and your Serial3 code for a while.

Further I was completely unaware that the main loop even had a priority level. I can understand it in terms of nested interrupts, but for the main loop it confuses me. Is this for some kind of RTOS/preemption system?

I can try to add in an adaptive transmit/receive system for the Master mode, but obviously it will take some time and studying. I seem to recall somewhere that ARM has a manual describing the NVIC, is that the place to read up on this?
 
Hi nox771,

Headroom suggests you might understand this problem I am currently having with the Wire.h TWI library. I know that one possible solution is to try out your new I2c_t3 library. Will it work on an AVR device like the 3.3V 8 MHz Pro Mini (ATMEGA 328-P)? I'd like to maintain compatibility between Arduino and Teensy, if possible. Here is a description of my problem:

The Invensense MPU-9150 (and -9250) is a 9DoF motion sensor that combines a 6 DoF gyro/accelerometer, the MPU-6050, with an Asahi Kasei AK8975A hall-sensor magnetometer into a single device. The microcontroller can access the magnetometer directly by placing the combined MPU-9150 device into a bypass mode through a CNTL register write that connects the internal AK8975A device to the MPU-9150 TWI bus. It is then directly addressed through its own 7-bit device address just like any other I2c device on the bus. This works perfectly well for the simple I2c.h and I2cdev.h TWI libraries but for some reason, when using Wire.h, the AK8975A is no longer accessible after the initial read of its WHO_AM_I register. This latter behavior is seen on both a 3.3V 8 MHz Pro Mini and on the Teensy 3.1. Does anyone have an idea what could be happening here?
 
Back
Top