lthiery / SPI-Py

Hardware SPI as a C Extension for Python
289 stars 150 forks source link

Accessing two SPI devices #11

Open msedv opened 8 years ago

msedv commented 8 years ago

As long as I'm only accessing ONE SPI device everything is fine, but when accessing TWO (in my case: MFRC522.py for two RFID-Readers) only one of them works. Looking at your code I think the problem is that

int fd;

is declared globally?!

msedv commented 8 years ago

If anyone is interested - here my version in which openSPI returns the file descriptor which then must be given to transfer and closeSPI. A little bit of a hack but works like a charm:

/* SPI testing utility (see copyright beow)
 *  adapted for use in Python
 *  by Louis Thiery
 *  Lots more flexibility and cleanup by Connor Wolf (imaginaryindustries.com)
 *
 * compile for Python using: "python setup.py build"
 * compiled module will be in "./build/lib.linux-armv6l-2.7/spi.so"
 *
 * SPI testing utility (using spidev driver)
 *
 * Copyright (c) 2007  MontaVista Software, Inc.
 * Copyright (c) 2007  Anton Vorontsov <avorontsov@ru.mvista.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 */

#include <Python.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

static void pabort(const char *s)
{
    perror(s);
    abort();
}

static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;

static PyObject* openSPI(PyObject *self, PyObject *args, PyObject *kwargs) {
    int ret = 0;
    int fd;
    static const char *device = "/dev/spidev0.0";
    static char* kwlist[] = {"device", "mode", "bits", "speed", "delay", NULL};

    // Adding some sort of mode parsing would probably be a nice idea for the future, so you don't have to specify it as a bitfield
    // stuffed into an int.
    // For the moment the default mode ("0"), will probably work for 99% of people who need a SPI interface, so I'm not working on that

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|siiii:keywords", kwlist, &device, &mode, &bits, &speed, &delay))
        return NULL;
    // It's not clearly documented, but it seems that PyArg_ParseTupleAndKeywords basically only modifies the values passed to it if the
    // keyword pertaining to that value is passed to the function. As such, the defaults specified by the variable definition are used
    // unless you pass a kwd argument.
    // Note that there isn't any proper bounds-checking, so if you pass a value that exceeds the variable size, it's just truncated before
    // being stuffed into  the avasilable space. For example, passing a bits-per-word of 500 gets truncated to 244. Unfortunately, the
    // PyArg_ParseTupleAndKeywords function only seems to support ints of 32 bits.

    PyErr_Clear();

    // printf("*** SPI.C openSPI: Mode: %i, Bits: %i, Speed: %i, Delay: %i\n", mode, bits, speed, delay);

    fd = open(device, O_RDWR);
    if (fd < 0)
        pabort("can't open device");

    // printf("*** SPI.C openSPI: fd: %i\n", fd);

    /*
     * Setup SPI mode
     */
    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
    if (ret == -1)
        pabort("can't set spi mode");

    ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
    if (ret == -1)
        pabort("can't get spi mode");

    /*
     * bits per word
     */
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if (ret == -1)
        pabort("can't set bits per word");

    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if (ret == -1)
        pabort("can't get bits per word");

    /*
     * max speed hz
     */
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        pabort("can't set max speed hz");

    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        pabort("can't get max speed hz");

    // Stuff the various initilization parameters into a dict, and return that.
    // Note that the returned values may not be completely real. It seems that, at least for the speed value,
    // the hardware only has several possible settings (250000, 500000, 1000000, etc...) Strangely enough, the
    // ioctl for setting the speed *returns the speed you specify*. However, the hardware seems to default to the
    // closest avalable value *below* the specified rate. (i.e. you will never get a speed faster then you spec),
    // but you may get a slower value.

    //It would probably be a good idea to bin-down the passed arguement to the available values, and return
    // that.

    PyObject* retDict;
    retDict = PyDict_New();

#if PY_MAJOR_VERSION >= 3
    PyDict_SetItem(retDict, PyBytes_FromString("fd"), PyLong_FromLong((long)fd));
    PyDict_SetItem(retDict, PyBytes_FromString("mode"), PyLong_FromLong((long)mode));
    PyDict_SetItem(retDict, PyBytes_FromString("bits"), PyLong_FromLong((long)bits));
    PyDict_SetItem(retDict, PyBytes_FromString("speed"), PyLong_FromLong((long)speed));
    PyDict_SetItem(retDict, PyBytes_FromString("delay"), PyLong_FromLong((long)delay));
#else
    PyDict_SetItem(retDict, PyString_FromString("fd"), PyInt_FromLong((long)fd));
    PyDict_SetItem(retDict, PyString_FromString("mode"), PyInt_FromLong((long)mode));
    PyDict_SetItem(retDict, PyString_FromString("bits"), PyInt_FromLong((long)bits));
    PyDict_SetItem(retDict, PyString_FromString("speed"), PyInt_FromLong((long)speed));
    PyDict_SetItem(retDict, PyString_FromString("delay"), PyInt_FromLong((long)delay));
#endif

    return retDict;
}

