Author Topic: i2c bus weirdness on Linux  (Read 3170 times)

0 Members and 1 Guest are viewing this topic.

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14596
  • Country: fr
Re: i2c bus weirdness on Linux
« Reply #25 on: April 30, 2022, 08:45:50 pm »
Using a decent logic analyzer (doesn't even have to be expensive) is still much more comfortable usually. (And a scope doesn't always have protocol decoding functionalities - sometimes comes with expensive options.) While a true scope is often more comfortable to use than a PC-based scope for all other tasks, for logic analysis, I find PC-based solutions beat the crap out of a scope. Having a decent GUI with a large screen and mouse is a lot more productive for this.

I use a DSView Plus for most logic analysis tasks and it's a time saver really.
 

Offline hpmaximTopic starter

  • Regular Contributor
  • *
  • Posts: 132
Re: i2c bus weirdness on Linux
« Reply #26 on: April 30, 2022, 11:06:31 pm »
I thought I had posted a response, but it doesn't appear I did.

I'm a bit confused as to why there is so much consternation over using a Scope.  I'm using a scope, because I have a scope.  While, I realize that a logic analyzer might be the "ideal" tool, given the advanced triggering functionality and decoding on the Rigol I see no real benefit to a logic analyzer over the scope.  I have a 500MHz HP scope, but it's older and I don't use it, because it's bigger, has less memory, has a monochrome display, no USB, less advanced triggers, no decoding etc.  In short, if you are operating below 100MHz, the Rigol is better in pretty much every way imaginable (I wish it had WiFi though).  I didn't buy the scope because I was looking for analyzer, I bought it because I was looking for a newer, more modern scope.  I have plenty of older HP logic analyzers, which despite the fact that they have tons of functionality (I have a 1661 and a 16500B with pretty much every plug in module you can imagine), I consider them boat anchors.  They're big and require a lot of effort to setup and use.

Nominal Animal...  The device in question is an ST LSM6DSM.  Datasheet can be found here: https://www.st.com/resource/en/datasheet/lsm6dsm.pdf

Page 43 states: "The slave address is completed with a Read/Write bit. If the bit is ‘1’ (Read), a repeated START (SR) condition must be issued after the two sub-address bytes; if the bit is ‘0’ (Write) the master will transmit to the slave with direction unchanged."

I'll take it you think that's the operative sentence there... That sending the write followed by a separate read is a violation that the ioctl(I2C_RDWR) corrects, rather than either the I2C_SLAVE or I2C_RDWR were invalid I2C messages?

Side question...  Is there a way to adjust SCL?  I appear to be transmitting at 100kHz, and that actually seems to occupy a lot of time.  Pretty much everything seems to support 400kHz.

FWIW, I've now transmitted approximately 22M samples over the I2C bus without an issue.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 829
Re: i2c bus weirdness on Linux
« Reply #27 on: May 01, 2022, 04:18:56 am »
In reply #13, this is what I was getting at (from the datasheet)-
Quote
If a slave receiver doesn’t acknowledge the slave address (i.e. it is not able to
receive because it is performing some real-time function)...
Since you were not checking the return values in your original post, any failure of the device to ack an address in the read/write transactions along with your code proceeding as if it did, could get you some strange results. For example, a write that was not address ack'd, the following read that succeeds is now returning some 'other' register value if register increment is enabled (default). Or if the read did not address ack, you are now (presumably) reading 'inbuffer' which also would then contain invalid data.

Which could explain-
Quote
The MEMS device has been acting erratically in an intermittent way
Its probably unusual for the mems device to nack the address, but its possible (says the datasheet). If your 'new' code is checking return values, then its not really a 'fair' comparison even though the new code may have solved some other problem.

Your original posted code could be tested with checking the return values, and acting accordingly. This could be a bad 'diagnosis' by me, but is easy enough to test. If wanted.

 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6342
  • Country: fi
    • My home page and email address
Re: i2c bus weirdness on Linux
« Reply #28 on: May 01, 2022, 03:41:09 pm »
For what it is worth, I think using a scope to check the transitions and levels first is a good approach.  When those are ruled fine, using a cheap logic analyzer with sigrok's Pulseview –– for which you can write your own protocol analyzers in Python, if it does not already have one; it does for I2C –– means you can catch even rare errors by running it continuously for quite some time.  And I suggested this before OP posted the scope traces, showing their scope already has a I2C protocol analyzer.

