diff --git a/README.md b/README.md index 805a4f4..899f1cd 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,17 @@ Connects to the specified SPI device, opening `/dev/spidev.` readbytes(n) -Read n bytes from SPI device. +Read n bytes from SPI device, returning a list of integers. + + readbytesb(n) + +Read n bytes from SPI device, returning an (immutable) bytes object (more efficient for larger transfers). + + readbuffer(buffer [, length [, offset]]) + +Read length bytes from SPI device into an existing bytearray object at the given offset (single SPI transfer) + If length is not specified or 0, tries to read to the end of the buffer. + If offset is not specified, default is 0. writebytes(list of values) diff --git a/spidev_module.c b/spidev_module.c index bcb6c86..d0b94a7 100644 --- a/spidev_module.c +++ b/spidev_module.c @@ -223,49 +223,147 @@ SpiDev_writebytes(SpiDevObject *self, PyObject *args) return Py_None; } + +/* return true on success */ +static int +SpiDev_read_lowlevel(SpiDevObject *self, uint8_t *buf, int buflen, int lenreq) +{ + int status; + + /* read at least 1 byte, no more than SPIDEV_MAXPATH */ + if (lenreq < 1) + lenreq = 1; + else if ((unsigned)lenreq > sizeof(buflen)) + lenreq = buflen; + memset(buf, 0, buflen); + +//#define SPIDEV_READ_LOWLEVEL_TESTING +#ifdef SPIDEV_READ_LOWLEVEL_TESTING + { status = lenreq; for (unsigned i = 0; i < lenreq; i++) buf[i] = 64 + i; } +#else + status = read(self->fd, &buf[0], lenreq); +#endif + + if (status < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return 0; // fail + } + + if (status != lenreq) { + printf("Short Read: %d/%d bytes\n", status, lenreq); + PyErr_SetString(PyExc_IOError, "SpiDev.read: I/O error: short read."); + return 0; // fail + } + return 1; // succeed +} + + +#define READBYTES_GENERIC_RESULTTYPE_LIST 0 +#define READBYTES_GENERIC_RESULTTYPE_BYTES 1 + +static PyObject * +SpiDev_readbytes_generic(SpiDevObject *self, PyObject *args, int resultType) +{ + static uint8_t rxbuf[SPIDEV_MAXPATH]; + int len = 0; + + if (!PyArg_ParseTuple(args, "i:read", &len)) + return NULL; + + // get the bytes here + if (!SpiDev_read_lowlevel(self, rxbuf, sizeof(rxbuf), len)) { + return NULL; + } + + if (resultType == READBYTES_GENERIC_RESULTTYPE_LIST) { // list + int ii; + PyObject *list; + list = PyList_New(len); + + for (ii = 0; ii < len; ii++) { + PyObject *val = Py_BuildValue("l", (long)rxbuf[ii]); + PyList_SET_ITEM(list, ii, val); + } + + return list; + } + else if (resultType == READBYTES_GENERIC_RESULTTYPE_BYTES) { // bytes + PyObject *bytes; + bytes = Py_BuildValue("y#", rxbuf, len); + return bytes; + } + else { // unknown type + PyErr_SetString(PyExc_RuntimeError, "SpiDev.read: internal problem in readbytes_generic."); + return NULL; + } +} + + + PyDoc_STRVAR(SpiDev_read_doc, "read(len) -> [values]\n\n" - "Read len bytes from SPI device.\n"); + "Read len bytes from SPI device, returning a list.\n"); static PyObject * SpiDev_readbytes(SpiDevObject *self, PyObject *args) { - uint8_t rxbuf[SPIDEV_MAXPATH]; - int status, len, ii; - PyObject *list; + return SpiDev_readbytes_generic(self, args, READBYTES_GENERIC_RESULTTYPE_LIST); +} - if (!PyArg_ParseTuple(args, "i:read", &len)) - return NULL; +PyDoc_STRVAR(SpiDev_readb_doc, + "read(len) -> bytes(len)\n\n" + "Read len bytes from SPI device, returning a bytes object of length len.\n"); - /* read at least 1 byte, no more than SPIDEV_MAXPATH */ - if (len < 1) - len = 1; - else if ((unsigned)len > sizeof(rxbuf)) - len = sizeof(rxbuf); +static PyObject * +SpiDev_readbytesb(SpiDevObject *self, PyObject *args) +{ + return SpiDev_readbytes_generic(self, args, READBYTES_GENERIC_RESULTTYPE_BYTES); +} - memset(rxbuf, 0, sizeof rxbuf); - status = read(self->fd, &rxbuf[0], len); - if (status < 0) { - PyErr_SetFromErrno(PyExc_IOError); - return NULL; - } +PyDoc_STRVAR(SpiDev_readbuffer_doc, + "read(bytearray [, length [, offset]]) -> [values]\n\n" + "Read bytes from SPI device, filling the supplied byte array.\n"); - if (status != len) { - perror("short read"); - return NULL; - } +static PyObject * +SpiDev_readbuffer(SpiDevObject *self, PyObject *args) +{ + Py_buffer pybuff; + int offset = 0, len = 0; - list = PyList_New(len); + if (!PyArg_ParseTuple(args, "y*|II", &pybuff, &len, &offset)) + return NULL; + if (!PyBuffer_IsContiguous(&pybuff, 'A')) { + PyErr_SetString(PyExc_RuntimeError, "SpiDev.readbuffer: buffer must be contiguous."); + return NULL; + } + if (pybuff.len < 1) { + PyErr_SetString(PyExc_RuntimeError, "SpiDev.readbuffer: buffer must not be empty."); + return NULL; + } + if (len < 0) { + PyErr_SetString(PyExc_RuntimeError, "SpiDev.readbuffer: length must be positive."); + return NULL; + } + if (offset < 0 || offset >= pybuff.len - 1) { // NB must be space for at least one byte + PyErr_SetString(PyExc_RuntimeError, "SpiDev.readbuffer: offset out of range."); + return NULL; + } + if (offset + len > pybuff.len) { + PyErr_SetString(PyExc_RuntimeError, "SpiDev.readbuffer: offset+length would overflow buffer."); + return NULL; + } - for (ii = 0; ii < len; ii++) { - PyObject *val = Py_BuildValue("l", (long)rxbuf[ii]); - PyList_SET_ITEM(list, ii, val); - } + if (len == 0) + len = pybuff.len - offset; - return list; + if (!SpiDev_read_lowlevel(self, pybuff.buf + offset, len, len)) + return NULL; + + Py_RETURN_NONE; } + static PyObject * SpiDev_writebytes2_buffer(SpiDevObject *self, Py_buffer *buffer) { @@ -861,6 +959,7 @@ SpiDev_xfer3(SpiDevObject *self, PyObject *args) return rx_tuple; } + static int __spidev_set_mode( int fd, __u8 mode) { __u8 test; if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) { @@ -1365,6 +1464,8 @@ static PyMethodDef SpiDev_methods[] = { SpiDev_fileno_doc}, {"readbytes", (PyCFunction)SpiDev_readbytes, METH_VARARGS, SpiDev_read_doc}, + {"readbytesb", (PyCFunction)SpiDev_readbytesb, METH_VARARGS, + SpiDev_readb_doc}, {"writebytes", (PyCFunction)SpiDev_writebytes, METH_VARARGS, SpiDev_write_doc}, {"writebytes2", (PyCFunction)SpiDev_writebytes2, METH_VARARGS, @@ -1375,6 +1476,7 @@ static PyMethodDef SpiDev_methods[] = { SpiDev_xfer2_doc}, {"xfer3", (PyCFunction)SpiDev_xfer3, METH_VARARGS, SpiDev_xfer3_doc}, + {"readbuffer", (PyCFunction)SpiDev_readbuffer, METH_VARARGS, SpiDev_readbuffer_doc}, {"__enter__", (PyCFunction)SpiDev_enter, METH_VARARGS, NULL}, {"__exit__", (PyCFunction)SpiDev_exit, METH_VARARGS,