adafruit / Adafruit_Blinka

Add CircuitPython hardware API and libraries to MicroPython & CPython devices
https://learn.adafruit.com/circuitpython-on-raspberrypi-linux
MIT License
439 stars 327 forks source link

OSError running sample script for MCP3008 on Libre Computer aml-s905x-cc "Le Potato" #681

Open anguselheimStudios opened 1 year ago

anguselheimStudios commented 1 year ago

I am running into an OSError trying to run sample code for MCP3008 on the Libre Computer aml-s905x-cc "Le Potato". I have made slight changes to sample code, changing the pin numbers, putting the print statements in a loop, and I've tried busio and extendid bus with the same results .

Here is the script I am trying to run:

import time
import busio
import digitalio
import board
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
from adafruit_extended_bus import ExtendedSPI as SPI

# create the spi bus
#spi = busio.SPI(clock=board.P23, MISO=board.P21, MOSI=board.P19)
spi = SPI(0, 0)

# create the cs (chip select)
cs = digitalio.DigitalInOut(board.P22)

# create the mcp object
mcp = MCP.MCP3008(spi, cs)

# create an analog input channel on pin 0
chan = AnalogIn(mcp, MCP.P0)

while True:
    print('Raw ADC Value: ', chan.value)
    print('ADC Voltage: ' + str(chan.voltage) + 'V')
    time.sleep(5)

Here is the error:

