User Space IO is Python 3 and Java 8 bindings for Linux user space GPIO, SPI, I2C, PWM, MMIO and Serial interfaces. Using best of breed user space C libraries provides a cross platform solution to SBC development. Primarly User Space IO will be targeting Armbian, but the scripts should work with most Ubuntu/Debian distributions. Demo applications are included that illustrate how to leverage the bindings.
The idea is to have consistent APIs across C, Python and JVM languages without having to use hacked up RPi.GPIO or Wiring Pi implementations for each distinct SBC model. The possibility of using other JVM based languages such as Groovy, Kotlin, Scala, etc. opens up language opprtunties that do not currently exist in the IoT space.
- If you are using Armbian then use
armbian-configor edit/boot/armbianEnv.txtto configure various devices. User space devices are exposed through /dev or /sys. Verify the device is showing up prior to trying demo programs. - If you are not using Armbian then you will need to know how to configure devices to be exposed to user space for your Linux distribution and SBC model. Check each log to be sure there were no errors after running install.sh. Submit a PR if you would like a different distribution supported. Conditionals can be added to the current scripts to handle different detectable distributions.
- You need kernel 4.8 or greater to use libgpiod.
- You need kernel 4.16 or greater to use non-root access for PWM.
- I have tested NanoPi Duo v1.1 for 32 bit and NanoPi Neo 2 Plus for 64 bit using the latest Armbian release. The ability to switch seemlessly between 32 and 64 bit platforms gives you a wide range of SBC choices.
CFFI is used to create the Python 3 bindings.
JNAerator is used to create Java bindings from the C header files. I added some post generation patching (see java-bindings.sh) to fix a couple of issues with the generated code. Note: JNAerator fails on ARMv8 (64 bit) with "architecture word width mismatch" because there is no ARMv8 version of the native library libbridj.so. This is not an issue since the Java bytecode compiled on ARMv7 works without the need to recompile. The wrapper script skips the JNAerator steps and just compiles the demos with the precompiled jars included in the project.
If you get the following error:
There is an incompatible JNA native library installed on this system
Expected: 1.2.3
Found: 4.5.6
See issue on Github. Basically you can add JVM arg
-Djna.nosys=true.
cd ~/git clone --depth 1 https://github.com/sgjava/userspaceio.git
cd ~/userspaceio./install.sh- Check various log files if you have issues running the demo code. Something could have gone wrong during the build/bindings generation processes.
If you want to access devices without root do the following (you can try udev rules instead if you wish):
sudo usermod -a -G dialout username(Use a non-root username)sudo groupadd i2csudo usermod -a -G i2c username(Use a non-root username)sudo groupadd spisudo usermod -a -G spi username(Use a non-root username)sudo groupadd gpiosudo usermod -a -G gpio username(Use a non-root username)sudo gpiodetect(Note chip names to add below for libgpiod access)ls /dev/spidev*(Note SPI channels below)ls /dev/i2c*(Note i2c devices below)sudo nano /etc/rc.local
chown -R root:gpio /dev/gpiochip0
chmod -R ug+rw /dev/gpiochip0
chown -R root:gpio /dev/gpiochip1
chmod -R ug+rw /dev/gpiochip1
chown -R root:i2c /dev/i2c-0
chmod -R ug+rw /dev/i2c-0
chown -R root:spi /dev/spidev1.0
chmod -R ug+rw /dev/spidev1.0sudo nano /etc/udev/rules.d/99-com.rules
SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c '\
chown -R root:gpio /sys/class/pwm && chmod -R 770 /sys/class/pwm;\
chown -R root:gpio /sys/devices/platform/soc/*.pwm/pwm/pwmchip* && chmod -R 770 /sys/devices/platform/soc/*.pwm/pwm/pwmchip*\
'"libgpiod is a C library and tools for interacting with the linux GPIO character device. Since linux 4.8 the GPIO sysfs interface is deprecated. User space should use the character device instead. libgpiod encapsulates the ioctl calls and data structures behind a straightforward API.
Master branch is using libgpiod 1.1. Use 1.0 branch for libgpiod 1.0 support.
Edit install.sh
usegitrepoto use libgpiod-1.0.1.tar.gz or git master (defaut).defkerverto specify pre-installed kernal headers. You need to do this if header files are not located by /etc/armbian-release or uname -r methods. Use something likeapt-cache search linux-headers-* | grep -i "4.16"(change version number) to find header version to match your kernel.
This is based on testing on a NanoPi Duo. gpiochip0 starts at 0 and gpiochip1 start at 352. Consider the following table:
| Name | Chip Name | Line | sysfs |
|---|---|---|---|
| DEBUG_TX(UART_TXD0)/GPIOA4 | gpiochip0 | 004 | 004 |
| DEBUG_RX(UART_RXD0)/GPIOA5/PWM0 | gpiochip0 | 005 | 005 |
| I2C0_SCL/GPIOA11 | gpiochip0 | 011 | 011 |
| I2C0_SDA/GPIOA12 | gpiochip0 | 012 | 012 |
| UART3_TX/SPI1_CS/GPIOA13 | gpiochip0 | 013 | 013 |
| UART3_RX/SPI1_CLK/GPIOA14 | gpiochip0 | 014 | 014 |
| UART3_RTS/SPI1_MOSI/GPIOA15 | gpiochip0 | 015 | 015 |
| UART3_CTS/SPI1_MISO/GPIOA16 | gpiochip0 | 016 | 016 |
| UART1_TX/GPIOG6 | gpiochip0 | 198 | 198 |
| UART1_RX/GPIOG7 | gpiochip0 | 199 | 199 |
| GPIOG11 | gpiochip0 | 203 | 203 |
| ON BOARD BUTTON | gpiochip1 | 003 | 355 |
| GPIOL11/IR-RX | gpiochip1 | 011 | 363 |
So basically you just need to know the starting number for each chip and realize libgpiod always starts at 0 and calculate the offset. Thus gpiochip1 starts at 352 and the on board button is at 355, so 355 - 352 = 3 for libgpiod.
libgpiod 1.1 includes Python bindings, so CFFI is not used.
To run demos:
alias python=python3export PYTHONPATH=/usr/local/lib/python3.5/site-packagescd ~/userspaceio/libgpiod/python/srcpython ledtest.py --chip 0 --line 203to run LED test after wiring up to line 203 (GPIOG11) on NanoPi Duo (the default).
To run demos:
cd ~/userspaceio/libgpiod/javajava -Djava.library.path=/usr/local/lib -cp ../../jnaerator/jna-4.5.2.jar:../../jnaerator/jnaerator-runtime.jar:libgpiod.jar:demo.jar com.codeferm.demo.LedTest 0 203to run LED test after wiring up to line 203 (GPIOG11) on NanoPi Duo (the default).
c-periphery is used to provide SPI, I2C, MMIO and Serial interfaces. I did not use the GPIO because it is based on the oudated sysfs interface. Helper methods were added to the objects to remove some repetitive operations (i.e. building I2C messages to read/write registers).
To run demos:
alias python=python3cd ~/userspaceio/c-periphery/python/srcpython spiloopback.py --device /dev/spidev1.0 --maxSpeed 500000to run SPI loop back on NanoPi Duo (the default). Use a jumper wire between MI and MO.
To run demos:
cd ~/userspaceio/c-periphery/javajava -Djava.library.path=/usr/local/lib -cp ../../jnaerator/jna-4.5.2.jar:../../jnaerator/jnaerator-runtime.jar:libperiphery.jar:demo.jar com.codeferm.demo.Mpu6050to run Triple Axis Accelerometer & Gyro MPU-6050 sensor example.
I wasn't able to find a good C library that handled hardware PWM, so I worte one based on /sys/class/pwm. Your SBC must support hardware PWM and be exposed to the kernel via /sys/class/pwm.
To run demos:
cd ~/userspaceio/pwmio/python/srcsudo python3 ledflash.py --device 0 --pwm 0to run I wired up the LED to the PWM pin.
To run demos:
cd ~/userspaceio/pwmio/javasudo java -Djava.library.path=/usr/local/lib -cp ../../jnaerator/jna-4.5.2.jar:../../jnaerator/jnaerator-runtime.jar:libpwmio.jar:demo.jar com.codeferm.demo.LedFlashto make LED flash and increase intensity.

