From 82b5390e093ba4762592054e4689f9bc99e23e08 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 5 Feb 2016 16:40:35 +0100 Subject: [PATCH] [New] Major refactoring - Added GpioException which is thrown when something goes wrong - it is a checked exception - Made project create an executable Jar - fixed thread safety - added GpioBridge.writeValue() - added setup scripts for BeagleBoneBlack --- README.md | 11 +- compile_dts.sh | 23 +++ exportPins.sh | 81 +++++++++++ pinctrl-eitchnet.dts | 45 ++++++ pinctrl-test-0.dts | 33 +++++ pom.xml | 80 ++++++++--- setOutPin.sh | 30 ++++ .../java/ch/eitchnet/beaglebone/Gpio.java | 15 +- .../ch/eitchnet/beaglebone/GpioBridge.java | 135 +++++++++++++----- .../ch/eitchnet/beaglebone/GpioException.java | 14 ++ .../beaglebone/GpioSignalListener.java | 2 +- src/main/java/ch/eitchnet/beaglebone/Pin.java | 25 ++-- .../java/ch/eitchnet/beaglebone/Signal.java | 5 + 13 files changed, 427 insertions(+), 72 deletions(-) create mode 100755 compile_dts.sh create mode 100755 exportPins.sh create mode 100644 pinctrl-eitchnet.dts create mode 100644 pinctrl-test-0.dts create mode 100755 setOutPin.sh create mode 100644 src/main/java/ch/eitchnet/beaglebone/GpioException.java diff --git a/README.md b/README.md index 35ef613..1c2366d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# ch.eitchnet.beaglebone -ch.eitchnet.beaglebone +# BeagleBoneBlack Java Pin Bridge + +## Setup + + +## Build + +## Running + diff --git a/compile_dts.sh b/compile_dts.sh new file mode 100755 index 0000000..f62b6f5 --- /dev/null +++ b/compile_dts.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +SLOTS=/sys/devices/platform/bone_capemgr/slots +PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins + +# http://kilobaser.com/blog/2014-07-28-beaglebone-black-devicetreeoverlay-generator +# https://github.com/jadonk/validation-scripts/tree/master/test-capemgr + +echo "Compiling pinctrl-eitchnet..." +dtc -O dtb -o pinctrl-eitchnet-00A0.dtbo -b 0 -@ pinctrl-eitchnet.dts + +echo +echo "Install with:" +echo "$ sudo cp pinctrl-eitchnet-00A0.dtbo /lib/firmware/" +echo "$ echo pinctrl-eitchnet | sudo tee $SLOTS" + +echo +echo "Remove with:" +echo "$ cat $SLOTS | grep pinctrl-eitchnet" +echo "15: P-O-L- 0 Override Board Name,00A0,Override Manuf,pinctrl-eitchnet" +echo "$ echo -15 | sudo tee $SLOTS" + +exit 0 \ No newline at end of file diff --git a/exportPins.sh b/exportPins.sh new file mode 100755 index 0000000..5c727c7 --- /dev/null +++ b/exportPins.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +set -e + +echo -e "Exporting Pins to userspace..." + +if [ "$(whoami)" != "root" ] ; then + echo "Please run as root!" + exit 1 +fi + +GPIO_DIR="/sys/class/gpio" +USER="ubuntu" + +if [ ! -d "${GPIO_DIR}" ] ; then + echo -e "ERROR: Missing GPIO directory ${GPIO_DIR}" +fi + +cd "${GPIO_DIR}" + +function export_pin() { + direction="$1" + gpio_no="$2" + gpio_name="gpio${gpio_no}" + + echo "Setting direction of ${gpio_name} to ${direction}..." + + if [ ! -d "${gpio_name}" ] ; then + echo "${gpio_no}" > export + else + echo "GPIO ${gpio_no} was already exported, only verifying direction..." + fi + + echo "${direction}" > "${gpio_name}/direction" + + if [ "${direction}" == "in" ] ; then + echo "Current value of ${gpio_name} is $(cat ${gpio_name}/value)" + else + echo "Setting value of ${gpio_name} to 0" + fi + + chown ${USER} "${gpio_name}/value" +} + +## +## IN Pins +## + +echo +echo "Setting input pins..." + +# P8.7 +export_pin in 66 +# P8.8 +export_pin in 67 +# P8.9 +export_pin in 69 + +## +## Out pins +## + +echo +echo "Setting output pins..." + +# P8.10 +export_pin out 68 +# P8.11 +export_pin out 45 +# P8.12 +export_pin out 44 +# P8.14 +export_pin out 26 +# P8.15 +export_pin out 47 +# P8.16 +export_pin out 46 + +echo -e "Done." + +exit 0 \ No newline at end of file diff --git a/pinctrl-eitchnet.dts b/pinctrl-eitchnet.dts new file mode 100644 index 0000000..cf9df5c --- /dev/null +++ b/pinctrl-eitchnet.dts @@ -0,0 +1,45 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "ti,beaglebone", "ti,beaglebone-black"; + + /* identification */ + part-number = "pinctrl-eitchnet"; + + /* https://github.com/derekmolloy/boneDeviceTree/blob/master/docs/BeagleboneBlackP9HeaderTable.pdf */ + fragment@0 { + target = <&am33xx_pinmux>; + __overlay__ { + pinctrl_eitchnet: pinmux_pinctrl_eitchnet { + pinctrl-single,pins = < + + /* 0x27 INPUT */ + 0x090 0x2f /* P8_07 66 INPUT MODE7 none */ + 0x094 0x2f /* P8_08 67 INPUT MODE7 none */ + 0x09c 0x2f /* P8_09 69 INPUT MODE7 none */ + + /* 0x07 OUTPUT */ + 0x098 0x0f /* P8_10 68 OUTPUT MODE7 none */ + 0x034 0x0f /* P8_11 45 OUTPUT MODE7 none */ + 0x030 0x0f /* P8_12 44 OUTPUT MODE7 none */ + 0x028 0x0f /* P8_14 26 OUTPUT MODE7 none */ + 0x03c 0x0f /* P8_15 47 OUTPUT MODE7 none */ + 0x038 0x0f /* P8_16 46 OUTPUT MODE7 none */ + >; + }; + }; + }; + + fragment@1 { + target = <&ocp>; + __overlay__ { + pinctrl_eitchnet_pinmux { + compatible = "bone-pinmux-helper"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_eitchnet>; + status = "okay"; + }; + }; + }; +}; diff --git a/pinctrl-test-0.dts b/pinctrl-test-0.dts new file mode 100644 index 0000000..191158e --- /dev/null +++ b/pinctrl-test-0.dts @@ -0,0 +1,33 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "ti,beaglebone", "ti,beaglebone-black"; + + /* identification */ + part-number = "pinctrl-test-0"; + + /* https://github.com/derekmolloy/boneDeviceTree/blob/master/docs/BeagleboneBlackP9HeaderTable.pdf */ + fragment@0 { + target = <&am33xx_pinmux>; + __overlay__ { + pinctrl_test: pinctrl_test_0_pins { + pinctrl-single,pins = < + 0x164 0x00 /* P9_42 muxRegOffset, OUTPUT | MODE0 */ + >; + }; + }; + }; + + fragment@1 { + target = <&ocp>; + __overlay__ { + test_helper: helper { + compatible = "bone-pinmux-helper"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_test>; + status = "okay"; + }; + }; + }; +}; diff --git a/pom.xml b/pom.xml index d33480f..497e9ff 100644 --- a/pom.xml +++ b/pom.xml @@ -1,27 +1,63 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - ch.eitchnet - ch.eitchnet.beaglebone - 0.1.0-SNAPSHOT - jar + ch.eitchnet + ch.eitchnet.beaglebone + 0.1.0-SNAPSHOT + jar - ch.eitchnet.beaglebone - http://maven.apache.org + ch.eitchnet.beaglebone + http://maven.apache.org - - UTF-8 - 1.8 - 1.8 - - - - - junit - junit - 4.12 - test - - + + UTF-8 + 1.8 + 1.8 + + + + + junit + junit + 4.12 + test + + + + + BeagleBone + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + true + + ch.eitchnet.beaglebone.GpioBridgeTest + + + + + + + + + + + + + + + + + + + + + + diff --git a/setOutPin.sh b/setOutPin.sh new file mode 100755 index 0000000..7e2f8d4 --- /dev/null +++ b/setOutPin.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +GPIO_DIR="/sys/class/gpio" + +if [ ! -d "${GPIO_DIR}" ] ; then + echo -e "ERROR: Missing GPIO directory ${GPIO_DIR}" +fi + +cd "${GPIO_DIR}" + +if [ "$#" != 2 ] ; then + echo "Usage: $0 " + exit 1 +fi + +gpio_no="$1" +gpio_name="gpio${gpio_no}" +value="$2" + +direction="$(cat ${gpio_name}/direction)" +if [ "${direction}" != "out" ] ; then + echo "Current direction is not in, can't set output pin!" +fi + +echo "Setting output pin ${gpio_name} to value ${value}" +echo ${value} | sudo tee "${GPIO_DIR}/${gpio_name}/value" + +exit 0 diff --git a/src/main/java/ch/eitchnet/beaglebone/Gpio.java b/src/main/java/ch/eitchnet/beaglebone/Gpio.java index 552d8a7..6a53807 100644 --- a/src/main/java/ch/eitchnet/beaglebone/Gpio.java +++ b/src/main/java/ch/eitchnet/beaglebone/Gpio.java @@ -3,7 +3,7 @@ package ch.eitchnet.beaglebone; public class Gpio { private final Pin pin; - private final String name; + private final String kernelName; private final Direction direction; private String label; private Signal signal; @@ -11,8 +11,8 @@ public class Gpio { public Gpio(Pin pin, Direction direction) { this.pin = pin; this.direction = direction; - this.name = "gpio" + pin.getGpioNr(); - this.label = name; + this.kernelName = "gpio" + pin.getGpioNr(); + this.label = kernelName; this.signal = Signal.LOW; } @@ -20,8 +20,8 @@ public class Gpio { return this.pin; } - public String getName() { - return this.name; + public String getKernelName() { + return this.kernelName; } public Gpio setLabel(String label) { @@ -45,4 +45,9 @@ public class Gpio { this.signal = signal; return this; } + + @Override + public String toString() { + return this.pin.toString(); + } } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/beaglebone/GpioBridge.java b/src/main/java/ch/eitchnet/beaglebone/GpioBridge.java index 3a14dea..09ba05c 100644 --- a/src/main/java/ch/eitchnet/beaglebone/GpioBridge.java +++ b/src/main/java/ch/eitchnet/beaglebone/GpioBridge.java @@ -2,6 +2,7 @@ package ch.eitchnet.beaglebone; import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; @@ -23,12 +24,20 @@ public class GpioBridge { this.listeners = Collections.synchronizedMap(new HashMap<>()); } - public void writeValue(Gpio gpio, Signal signal) { + private File getGpioValuePath(Gpio gpio) { + return new File(GpioBridgeTest.GPIO_PATH, gpio.getKernelName() + "/value"); + } + + private File getGpioDirectionPath(Gpio gpio) { + return new File(GpioBridgeTest.GPIO_PATH, gpio.getKernelName() + "/direction"); + } + + public void writeValue(Gpio gpio, Signal signal) throws GpioException { if (gpio.getDirection() != Direction.OUT) throw new IllegalArgumentException("For writing the direction must be " + Direction.OUT); - File file = new File(GpioBridgeTest.GPIO_PATH, gpio.getName() + "/value"); + File file = getGpioValuePath(gpio); try (FileOutputStream out = new FileOutputStream(file)) { out.write(signal.getValueS().getBytes()); @@ -37,12 +46,35 @@ public class GpioBridge { gpio.setSignal(signal); } catch (Exception e) { - e.printStackTrace(); + throw new GpioException("Failed to write GPIO " + gpio + " with signal " + signal, e); } System.out.println("Set GPIO " + gpio.getPin() + " signal to " + gpio.getSignal()); } + public Signal readValue(Gpio gpio) throws GpioException { + + synchronized (gpio) { + + if (gpio.getDirection() != Direction.IN) + throw new IllegalArgumentException("For reading the direction must be " + Direction.IN); + + File file = getGpioValuePath(gpio); + try (BufferedReader fin = new BufferedReader(new FileReader(file))) { + + String valueS = fin.readLine(); + Signal signal = Signal.getSignal(valueS); + if (!gpio.getSignal().equals(signal)) + gpio.setSignal(signal); + + return signal; + + } catch (Exception e) { + throw new GpioException("Failed to read GPIO " + gpio, e); + } + } + } + public void start() { this.run = true; @@ -52,8 +84,10 @@ public class GpioBridge { synchronized (this) { try { wait(1000l); - } catch (Exception e) { - e.printStackTrace(); + } catch (InterruptedException e) { + System.out.println("Was interrupted. Stopping thread."); + this.run = false; + break; } } } else { @@ -62,19 +96,18 @@ public class GpioBridge { synchronized (this.listeners) { for (Gpio gpio : this.listeners.keySet()) { - - File file = new File(GpioBridgeTest.GPIO_PATH, gpio.getName() + "/value"); - try (BufferedReader fin = new BufferedReader(new FileReader(file))) { - - String valueS = fin.readLine(); - Signal signal = Signal.getSignal(valueS); - if (!gpio.getSignal().equals(signal)) { - gpio.setSignal(signal); - changes.add(gpio); + try { + synchronized (gpio) { + Signal currentSignal = gpio.getSignal(); + Signal newSignal = readValue(gpio); + if (currentSignal != newSignal) + changes.add(gpio); } - } catch (Exception e) { + System.out.println("Failed to read GPIO " + gpio + " due to:"); e.printStackTrace(); + this.run = false; + break; } } } @@ -89,20 +122,28 @@ public class GpioBridge { + ". Notifying " + listeners.size() + " listeners."); for (GpioSignalListener listener : listeners) { - listener.notify(gpio); + try { + listener.notify(gpio); + } catch (Exception e) { + System.out.println("Failed to update listener " + listener + " due to:"); + e.printStackTrace(); + } } } } try { Thread.sleep(200l); - } catch (Exception e) { - e.printStackTrace(); + } catch (InterruptedException e) { + System.out.println("Was interrupted. Stopping thread."); + this.run = false; + break; } } } } , "gpio_reader"); this.thread.start(); + System.out.println("Started GPIO bridge."); } public void stop() { @@ -111,38 +152,66 @@ public class GpioBridge { try { this.thread.join(5000l); } catch (InterruptedException e) { - e.printStackTrace(); + System.out.println("Was interrupted while waiting for thread to stop?!"); } } - public Gpio getGpio(Pin pin, Direction direction) { + public synchronized Gpio getGpio(Pin pin, Direction direction) throws GpioException { Gpio gpio = this.cache.get(pin); if (gpio == null) { + gpio = new Gpio(pin, direction); - File file = new File(GpioBridgeTest.GPIO_PATH, gpio.getName() + "/direction"); - try (BufferedReader fin = new BufferedReader(new FileReader(file))) { + // validate direction + validateDirection(pin, direction, gpio); - String directionS = fin.readLine(); - Direction dir = Direction.getDirection(directionS); - if (dir != direction) - throw new IllegalArgumentException( - "Actual direction of GPIO " + gpio.getPin() + " is " + dir + " not " + directionS); - - } catch (IOException e) { - e.printStackTrace(); - } + // validate file permissions + validateFilePermissions(direction, gpio); this.cache.put(pin, gpio); + System.out.println("Initialized pin " + pin + " with direction " + direction + "."); } return gpio; } - public void register(Gpio gpio, GpioSignalListener listener) { + private void validateDirection(Pin pin, Direction direction, Gpio gpio) throws GpioException { + File file = getGpioDirectionPath(gpio); + try (BufferedReader fin = new BufferedReader(new FileReader(file))) { + + String directionS = fin.readLine(); + Direction dir = Direction.getDirection(directionS); + if (dir != direction) + throw new GpioException( + "Actual direction of GPIO " + gpio.getPin() + " is " + dir + " not " + directionS); + + } catch (FileNotFoundException e) { + throw new GpioException("GPIO " + pin + " does not exist, was the pin exported to user space?", e); + } catch (IOException e) { + throw new GpioException("Failed to open GPIO " + pin, e); + } + } + + private void validateFilePermissions(Direction direction, Gpio gpio) throws GpioException { + File gpioValuePath = getGpioValuePath(gpio); + if (direction == Direction.IN) { + if (!gpioValuePath.canRead()) + throw new GpioException("GPIO " + gpio + " has direction " + direction + + " and is not readable. Are the file permissions ok?"); + + } else if (direction == Direction.OUT) { + if (!gpioValuePath.canWrite()) + throw new GpioException("GPIO " + gpio + " has direction " + direction + + " and is not writable. Are the file permissions ok?"); + } else { + throw new RuntimeException("Unhandled Direction " + direction); + } + } + + public void register(Gpio gpio, GpioSignalListener listener) throws GpioException { if (gpio.getDirection() != Direction.IN) - throw new IllegalArgumentException("For reading the direction must be " + Direction.IN); + throw new GpioException("For reading the direction must be " + Direction.IN); synchronized (this.listeners) { List listeners = this.listeners.get(gpio); diff --git a/src/main/java/ch/eitchnet/beaglebone/GpioException.java b/src/main/java/ch/eitchnet/beaglebone/GpioException.java new file mode 100644 index 0000000..d1dc730 --- /dev/null +++ b/src/main/java/ch/eitchnet/beaglebone/GpioException.java @@ -0,0 +1,14 @@ +package ch.eitchnet.beaglebone; + +public class GpioException extends Exception { + + private static final long serialVersionUID = 1L; + + public GpioException(String message, Throwable cause) { + super(message, cause); + } + + public GpioException(String message) { + super(message); + } +} diff --git a/src/main/java/ch/eitchnet/beaglebone/GpioSignalListener.java b/src/main/java/ch/eitchnet/beaglebone/GpioSignalListener.java index d8722ca..c997ce5 100644 --- a/src/main/java/ch/eitchnet/beaglebone/GpioSignalListener.java +++ b/src/main/java/ch/eitchnet/beaglebone/GpioSignalListener.java @@ -1,5 +1,5 @@ package ch.eitchnet.beaglebone; public interface GpioSignalListener { - public void notify(Gpio gpio); + public void notify(Gpio gpio) throws Exception; } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/beaglebone/Pin.java b/src/main/java/ch/eitchnet/beaglebone/Pin.java index 338950e..998b398 100644 --- a/src/main/java/ch/eitchnet/beaglebone/Pin.java +++ b/src/main/java/ch/eitchnet/beaglebone/Pin.java @@ -2,13 +2,13 @@ package ch.eitchnet.beaglebone; public enum Pin { - P8_3("P8.3", 1, 6), - P8_4("P8.4", 1, 7), - P8_5("P8.5", 1, 2), - P8_6("P8.6", 1, 3), - P8_7("P8.7", 2, 2), - P8_8("P8.8", 2, 3), - P8_9("P8.9", 2, 2), + P8_03("P8.03", 1, 6), + P8_04("P8.04", 1, 7), + P8_05("P8.05", 1, 2), + P8_06("P8.06", 1, 3), + P8_07("P8.07", 2, 2), + P8_08("P8.08", 2, 3), + P8_09("P8.09", 2, 5), P8_10("P8.10", 2, 4), P8_11("P8.11", 1, 13), P8_12("P8.12", 1, 12), @@ -68,8 +68,10 @@ public enum Pin { P9_29("P9.29", 3, 15), P9_30("P9.30", 3, 16), P9_31("P9.31", 3, 14), - P9_41("P9.41", 0, 20), - P9_42("P9.42", 0, 7); + P9_41A("P9.41A", 0, 20), + P9_41B("P9.41B", 3, 20), + P9_42A("P9.42A", 0, 7), + P9_42B("P9.42B", 3, 18); private String label; private int chip; @@ -96,4 +98,9 @@ public enum Pin { public int getGpioNr() { return this.chip * 32 + this.pin; } + + @Override + public String toString() { + return this.label; + } } diff --git a/src/main/java/ch/eitchnet/beaglebone/Signal.java b/src/main/java/ch/eitchnet/beaglebone/Signal.java index 2e9d969..6708964 100644 --- a/src/main/java/ch/eitchnet/beaglebone/Signal.java +++ b/src/main/java/ch/eitchnet/beaglebone/Signal.java @@ -52,4 +52,9 @@ public enum Signal { return HIGH; throw new IllegalArgumentException("No signal for value " + valueS); } + + @Override + public String toString() { + return name(); + } } \ No newline at end of file