[CentralOH] 2017-08-28 會議 Scribbles 落書/惡文?: Neil Ludban Python vs the Hardware; freebsd raspberry pi2 cnc real I/O

jep200404 at columbus.rr.com jep200404 at columbus.rr.com
Fri Sep 29 18:21:53 EDT 2017


Thanks to christoph baker and pillar for their generous hospitality
there was pizza, salad, cookies, and beverages for all

devopsdays ohio in columbus 2017-11-08 and 2017-11-09
https://www.devopsdays.org/events/2017-ohio/welcome/

29 folks

columbus state
software development
python

database admin work for state of ohio
powershell
python
interested in anyone who has used ironpython
for integration with C#/.net

bioinformatics at ohio state
python is for 90% of what I do
R is for other 10%

september
erik welch will be presenting tips and tricks for jupyter notebooks

october
len and mike handler
python data analysis
temperature and humidity sensor
graph it in real time
len did back end
mike did front end
dallas one-wire interface sensor

first monday in december for social
maybe meet at smokehouse brewing company

january
travis will talk about async await and curio library
which was written by david beazley (dabeaz)

###############################################################################

Python vs the Hardware
by Neil Ludban
https://bitbucket.com/nludban/python-vs-the-hardware/
    private repo?

theme: python and embedded systems
several small python / freebsd / raspberry pi projects

He used Raspberry Pi 2, because that version is best supported by FreeBSD.

usb hid
i2c
gpio

part 1 usb hid

build cnc machine
free usb game controller
12 buttons
got it to work with python
https://pypi.python.org/pypi/libusb1

adafruit circuitpython
https://circuitpython.readthedocs.io
beta
micropython derivative
attaches as USB device
    keyboard
    mouse
    midi

feather m0 express
metro m0 express

the "right way" == the "wrong way"
supposed to
- get a device driver
  - find existing
  - modify it
  - write from scratch
- configure kernel
- one of
  - write python module to interface to kernel device
  - configure x11 to ignore device
or
  - configure x11 to enable device
  - write python module to interface to x11 device
  - application now revolves around x11

1-bit message for a button click

sudo usbhidctl -f /dev/uhid1 -a -v -l

import usb1
import libusb1

part 1

freebsd.org 11.1 july 2017
- production servers
- high end embedded systems

products based on freebsd

- mac os x
- juniper routers
- netflix
- game consoles: ps3, ps4, switch

freebsd raspbian

all i/o works on RPi2, dunno about RPi3

x11 not running yet
ARM6/7 ports confusion
no official (RPi) pre-built packages
native builds are slow

#hardware build tools

usb data capture www.saleae.com
mixed signal oscilloscope www.rigolna.com

agilent 54622D mixed signal oscilloscope
- circa 2000
- 2 analog
- 16 digital
  - some serial protocol support
  - good parallel bus support
- excellent real-time hi-res display
- https://www.youtube.com/watch?v=aVNfFewFn_Y

#part 3 - i2c for python on freebsd

found at microcenter
pimoroni four-letter-phat $12
    four 15-segment LED displays
    f007347
    pimoroni

- pure python driver from the vendor
  https://github.com/pimoroni/fourletter-phat
- uses https://github.com/pimoroni/py-smbus
  - which uses linux #include <linux/i2c-dev.h>

freebsd
- wp:SMB
- SMB is a subset of I2C

- no python i2c or smbus for freebsd
- freebsd i2c is spelled iic

man 4 iic

only did single byte reads and writes
did not need to do multi-byte reads or writes

plan #1

- python module iic.py
  - class IIC implemented with
    - file descriptor from os.open
    - I/O using os.read, os.write

- C extension module (_bsdiic.c) for ioctl
  - I2CSADDR

- dummy module (smbus.py)
  - class SMBus with methods:
    - open()
    - set_address()
    - write_i2c_block_data()

_bsdiic.c

#include <Python.h>
#includ <dev/iicbus/iic.h>