static PyObject* transfer(PyObject* self, PyObject* arg) {
    int ret = 0;
    int fd;
    PyObject* transferTuple;

                                // "O" - Gets non-NULL borrowed reference to Python argument.
                                // As far as I can tell, it's mostly just copying arg[0] into transferTuple
                                // and making sure at least one arg has been passed (I think)
    if(!PyArg_ParseTuple(arg, "iO", &fd, &transferTuple))
        return NULL;                    

    // printf("*** SPI.C transfer: fd: %i\n", fd);

                                // The only argument we support is a single tuple.
    if(!PyTuple_Check(transferTuple))
        pabort("Only accepts a single tuple as an argument\n");

    uint32_t tupleSize = PyTuple_Size(transferTuple);

    uint8_t tx[tupleSize];
    uint8_t rx[tupleSize];
    PyObject* tempItem;

    uint16_t i=0;

    while(i < tupleSize)
    {
        tempItem = PyTuple_GetItem(transferTuple, i);       //
#if PY_MAJOR_VERSION >= 3
        if(!PyLong_Check(tempItem))
#else
        if(!PyInt_Check(tempItem))
#endif
        {
            pabort("non-integer contained in tuple\n");
        }
#if PY_MAJOR_VERSION >= 3
        tx[i] = (uint8_t)PyLong_AsSsize_t(tempItem);
#else
        tx[i] = (uint8_t)PyInt_AsSsize_t(tempItem);
#endif

        i++;

    }

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = tupleSize,
        .delay_usecs = delay,
        .speed_hz = speed,
        .bits_per_word = bits,
                .cs_change = 1,
    };

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 1)
        pabort("can't send spi message");

    transferTuple = PyTuple_New(tupleSize);

    for (i = 0; i < tupleSize; i++)
        PyTuple_SetItem(transferTuple, i, Py_BuildValue("i",rx[i]));

    return transferTuple;
}

static PyObject* closeSPI (PyObject* self, PyObject* arg) {
    int fd;

    if(!PyArg_ParseTuple(arg, "i", &fd))
        return NULL;

    // printf ("*** SPI.C closeSPI: fd: %i\n", fd);
    PyErr_Clear();

    close (fd);
    Py_RETURN_NONE;
}

static PyMethodDef SpiMethods[] = {
    {"openSPI", (PyCFunction)openSPI, METH_VARARGS | METH_KEYWORDS, "Open SPI Port."},
    {"transfer", (PyCFunction)transfer, METH_VARARGS, "Transfer data."},
    {"closeSPI", (PyCFunction)closeSPI, METH_VARARGS, "Close SPI port."},
    {NULL, NULL, 0, NULL}
};

#if PY_MAJOR_VERSION >= 3
    static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "spi",     /* m_name */
        "spi library",  /* m_doc */
        -1,                  /* m_size */
        SpiMethods,    /* m_methods */
        NULL,                /* m_reload */
        NULL,                /* m_traverse */
        NULL,                /* m_clear */
        NULL,                /* m_free */
    };
#endif

PyMODINIT_FUNC

#if PY_MAJOR_VERSION >= 3
PyInit_spi(void)
#else
initspi(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
        PyObject *module = PyModule_Create(&moduledef);
#else
    (void) Py_InitModule("spi", SpiMethods);
#endif
;

#if PY_MAJOR_VERSION >= 3
    return module;
#endif
}
lthiery commented 8 years ago

do you know how to make a pull request out of this? that way i can look at the diffs more easily and pull it in potentially?

msedv commented 8 years ago

Puh, I'm not very experienced with github.... (: I tried my best, but since I have no write access to the project github forced my to make a fork which is here: https://github.com/msedv/SPI-Py

boconn7782 commented 8 years ago

So I'm just starting to look into this but I want to use the RC522 and a touchscreen display (https://www.adafruit.com/products/2441). The touch screen display uses GPIO25 already. I know that's a general purpose I/O pin so I should be able to change the connection for the RC522's rst pin to another one of the general purpose I/O pins. I am just unsure where in the code to do this or if there's some reason I am not aware of that i cannot. Any help?

magneticlab-ch commented 7 years ago

Hi, I try to use my RPi3 and the RC522 and a touchscreen display aswell so i need to put my RC522 on the auxiliary SPI1 right ? How can I do it ? The actual wires are :

SS   --> PIN26 CE1 (BCM7)
SCLK --> PIN40 (BCM21)
MOSI --> PIN38 (BCM20)
MISO --> PIN35 (BCM19)
IRQ  --> not connected
GND  --> GND
RST  --> PIN37 (BCM26) 
VCC  --> 3.3v

using this as the reference layout : http://pinout.xyz/ My question is how to change the code in order to get this RC522 to work on SP1 ?