http://qs321.pair.com?node_id=11114786


in reply to i2c bus contention

Possibly the solution, if practical, is to combine the scripts. You can then coordinate access to the I2C port. If that is not practical you need to push that management function into a daemon and have your scripts access the daemon to get work done. Without a single "manager" that can serialize access to the I2C buss you are in a world of hurt - as you have already discovered.

Note that often you will need to manage atomic transactions consisting of a series of writes and reads to get work done. So a manager that just provides atomic read or write of blocks of data probably won't cut it. That may mean the manager needs more domain knowledge than is appropriate for a generic black box or has a complicated API.

That said, if you combine your common module idea with a lock file you can probably solve the problem without needing to go the daemon route.

Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

Replies are listed 'Best First'.
Re^2: i2c bus contention
by jcb (Parson) on Mar 30, 2020 at 01:48 UTC

    If I remember I2C correctly, that is actually over-complicated. The master selects a device by emitting an address onto the bus, possibly writes some number of bytes to that device (a device-internal register number and/or data to write), and possibly reads some number of bytes from that device. The bus then returns to idle state.

    So the manager only needs a slave-address/write-buffer/read-count 3-tuple to define a transaction that can be performed atomically. If read-count is zero, only a completion can be returned; otherwise, the data read from the device is returned. In pseudo-C with the uint8_t[] type as a Perl-like scalar that carries its own length, an I2C transaction has this prototype:

    uint8_t[] i2c_xact(i2c_address slave, uint8_t[] write, size_t read_count);

      In principle yes. In practice it ain't quite as simple as you suggest. The complication isn't the function prototype (or even the implementation) anyway, but even there your suggested API is a little simplistic for practical use. The devil is in the error handling. Not only may either the write or the read fail, but either may partially succeed. So, sticking with pseudo-C/C++ where Buffer is a suitable C++ class equivalent to a Perl string, the function prototype changes a little to:

      Status TransactI2C(uint8_t address, Buffer wData, uint8_t &written, Bu +ffer &rBuffer, uint8_t expected);

      and the calling code needs to be able to handle statuses like: I2C busy, Write failed, Partial Write, Read Failed, Partial Read, Complete OK.

      But the real issue is exactly what the OP is struggling with: simultaneous access. And that isn't solved by just defining a nice API. That has to be solved using some common "process" that serialises access to the I2C port as provided by the OS. That can be non-trivial indeed depending on what the system provides and what your actual use case is. As it happens I've been working to solve exactly those sorts of problems with an embedded system that uses 6 I2C buses, a couple of SPI buses and the odd ADC, USB controller and Ethernet controller thrown in. With all of that stuff running interrupt driven and half of it driving DMA things get pretty interesting!

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        That API is for submitting a transaction to a shared daemon that will queue the transaction and process it later, so the "I2C busy" status cannot occur. You are probably correct about the possibilities of partial success, but the application I previously worked with did not handle that and did not have problems from lacking that handling. It also drove only very simple peripherals.

        Defining a nice API is the first step towards solving simultaneous access. The API must be an interface to an implementation that can queue and serialize transactions from multiple clients. What I suggested is similar to the I2C module I once wrote for an embedded application that handled some I/O in interrupt handlers or with DMA but did nearly all processing in a cooperative multitasking loop. The "I2C daemon" was another function called as part of that loop and an interrupt handler for advancing an "in-progress" transaction.

      @jcb As GrandFather notes, I still need to get a common function that handles calls to the i2c bus from two or more scripts.

      My access to the i2c bus from the two existing scripts works well and is stable when contention is managed on a time slot basis.

Re^2: i2c bus contention
by anita2R (Scribe) on Mar 30, 2020 at 13:07 UTC

    @GrandFather I would rather not combine my scripts, as they each have very different purposes, and the two scripts are also used independently on other machines. I think that creating a daemon is worth a try. As I have no formal IT training, this is going to be an interesting learning experience.

    As to the common module with a lock file, I am still stuck with how to have two scripts accessing a common module. I have now tried running a script that has package functions and calls the two test scripts. Pretty messy to put it mildly, but is was just an experiment.

    #!/usr/bin/perl package Hvac::i2cAccess; use strict; use HiPi::Device::I2C; use Exporter qw( import ); our @EXPORT_OK = qw( i2c_test1 i2c_test2 ); my $copyit = "Empty"; # call test scripts # test1 just gets the $copyit value # test2 sends an i2c object and gets the voltage value in return # the voltage value is also copied into $copyit do "/home/huw/cron_scripts/test1.pl"; print "A\n"; do "/home/huw/cron_scripts/test2.pl"; print "B\n"; do "/home/huw/cron_scripts/test1.pl"; print "C\n"; while( 1 ){ sleep 1; } exit 0; # ********************************************************* # sub i2c_test2 { # i2c object passed from test2.pl my $obj = $_[0]; # read 2 bytes of data from bus voltage register (address 0x02) my @bvr; eval{ @bvr = $obj->bus_read( 0x02, 2 ); 1; } or do { $bvr[0] = 0; $bvr[1] = 0; }; # result in upper 13 bits - big-endian order my $busV = pack 'C2', $bvr[0], $bvr[1]; $busV = ( unpack 'S>', $busV ) >> 3; # scale bus voltage register value my $bv = 16; my $bvScale = 4000; $busV = $bv / $bvScale * $busV; $copyit = $busV; return $copyit; } # ********************************************************* # sub i2c_test1 { print "Test 1 subroutine\n"; return "Copy $copyit"; } # ********************************************************* # # must end modules with a 'true' value 1;

    but I don't get past the first 'do'. I presume that 'do' is waiting for the script it calls to end and return a value. 'A' is never printed

    The module code without the 'calls' to the two test scripts was used to test the common module approach, but as I said, it looks like each test script invokes a separate copy of the module.

Re^2: i2c bus contention
by cxw (Scribe) on Mar 30, 2020 at 13:32 UTC
    If you do wind up needing to use a daemon, TheSchwartz might do it. I recently ran across STEVED's article series that walks through the process step-by-step. I haven't used it myself, but it's first on my list to try next time I need a job queue.

      @cxw Thanks for those links.

      It looks like I have plenty to read. If I do go down that route and it works, I will post a note about it