angus@aml-s905x-cc:~/scripts$ python3 mpc3008_test.py 
Traceback (most recent call last):
  File "/home/angus/scripts/mpc3008_test.py", line 23, in <module>
    print('Raw ADC Value: ', chan0.value)
  File "/usr/local/lib/python3.10/dist-packages/adafruit_mcp3xxx/analog_in.py", line 60, in value
    self._mcp.read(self._pin_setting, is_differential=self.is_differential) << 6
  File "/usr/local/lib/python3.10/dist-packages/adafruit_mcp3xxx/mcp3xxx.py", line 91, in read
    spi.write_readinto(self._out_buf, self._in_buf)
  File "/home/angus/.local/lib/python3.10/site-packages/busio.py", line 396, in write_readinto
    return self._spi.write_readinto(
  File "/home/angus/.local/lib/python3.10/site-packages/adafruit_blinka/microcontroller/generic_linux/spi.py", line 133, in write_readinto
    data = self._spi.transfer(list(buffer_out[out_start : out_end + 1]))
  File "/home/angus/.local/lib/python3.10/site-packages/Adafruit_PureIO/spi.py", line 420, in transfer
    ioctl(self.handle, SPI._IOC_MESSAGE, spi_ioc_transfer)
OSError: [Errno 22] Invalid argument

System Info:

angus@aml-s905x-cc:~/scripts$ neofetch
           `:+shmNNMMNNmhs+:`              angus@aml-s905x-cc 
        .odMMMMMMMMMMMMMMMMMMdo.           ------------------ 
      /dMMMMMMMMMMMMMMMmMMMMMMMMd/         OS: Ubuntu MATE 22.04.2 LTS aarch64 
    :mMMMMMMMMMMMMNNNNM/`/yNMMMMMMm:       Host: aml-s905x-cc 
  `yMMMMMMMMMms:..-::oM:    -omMMMMMy`     Kernel: 6.1.21-04467-gc1663d8fbf25 
 `dMMMMMMMMy-.odNMMMMMM:    -odMMMMMMd`    Uptime: 9 hours, 14 mins 
 hMMMMMMMm-.hMMy/....+M:`/yNm+mMMMMMMMh    Packages: 2404 (dpkg), 9 (snap) 
/MMMMNmMN-:NMy`-yNMMMMMmNyyMN:`dMMMMMMM/   Shell: bash 5.1.16 
hMMMMm -odMMh`sMMMMMMMMMMs sMN..MMMMMMMh   Resolution: 1680x1050 
NMMMMm    `/yNMMMMMMMMMMMM: MM+ mMMMMMMN   DE: MATE 1.26.0 
NMMMMm    `/yNMMMMMMMMMMMM: MM+ mMMMMMMN   WM: Metacity (Marco) 
hMMMMm -odMMh sMMMMMMMMMMs oMN..MMMMMMMh   Theme: Yaru-MATE-dark [GTK2/3] 
/MMMMNNMN-:NMy`-yNMMMMMNNsyMN:`dMMMMMMM/   Icons: Yaru-MATE-dark [GTK2/3] 
 hMMMMMMMm-.hMMy/....+M:.+hNd+mMMMMMMMh    Terminal: tilix 
 `dMMMMMMMMy-.odNMMMMMM:    :smMMMMMMd`    CPU: (4) @ 1.512GHz 
   yMMMMMMMMMms/..-::oM:    .+dMMMMMy      Memory: 1573MiB / 1924MiB 
    :mMMMMMMMMMMMMNNNNM: :smMMMMMMm:
      /dMMMMMMMMMMMMMMMdNMMMMMMMd/                                 
        .odMMMMMMMMMMMMMMMMMMdo.                                   
           `:+shmNNMMNNmhs+:`

angus@aml-s905x-cc:~/scripts$ pip3 list | grep adafruit
adafruit-circuitpython-busdevice 5.2.4
adafruit-circuitpython-mcp3xxx   1.4.14
adafruit-circuitpython-pixelbuf  2.0.0
adafruit-circuitpython-requests  1.13.0
adafruit-circuitpython-seesaw    1.11.7
adafruit-circuitpython-typing    1.9.0
adafruit-extended-bus            1.0.2
angus@aml-s905x-cc:~/scripts$ pip3 list | grep Adafruit
Adafruit-bitfield                1.5.1
Adafruit-Blinka                  8.16.1
Adafruit-GPIO                    1.0.3
Adafruit-PlatformDetect          3.42.0
Adafruit-PureIO                  1.1.10

I was made aware of this issue by this forum post:

https://hub.libre.computer/t/problems-accessing-mcp3008-via-spi-invalid-argument/1844

anguselheimStudios commented 1 year ago

At Libre Computer's suggestion I have written a Device Tree Overlay for the MCP3008 which now shows up as an iio device, and can be read using the open() function. It seems to work, however think I cooked my MCP wiring it improperly. I'm getting some strange results now (see the thread linked in my initial post).

I'm going to order a replacement and test it. If it works properly, and I can manage not to melt this one, I can try to add support for iio devices to this module.

Is that something anybody is interested in or should I not bother?

anguselheimStudios commented 1 year ago

I have the MCP3008 working as an iio device. My test code reads the device with the open() built-in.


def read_mcp3008(channel=0, device=1):
    '''Reads MCP3008 connected as an iio device.Takes a channel number (0-7)
    and iio device number as arguments. Returns an string containing a number from 0-1023'''
    channel_path = f"/sys/bus/iio/devices/iio:device{device}/in_voltage{channel}_raw"
    with open(channel_path, 'r') as f:
        return(f.read())

Since I haven't heard anything I'm just going to go for it.

I plan on creating an alternate "mcp3xxx-iio" class and adding an "iio_device=-1" argument to the "mcp3008" class. This argument will default to -1 for the regular "mcp3xxx" class. If it is given a non-negative value it will specify the iio device number and inherit from the new "mcp3008-iio" impostor class.

I do have a few questions, as I'm new to github:

  1. How should I handle the Author(s) line?
  2. Is there a style guide?
  3. I don't have the other 2 devices covered by this module. Is there someone else available to test them?
  4. Would you rather I just keep this to myself and my potato people?

I would appreciate any input or criticism.

Thanks, Angus

EDIT: corrected docstring

tekktrik commented 1 year ago

It looks like Blinka is using the generic Linux code, is that normal for the Libre Computer aml-s905x-cc "Le Potato", or should it be using another implementation / pin definition?

anguselheimStudios commented 1 year ago

I was told when a Linux driver exists you should use it. Either way, it's not working on the Le Potato in it's present form, so somethings got to give.

The way it's been looking, I'm just going to make a separate set of classes for use with these chips iio devices, since I can't really implement my plan and preserve backwards compatibility.

At the end of the day I want it to work, and to be able to keep my code portable for use with my microcontrollers with as few changes as possible.

tekktrik commented 1 year ago

I think this should be using the amlogic pin definitions as opposed to the generic_linux ones: https://github.com/adafruit/Adafruit_Blinka/tree/main/src/adafruit_blinka/microcontroller/amlogic

I'm not sure if that is the entire problem, but do think it's a large part of the issue as it is right now. I'm moving this to Blinka because as of right now the issue is that Adafruit_Blinka isn't detecting your specific board and using that information.

anguselheimStudios commented 1 year ago

OK, I'm glad to test solutions if needed.

I'm still going to use the driver in the kernel as my long term solution whether it gets merged or not. I don't know why, but it brings me immense joy to see a device stuck to a breadboard show up as files in my os.

anguselheimStudios commented 1 year ago

I've been poking around, but this is all a little over my head. Too many browser tabs open for my poor little potato and my poor little brain.

These lines in busio.py may be the culprit then?

        if detector.board.any_embedded_linux:
            from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI

https://github.com/adafruit/Adafruit_Blinka/blob/main/src/busio.py lines 330-331

I got an identical error using extended_bus, so I guess I'll have to look there next.

Also, as far as my iio addition, I have boiled it down to a separate AnalogIn class that works independently of the rest of the mcp3xxx class or Blinka. It only imports os and works pretty much the same way as the function I posted above. I will be pushing it to github later today or tomorrow. Should I treat iio support as a separate issue, or continue to discuss here? If so, should I open a new issue or just submit a PR?

EDIT: Been digging some more. extended_bus just calls busio. There is no s905x/SPI.py acorrding to help(board):

    MISO = (1, 88)
    MOSI = (1, 87)
...
    SCLK = (1, 90)
...
    SPI_CS = (1, 89)

This matches the info on Libre's pinmap. They're pretty big on "Generic Linux" over there, hence their insistence on using the driver in the kernel.

Board works. Digitalio works.

>>> print(board.board_id)
AML-S905X-CC
>>> print(board.detector.get_device_compatible())
libretech,aml-s905x-ccamlogic,s905xamlogic,meson-gxl

I'm thinking something else is wrong.

anguselheimStudios commented 1 year ago

Here is my new AnalogIn for iio devices. Guidance about what to do with it would be appreciated; I don't really understand this process or any of the etiquette surrounding it..

https://github.com/anguselheimStudios/Adafruit_CircuitPython_MCP3xxx/blob/main/adafruit_mcp3xxx/analog_in_iio.py

Thanks -Angus

anguselheimStudios commented 1 year ago

I just noticed I was trying different pins before I tried extended_bus, and kept the wrong chip select. Now with extended_bus I get a different error:

Traceback (most recent call last):
  File "/home/angus/Projects/Adafruit_Blinka/src/mcp3008_test.py", line 12, in <module>
    cs = digitalio.DigitalInOut(board.P24)
  File "/home/angus/Projects/Adafruit_Blinka/src/digitalio.py", line 165, in __init__
    self.direction = Direction.INPUT
  File "/home/angus/Projects/Adafruit_Blinka/src/digitalio.py", line 195, in direction
    self._pin.init(mode=Pin.IN)
  File "/home/angus/Projects/Adafruit_Blinka/src/adafruit_blinka/microcontroller/generic_linux/libgpiod_pin.py", line 108, in init
    self._line.request(
OSError: [Errno 16] Device or resource busy

I don't know if the set_no_cs would apply to this https://github.com/adafruit/Adafruit_Blinka/blob/main/src/adafruit_blinka/microcontroller/generic_linux/spi.py#L62-L65

I also noticed, going back using the REPL, just so I could get past the error:

>>> import board
>>> import digitalio
>>> from adafruit_extended_bus import ExtendedSPI as SPI
>>> spi = SPI(0, 0)
>>> print(board.detector.chip.id)
S905X
>>> print(spi._spi.chip.id)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'id'

After changing line 29 of that same SPI class from "self.chip = None" to "self.chip = detector.chip" I get:

>>> import board
>>> import digitalio
>>> from adafruit_extended_bus import ExtendedSPI as SPI
>>> spi = SPI(0, 0)
>>> print(spi._spi.chip.id)
S905X
>>> 

So something's amiss. I don't see init() getting called anywhere, is that supposed to be automatic like __init__()?

Still the same errors depending on what I set the cs to.

makermelissa commented 1 year ago

Hi, sorry for the delayed response. I thought @tekktrik had answered your questions, but I see he was just offering suggestions. I'll help as well as I can. I'm not super familiar with the "Le Potato" board nor do I have one, but I did add the PureIO SPI code (adapted really) and wrote the extended_bus library, and generally maintain Blinka.

The generic_linux stuff is used unless there a good reason to use something more specific to the board. The extended_bus library was more a way of making use of multiple SPI and I2C ports without breaking compatibility with CircuitPython. set_no_cs just doesn't do anything in linux for some strange reason (despite the source code appearing like it does). We've had long discussions about that and I've also tested this to confirm.

First I was wondering if #654 may be related to your issue, though the error message is different. I don't think they ever submitted a PR. However, after doing some google searching, I came across this: https://github.com/rm-hull/luma.oled/issues/253 where somebody was running into OSError: [Errno 22] Invalid argument, though in their case it was for I2C.

It talks about a limit on the buffer size being 1024. I added code to split the buffer into chunks to avoid hitting a limit and it is using a chunk size of 4096 (https://github.com/adafruit/Adafruit_Python_PureIO/blob/main/Adafruit_PureIO/spi.py#L72). Maybe you could find where your python libraries are installed and try changing this value to be smaller and see if that fixes the issue. I don't think there's currently an easy way to override this in Blinka, so if this is the cause, we could either make this smaller or add a way to override.

anguselheimStudios commented 1 year ago

Hi Melissa Thanks for the response! This is a little over my head, and I've pretty much run out of Ideas.

I just tried changing the value to 1024 and 512, no luck. I'm getting the same OSError 22 for chip select of 24, and OSError 18 for any other still.

I looked at #654 earlier and found that the correct pin values are now there, though no mention of a PR, and the issue is still open. (Has someone at Adafruit been leaving milk out for the Github elves?)

Thanks for the help!

dsx724 commented 11 months ago

I think a distinction has to be made about how Libre Computer boards work and what is causing the confusion.

1) There are two ways to drive and read sensors and this creates significant confusion. 2) Adafruit libraries interacts with device/sensors by driving the GPIOs and interfaces like I2C/SPI. The Adafruit library translates sensor values from the data on the interfaces. This is the de-facto standard in the Raspberry Pi world. 3) Upstream Linux kernel already has drivers for many device/sensors. They need to be hooked up via device-trees to existing buses and IOs. The in kernel drivers read the sensor data and exposes the IIO devices in /sys as files. This is the preferred way of interacting with sensors in the Linux world. I won't get into why this or that is better but this is the embedded industry standard and the one that Libre Computer follows. We have created the libretech-wiring-tool specifically to make this easier to use and deploy across our board line. 4) Adafruit libraries should add a bypass to read the sensors from the appropriate kernel interfaces instead of having root permissions to access hardware. Given the popularity of our boards as substitutes, we can also help create maintainable method to facilitate this.

ladyada commented 11 months ago

hiya we dont have any plans to have Blinka use the kernel interfaces for any sensors: most folks are not able to compile a kernel with new drivers or understand using device trees, and adding new kernel drivers is non-trivial. finally, tbh i find many of the kernel drivers are minimal and unable to be expanded or improved. if there's bugs its a much bigger effort to get upstreamed C code patches than PRing against the python libs we maintain. we dont necessarily need root perms, altho its easier than researching each distro's method of granting GPIO access.

so - the best way we've found to get hundreds of different devices working is the raspi way: expose the standard digital io gpio, i2c and spi devices and then control those within python. if someone from libre can document how to do that on their distro, we can point to that doc & it will make users pretty happy :)

dsx724 commented 11 months ago

Hi Lady Ada!

Your concern about upstream is legitimate and has been the reason for many vendors avoid upstream. If development speed and flexibility is a central priority, having your own Python to interpret the bus data is the most flexible route. However, things are not the same as a few years ago and you should re-evaluate.

i find many of the kernel drivers are minimal and unable to be expanded or improved

Device trees are very extensible and drivers can parameterize variants of a device. For example, we support half a dozen ST7735, ST7789, ILI9341, and ILI9486 displays with the same parameterized driver. Of course we had to fix upstream at times to make it work. Otherwise it also leads to long term underdevelopment of upstream kernel drivers, which is one of the problems you mentioned. Then everyone has to re-invent the wheel. This re-engineering is a waste of precious human resources.

we dont necessarily need root perms

IIO devices are broken down by group access that's set in udev when the device is enumerated. This allows some applications access certain based on the running user and group. The benefit here is that multiple applications can access the SPI/I2C bus at the same time as the kernel will serialize access instead of being limited to one thread that does both things. For example if you have two sensors on the I2C bus and two programs need to access it, it just cannot be done with userspace libraries. Using a kernel sensor driver will allow you to have both programs access the sensor on the same bus without having to know about each other.

the best way we've found to get hundreds of different devices working is the raspi way

If you take a look at how MIPI based displays are done upstream, they're far superior to the downstream methods (fbcp etc) Raspberry Pi uses. The same will apply to sensors. The Raspberry Pi method is obsolete and hindering progress. But as there is a mountain of guides, it's kneecapping upstream driver progress since users can tolerate the limitations for basic functionality. We have been investing in upstream drivers for many years now but we're fighting an uphill battle without the capital resources and with a competitor re-enforcing the proprietary lock-in.

if someone from libre can document how to do that on their distro, we can point to that doc

On any of our images for Le Potato, these is an one liner. They are different but similar across all of our boards. You can run sudo ldto list to see all available overlays. I2C: sudo ldto merge i2c-ao SPI: sudo ldto merge spicc-cs1 spicc-cs1-spidev Then reboot. If you need to apply them on-demand, replace "merge" with "enable" which temporarily hot-adds the bus until the system is rebooted.

We understand your business case and the legacy reasons that things are the way they are. However, we really want to make it simpler, more consistent, and reliable for everyone. Adafruit is the gateway to a lot of educators and students so if any technical assistance is needed, please feel free to reach out. We're happy to fix something upstream if you want to utilize upstream and something does not work. For example, the libretech-wiring-tool provides tooling and mechanisms to quickly develop and test device trees that was not possible before on any SBC. If you would like any device supported on any of our products done in an upstream way, feel free to have any of your staff open an issue. We're very responsive on the engineering side. We also have a lot of searchable documentation on hub.libre.computer but as you mentioned, it should be organized better.

ladyada commented 11 months ago

If you take a look at how MIPI based displays are done upstream, they're far superior to the downstream methods (fbcp etc) Raspberry Pi uses.

displays are in fact the one place we do recommend using the kernel driver when it is available: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/kernel-module-install fbcp is used to deal with the pi's hardware acceleration writing only to HDMI, https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/using-fbcp . you still need a kernel driver for it to work.

The same will apply to sensors. The Raspberry Pi method is obsolete and hindering progress. But as there is a mountain of guides, it's kneecapping upstream driver progress since users can tolerate the limitations for basic functionality.

i just disagree on this, completely. other than displays, touch screens, RTCs and maybe battery monitors, i don't see any benefit for why exposed-gpio embedded linux computers should have kernel drivers for sensors: the i2c/spi ports have an ioctl interface that handles transactions and bus sharing. we have found that changing DTO and kernel modules incredibly challenging for folks when they just want to read temperature, humidity or in this case read an SPI ADC. like i have personally never seen anyone ever say "i just configured a DTO and it was really easy / painless / worked immediately" - including myself :)

We have been investing in upstream drivers for many years now but we're fighting an uphill battle without the capital resources and with a competitor re-enforcing the proprietary lock-in.

if you'd like to document using hundreds of different sensors in your way: please do! folks may use that method! however, note that there's absolutely nothing in Blinka, adafruit drivers or raspberry pi's kernel-level i2c/spi/gpio interface that is at all "proprietary lock-in", everything is published under permissive open source licenses.

if le potato want to have folks use our libraries & tutorials with le potato boards, what they can do to help folks is follow this guide https://learn.adafruit.com/adding-a-single-board-computer-to-blinka to make sure that the Blinka support for this board the OP is using is correctly supported.