Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple MCP2221 I2C buses (improved) #637

Closed

Conversation

ezio-melotti
Copy link
Contributor

@ezio-melotti ezio-melotti commented Dec 5, 2022

This PR is an improved version and a follow-up of:

It includes all the changes from #496 with some additional changes:

  • Replaced __init__ (and get_instance()) with a __new__ method that either returns a new instance or a cached one;
  • Added an available_paths() method that returns a list of all available MCP2221 addresses;
  • Added a new_instance() method that loops through all the available MCP2221 addresses and tries to open them;
  • Updated one of the examples;
  • Restored the module-level mcp2221 variable in mcp2221.py

The PR is against main, so the diff doesn't show the removal of get_instance and the restoration of the mcp2221 var.

I left some inline comments regarding the implementation and API and marked the PR as draft, since it still contains some debug print() to help debugging and the API is still subject to changes.

cc @caternuson, @fgervais

examples/mcp2221_multiple_busio_i2c.py Outdated Show resolved Hide resolved
Comment on lines 68 to 73
if bus_id is None and cls.instances:
# return the instance for the first bus_id, if already cached
first_bus_id = cls.available_paths(require_mcps=True)[0]
if first_bus_id in cls.instances:
print(f'Returning cached {first_bus_id}...')
return cls.instances[first_bus_id]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I tried to emulate the original behavior: if the user calls MCP2221() without the bus, it will check if the first device is already opened/cached and return that, if not it will try to open a different one below.

Other possible behaviors are:

  • return one of the cached MCP (if any), even if it's not the first
  • if no bus is specified, only try to open the first device

Note that simply importing the module will automatically create an instance of the first device, so any subsequent MCP2221 call (without the bus) will return that instance. This might not happen if another process already opened the first device -- in that case the class will try to open a different one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now changed this so that:

  • MCP2221() (with no bus_id specified) opens and returns the first available MCP (and raises an error if there are no MCP2221 available).
  • MCP2221(bus_id) opens and returns the corresponding MCP2221, or return the cached instance if it's already open.

cls.instances[bus_id] = self
else:
# find the first available MCP
self = cls.new_instance()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no bus is specified and the first device is not cached (e.g. because some other process already opened it), then it will try to find and open another one. This behavior is non-deterministic, since it depends on the order the devices have been connected, and on whether other processes have opened the device.

If we want MCP2221() (without the bus) to only ever access the first device, the call to new_instance can be removed and the code changed to just try to open the first device (and possibly failing and raising an error without falling back on the next available device).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now removed the new_instance method and updated the code so that MCP2221 will try to find and open the first available MCP.

return self

@staticmethod
def available_paths(*, require_mcps=False):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this method I considered a few different options:

  • a read-only @property, but in order to work in __new__ it must work without instance (and @property requires the instance);
  • a stand-alone function, but it would need to be imported separately, and having it as a method is handy.

Regarding the name:

  • I used paths because that's the name used by hid.enumerate. Perhaps addresses or devices might be better.
  • I used required_mcps, but required_mcp2221s might be better even if longer. Other options (like raise_if_no_mcp2221s) feel too verbose.

src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py Outdated Show resolved Hide resolved
Comment on lines 128 to 136
except (OSError, RuntimeError) as e:
print(f'[ERR]: {e}')
if len(bus_ids) == 1:
raise # we failed to open the only MCP
continue # try opening the next one
else:
print('Failed to open all hid devices')
raise RuntimeError(f'Can not open any of the {len(bus_ids)} '
f'connected MCP2221 devices.')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To maintain backward compatibility, if there's only one MCP, the error is re-raised as is. When there are multiple MCPs, a generic error is returned if none of them can be opened. This might end up hiding some information on the cause of the errors though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is now integrated in __new__. The same comment applies.

src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py Outdated Show resolved Hide resolved
src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py Outdated Show resolved Hide resolved
src/adafruit_blinka/microcontroller/mcp2221/pin.py Outdated Show resolved Hide resolved
args = parser.parse_args()

