I2C frequency disparity in wireless sensing network

I am using a pair of Wixels to create a wireless link between an accelerometer (measuring vibrations up to 1KHz of a system) and an oscilloscope for a lab on my campus. The idea is that one Wixel will sample the accelerometer (at around 5KHz), fill up an internal packet buffer, and send the data to the receiver Wixel. I set up timer 3 to overflow and call an ISR every 200us (5KHz frequency, 5x the maximum input frequency I expect) in which I sample the accelerometer (using adcRead() 12-bit resolution) and add the sample to the packet buffer (I encode 2 12-bit readings and place them in 3 8-bit buffers of the packet, and when I receive the data on the other Wixel, I decode the original sample). When the packet buffer is full, I set a flag in the ISR, and my main loop catches that flag and calls my sendPacket() function (I wrote a new radio protocol library, which optomized the system to dedicate one wixel as the transmitter and one as the receiver).

The receiver Wixel is connected to a DAC which uses the I2C protocol to update its analog output voltage (it is a 12-bit resolution DAC). The problem that I am having is this: I am using the same timer (timer 3) to call an ISR in my receiver Wixel. When the ISR fires, the Wixel sends the next sample to the DAC (in essence, I am repeating the samples that were sent over on the radio at the same rate they were sampled, but with a delay). I am worried that using I2C to transfer the data to the DAC is too slow. I set the I2C frequency to the highest setting (500KHz), but it appears that I am spending nearly the whole 200us inside of the ISR. I suspect this is the case because after I added the i2c function calls in my ISR, my main loop is not running quickly enough to call usbComService() often enough to let me get into bootloader mode from a usb command. Before I added the i2c function calls, I was able to start the Wixel in bootloader mode every time from the Wixel Configuration Utility. What I suspect is happening is that the i2c function calls added too much overhead to my ISR, and now I am spending nearly the entire 200us inside of my ISR, and nearly no time in my main loop.

I tested the I2C code trying to find the root of the problem, and found that if I only call i2cStart() immediately followed by i2cStop() (this is within the ISR), the code runs as expected. However, as soon as I add one or more calls to i2cWriteByte(uint8 byte), I get the problem where I am not calling usbComService() often enough in my main loop to get into bootloader from the Wixel Configuration Utility.

My theory is that there is a serious difference between the i2c frequency set and the actual frequency the i2c library communicates with peripherals is. Can anyone answer if this is the case? If so, is there is any way to speed up the i2c library, or do I need to implement a new i2c library which optomizes the system for the set up I have so that the i2c communication speed is improved?

As a first guess, I saw that the i2c library calls the setDigitalInput() and setDigitalOutput() functions quite often. In the gpio library, it mentions that these calls take ~3.2us to complete. I am using 3 i2cWriteByte() calls in my ISR, could this be the source of the delay in communication speed? (if each call to setDigital…() takes 3.2us, but the bus frequency is 400KHz = 2.5us per clock tick, could this be the problem?)

Thank you tremendously for your help. I understand the post here was long but I wanted to appropriately educate the reader on the set-up I am trying to implement so I may get the best help.

Hello, hamiltonlittle.

I’m glad you have been able to do so much with the Wixel!

One thing to note though: you should be careful about using Wixel libraries from interrupts. It takes some effort to make a library be interrupt-safe, and if we don’t claim it’s interrupt safe in the documentation then you could have serious problems when you try to use it in an interrupt.

Yes, this is the case. As we say in the documentation for i2c library, “the actual frequency might be lower than the selected frequency”.

Yes. I think the main cause of overhead in the i2c library is the fact that it gives you complete control over which pins to use. As a result, it must store the pin identities in variables and every time it wants to do some operation on a pin it has to read the variable from memory and pass the value to a function in the GPIO library, which uses the pin identity to calculate the location of the code that actually carries out the operation on the pin. You could make a faster i2c library if you manipulated the pins directly by using the proper SFRs.

Still, the CC2511 is not a great processor for doing high-speed I2C because it has no hardware support for I2C. It does have a hardware I2S interface, which is a protocol commonly used to transmit audio to DACs. I don’t know much about I2S, but if you got an I2S DAC and connected it to your Wixel, I think you should be able to send data to it with less CPU overhead.

If all you’re doing with the DAC output is looking at it on an oscilloscope, then there is another option that would probably be easier: you could stream the data to your computer over USB (using the library we have that makes the Wixel be a USB virtual COM port). You could then graph the data in a spreadsheet program or write your own software to display it.

This sounds like a cool project, so let us know how it goes!

–David

The I2C library was written with the assumption that there is no overhead causing any additional delay beyond the intentional timing delays. You are right that it can generate an actual frequency that is significantly slower than the frequency that was requested; with the current implementation, the maximum attainable frequency seems to be about 40 kHz. We will look into updating the documentation to explain this limitation, and I would like to try to change the code to compensate for the overhead so that any selected frequencies below 40 kHz match with the actual frequency more precisely.

- Kevin

Thank you for the quick responses.

To Kevin:

I was able to write an optomized implementation of an i2c protocol and am achieving ~400KHz speed (top end speed useable by my DAC). If you would like to see the source code I can send it, however it is a very stripped down version of the full i2c protocol.

This seems to be the source of the problem. I made two changes to increase the speed:

  1. All delays are taken care of by calling assembly NOPS (in the SDCC manual it laid out how to define a NOP). I originally called a function which had the NOPS inside it, and I was getting a clock speed of ~150KHz.
// Added these #defines to make code-reading easier
#define SCL P1_0
#define SDA P1_1
...
// Example code: send a 1 to the DAC
// data line high, clock high, clock low with delays in between
SDA = 1;
i2cDelay(); 
SCL = 1;
i2cDelay();
SCL = 0;
i2cDelay()
...
// i2cDelay function definition
void i2cDelay()
{
    // 6x24MHZ clock cycle pause (arbitrarily picked)
    // plus overhead of calling i2cDelay()
    NOP; NOP; NOP; NOP; NOP; NOP; 
}

To achieve the higher speed, I replaced the i2cDelay() calls with NOPS (removing the overhead of calling another function).

// Added these #defines to make code-reading easier
#define SCL P1_0
#define SDA P1_1
...
// Example code: send a 1 to the DAC
// data line high, clock high, clock low with delays in between
SDA = 1;
NOP; NOP; NOP; NOP; NOP; NOP;
SCL = 1;
NOP; NOP; NOP; NOP; NOP; NOP;
SCL = 0;
NOP; NOP; NOP; NOP; NOP; NOP;
  1. I manipulated the pin registers directly, rather than calling setDigitalOutput() and setDigitalInput().
// I used
SDA = 0;
// instead of
setDigitalOutput(11, 0);

My version may not be appropriate for the distributed SDK because it does not allow for clock stretching or easily configurable speeds, but these changes did significantly increase the i2c transfer speed. Including these (and the other necessary i2c functions) may not be possible with the approach I used.

To David:

The wixel has been great to use. It integrated the wireless functionality called for with a microcontroller I could use to be the “brains” of the system. Also, the small size of the Wixel is perfect for my set up. There is a restriction on the size and shape of the package I can attach to the vibrating system I am measuring, so using a microcontroller with a larger footprint was out of the question.

I discussed this with the professor I am working under, however, part of the experience students gain from the lab is that they can change the input frequency from a signal generator, and see the results immediately in front of them on a tool standard to the field (the oscilloscope. The experiment has a signal generator hooked up to a shake table, with a cantilevered beam at the end. We measure the input amplitude and frequency at the restrained end of the beam and the free end to find the natural frequencies of the beam, among other things). The idea is to make the implementation of how the measurement system is set up in the lab as transparent as possible, so the students focus on the objectives of the lab. We determined that introducing a computer would take away from the experience of students learning how to use and interpret the information displayed an oscilloscope.

It is WAY cool, thank you. I have a prototyped set up on a bread board, and now that the i2c transfer is fast enough, I have every component working correctly!