Shop OBEX P1 Docs P2 Docs Learn Events
i2c speed [C] — Parallax Forums

i2c speed [C]

sconyscony Posts: 14
edited 2015-06-18 10:20 in Propeller 1
Howdy,

today I've started developing propeller library for LSM9DS0 intertial module using i2c functions from simpletools.h.

First thing I did, I've performed a read of 2-byte temperature value. As long as I read from 2 registers, I've made two i2c_in() calls.
What I've found, is: each i2c_in() call takes 2.5ms. In total, my function takes 5.1ms to execute. I'm wondering why this i2c is so slow ?

I've made some in-depth research of simpletools.h sources, and for now I see a possibility of tiny grow.

In general, I have to perform a read from 18 registers to obtain desired values. This is not acceptable to wait like 18*2.5 ms for this.

How can I tune i2c speed in propeller ?

Here's a fragment of my code:
byte lsm9ds0_readByte(int device, int registr)
{
    byte data;
    i2c_in(lsm9ds0_eeBus, device, registr, 1, &data, 1);
    return data;
}

void lsm9ds0_readTemp()
{
  lsm9ds0_temp = lsm9ds0_readByte(LSM9DS0_ADDRESS_XM, LSM9DS0_TEMP_OUT_L_XM);
  lsm9ds0_temp |= lsm9ds0_readByte(LSM9DS0_ADDRESS_XM, LSM9DS0_TEMP_OUT_H_XM) << 8;
}

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2015-06-13 10:19
    There's a lot of overhead when you read a group of registers individually. In addition to the code being executed, the I2C device has to have a register address selected which requires writing to the device, then the device has to be switched to read mode, etc. Most multi-register I2C devices have an autoincrement mode, sometimes by default, where you select the initial register to be read, then you read a block of bytes and get successive register contents. Check your device's datasheet for details. When you call i2c_in, give the lowest register number you want and the starting address of a buffer of the proper size (18 probably in your case) along with a byte count of 18.

    Even if the device has more than 18 registers with some extra ones mixed in with the 18 ones you want, it may be faster to read in 20 or 24 or whatever as a single block than to do several i2c_in calls to get only the 18 ones you want.
  • sconyscony Posts: 14
    edited 2015-06-13 10:54
    So in above case you are suggesting:
    void lsm9ds0_readTemp()
    {
      byte data[2];
      i2c_in(lsm9ds0_eeBus, LSM9DS0_ADDRESS_XM, LSM9DS0_TEMP_OUT_L_XM, 1, data, 2);
      //LSM9DS0_TEMP_OUT_L_XM + 1 = LSM9DS0_TEMP_OUT_H_XM
      lsm9ds0_temp = data[0];
      lsm9ds0_temp |= data[1] << 8;
    }
    

    I've tried this earlier and it's not working as expected (probably due to i2c_in's internal implementation).
    After execution data[0] == data[1]. Seems like code above reads only one register two times.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-06-13 12:31
    The datasheet (not the clearest document I've seen) indicates that the auto-increment of the register address pointer is enabled by setting the MSB of the device register address to 1. In other words, use LSM9DS0_TEMP_OUT_L_XM + 0x80 for the device register address.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2015-06-13 19:28
    scony wrote: »
    Howdy,

    today I've started developing propeller library for LSM9DS0 intertial module using i2c functions from simpletools.h.

    First thing I did, I've performed a read of 2-byte temperature value. As long as I read from 2 registers, I've made two i2c_in() calls.
    What I've found, is: each i2c_in() call takes 2.5ms. In total, my function takes 5.1ms to execute. I'm wondering why this i2c is so slow ?

    I've made some in-depth research of simpletools.h sources, and for now I see a possibility of tiny grow.

    In general, I have to perform a read from 18 registers to obtain desired values. This is not acceptable to wait like 18*2.5 ms for this.

    How can I tune i2c speed in propeller ?

    Practically all devices these days operate at 400kHz or faster while still supporting the old 100kHz mode. So at 400kHz your time should be way better than what you have been getting. Now, not to compare "languages", but more to set the bar with I2C speeds and "realtime" speeds in general since we are using a microcontroller and not a PC, this is what I get in Tachyon which is only bit-bashing in byte code, no assembler or LMM/CMM etc.

    For the test I first create two general functions for the device, one to read a byte from a register with REG@ and one to read to a buffer using a starting register with RDREGS.
    Straight from the serial terminal this is the code for reference and the times are shown at the end:
    : REG@ ( reg -- byte ) I2CSTART $D4 I2C! I2C! I2CSTART $D5 I2C! 1 I2C@ I2CSTOP ;  ok
    : RDREGS ( reg buf cnt -- )   I2CSTART $D4 I2C! ROT $80 OR I2C! I2CSTART $D5 I2C! ADO 0 I2C@ I C! LOOP 1 I2C@ DROP I2CSTOP ;  ok
      ok
    0 LAP REG@ LAP .LAP  193.600us ok
    0 BUFFERS #20 LAP RDREGS LAP .LAP  969.200us ok
    

    Using LAP <function> LAP to capture the time and .LAP to display the result we read a single random byte and that takes less than 200us. To read 20 bytes takes less than 1ms. Looking at this I can see that these times could be even better than this too.
  • sconyscony Posts: 14
    edited 2015-06-14 01:49
    Well, Mike, I will try with autoincrement later on. It sounds promising. Arduino library also reads few bytes at once so thats probably correct approach.

    However, I have timed a read of 8 byte string from i2c EEPROM and it took like 6.9ms. I think it is still much. Or maybe it is EEPROM that bottlenecks ?

    Peter, in other words you have developed your own solution on the bytecode level. Well, how you managed to eliminate builtin i2c lib overhead?

    And finally, apart from groupped read and reimplementing i2c lib, are there another ways to speed up i2c?
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2015-06-14 01:54
    scony wrote: »
    Well, Mike, I will try with autoincrement later on. It sounds promising. Arduino library also reads few bytes at once so thats probably correct approach.

    However, I have timed a read of 8 byte string from i2c EEPROM and it took like 6.9ms. I think it is still much. Or maybe it is EEPROM that bottlenecks ?

    Peter, in other words you have developed your own solution on the bytecode level. Well, how you managed to eliminate builtin i2c lib overhead?

    And finally, apart from groupped read and reimplementing i2c lib, are there another ways to speed up i2c?

    Sorry, Tachyon is Forth, not C, but I referenced it here to show that this is the kind of speed that should be possible from C, but I'm not sure what the bottleneck is in i2c.lib.
  • sconyscony Posts: 14
    edited 2015-06-14 02:15
    Thanks Peter. This is still very useful information. So, I aim for 20 bytes under 1ms in C. I will perform fixes and try to reach it out.
  • Hal AlbachHal Albach Posts: 747
    edited 2015-06-14 09:21
    Using my Saleae Analyzer on the I2C bus on the Propeller Project Board USB I was able to obtain the following readings:

    When the propeller is reset and loads 32KB from its EEPROM it does the entire transfer in 1.233 seconds.
    The SCL frequency is around 266 KHz.
    One byte cycle takes 37 microseconds and includes the gap between bytes.

    When I use a C program utilizing i2c_in & i2c_out to transfer 32 bytes from a 24LC32A EEPROM I observed the following:

    32 byte read took 0.2737 seconds to complete.
    SCL frequency is around 18 KHz.
    One byte cycle took 0.546 milliseconds.

    If you can find the I2C routines used by the propeller internally you should be able to reach your goal. Have you considered SPI? Seems like it would be much faster.

    From what I gathered regarding the LSM9DS0 data sheet, you won't be able to read all the registers sequentially because the gyroscope and the accelerometer have two separate I2C addresses. This will require setting up two I2C reads to get it all.

    Hal
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-06-14 11:26
    It's surprising that I2C is that slow in C. Did you compile with -Os optimization and the LMM memory model? Maybe you compiled with no optimization or CMM memory model.
  • sconyscony Posts: 14
    edited 2015-06-14 12:03
    Well, thank you. Sounds promising. I will try to boost i2c as far as I can. And yes, I know I have to read from few places. This is what scares me the most... It will add a lot of overhead. However, to reach higher sampling rates I am always able to use that SPI. I have been considering this, but also I have been trying to avoid it. Now I see, I should implement SPI in my driver aswell as i2c.

    For lower data rates I will use i2c, but in case of need of higher data rates I will use SPI.

    Dave, I am also suprised. I got -Os but I am not sure of model. I will chceck this out.
  • Hal AlbachHal Albach Posts: 747
    edited 2015-06-14 14:00
    Dave,
    I did compile with Os and CMM. Reran the readings after compiling with O2 (speed) and LMM and HOLY Smile WHAT A DiFFERENCE!!!!!

    The readings:
    32 byte transfer (read) took 3.834 milliseconds vs 0.2737 seconds.
    SCL Frequency was rock solid at 100 KHz vs 18 KHz.
    One byte cycle with gap is 0.105 milliseconds vs 0.546 milliseconds.

    I had no idea those two changes in compilation would be so dramatic!

    Code size increased by 3000 bytes using O2 & LMM.
  • sconyscony Posts: 14
    edited 2015-06-15 09:03
    Dave, yes, I compiled with -Os and CMM... With LMM I performed a read of 8 byte string from EEPROM in 1.3ms vs 6.9ms (CMM). Memory model explains a lot. Thank you so much !!

    However speaking my library, I ran into another problem: region `hub' overflowed by 468 bytes Seems like I overflow hub's 32K. Most of space is utilized by simpletools right ?
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-06-15 11:00
    The linker should pull in only the object modules that you reference from the simpletools library. You might be able to do a few things to reduce the code size. If you can post your code we could provide suggestions on reducing the code size.

    Another possibility is to stick with the CMM memory model, but use an I2C driver written in PASM to get higher speed I2C performance. You can port an existing Spin/PASM I2C object to C by using spin2cpp.
  • sconyscony Posts: 14
    edited 2015-06-15 14:25
    Reduction of my code may be problematic as long as it overflows memory while there is like 3% of target code wrote for now.

    I think, I'll try to port PASM lib. I have seen it, but I had no idea, I can convert it to C.

    I will also try SPI out just to see, how fast it is. When I finish, I will post brief summary & timings so we can close this discussion.
  • RaymanRayman Posts: 14,652
    edited 2015-06-15 15:37
    I posted a Spin/Assembly driver for that chip recently.
    Another path might be to use Spin2cpp to convert that to C or C++...
  • ersmithersmith Posts: 6,053
    edited 2015-06-15 17:26
    For LMM mode there's little difference between -Os and -O2, but for CMM mode they can be quite different. -Os will optimize for size, and in CMM will inhibit the fcache optimization (because fcache uses LMM instructions). -O2 optimizes for speed, and so allows loops to go in fcache, where they will run *much* faster than regular CMM code.
  • sconyscony Posts: 14
    edited 2015-06-17 10:34
    Well, I finally tuned i2c speed for my library.

    I would like to thank all of you. This community is really fantastic.

    The solution was to use another implementation of i2c. I've used PASM i2c Driver (ported to C).

    As suggested, I've used spin2cpp (with --ccode to get C sources).
    It's worth to mention that in my case, LSM9DS0 uses 1B for register address and 1B for each register's data. It forced me to modify PASM i2c Driver's code to use 1B register addresses only (as Dave Hein suggested in PM).
    It's also worth to mention, that usage of PASM i2c Driver is different from simple i2c only in fact that the device address had to be shifted left by one bit (as Mike Green suggested in PM) as long as there must be space for read/write bit (due to lower level implementation).

    Timings summary:

    - read of 2 bytes separately with simpletools i2c (CMM -Os): 5.108 ms
    - read of 2 bytes separately with simpletools i2c (LMM -Os): overflow (binary too big) //but there is huge potential
    - read of 2 bytes with i2c auto increment and with simpletools i2c (CMM -Os): 3.109 ms
    - read of 2 bytes with i2c auto increment and with PASM i2c Driver (CMM -Os): 0.21 ms

    - read of 1 byte with simpletools i2c (CMM -Os): 2.5568 ms
    - read of 1 byte with simpletools i2c (CMM -O2): 2.5562 ms

    Due to great PASM i2c Driver's performence I skip SPI research. I've made only simple comparison on Arduino:
    - read of 20B via i2c: 4.37 ms
    - read of 20B via SPI: 0.74 ms
  • SRLMSRLM Posts: 5,045
    edited 2015-06-17 14:35
    libpropeller has a nice C++ I2C implementation that uses inline FCACHE'd assembly to get the maximum speed without wasting a cog. I've written code to interface to the LSM303DLHC and the L3GD20, which are roughly compatible predecessors to the LSM9DSX series.

    https://github.com/libpropeller/libpropeller/blob/master/libpropeller/i2c/i2c.h

    I've never felt the I2C limitations with this code.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2015-06-17 18:36
    scony wrote: »
    - read of 2 bytes separately with simpletools i2c (CMM -Os): 5.108 ms
    - read of 2 bytes separately with simpletools i2c (LMM -Os): overflow (binary too big) //but there is huge potential
    - read of 2 bytes with i2c auto increment and with simpletools i2c (CMM -Os): 3.109 ms
    - read of 2 bytes with i2c auto increment and with PASM i2c Driver (CMM -Os): 0.21 ms

    - read of 1 byte with simpletools i2c (CMM -Os): 2.5568 ms
    - read of 1 byte with simpletools i2c (CMM -O2): 2.5562 ms

    Due to great PASM i2c Driver's performence I skip SPI research. I've made only simple comparison on Arduino:
    - read of 20B via i2c: 4.37 ms
    - read of 20B via SPI: 0.74 ms

    Something is still wrong with the speed, it is still "slow" as it seems to be running with less than 100kHz throughput perhaps or maybe that is as much steam as it has. You remember that my timings were less than 1ms for 20 byte read from a random address and even then I didn't feel it was as fast as it could be even though it was plain bytecode, not PASM. If for instance the drivers were assuming that they have to run at 100kHz then I think that this is an incorrect assumption as that is a legacy speed for legacy chips and it's a bit like running a USB port at legacy 12Mb speeds just in case, you wouldn't do it. Many EEPROMs run at speeds of 1Mb or more but certainly the LSM9DS0 runs at 400kHz.
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-06-17 19:40
    The pasm_i2c_driver is designed to run at 400 KHz. When reading it will send out the device address and 2 additional address bytes, and then send the device address again before reading the data bytes. This was modified to send only 1 additional address byte, so reading 2 bytes will take 5 byte times. At 400 KHz that works out to 0.1 ms. The actual measured time of 0.21 ms is probably due to the extra overhead of the CMM code.

    I'm not familiar with the LSM9DS0, so I don't know if there is a more efficient way to address it than sending the three device/address/device bytes. The pasm_i2c_driver can be sped up by adjusting a delay value. It should be able to run at 1 MHz without any problems.
  • sconyscony Posts: 14
    edited 2015-06-18 02:15
    Well, I've timed that 0.21ms with default setting as long as its enough for me.
    DELAY_CYCLES  = 52           ' I2C Delay time.  Must be between 12 and 511
    

    I believe, that changing 52 to 12 aswell as changing CMM to LMM may provide 1MHz. I will provide more timings later on.

    EDIT: changing 52 to 12 haven't helped
  • RaymanRayman Posts: 14,652
    edited 2015-06-18 10:20
    If you really want to speed up read speed, I'd suggest a driver with custom code for the LSM9DS0, like this one:
    http://forums.parallax.com/showthread.php/161040

    The PTP2_i2cDriver2a.spin contains an assembly driver with codes especially for reading that chip at high speed.

    There's a lot of other things you could remove from the Spin section if you only need to talk to that chip.
Sign In or Register to comment.