# Need to be bytes, always function 2
device_path = "{:04x}:{:04x}:02".format(args.bus, args.device).encode()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script doesn't work for me because of this line. The paths to my devices look like 2-3.3:1.2, whereas this will produce something like 0002:0003:02. Perhaps the example could be updated to print a list of available addresses, and prompt the user to select one.

@ezio-melotti ezio-melotti marked this pull request as draft December 5, 2022 05:25
@fgervais
Copy link
Contributor

fgervais commented Dec 8, 2022

Looks good to me, I will give it a try.

@ezio-melotti
Copy link
Contributor Author

Looks good to me, I will give it a try.

Thanks! I'll work on fixing the CI failures and if it works for you I'll mark the PR as "ready for review".

@ezio-melotti
Copy link
Contributor Author

Pylint seems to get confused by the use of __new__ (possibly related to pylint-dev/pylint#7258), so I just ignored the errors/warnings.

@fgervais
Copy link
Contributor

I did a quick test this morning. I cannot debug more at this time so I will just put my current state here.

My script is:

import board

I have another unrelated script which consumes one MCP and then I run the script above and I get:

MCP2221.__new__; bus_id=None; instances=[]
bus_ids=[b'1-4.4.4.2:1.2', b'1-4.4.2.4:1.2']
Opening b'1-4.4.4.2:1.2'... [ERR]: open failed
Opening b'1-4.4.2.4:1.2'... [OK]
MCP2221.__new__; bus_id=None; instances=[b'1-4.4.2.4:1.2']
bus_ids=[b'1-4.4.4.2:1.2', b'1-4.4.2.4:1.2']
Opening b'1-4.4.4.2:1.2'... [ERR]: open failed
Opening b'1-4.4.2.4:1.2'... [ERR]: open failed
Failed to open all hid devices
Traceback (most recent call last):
  File "/app/mute.py", line 1, in <module>
    import board
  File "/.venv/lib/python3.10/site-packages/board.py", line 200, in <module>
    from adafruit_blinka.board.microchip_mcp2221 import *
  File "/.venv/lib/python3.10/site-packages/adafruit_blinka/board/microchip_mcp2221.py", line 5, in <module>
    from adafruit_blinka.microcontroller.mcp2221 import pin
  File "/.venv/lib/python3.10/site-packages/adafruit_blinka/microcontroller/mcp2221/pin.py", line 8, in <module>
    mcp2221 = MCP2221()
  File "/.venv/lib/python3.10/site-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 89, in __new__
    self = cls.new_instance()
  File "/.venv/lib/python3.10/site-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 138, in new_instance
    raise RuntimeError(
RuntimeError: Can not open any of the 2 connected MCP2221 devices.

It seems to work fine but then tries to open a second free device and there isn't anymore and so fails.

@ezio-melotti
Copy link
Contributor Author

ezio-melotti commented Dec 13, 2022

Thanks for testing!

The error you see seems to be caused by a combination of factors:

  • the unrelated script is using the first MCP2221, so it's not available to the other process
  • with my changes, if you call MCP2221() without specifying the bus, the code will try to:
    1. return the first MCP2221 if it's already opened/cached (in this case it's not)
    2. open the first MCP2221 if it's available (and in this case it's not, since it's used by the other script)
    3. open the next available MCP2221 -- this will open a new MCP2221 every time MCP2221() is called
  • importing board might now trigger two calls to MCP2221():

Therefore, with my changes, calling MCP2221() without specifying the bus will do one of the following two things:

  • if the first MCP2221 is available, open and return it (including in subsequent calls to MCP2221(), where the cached instance is returned)
  • if it's not available, instead of returning an error like it did before, find the next available/unopened MCP2221 and open/return it

This is explained in #637 (comment), where other possible options are also discussed.

So, normally importing board would open and cache the first MCP2221 in mcp2221.py (the module-level var), and the other call in pin.py would return the same cached instance (since it's the first). In your case however, the first MCP2221 wasn't available so the first MCP2221() call in mcp2221.py opened the second MCP2221 (successfully), and the call in pin.py tried (and failed) to opened a third MCP2221 (since the first wasn't available, and there was no third).

This issue can be mitigated easily by removing the second call to MCP2221() in pin.py1, however, with the current design, any other attempt to call MCP2221() will still try to open a new MCP2221 and fail since the two available ones are both already opened.

Footnotes

  1. I now removed it.

@fgervais
Copy link
Contributor

fgervais commented Jan 1, 2023

So I guess this is a mergeable form at this point right?

It might be a good time to cancel my PR?

@ezio-melotti
Copy link
Contributor Author

ezio-melotti commented Jan 1, 2023

If this PR works for you, you can cancel your PR and I'll remove the debug prints and mark it as ready for review.

@ezio-melotti ezio-melotti marked this pull request as ready for review January 2, 2023 00:35
@ezio-melotti
Copy link
Contributor Author

ezio-melotti commented Jan 2, 2023

I now removed the debug prints and marked the PR as ready for review.

To summarize, this PR:

  • enables the user to have multiple MCP2221 connected at the same time
  • builds on @fgervais's work started in Add support for multiple mcp2221 i2c busses #496
  • adds a new optional bus_id argument to the constructor of the MCP2221 class:
    • if the bus_id is specified, the class will return a new instance corresponding to that bus_id
    • if not, it will open and return the first available MCP.
  • adds a new available_path() static method that return a list of valid paths/bus_id
  • removes the module-level mcp2221 instance, replacing it with a fallback that uses __getattr__ to maintain backward-compatibility in case someone was using it directly

To test this PR, connect 2+ MCP2221s to your machine and run the example scripts. Possibly add some sensors/devices to the MCPs.

(Click to see my setup)

Physical setup:

     +-----+
PC==>| USB |==>MCP2221-->SGP30-->SCD30
     | HUB |==>MCP2221-->BME688
     +-----+
     
USB: ==>  /  STEMMA QT: -->

Sample output:

$ BLINKA_MCP2221=1 python3 examples/mcp2221_multiple_busio_i2c.py 
2 MCP2221(s) found: [b'2-2.4:1.2', b'2-2.3:1.2']
I2C devices found on 2-2.4:1.2: ['0x77']
I2C devices found on 2-2.3:1.2: ['0x58', '0x61']

(0x77, 0x58, 0x61 are, respectively, the default I2C addresses of the BME688, SGP30, and SCD30).


Optional TODOs (both have inline comments with more details):

  • Bikeshed on the terminology and methods/argument names (e.g. path vs bus_id, or require_mcps vs require_mcp2221s, etc.);
  • Rewrite the mcp2221_single_busio_i2c.py example to accept a full path instead of reconstructing it from the bus/device.

cc @caternuson.

@caternuson
Copy link
Contributor

caternuson commented Jan 16, 2023

Sorry for lagging in response here. Just to double check intentions - this is intended to be only for I2C? So it would be a very special use case? There is no expectation of support for accessing the other MCP2221's features - GPIO/ADC/Serial?

@ezio-melotti
Copy link
Contributor Author

I've only used and tested the MCP2221 with a Stemma Qt connected to sensors such as SCD30, SGP30, BME688, and I'm not familiar with the other MCP2221 features.

By looking at the code, both GPIO and ADC seem to go through self._hid_xfer(), which then calls self._hid.write().
Since now each MCP2221 instance is created from a different hid path, then I would expect the self.hid_xfer() method to write to the right MCP2221 and therefore support the other MCP2221 features, however I have not tested this myself.

@fgervais
Copy link
Contributor

I did not test extensively but GPIO seems to work on my side.

@caternuson caternuson self-assigned this Jan 16, 2023
@caternuson
Copy link
Contributor

The question is general and philosophical at this point. Not really specific to what this PR is actually doing or not doing code-wise. What specific capabilities do you see this PR as trying to add? Consider how others will interpret and use what this PR is providing. Many will only see "multiple MCP2221" and think that means they can use multiple MCP2221's to create tons of GPIO pins. Or lots of ADC's. etc. However, the title of the PR is "multiple MCP2221 I2C buses". Supporting "multiple MCP2221's" is much different than supporting only "multiple MCP2221 I2C buses".

Are either of you using I2C in conjunction with anything else on the MCP2221 in your multiple MCP2221 setups? Like GPIO or ADC?

@fgervais
Copy link
Contributor

I posted some general information on my use-cases in my original PR: #496 (comment)

For the moment I'm using multiple mcp2221 in 2 projects I can easily share:

  1. My fridge project where I use multiple MCP2221s in the same python script/process:
    https://github.com/fgervais/project-smart-fridge/blob/043bafc83f9bad86ea2f18b37e432731a74de9db/firmware/main.py#L106

Note that it it using my original PR code which is not as optimal as this new improved PR.

  1. My mute button project which uses GPIOs
    https://github.com/fgervais/project-mute-button/blob/b79803f304832e2889fec3c4005d8461c703db71/firmware/mute.py#L9
    You actually don't see anything special in the code but on my computer I have other MCP2221s connected and so without this PR, the script would simply fail on import board.

It shows the usefulness when using multiple MCP2221s with multiple scripts/processes.


Also I think it actually allows for a lot of use-cases combination which "tons of GPIO pins" is also part of.

@caternuson
Copy link
Contributor

Example 1 is only using I2C on multiple MCP's.
Example 2 is only using GPIO on a single MCP.
There is no example with mixed usage on multiple MCPs?

You actually don't see anything special in the code but on my computer I have other MCP2221s connected and so without this PR, the script would simply fail on import board.

The issue here seems to be supporting access to a single MCP on setups that potentially have multiple MCP's. The current library code expects only a single MCP to be present since it initiates access using USB VID and PID.

Also I think it actually allows for a lot of use-cases combination which "tons of GPIO pins" is also part of.

So you think this PR should provide access to the GPIO pins of all the attached MCP's in addition to I2C?

@fgervais
Copy link
Contributor

There is no example with mixed usage on multiple MCPs?

Unfortunately these are the only 2 examples I can link to right now.

So you think this PR should provide access to the GPIO pins of all the attached MCP's in addition to I2C?

I will let @ezio-melotti post his opinion on how wide he wants to push this PR. On my side I'm happy with how it is right now.

However if we do want to allow all use-cases for Pin, it seems to me like we are not that far.

@ezio-melotti
Copy link
Contributor Author

What specific capabilities do you see this PR as trying to add?

My use case is accessing multiple sensors connected with Stemma Qt cables to multiple MCP2221, in turn connected to my PC via USB. All the libraries for these sensors accept an I2C bus, e.g.:
https://github.com/adafruit/Adafruit_CircuitPython_SCD30/blob/d5698affa8bd2af49c4933d83dd0579fe23ec495/adafruit_scd30.py#L95

Therefore the primary goal of this PR is to add support for multiple MCP2221 instances, each connected to a different MCP2221 through hid, instead of being limited to a single global instance connected to the first MCP2221 and no easy way to access the others.

Since this PR updates the code so that each instance of the MCP2221 class is connected -- through hid -- to a specific MCP2221 device, the change should be transparent to most of the methods (including the ones for GPIO and ADC), since they write on and read from the hid device (self._hid).

In fact, the only changes made by this PR to the MCP2221 class are related to the opening/closing of the hid device, and there are no I2C-specific changes to its methods.

However the pin.py file still tries to open and access the first available MCP2221, so perhaps it also needs to be updated (like the i2c.py file)?

The issue here seems to be supporting access to a single MCP on setups that potentially have multiple MCP's. The current library code expects only a single MCP to be present since it initiates access using USB VID and PID.

My understanding is that:

  • the MCP2221(s) are accessed via hid
  • the VID and PID are the same for all the MCP2221s (0x04D8 and 0x00DD respectively)
  • hid.enumerate(VID, PID) returns a list of all connected MCP2221s
  • hid.open(VID, PID) always tries to open the first MCP2221 of the list, and fails if it's already opened
  • the path is different for each MCP2221 (depending on the USB port it is connected to), allowing connection to different MCP2221s (through hid.open_path(path))

The current code uses hid.open(VID, PID) to open the first MCP2221 and is therefore limited to a single (the first, assuming it's not open) MCP2221:

self._hid.open(MCP2221.VID, MCP2221.PID)

This PR either opens the first available MCP2221 if no path is specified, or opens the MCP2221 that corresponds to the given path. The list of paths is returned by MCP2221.available_path(), and they corresponds to specific USB ports (so they are consistent across reboots).

So you think this PR should provide access to the GPIO pins of all the attached MCP's in addition to I2C?

It should already allow this, but it should be tested and verified.

@caternuson
Copy link
Contributor

It should already allow this, but it should be tested and verified.

It does not and is easy to demonstrate with the following, with multiple MCP's attached:

import board
dir(board)

I think implementing access to all the GPIO pins will be non-trivial. Further, there are the ADC's to consider.

It sounds like your motivation and use case was entirely I2C driven. But you feel the other MCP2221 feature's should be supported?

@tannewt
Copy link
Member

tannewt commented Jan 17, 2023

I don't think that the board module makes any sense in the context of MCP because they aren't an inherent part of the board. Instead, one should instantiate an MCP object that has library compatible APIs. Maybe that's best done as a new library?

@ezio-melotti
Copy link
Contributor Author

But you feel the other MCP2221 feature's should be supported?

They could, but it's probably better to implement them with separate PRs. This PR is already an improvement over the existing code, and solves both my and @fgervais use case. If other people need GPIO/ADC/etc. they could submit new PRs, but I don't think that should block this PR.

I don't think that the board module makes any sense in the context of MCP

FWIW I need to import board to use the sensors that I have connected to the MCP2221s (see e.g. this example: https://github.com/adafruit/Adafruit_CircuitPython_SCD30#usage-example).

@tannewt
Copy link
Member

tannewt commented Jan 18, 2023

They could, but it's probably better to implement them with separate PRs. This PR is already an improvement over the existing code, and solves both my and @fgervais use case. If other people need GPIO/ADC/etc. they could submit new PRs, but I don't think that should block this PR.

This adds a new kwarg, bus_id, to the busio.I2C interface that means code written for it won't work on other CircuitPython devices. It really shouldn't be merged here. The separate library is a better option. (more below)

FWIW I need to import board to use the sensors that I have connected to the MCP2221s (see e.g. this example: https://github.com/adafruit/Adafruit_CircuitPython_SCD30#usage-example).

For that exact example yes. However, the MCP library could provide its own busio.I2C equivalent that is then passed into the SCD30 object. That way you don't need busio or board replaced. So you'd do something like:

import time
import mcp2221
import adafruit_scd30

# SCD-30 has tempremental I2C with clock stretching, datasheet recommends
# starting at 50KHz

# This is an object for the specific connected MCP.
mcp = mcp2221.MCP2221(b'2-3.3:1.2')
# mcp.i2c is an instance of the I2C class for this specific MCP object.
scd = adafruit_scd30.SCD30(mcp.i2c)

CircuitPython drivers should work with any busio.I2C-like object. No need for board and busio.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds non-standard kwarg to busio.I2C.

@ezio-melotti
Copy link
Contributor Author

Adds non-standard kwarg to busio.I2C.

FWIW the arg is optional, so it shouldn't affect existing code. If the MCP2221 is removed from the equation, I would assume the code will need to be updated anyway and the path that corresponds to a specific USB removed (unless there are also other USB-based I2C-supporting devices). However, if there is a requirement for all the I2C interface to have the exact same interface (with no extra args), then I understand.

# This is an object for the specific connected MCP.
mcp = mcp2221.MCP2221(b'2-3.3:1.2')
# mcp.i2c is an instance of the I2C class for this specific MCP object.
scd = adafruit_scd30.SCD30(mcp.i2c)

This is actually not a bad idea, however the I2C object in the original example is instantiated with busio.I2C(board.SCL, board.SDA, frequency=50000).

If we simply add an mcp.i2c attribute, it is not possible to pass different arguments, but that can be solved easily by converting it into an mcp.get_i2c(...) method, roughly implemented as:

class MCP2221:
    ...
    def get_i2c(self, *args, **kwargs):
        self._i2c = mcp2221.I2C(*args, ** kwargs)
        return self._i2c

Even if we do this, we would still need to import board, at least in the case of the SCD30, in order to access board.SCL and board.SDA.

The final code will then look like:

import board
import mcp2221
import adafruit_scd30

# SCD-30 has tempremental I2C with clock stretching, datasheet recommends
# starting at 50KHz

# This is an object for the specific connected MCP.
mcp = mcp2221.MCP2221(b'2-3.3:1.2')
# i2c is an instance of the I2C class for this specific MCP object.
i2c = mcp.get_i2c(board.SCL, board.SDA, frequency=50000)
scd = adafruit_scd30.SCD30(i2c)

Finally, we might want to rethink the available_paths method -- the paths it returned were then fed to I2C(), but if we don't do that anymore we might as well returns instances directly, to make the code more convenient.

@tannewt
Copy link
Member

tannewt commented Jan 18, 2023

FWIW the arg is optional, so it shouldn't affect existing code. If the MCP2221 is removed from the equation, I would assume the code will need to be updated anyway and the path that corresponds to a specific USB removed (unless there are also other USB-based I2C-supporting devices). However, if there is a requirement for all the I2C interface to have the exact same interface (with no extra args), then I understand.

I generally don't want extra options here because code written for it won't work on other platforms. Code written for other platforms won't work with this code because it needs bus_id.

If we simply add an mcp.i2c attribute, it is not possible to pass different arguments, but that can be solved easily by converting it into an mcp.get_i2c(...) method

Yup, that would work. You'd want to ensure you only return one object and that the frequency matched.

Even if we do this, we would still need to import board, at least in the case of the SCD30, in order to access board.SCL and board.SDA.

You don't because the MCP I2C constructor ignores the pin arguments. There must only be one set of i2c pins on it.

def __init__(self, *, frequency=100000, bus_id=None):
self._mcp2221 = MCP2221(bus_id)
self._mcp2221._i2c_configure(frequency)

Finally, we might want to rethink the available_paths method -- the paths it returned were then fed to I2C(), but if we don't do that anymore we might as well returns instances directly, to make the code more convenient.

That would be more convenient but it may not be what you want. Returning an instance for each would claim them all, even if the user only wanted to use one of them. I think it would be useful to still have the discovery function at the top level of the library.

@caternuson
Copy link
Contributor

I suggest we close this. I agree with @tannewt 's suggestion that this is better done in a separate library. To be a part of Blinka carries specific requirements on maintaining API compatibility. While a single MCP2221 can be represented via the board module (as currently done), this generally does not hold up when considering multiple MCP2221's.

Similar arguments apply to FT232H.

@tannewt tannewt closed this Jan 25, 2023
@ezio-melotti
Copy link
Contributor Author

With separate library, do you mean a separate module in this repo, a new repo under adafruit, or a standalone repo (and possibly related PyPI package) under my account?

FWIW I'm also fine implementing the solution suggested above by @tannewt (i.e. adding an mcp.get_i2c() method).

@caternuson
Copy link
Contributor

A separate repo. There is the Community Bundle repo which maybe this could be added to?
https://github.com/adafruit/CircuitPython_Community_Bundle
But also maybe not. This would end up being sort of a special use case library. So not like a typical CircuitPython Library. But it also wouldn't be a general purpose library, since it would be providing a CircuitPython specific interface.

@tannewt
Copy link
Member

tannewt commented Jan 26, 2023

I'd suggest a separate repo under your account @ezio-melotti. Once it is setup, feel free to add a link to it from a README here. Happy to link folks to it.

ezio-melotti added a commit to overthesun/simoc-sam that referenced this pull request Feb 27, 2024
This is a follow-up of:
* #48 

#48 requires adafruit/Adafruit_Blinka#637 in
order to work, so the `get_sensor_i2c_bus()` function can't be used
unless we install `Adafruit_Blinka` from my fork.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants