i2c speed [C]
scony
Posts: 14
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:
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
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.
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.
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:
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.
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.
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
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.
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.
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 ?
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.
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.
Another path might be to use Spin2cpp to convert that to C or C++...
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
https://github.com/libpropeller/libpropeller/blob/master/libpropeller/i2c/i2c.h
I've never felt the I2C limitations with this code.
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.
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.
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
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.