Recent

Author Topic: I2C communications using BaseUnix: How to check write (busy) status  (Read 4945 times)

witenite

  • New Member
  • *
  • Posts: 41
Hi guys,
recently I have been using a very good (in my opinion) library located here, for communication to an Analog to Digital converter via I2C
https://github.com/ATILIUS-REGULUS/ads1115_pascal_library

I am cross-compiling for a Raspberry Pi 3B+ running Raspbian. I have the library up and running and almost everything runs very well. The only problem I have found (through modifications of my own to the main program) is that when I do repeated writes to the I2C slave device, every now and then (probably 1 in 5 writes) I get either an exception (handled internally by the library) or the program locks up completely. Rapid and repeated I2C reads have no such side-effect. I have discovered that by putting in a small delay between I2C writes totally eliminates the problem, so I am quite sure the issue is that midway through a write operation, the low level kernel driver is getting hit with another write operation and falls over.

A lot of searching online has revealed very little about the BaseUnix library, and I was only able to find the following, which says nothing in terms of checking on communications status:
https://www.freepascal.org/docs-html/rtl/baseunix/index-5.html

I personally think that sticking delays like Sleep(50) in your code is very bad practice. I would prefer to poll a Busy status flag (or similar) to determine whether the kernel driver is still busy or not, prior to attempting another write. I am using the BaseUnix library for my communications, as can be seen in some of my code below (note the delay immediately after the actual writing of buffer data to I2C port).

Code: Pascal  [Select][+][-]
  1. { --------------------------------------------------------------------------------------- }
  2. Function T_ADS1115.I2C_Write_Buffer (F_Command : U_Int_8; F_Length : U_Int_8; F_P_Buffer : P_U_Int_8) : Integer; Inline;
  3. { Return last conversion result                                                           }
  4. { --------------------------------------------------------------------------------------- }
  5. Var
  6.   I2C_Buffer_A : T_I2C_Buffer_A;
  7.   IOCTL_Data :   T_I2C_IOCTL_Data;
  8.  
  9. Begin
  10.   F_Length := Min (F_Length, ADS1115_I2C_BUFFER_MAX_SIZE);
  11.  
  12.   Move (F_P_Buffer^, I2C_Buffer_A [1], F_Length);
  13.   I2C_Buffer_A[0] := F_Length;
  14.  
  15.   IOCTL_Data.Read_Write_Mode := I2C_READ_WRITE_MODE_WRITE;       // Prepare buffer data to port to I2C slave
  16.   IOCTL_Data.Command         := F_Command;
  17.   IOCTL_Data.Transaction     := I2C_TRANSACTION_I2C_BLOCK_BROKEN;
  18.   IOCTL_Data.P_Buffer        := @I2C_Buffer_A;
  19.  
  20.   Result := FpIOCtl (M_I2C_Handle, ADS1115_I2C_SMBUS, @IOCTL_Data); // Write to I2C (/dev/i2c-1)
  21.   sleep(50); // a small delay helps to eliminate I2C comms problems when multiple writes occur in short succession
  22. End;        

Any ideas as to how I can poll a busy flag or status variable using BaseUnix (or where I can find such information)?
« Last Edit: October 24, 2019, 10:42:45 am by witenite »

krolikbest

  • Sr. Member
  • ****
  • Posts: 276
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #1 on: October 24, 2019, 12:33:52 pm »
Hi,

instead of sleep (really bad solution) use GetTickCount. Here simple example:

Code: Pascal  [Select][+][-]
  1. procedure MyDelay(dt : DWord);
  2. var
  3.     tc : DWord;
  4. begin
  5.  tc:=GetTickCount;
  6.  while (GetTickCount<tc+td) do
  7.  begin
  8.   Application.ProcessMessages; //be  using it careful if you use threads.If threads simply don't use.
  9.  end;
  10. end
  11.  

