Java Periphery is a high performance library for GPIO, LED, PWM, SPI, I2C, MMIO and Serial peripheral I/O interface access in userspace Linux. Rather than try to build this from scratch I used c-periphery and HawtJNI to generate the JNI wrappers. This saves a lot of hand coding and allows for easier synchronization with c-periphery changes moving forward.
- Generates JNI source code for c-periphery.
- Generates an autoconf and msbuild source project to build the native library. This gets attached to the Maven project as as the native source zip file.
- Builds the native source tar for the current platform.
- Built native library is stored in a platform specific jar. This gets attached to the Maven project as a platform specific jar file.
- All wrapper classes support AutoCloseable, so you can use the try-with-resources statement to automatically close and free native resources. This prevents hard to track down native memory leaks.
try (final var spi = new Spi("/dev/spidev1.0", 0, 500000)) {
final var txBuf = new byte[128];
// Change some data at beginning and end.
txBuf[0] = (byte) 0xff;
txBuf[127] = (byte) 0x80;
final var rxBuf = new byte[128];
Spi.spiTransfer(spi.getHandle(), txBuf, rxBuf, txBuf.length);
logger.info(String.format("%02X, %02X", (short) rxBuf[0] & 0xff, (short) rxBuf[127] & 0xff));
}
Behold the FrankenDuo which is used to test all Java Periphery features.
Java Periphery will be targeting Armbian, but the code should work with most Linux distributions. Demo apps are included that illustrate how to leverage the bindings. The idea is to have consistent APIs across C, Python, Lua and JVM languages without having to use board specific drivers or the deprecated sysfs interface for GPIO. The possibility of using other JVM based languages such as Groovy, Kotlin, Scala, etc. opens up language opportunities that do not currently exist in the IoT space.
- Why Linux userspace? This is really the only way to get cross platform libraries to work since most SBCs have different chip sets. The trade off is performance compared to native C written to specific chip sets. However, since I'm wrapping C with JNI it guarantees the fastest userspace experience for Java.
- Why Armbian? Because Armbian supports many SBCs and the idea is to be truly SBC cross platform. See downloads.
- Why Java 11? Because Java 11 is the current LTS version of Java. Java 8 the previous LTS release was end of life January 2019 for Oracle (commercial) and will be end of life December 2020 for Oracle (personal use). I'm only moving forward with Java. You can always create a fork and make a Java 8 version of Java Periphery.
- Why Zulu OpenJDK? Because it's easy to download without all the crap Oracle puts you through. You can always use another JDK 11 vendor, but you will have to do that manually.
- If you are using Armbian then use
armbian-config
or edit/boot/armbianEnv.txt
to configure various devices. Userspace devices are exposed through /dev or /sys. Verify the device is showing up prior to trying demo apps.sudo apt install armbian-config
- If you are not using Armbian then you will need to know how to configure devices to be exposed to userspace for your Linux distribution and SBC model. Check each log in scripts directory to be sure there were no errors after running install.sh.
- Since linux 4.8 the GPIO sysfs interface is deprecated. Userspace should use the character device instead.
- 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. I'm currently testing with Ubuntu 20.04 LTS Focal Fossa using 5.6 kernel.
On the NanoPi Duo the built in button causes it to shutdown by default. You can remove the r_gpio_keys section in the DTB as follows (this may work on other SBCs, but you'll need to know the correct dtb file and section to remove) :
cd /boot/dtb
sudo cp sun8i-h2-plus-nanopi-duo.dtb sun8i-h2-plus-nanopi-duo.dtb.old
sudo dtc -@ -I dtb -O dts -o sun8i-h2-plus-nanopi-duo.dts sun8i-h2-plus-nanopi-duo.dtb
sudo nano sun8i-h2-plus-nanopi-duo.dts
- Remove
r_gpio_keys
section
- Remove
sudo dtc -@ -I dts -O dtb -o sun8i-h2-plus-nanopi-duo.dtb sun8i-h2-plus-nanopi-duo.dts
reboot
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 periphery
sudo usermod -a -G periphery username
(Use a non-root username)ls /dev/gpio*
(Note chip names to add below)ls /dev/spidev*
(Note SPI channels below)ls /dev/i2c*
(Note i2c devices below)sudo nano /etc/rc.local
chown -R root:periphery /dev/gpiochip0
chmod -R ug+rw /dev/gpiochip0
chown -R root:periphery /dev/gpiochip1
chmod -R ug+rw /dev/gpiochip1
chown -R root:periphery /dev/i2c-0
chmod -R ug+rw /dev/i2c-0
chown -R root:periphery /dev/spidev1.0
chmod -R ug+rw /dev/spidev1.0
chown -R root:periphery /sys/devices/platform/leds/leds
chmod -R ug+rw /sys/devices/platform/leds/leds
- PWM udev rules
- You need kernel 4.16 or greater to use non-root access for PWM.
sudo nano /etc/udev/rules.d/99-pwm.rules
SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c '\ chown -R root:periphery /sys/class/pwm && chmod -R 770 /sys/class/pwm;\ chown -R root:periphery /sys/devices/platform/soc/*.pwm/pwm/pwmchip* && chmod -R 770 /sys/devices/platform/soc/*.pwm/pwm/pwmchip*\ '"
sudo apt install git
cd ~/
git clone --depth 1 https://github.com/sgjava/java-periphery.git
The install script assumes a clean OS install. If you would like to install on a OS with your own version of Java 11, etc. then you can look at what install.sh does and do it manually. What does the script do?
- Install build dependencies for HawtJNI
- Installs Zulu OpenJDK 11 to /usr/lib/jvm
- Installs Maven to /opt
- Build HawtJNI
- Build Java Periphery
The Java Periphery POM uses download-maven-plugin to download c-periphery source
to
src/main/native-package/src
. The files are cached in~/.m2/repository/.cache/download-maven-plugin
, so they are not downloaded again unless they are updated. If you want to build the GPIO C code to use sysfs comment out<configureArgs>
in thehawtjni-maven-plugin
section of the POM.
cd ~/java-periphery/scripts
./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.
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 | dev | 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 GPIO character devices 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 GPIO character device.
cd ~/java-periphery/target
java -cp java-periphery-1.0.0-SNAPSHOT.jar:java-periphery-1.0.0-SNAPSHOT-linux32.jar com.codeferm.periphery.demo.GpioPerf -d /dev/gpiochip0 -l 203
Note that the native library jar has a suffix such as linux32, so depending on your target platform it could be different. To see a list of demos browse code.
After bulding Java Periphery simpily add the following artifact:
<groupId>com.codeferm</groupId>
<artifactId>java-periphery</artifactId>
<version>1.0.0-SNAPSHOT</version>
Note that most performance tests focus on writes and not CPU overhead, so it's hard to compare. Technically you will actually be doing something like bit banging to simulate a protocol, so you need extra CPU bandwidth to do that.
SBC | OS | CPU Freq | Write KHz | Read KHz | Average CPU |
---|---|---|---|---|---|
Odroid XU4 | Armbian Focal | 2.0 MHz | 192 | 195 | 14% |
Nano Pi Duo v1.0 | Armbian Focal | 1.0 MHz | 500 | 318 | 27% |
Nano Pi Neo Plus2 | Armbian Focal | 1.0 MHz | 654 | 413 | 27% |
Odroid C2 | Armbian Focal | 1.5 MHz | 689 | 488 | 29% |
Zulu Mission Control allows
you to profile your applications.
Download
zmc and launch on your desktop. To profile your Java Periphery application use:
java -XX:+FlightRecorder -Djava.rmi.server.hostname=your_ip -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp java-periphery-1.0.0-SNAPSHOT.jar:java-periphery-1.0.0-SNAPSHOT-linux32.jar com.codeferm.periphery.demo.GpioPerf