It's not like there is just one way to do this stuff.

Also, I kinda-sorta assumed hpmaxim did check read() and write() return values, because in Linux, they have to be.  Only when you do not care if the data is read or written at all or if an error occurs, is it okay to not check the return value.  (The ioctl(fd, I2C_RDWR, &op) returns the number of successfully completed messages, not the byte count.)  I sometimes have heated arguments with people who claim it shouldn't be necessary with say ordinary files; I say it always is necessary, because even running out of disk space is something you need to be prepared to deal with (and cannot verify reliably beforehand, TOCTOU).  I hate the idea of a program I'd use just silently ignore an error, losing or garbling my precious data.

The device in question is an ST LSM6DSM.  Datasheet can be found here: https://www.st.com/resource/en/datasheet/lsm6dsm.pdf

Page 43 states: "The slave address is completed with a Read/Write bit. If the bit is ‘1’ (Read), a repeated START (SR) condition must be issued after the two sub-address bytes; if the bit is ‘0’ (Write) the master will transmit to the slave with direction unchanged."

I'll take it you think that's the operative sentence there... That sending the write followed by a separate read is a violation that the ioctl(I2C_RDWR) corrects, rather than either the I2C_SLAVE or I2C_RDWR were invalid I2C messages?
Yes, exactly.

If you think about how the state machine is implemented in the LSM6DSM, it is much easier to require the write and the following read to be a single transaction; otherwise, it would have to save the sub-address bytes somewhere, and deal with partial sub-address bytes and such.