MarkMLl

  • Hero Member
  • *****
  • Posts: 8563
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #2 on: October 24, 2019, 01:00:49 pm »
I've used I2C from an RPi to a piggyback Arduino-compatible without lockups. I'd not expect anything in baseunix to be generating traffic, there's always a risk that either you've got some daemon generating traffic (polling something that doesn't exist) or that the library or attached hardware is doing something unexpected... "very good library" and "it's locking up" are incompatible statements.

If in doubt get hook a Bus Pirate onto the bus and sniff what's happening.

https://hackaday.com/2016/07/19/what-could-go-wrong-i2c-edition/

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

dogriz

  • Full Member
  • ***
  • Posts: 130
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #3 on: October 25, 2019, 09:19:30 am »
I can't tell you about polling a busy flag, but some hardware issues I had with I2C. Good practise is putting pull-up resistors on both SCK and SDA. In many cases it solves "awkward" issues.
You can read about it on that link MarkMLl attached in previous post.
FPC 3.2.2
Lazarus 4
Debian x86_64, arm

MarkMLl

  • Hero Member
  • *****
  • Posts: 8563
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #4 on: October 25, 2019, 09:28:35 am »
On reflection, I think it's also important to recognise that the slave might have its own timing constraints, and might quite simply be unable to respond correctly without a pacing delay between messages.
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

witenite

  • New Member
  • *
  • Posts: 41
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #5 on: October 25, 2019, 10:20:17 am »
Hi,

instead of sleep (really bad solution) use GetTickCount. Here simple example:

Code: Pascal  [Select][+][-]
  1. procedure MyDelay(dt : DWord);
  2. var
  3.     tc : DWord;
  4. begin
  5.  tc:=GetTickCount;
  6.  while (GetTickCount<tc+td) do
  7.  begin
  8.   Application.ProcessMessages; //be  using it careful if you use threads.If threads simply don't use.
  9.  end;
  10. end
  11.  

I agree, I never like using sleep or any other form of needless delay in code. Hence the reason why I'm looking for a more effective means of determining whether or not I2C communications is ready to receive another written data packet. The code in question is indeed running in a separate thread too, so it may be dangerous using message processing as you say. I'll keep investigating.

witenite

  • New Member
  • *
  • Posts: 41
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #6 on: October 25, 2019, 10:26:04 am »
I've used I2C from an RPi to a piggyback Arduino-compatible without lockups. I'd not expect anything in baseunix to be generating traffic, there's always a risk that either you've got some daemon generating traffic (polling something that doesn't exist) or that the library or attached hardware is doing something unexpected... "very good library" and "it's locking up" are incompatible statements.

If in doubt get hook a Bus Pirate onto the bus and sniff what's happening.

https://hackaday.com/2016/07/19/what-could-go-wrong-i2c-edition/

MarkMLl

I don't believe this is a problem with the library. The library does a very good job of managing the ADS1115 converter IC communications, however if there is insufficient documentation on BaseUnix (which is the lower level interface the library relies upon for I2C communications) then this may explain why I can't get it to work reliably. I have searched thoroughly online, but can find no relevant documentation or any mention of detecting when a write operation has completed by the I2C hardware interface. I come from a low level (embedded) programming background, and every serial hardware device I have used has at the very least a busy flag (and often also an error flag or more) that can either be polled or generate an interrupt upon a read/write cycle completion. the kernel and/or BaseUnix should therefore provide access to said flags.

To clarify, the application is up and running. It performs a single write at the start of execution (to configure the device) and thereafter performs continuous reads of the analog to digital conversion (buffer) register. I have tested and concluded that there is no issue with a read/write collision, however it definitely does not like two writes in quick succession of each other. With the small delay introduced the problem goes away completely. I have hammered the I2C with the small delay instated, and have not had a single exception or crash, and the ADS1115 chip behaves as expected every time.
« Last Edit: October 25, 2019, 10:33:05 am by witenite »

MarkMLl

  • Hero Member
  • *****
  • Posts: 8563
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #7 on: October 25, 2019, 10:29:20 am »
It's not BaseUnix you should be looking at. You should be looking at the way the ioctl is handled in the kernel: BaseUnix is a very thin wrapper.
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

witenite

  • New Member
  • *
  • Posts: 41
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #8 on: October 25, 2019, 10:41:21 am »
I can't tell you about polling a busy flag, but some hardware issues I had with I2C. Good practise is putting pull-up resistors on both SCK and SDA. In many cases it solves "awkward" issues.
You can read about it on that link MarkMLl attached in previous post.

Thanks, I'll put my oscilloscope on the clock/data lines and take a closer look. I may have to analyze the I2C data (communications) too on the scope to see evidence of I2C write collisions. Given my findings thus far (see my most recent reply to MarkMLI) I really just think this is a software issue whereby either the kernel/driver does not like another I2C write being performed while one is already in progress (and simply crashes) or the ADS1115 chip itself locks up. I am relatively certain it's not the IC itself though, as it would mean it magically recovers when I shutdown the application and restart it (while not power cycling the board/IC). time could be a factor in that though.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8563
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #9 on: October 25, 2019, 11:41:12 am »
I don't believe that anybody said anything about the device locking up, but it is entirely plausible that a configuration change (i.e. a write operation) should take a finite time to complete and that it is not well documented.

BaseUnix doesn't AFAIK buffer I2C, so it's down to the application program to ensure that messages being sent don't overlap or hit the slave faster than it can handle. You're not really going through a handle in the normal way, and so the standard "is anything available?" etc. calls won't work; the last time I looked at that I don't think there was an I2C-specific status check.

Code: Pascal  [Select][+][-]
  1. uses
  2.   Sysutils, BaseUnix;
  3.  
  4. const
  5.   I2C_SLAVE = 1795;
  6.  
  7. var
  8.   slave: dword= $55;
  9. ...
  10.  
  11.  
  12.   (* Convert the Data parameter from a word to a pointer. This should have
  13.     lots of sanity checks, i.e. that the IOCTL number is known to be one
  14.     that doesn't take a pointer, that the value being cast is actually small
  15.     and so on.
  16.   *)
  17.   function fpIoctl(Handle: cint; Ndx: TIOCtlRequest; Data: dword): cint;
  18.  
  19.   begin
  20.     Assert(Ndx = I2C_SLAVE, 'Bad IOCTL number');
  21.     Assert(Data < 256, 'Bad IOCTL parameter');
  22.     result := BaseUnix.FpIOCtl(Handle, Ndx, pointer(ptruint(Data)))
  23.   end { fpIoctl } ;
  24.  
  25.  
  26. begin
  27.   fd := fpOpen('/dev/i2c-1', O_RDWR);
  28.   scratch := fpIoctl(fd, I2C_SLAVE, slave);
  29.   scratch := fpWrite(fd, ei[1], 3);
  30. ...
  31.  
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

witenite

  • New Member
  • *
  • Posts: 41
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #10 on: October 28, 2019, 01:19:17 am »
 OK, subsequent to further analysis, these are my findings:

1. Scoped the clock and data waveforms, and everything looks good. Good/Clean waveforms with about a 100ns rise time and considerably faster fall time (makes sense with an open collector/pull-up resistor arrangement). 10us pulse width (or clock speed of 100kHz). It appears there is ample margin for greater speed given how clean the waveform is.

2. Experimented with disconnecting clock/data lines during communications. A disconnect on transmit causes the software to crash (no exception raised, it just locks up and you have to terminate the program and restart it to regain control. A disconnect on continuous receive did not crash the system, however a data value of 0 is returned which, without additional flags or raised exception to let you know the data is invalid or communications is lost, is not acceptable. It is nonsense data.

As an example of the sort of information I am looking for, see the attached screenshots. This is a random example from a PIC micro-controller (I2C registers) that clearly detail the type of information you need for successful I2C communications. Simply sending and receiving data bits successfully without the additional "handshaking" is nowhere near sufficient when designing a proper application (though I bet a good deal of this sort of "tinkerer" code makes its way into everyday applications/hardware).

Sorry if I sound as if I'm venting a bit, I really do appreciate the help I have had from the guys here. It's simply so frustrating when I find I have to consistently work with a lack of sufficient documentation or a clear way forward. At least from a serial communications perspective, it appears to me that BaseUnix is only fit for hobbyists or workbench experiments. I would certainly not deploy an application to 3rd parties without the appropriate protective measures in place that are required for reliable serial communications (whether it's I2C or any other protocol). Maybe the thin BaseUnix interface to the kernel provides the flags/signals that I require, but if it does they are not documented anywhere. On the other hand, perhaps the fault lies with the kernel itself, and it's not picking up said flags/signals from the Broadcom device, but who's to know. The only part I am certain of is that the Broadcom I2C hardware interface will definitely have all these flag/status options available as is typical across manufacturers who comply with the I2C standard.

Rant complete, it's back to the drawing board for me and my Raspberry Pi project. Thanks again to those that provided assistance here.
« Last Edit: October 28, 2019, 02:02:39 am by witenite »

MarkMLl

  • Hero Member
  • *****
  • Posts: 8563
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #11 on: October 28, 2019, 09:44:40 am »
"Vent" entirely justified IMO, and thanks for keeping us updated. However AIUI BaseUnix is a thin wrapper around the kernel APIs, and if (as appears to be the case) you're entirely certain that the physical layer is OK then it would suggest either a kernel problem or something in the lower-level Broadcom-specific stuff.

You are, quite frankly, out of order saying that "BaseUnix is only fit for hobbyists or workbench experiments": it's a fundamental API on unix systems, and if it's broken it's a serious issue. More appropriate might be "attempting to use BaseUnix for this type of hardware without making sure that the calls are wrapped in a timeout" using ualarm etc. might be more appropriate.

I don't think that you'll get very far looking for totally-open equivalents to all the Broadcom stuff, I think that the projects got bogged down... assuming, that is, that this actually is in the data path and that it's not "simply" a case of fixing a broken kernel module. One approach that might be interesting would be attempting to duplicate the issue on something like an Arduino where the entire library sources etc. /are/ available.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

avra

  • Hero Member
  • *****
  • Posts: 2586
    • Additional info
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #12 on: October 28, 2019, 10:06:22 am »
I have discovered that by putting in a small delay between I2C writes totally eliminates the problem
Have you checked the datasheet and errata of the device you're talking to? Maybe some delay is actually required?

Any ideas as to how I can poll a busy flag or status variable using BaseUnix (or where I can find such information)?
Shouldn't you query return result of i2c writing function? For example, 16 should be returned for busy. Have you tried to display return result? Here is a starting point for the list of error codes: https://www.kernel.org/doc/Documentation/i2c/fault-codes and https://nuetzlich.net/errno.html. In FPC you can see that baseunix.pp includes errno.inc. That's where error codes are, and you can see that 16 is Sys_EBUSY.

I2C_SMBUS is not the only way to use ioctl(). There are others that might be interesting to you. More details here: https://www.kernel.org/doc/Documentation/i2c/dev-interface

As an example of the sort of information I am looking for, see the attached screenshots. This is a random example from a PIC micro-controller (I2C registers) that clearly detail the type of information you need for successful I2C communications.
Such level of control is easy to achieve with micro controllers, but very hard with multi threaded OS which only has a high level universal driver, and for each new supported MCU it depends on developers who adapt all those different manufacturers varieties into that universal driver. This means that not every existing hardware feature and fine details can be mapped into the driver.

If you really want such level of control on linux, you would probably need to write your own i2c driver which would not depend on universal driver and would be specific for your board. For Broadcom there is not much documentation so good luck with that. Or you can use bit banging for which you might want to look at https://lwn.net/Articles/230571/.

On the other hand, BaseUnix is not the only way to access i2c and you will probably have much better luck with higher level libraries. Look here for some good alternatives: https://wiki.freepascal.org/Lazarus_on_Raspberry_Pi

Do you use I2C-1 on pins 3 and 5 of the 40-pin connector (marked SDA and SCL on the Pi Wedge) or the other i2c which should be used for EEPROMS only?

Have you verified that your hardware is 100% good doing the same with C, Python or shell and getting good results with them?
« Last Edit: October 28, 2019, 11:04:10 am by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

krolikbest

  • Sr. Member
  • ****
  • Posts: 276
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #13 on: October 28, 2019, 08:50:12 pm »
lately (but still) i develop my robot-project. RPi3 (master) through I2C "is talking" with 4 Arduino Unos (as a slave). I use in this case wiringPi. Some delay in exchange frame between RPi and Unos is needed. On this basis I think that probably BaseUnix would do such exchange in similar way.
That such a delay is needed I've come to this conclussion after many, many attemps, especially if all Unos should work simultaneously and I don't think that there is something wrong with using delays.
Here you can see my up to now work https://www.youtube.com/watch?v=nW0IslMvxqQ&t=146s


witenite

  • New Member
  • *
  • Posts: 41
Re: I2C communications using BaseUnix: How to check write (busy) status
« Reply #14 on: October 30, 2019, 02:42:09 am »
"Vent" entirely justified IMO, and thanks for keeping us updated. However AIUI BaseUnix is a thin wrapper around the kernel APIs, and if (as appears to be the case) you're entirely certain that the physical layer is OK then it would suggest either a kernel problem or something in the lower-level Broadcom-specific stuff.

You are, quite frankly, out of order saying that "BaseUnix is only fit for hobbyists or workbench experiments": it's a fundamental API on unix systems, and if it's broken it's a serious issue. More appropriate might be "attempting to use BaseUnix for this type of hardware without making sure that the calls are wrapped in a timeout" using ualarm etc. might be more appropriate.

MarkMLl
Apologies, I should have elaborated what I was thinking. Within the context of BaseUnix it appears that there are some shortcomings when it comes to communications like I2C. I'm sure it does a lot of other things very well though. Now, with that said I have had some good success over the last couple of days, getting this to work at last. The main shortcoming (for me anyway) has been documentation. Read my later reply for more information.

Oh, and thanks again MarkMLI, for giving me some ideas and pointing me in the right direction etc.

 

TinyPortal © 2005-2018