static
PyObject * foo(
    PyObject *self,
    PyObject *args
) {
    PyArg_ParseTuple(args, "ii", &fd, &addr);
    PyErr_SetFromErrno(PyExc_IOError);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef foo_methods[] = {
    {
        "set_slave_address",
        foo,
        METH_VARARGS,
        "I2CSADDR"
    }, {
        0
    }
};

static struct PyModuleDef foo_module =
{
    PyModuleDef_HEAD_INIT,
    "_bsdiic",
    NULL,
    -1,
    foo_methods,
    0
};

PyObject *
PyInit__bsdiic(void)
{
    PyObject *m = PyModule_Create(&bsdiic_module);
}

smbus.py

from iic ipmort IIC

class SMBus:

setup.py

#!/usr/bin/env python3.6

from distutils.core import setup, Extension

/usr/src/sys/arm/broadcom/bcm2835/bcm2835_bsc.c

    NOTE: Important detail uncovered by web searches:
    this kernel driver does not implement read and write.

static device_method_t bcm_bsc_methods[] = {
...
    /* iicbus interface */
    DEVMETHOD(iicbus_reset,             bcm_bsc_iicbus_reset),
    DEVMETHOD(iicbus_callback,          iicbus_null_callback),
    DEVMETHOD(iicbus_transfer,          bcm_bsc_transfer),
...

(what does my C code read talking to thermal sensor use?)

Plan #2

- Python class IIC
  - Verify data is bytes
  - remember slave_address
- C extension adds read and write functions using I2CRDWR ioctl

iic.py

_bsdiic.c

static
PyObject *
bsdiic_write(
    PyObject *self,
    PyObject *args
) {
    PyObject *obj;
    !PyArg_ParseTuple(args, "ii0", &fd, &addr, &obj))
    PyBytes_Check(obj)
    PyBytes_Size(obj)
    PyErr_SetString(PyExc_ValueError, "hello world");
    PyBytes_Size(obj);
    PyBytes_AsString(obj);
    PyErr_SetFromErrno(PyExc_OSError);
    Py_INCREF(Py_None);
}

_bsdiic.c

had to shift I2C address one bit left

PyBytes(obj)
(uint8_t *)PyBytes_AsString(Obj)

demo has pirate theme
https://github.com/pimoroni/fourletter-phat/tree/master/examples
https://github.com/pimoroni/fourletter-phat/blob/master/examples/demo.py
https://github.com/pimoroni/fourletter-phat/blob/master/examples/countdown.py

# Part 4 GPIO for Python on FreeBSD

- found pimoroni displayotron hat at microcenter for about $25
  https://shop.pimoroni.com/products/display-o-tron-hat
  https://github.com/pimoroni/displayotron

  - lcd driver (spi data; gpio select and reset)
  - capacitive touch interface (i2c data; gpio ???)
  - backlight driver (i2c data)

  - already have i2c
  - need gpio
  - can implement (slow) SPI over GPIO

existing python packages

- https://pypi.python.org/pypi/RPi.GPIO
  - Raspberry Pi and Linux specific
    - memory mapped I/O device access
    - mostly C code (~3k lines)

- https://github.com/gonzoua/freebsd-gpio
  - not maintained, suggests following:

- https://github.com/evadot/fbsd_gpio_py
  - wrapper for libgpio
  - uses CFFI (C foreign Function Interface for Python)
    https://cffi.readthedocs.io/
    looks like write by someone trying out a bunch of new things in Python
    without catching on to what one is really supposed to do
    as opposed to what one can do
    neil hopes he never sees CFFI again
  - throws exceptions while handling exceptions
  - clunky interface
    pin = getattr(controller, 'pin 42')
        (arduinoesque)
  - properties with major side effects:
    pin.name = 'foobar'

freebsd options

- search for detected devices
- kernel supplied devices
- system header files

dmesg | grep -i gpio

ls -l /dev/gpio*
/dev/gpioc0
man 8 gpioctl  ;# note wrong section
sudo gpioctl -l -v

man 3 gpio
came out of netbsd originally?

more questions

- sysctl or libgpio?
  - sysctl is low level
  - libgpio requires gpio_handle_t
- result of setting conflicting flags?
  - last takes precedence?
  - error?
- after more searching:
  - nothing gained with libgpio
    - very thin wrapper over ioctl()s
  - kernel device driver has implicit priorities
    additional cpio knobs supported through sysctl(8)

/usr/src/lib/libgpio/gpio.c
/usr/src/sys/arm/broadcom/bcm2835/bcm2835_gpio.c

sysctl -a | grep -i gpio

came from intel

so he wrote is own interface

a gpio example

    NOTE: The biggest requirement is a clean Pythonic API.
    Test first by writing a small but complete application.
    (Everything should be built top-down, except the first time.)
    ("Facade" design pattern.)

import gpio

c = gpio.open('/dev/gpioc0')
for pin in c.list_pins():
    print(pin)

bcm2 = c.get_pin(number=2)      # (i2c SDA / header pin 3)
bcm3 - c.get_pin(name='pin 3')  # (i2c SCL / header pin 5)

# bug in following?
# should write initial value before configuring for output?

with bcm2.configure() as pin_config:
    pin_config.output = True
    pin_config.initial = 0  # Why not 1?

with bcm3.configure() as pin_config:
    pin_config.output = True
    pin_config.initial = 0  # Why not 1?

while True:
   bcm2.set(1)  # 1,0
   bcm3.set(1)  # 1,1
   bcm2.set(0)  # 1,0
   bcm3.set(0)  # 0,0

# above loop ran at 10.7 kHz (four pin.set() calls)
# neil did not notice jitter

but first, some refactoring

    NOTE: Created a top-level "pybsd" package to contain iic and gpio
    sub-projects. To avoid changing application code smbus and RPi stay
    at the top level

pybsd/gpio/__init__.py

    NOTE: The package __init__ exports the public interfaces.

pybsd/gpio/constants.py

    NOTE: Copying constant definitions from C to a more convenient
    namespace. XXX import _bsdgpio.constants as ?

pybsd/gpio/controller.py

    NOTE: The GPIO Controller manages the file descriptor and
    provides access to individual pins.

pybsd/gpio/pin.py

    NOTE: The Pin class is supported by Flags, Capabilities,
    and Configuration.

    NOTES: Configuration extends the read-only Flags properties with
    setter properties.

class Flags

    @property

class Capabilities(Flags)

class Configuration(Flags)

    @Flags.input.setter
    def input(self, yes_no)

class Pin

_bsdgpio.c

    NOTE: Mostly boilerplate; abridged to highlight new techniques.

#include <Python.h>
#include <sys/gpio.h>

static
PyObject *
bsdgpio_get_pin_config(
    PyObject *self,
    PyObject *args
) {
    PyArg_ParseTuple(args, "ii", &fd, &pin_number)
    PyErr_SetFromErrno(PyExc_IOError)
    PyObject *t = PyTuple_New(3);
    PyTuple_SetItem(t, 0, PyBytes_FromString(gp.gp_name));
    PyTuple_SetItem(t, 1, PyLong_FromLong(gp.gp_caps));
    PyTuple_SetItem(t, 2, PyLong_FromLong(gp.gp_flags));
}

    PyObject *rv = (gr.gp_value ? Py_True : Py_False);
    Py_INCREF(rv);
    return rv;

PyObject *
PyInit__bsdgpio(void)
{
    PyObject *m = PyModule_Create(&bsdgpio_module);
    PyModule_AddIntConstant(m, "PIN_INPUT", GPIO_PIN_INPUT);
    PyModule_AddIntConstant(m, "PIN_OUTPUT", GPIO_PIN_OUTPUT);
    PyModule_AddIntConstant(m, "PIN_OPEN_DRAIN", GPIO_PIN_OPEN_DRAIN);
    PyModule_AddIntConstant(m, "PIN_PUSHPULL", GPIO_PIN_PUSHPULL);
    PyModule_AddIntConstant(m, "PIN_TRISTATE", GPIO_PIN_TRISTATE);
}


More information about the CentralOH mailing list