Home

Awesome

Title

Java Periphery has been merged into Java UIO. I'll leave this up as a place holder for a while.

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.

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));
}

Duo

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, Dart and JVM languages without having to use one off board specific drivers, deprecated wiringPi or the deprecated sysfs interface. The possibility of using other JVM based languages such as Groovy, Kotlin, Scala, etc. opens up more language opportunities.

SBC configuration

Armbian and built in buttons

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) :

Non-root access

If you want to access devices without root do the following (you can try udev rules instead if you wish):

<pre><code>chown -R root:periphery /dev/gpiochip* #/dev/gpiomem for sandbox chmod -R ug+rw /dev/gpiochip* #/dev/gpiomem for sandbox chown -R root:periphery /dev/i2c* chmod -R ug+rw /dev/i2c* chown -R root:periphery /dev/spidev* chmod -R ug+rw /dev/spidev* chown -R root:periphery /sys/devices/platform/leds/leds chmod -R ug+rw /sys/devices/platform/leds/leds</code></pre>

Download project

Install script

The install script assumes a clean OS install. If you would like to install on a OS with your own version of Java 17, etc. then you can look at what install.sh does and do it manually. What does the script do?

Run script

Make sure you run script as non-root user and without sudo.

Build java-periphery with proper gpio.h

The gcc default include paths usually do not point to the latest gpio.h header. In order to use the latest features in c-periphery you will need to use the correct gpio.h include. After the install.sh script completes:

High performance GPIO using MMIO

I have created a generic way to achieve fast GPIO for times when performance (bit banging, software based PWM, low CPU latency, etc) is required. I have written a mapper, so you can extract the data register masks without having to do it by hand from the datasheet. Doing this totally by hand is tedious and error prone. The method I use is using a well know interface (GPIO device) to make changes and detecting register deltas. You still need to create a input file with various board specific parameters. Make sure you disable all hardware in armbian-config System, Hardware and remove console=serial from /boot/armbianEnv.txt. You want multi-function pins to act as GPIO pins.

NanoPi Duo (H2+) example:

NanoPi Neo Plus2 (H5) example:

As you can see above the same performance test code works on a 32 bit H2+ and a 64 bit H5 CPU. This means almost all boards can be easily supported with the right input file. This is probably the only high performance GPIO code that is truly cross platform. No custom adapters or other one off code is required currently. Also, I use the same pin numbers as the GPIO device, so no goofy wiringPi or BCM pin numbering. Keep in mind that only one core is used, so the CPU will never exceed 25% on a quad core system.

If you want to map your own board you start by getting the data sheet and finding the data registers. I've written a little memory tool MemScan that will allow you to see what bits change for a range of registers using mode, data and pull operations. For example on the ODROID C2 lets look at chip 0 and line 9:

sudo java -cp $HOME/java-periphery/target/java-periphery-1.0.0-SNAPSHOT.jar:$HOME/java-periphery/target/java-periphery-1.0.0-SNAPSHOT-linux64.jar com.codeferm.periphery.mmio.MemScan -a 0xc8100024 -w 0x03 -d 0 -l 9

Output:

11:55:39.538 [main] INFO  MemScan - Mode difference found at offset 0x00000000 before 0xa0003ef7 after 0xa0003cf7 difference 0x00000200
11:55:39.540 [main] INFO  MemScan - Mode difference found at offset 0x00000004 before 0x80003ef7 after 0x80003cf7 difference 0x00000200
11:55:39.543 [main] INFO  MemScan - Data difference found at offset 0x00000000 before 0xa0003cf7 after 0xa2003cf7 difference 0x02000000
11:55:39.545 [main] INFO  MemScan - Data difference found at offset 0x00000004 before 0x80003cf7 after 0x80003ef7 difference 0x00000200
11:55:39.548 [main] ERROR MemScan - Device 0 line 9 Error Kernel version does not support configuring GPIO line bias

Note the bias error is due to no compiling with latest gpio.h header.

GPIO Performance using Perf

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. Please note write frequency is based on square wave (rapid on/off). You can increase clock speed to improve performance on some boards. I used the OS defaults.

SBCOSCPU FreqGPIOD Write KHzMMIO Write KHzAverage CPU
Nano Pi Duo v1.0Armbian Focal1.0 GHz242188025%
Nano Pi M1Armbian Focal1.2 GHz320235525%
Nano Pi Neo Plus2Armbian Focal1.0 GHz347235625%
Odroid C2Armbian Focal1.5 GHz365234625%
Odroid XU4Armbian Focal2.0 GHz4430012%
Raspberry Pi 3Ubuntu Focal1.2 GHz119454125%

How GPIO pins are mapped

This is based on testing on a NanoPi Duo. gpiochip0 starts at 0 and gpiochip1 start at 352. Consider the following table:

NameChip Namedevsysfs
DEBUG_TX(UART_TXD0)/GPIOA4gpiochip0004004
DEBUG_RX(UART_RXD0)/GPIOA5/PWM0gpiochip0005005
I2C0_SCL/GPIOA11gpiochip0011011
I2C0_SDA/GPIOA12gpiochip0012012
UART3_TX/SPI1_CS/GPIOA13gpiochip0013013
UART3_RX/SPI1_CLK/GPIOA14gpiochip0014014
UART3_RTS/SPI1_MOSI/GPIOA15gpiochip0015015
UART3_CTS/SPI1_MISO/GPIOA16gpiochip0016016
UART1_TX/GPIOG6gpiochip0198198
UART1_RX/GPIOG7gpiochip0199199
GPIOG11gpiochip0203203
ON BOARD BUTTONgpiochip1003355
GPIOL11/IR-RXgpiochip1011363

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.

Run demos

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. Just pass in --help to get list of command line arguments.

Use Java Periphery in your own Maven projects

After bulding Java Periphery simpily add the following artifact:

<groupId>com.codeferm</groupId>
<artifactId>java-periphery</artifactId>
<version>1.0.0-SNAPSHOT</version>

Zulu Mission Control (JDK 17 not supported yet)

Azul 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