Now, it is a completely separate question whether I'm right or wrong.  As a honest uncle bumblefug hobbyist, I'm often wrong.
(I love being pointed out exactly how I'm wrong, because that's the best opportunity to learn.  I only get irate, when I'm told I'm wrong, but not exactly why or how.  I can't learn anything from that, just wastes my time forcing me to re-examine what I said..  and as nctnico recently noted elsewhere, I have my own pitfalls I can get stuck into.)

What cv007 wrote does apply, I believe.  It's just that because I believe the separate transactions produces unreliable results in all cases anyway, I focused on the I2C_RDWR ioctl instead.  (Logic being, it is of tertiary interest at best as to what kind of "data" one obtains via the erroneous approach.  Make sure the approach itself is correct first, then deal with any coding details involved.)

The reason I posted my own interpretations of the traces is so that if my understanding is wrong, it would be easier for others to point it out.

Is there a way to adjust SCL?  I appear to be transmitting at 100kHz, and that actually seems to occupy a lot of time.  Pretty much everything seems to support 400kHz.
Yes, by modifying the Device Tree.  In your case, it would be the &i2c1 section, clock-frequency parameter (defaults to 100000).

I believe you have a /boot/dtbs/sun7i-a20-cubieboard2.dtb file.
You copy this somewhere for backup purposes, then run
    dtc -I dtb -O dts /boot/dtbs/sun7i-a20-cubieboard2.dtb > sun7i-a20-cubieboard2.dts
to decompile (the "device tree blob" back into "device tree source").
Edit it in your favourite editor (vim, emacs, nano), changing the clock-frequency = <100000>; to clock-frequency = <400000>; in the &i2c1 section, then run
    dtc -I dts -O dtb sun71-a20-cubieboard2.dts /boot/dtbs/sun71-a20-cubieboard2.dtb
to compile the source back to blob at the expected place.  Reboot, and /dev/i2c-1 should run at 400 kHz.

Before you ask: No, I do not believe there is a way to change it programmatically.  If you consider the typical use cases of single-board computers (SBCs), it is typically safer to choose the I2C bus clocks only once during boot, preferably as early as possible, and not trust individual applications or kernel I2C-using code to do it correctly.  It is easy enough to modify the device tree blob instead.
« Last Edit: May 01, 2022, 03:43:40 pm by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3923
  • Country: gb
Re: i2c bus weirdness on Linux
« Reply #29 on: May 01, 2022, 04:02:02 pm »
I do not believe there is a way to change it programmatically.  If you consider the typical use cases of single-board computers (SBCs), it is typically safer to choose the I2C bus clocks only once during boot, preferably as early as possible, and not trust individual applications or kernel I2C-using code to do it correctly.  It is easy enough to modify the device tree blob instead.

that's what I meant in my previous post, when I say that DTS is so problematic and annoying with things like SPI and i2c that I prefer to implement these functionality in an MPU and then attach the MPU to the SoC over a serial line.

Code: [Select]
._____________
|             |
|     SoC     |
| ARM64-LE-HF |
|  Allwinner  |
|             |
|  /dev/ttyS0 |==== serial line0 ==== console
|     ...     |                        _______
|             |                       |       |
|  /dev/ttyS2 |==== serial line2 ==== |  MPU  | ==== i2c IMU
|_____________|                       |_______|


For me, if I want to change the clock frequency of the SPI master, or of the i2c master, what I have to do is issuing a command over the serial

echo "clock=400Khz" > /dev/ttyS2
done  :D

Of course, you have to implement for the MPU the specific i2c driver for the device you want to use, while Linux offers a lot of stuff already done, but dealing with an MPU is (for me) easier to verify that things are correctly working because you don't have to deal with all the extra-complexity introduced by the Linux kernel + userspace.

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 
The following users thanked this post: Nominal Animal

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6342
  • Country: fi
    • My home page and email address
Re: i2c bus weirdness on Linux
« Reply #30 on: May 01, 2022, 04:37:01 pm »
that's what I meant in my previous post, when I say that DTS is so problematic and annoying with things like SPI and i2c that I prefer to implement these functionality in an MPU and then attach the MPU to the SoC over a serial line.
For non-built-in things, yes.  For built-in things, like the laptop I'm typing this on (HP EliteBook 840 G4) having a built-in accelerometer, an ST LIS3LV02DL (connected via SPI or I2C), that the Linux kernel exports as a simple input event device, the DTS is really nice.  Everything is done in-kernel (in this laptop case, as directed by ACPI tables: the x86-64 BIOS equivalent, more or less, of Linux-specific Device Tree stuff.)

It's a compromise, for sure.

For development, using a dedicated microcontroller is much faster.  So much so that if I were to develop something like an accelerometer for a laptop, I'd for sure do the very initial test prototype using an MCU.  I really like using Teensy microcontrollers and ATmega32U4-based "pro micro" clones for such, because they have native USB, lots of libraries and support, and if something like USB Serial or USB Human Interface Device suits the use case, I'll just use that, and not have to write any OS support code.  (My first microcontroller project was an arcade-style joystick and buttons embedded in a large birch plank.)

Even there, having a scope to check (especially whether the I2C pullup-pulldown resistors are correctly sized), and some kind of logic analyzer to examine the actual contents ("long term", over millions of transfers), makes a lot of sense.  In my opinion, it even tends to be fun; especially if you automate the uninteresting parts.  Maybe a 28BYJ-48 stepper with an ULN2003 driver to rock the sensor according to input provided by a test program, which also records the accelerometer data.  Maybe stick a RasPi camera module to take pictures, but store only those that turn out to be of interesting cases.

(On Youtube, Matthias Wandel often does various sorts of el-cheapo measurement contraptions like this.  Most recent one (of this writing) is comparing construction adhesives for woodworking [when your shop is too cold to use normal wood glue].)
 
The following users thanked this post: DiTBho

Offline hpmaximTopic starter

  • Regular Contributor
  • *
  • Posts: 132
Re: i2c bus weirdness on Linux
« Reply #31 on: May 01, 2022, 09:44:56 pm »

Quote
The MEMS device has been acting erratically in an intermittent way
Its probably unusual for the mems device to nack the address, but its possible (says the datasheet). If your 'new' code is checking return values, then its not really a 'fair' comparison even though the new code may have solved some other problem.

Your original posted code could be tested with checking the return values, and acting accordingly. This could be a bad 'diagnosis' by me, but is easy enough to test. If wanted.

The code I initially posted was a highly simplified version of what I was actually doing, but I think it contained the operative issue, and Nominal Animal correctly identified the issue that I should have been using I2C_RDWR not I2C_SLAVE.  I'm using test code which I was constantly tweaking...  At different times I did check or didn't check the results, however, my experience has been it either works or it doesn't, in terms of the return value.  That is whenever I have checked the return value of write() or read() if I was getting data out, the results were always correct -- even when I saw the erratic behavior.  This is a red herring.
« Last Edit: May 01, 2022, 09:47:48 pm by hpmaxim »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf