Wanted: A good SPI mutex

Great idea, except for one tiny fly in the ointment:

The resultant filename would be “DI_Mutex_[name]”

What I want is a generic SPI mutex that will be suitable for anyone’s code - that I could push to Waveshare or Adafruit, or [etc.].

What I did was to make a copy of di_mutex and rename it to spi_mutex, with the following changes:

class SPI_Mutex(object):
    """ Generic SPI mutex """

    def __init__(self, loop_time = 0.0001):
        """ Initialize """

        self.Filename = "/run/lock/SPI_Mutex"
        self.LoopTime = loop_time
        self.Handle = None
  1. I changed the name from DI_Mutex to SPI_Mutex.
  2. I removed the “name” parameter since it is automatically “SPI_Mutex”
  3. I changed the descriptive text.

Hopefully this will work and create a generic SPI_Mutex lock.

Viz,:

from spi_mutex import SPI_Mutex

spi_mutex = SPI_Mutex


try:
    spi_mutex.acquire()
    # do protected SPI access stuff here
finally:
    spi_mutex.release()

Yes, I know that the DI_Mutex class already exists and - for my very specific GoPiGo application I can use it.

However, I am thinking of a generalized mutex that anyone can use without all the references to Dexter Industries.

Attached:
Entire library code of the proposed spi_mutex library.  Please examine.
spi_mutex.py (2.1 KB)

2 Likes

Really? You are trying to solve “world peace” for all the non-Dexter uses? Good luck.

Previously when attempting to solve the generalized SPI mutex problem, you balked at having to put a generalized mutex code somewhere accessible to all SPI users. So now where are you going to put it? In a venv?

Stick with solving your problem and be happy if the DI mutex solves your problem on your robot. Do you really want to invest all the time and effort to making a generic solution which in actuality is one specific solution for a virtual crowd of “users” you don’t know and don’t know you?

2 Likes

Question:
If you “enclose” a bit of class method code within a mutex block as above, does all the code referenced by that code get included?

Assumptions:

  1. I am assuming that nothing is multi-threaded.
    Actually, I don’t know if ANYTHING within the GoPiGo library code is multi-threaded. :man_shrugging:
  2. Everything necessary has already been included.
  3. I am also assuming that the pseudo-code I’ve written “works”.  (Though you can correct obvious usage errors.)

Viz.:

[class instances]
spi = SPI_Communication_Library
spi_mutex - SPI_Mutex
something_else = Some_Other_SPI_Class

[. . . . some code goes here . . . .]

#  Do an SPI communication transaction
    try:
        spi_mutex.acquire()
        spi.comm(data, address, [r|w])
    finally:
        spi_mutex.release

Where within “spi.comm” there is a reference to “something_else.method”

Viz.:

def spi.comm(data, address, direction)
    [some code]

# "Some_Other_SPI_Class is an additional class that's
# essential for SPI communication.
# (maybe it bit-bangs the bytes?)

    something_else.method(parm1, parm2, parm-n)

    [more code]

    return(result)

Do enclosed classes/methods “inherit” the mutex’s protection?

2 Likes

When I questioned Waveshare about mutexes to see if they’ve already done anything like this, (and if they’ve already proved it doesn’t work, why reinvent the wheel?), they requested a pull request to their repo if I got a mutex working.

So, I figured I’d give it a try.

All I’m going to do is provide the mutex library in the same way that Waveshare provides its own libraries.  If the user wants to put this stuff in a venv, that’s the moose’s problem. :wink:

1 Like

Here is a concrete example:

“spi_transfer_array” is the method called by everything else in the class(es) for any SPI communication

    def spi_transfer_array(self, data_out):
        """
        Conduct a SPI transaction

        Keyword arguments:
        data_out -- a list of bytes to send. The length of the list will determine how many bytes are transferred.

        Returns a list of the bytes read.
        """
        result = GPG_SPI.xfer2(data_out)
        return result

However, spi_transfer_array references another class/method, “GPG_SPI.xfer2”, which is defined near the top as:

    import spidev

<snip additional code>

if hardware_connected:
    GPG_SPI = spidev.SpiDev()
    GPG_SPI.open(0, 1)
    GPG_SPI.max_speed_hz = 500000
    GPG_SPI.mode = 0b00
    GPG_SPI.bits_per_word = 8

. . . where GPG_SPI is an instance of spidev, and I am assuming that spidev does the actual bit-banging of the SPI port, etc.

Does spidev, as instantiated here, become a part of the mutex, or do I have to surround the spidev code itself?

2 Likes

Please think about the sequence of “how any SPI code is called?”

If the caller (and all other callers) pays attention to the mutex, the action is mutually exclusive.

2 Likes

So you have to create a setup to create the egg with the mutex code, or you have to fork the entire waveshare library and add your mutex to their codebase and egg setup.

And BTW, setup tools is deprecated and I don’t know what the new library creation tool is. My modified-for-Bookworm GoPiGo3 API still uses setup, and I ignore the deprecated warning.

2 Likes

I am translating this to mean that:

  1. spi_transfer_array calls GPG_SPI, and GPG_SPI (eventually) does the work via spidev and returns.
  2. If I don’t call spi_transfer_array until I have a lock, and release it when I get the result, everything within it will be protected.
1 Like

No idea what that is.

with mutex.acquire()
    protected stuff stuff
    mutex.release()

IIRC…. The usage pattern

They don’t have eggs, just a lot of discrete chickens. (.py files) for their libraries. All I would need to do is push mine to their repo.

In the gopigo library there is a “master” routine that sends SPI messages called spi_transfer_array as noted in the code snippit above.

The active part of it is a direct call to spidev which is instantiated as GPG_SPI.

My thought is to “wrap” the call to GPG_SPI in a mutex “try” block.

I suspect that spidev opens the SPI channel, asserts the correct chip select line, bit-bangs the data, releases the chip select line, and returns whatever value(s) are returned.

If my assumption is correct than “wrapping” the GPG_SPI with the mutex should be sufficient.

This assumes that there are no other paths to spidev, (or hard-coded SPI routines), outside of the ones I’ve found. If there are other, undocumented, SPI paths I’m doomed!

1 Like

Right, wrap all operations involving the GPG_SPI object AND the WaveShare object.

DI uses ifMutexAcquire() and ifMutexRelease() because the Easy Sensors can be instantiated to use the I2C mutex or not, not being the default, because 99% of DI product programs are single threaded. The fact that the waveshare device has a daemon based API rather than a single threaded API means you don’t have that luxury. Also the I2C mutex has to cover three I2C interfaces - HW I2C, SW1, and SW2 I2C.

1 Like

Did you look at how this guy structured a “pythonic” interface to the waveshare display?

I don’t see a mutex. He is checking a GPIO “display busy” pin to avoid overlapping the SPI bus. The GoPiGo3 could do the same idea by always waiting for any display requests to finish (display not busy) before issuing any GoPiGo3 SPI commands.

Using a mutex is the more “software engineering principled” approach, so perhaps you want to fork his repo, add your mutex, mutex acquire/release in the appropriate places, and setup.py and have a cleaner interface.

1 Like

I saw that.

If my understanding is correct, it’s not a “mutex” per se, but rather “hardware flow control” to make sure that they don’t try to send additional commands until the display has finished the present one.

Based on my examination with a 'scope, the sequence is more like this:

  1. Everything static, paper_busy is not asserted, SPI quiet.

  2. Command is sent by the Waveshare SPI routine.

    • Chip Select asserted.
    • SPI comm takes place
    • Chip Select released.
    • Paper busy asserted.
  3. The SPI buss has been released because some e-Paper commands can take upwards of 15 seconds to complete, particularly the “init” and “clear” commands.

  4. If more than one thing is happening, the code spins on the paper_busy status, not the SPI buss.

  5. Once paper_busy is released, the code goes back to step 2 and continues until there is nothing left to do whereupon it sends a “sleep/power-down” command that turns off 99% of the electronics and releases the electric charges used to create the display.  This is why the “init” part of the wake-up routine takes so long, it has to init all the electronics and pre-charge the e-Paper display layers.

During all of this, the SPI buss is not busy, so waiting on the paper_busy wouldn’t be the best choice IMHO.

1 Like

Totally useless for your purposes.

I didn’t see an official example of using the display - only how to use the display as a login window session.

Do you even need the waveshare daemon running if you are not trying to use the OS windowing system on the device? Like I was saying before, if the GoPiGo3 program is the only user, then it can wait until the non-window-API returns “done”, before using the SPI bus for GoPiGo3 stuff.

1 Like

There is no daemon.

It’s a piece of software that’s run if, and only if, you want to update the display.

When you run it, it runs, displays something, and then quits.  That’s it.

1 Like

Are we looking at the same thing?


 

Here is the product page detailing the various e-Paper displays for the Raspberry Pi:
https://www.waveshare.com/product/raspberry-pi/displays/e-paper.htm

Here’s the product page for the 2.7" e-Paper HAT:
https://www.waveshare.com/product/raspberry-pi/displays/e-paper/2.7inch-e-paper-hat.htm

And here’s the support page where all the software and libraries are downloaded.  Pay particular attention to the “Python” section under “Raspberry Pi”
https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT

This thing is so slow that there’s no way in :face_with_symbols_over_mouth: that it could be used as a primary display.  Especially since its primary use-case is as a static display for store shelves or electronic badges, etc.

Maybe you have this confused with the Vellman VMP-400?

1 Like

There appears to be a difference between the older 26 pin GoPiGo boards and the newer 40 pin boards, at least with respect to how they interact with the Waveshare e-Paper displays.

Charlie, a 26 pin robot, behaves nicely with the Waveshare displays whereas Charline doesn’t.

I am going to have to do additional research on this.

1 Like

Interesting - I did not recall that Carl is a 26pin bot and Dave is a 40pin bot. I don’t have any record of GoPiGo3 use of pins 27-40:

GoPiGo3: Unused GPIO Pins of 26 pin connector (3 GPIO available):
  7:GPIO  4    
24:GPIO 8  SPI_CE0
11:GPIO 17
13:GPIO 27

Unused pins of 40 pin connector (9 GPIO available):

27: ID_SD (I2C ID use only)
28: ID_SC (I2C ID use only)
29: GPIO5
30: Gnd
31: GPIO6
32: GPIO12
33: GPIO13
34: Gnd
35: GPIO19
36: GPIO16
37: GPIO26
38: GPIO20
39: Gnd
40: GPIO21
1 Like

Like I said, I need to do additional research on this to discover what’s going on here.

I think this is EXTREMELY interesting and since there’s a difference in behavior, this could break the issue wide open!

There could be a side-effect caused when Waveshare’s display(s) have access to all 40 pins.  As far as I can tell, (as of now), that’s the big difference.  I will have to put a test cover on Charlie and a 26 pin extension header so I can connect and remove displays.

Test cover:
A top plate with a cutout that allows a tall pin header to poke through so I can connect things while still having a top cover in place.

I have two now.  One that fits Charlie’s 26 pin header, (which I have had for awhile), and a “new” one that fits Charline’s 40 pin header.

1 Like

Scandalous!

1 Like