Merge pull request #3985 from gohai/for-ben-12

I/O library
This commit is contained in:
Ben Fry
2015-10-13 04:23:40 -04:00
25 changed files with 2131 additions and 3 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<project name="Processing I/O Library" default="build">
<target name="clean" description="Clean the build directories">
<delete dir="bin" />
<delete file="library/io.jar" />
</target>
<target name="compile" description="Compile sources">
<condition property="core-built">
<available file="../../../core/library/core.jar" />
</condition>
<fail unless="core-built" message="Please build the core library first and make sure it sits in ../../../core/library/core.jar" />
<mkdir dir="bin" />
<javac source="1.7"
target="1.7"
srcdir="src" destdir="bin"
encoding="UTF-8"
includeAntRuntime="false"
classpath="../../../core/library/core.jar"
nowarn="true"
compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
<compilerclasspath path="../../mode/org.eclipse.jdt.core.jar;
../../mode/jdtCompilerAdapter.jar" />
</javac>
</target>
<target name="build" depends="compile" description="Build I/O library">
<jar basedir="bin" destfile="library/io.jar" />
</target>
</project>

View File

@@ -0,0 +1,42 @@
import processing.io.*;
I2C i2c;
// HMC6352 is a digital compass module using I2C
// datasheet: https://www.sparkfun.com/datasheets/Components/HMC6352.pdf
void setup() {
//printArray(I2C.list());
i2c = new I2C(I2C.list()[0]);
setHeadingMode();
}
void draw() {
background(255);
float deg = getHeading();
println(deg + " degrees");
line(width/2, height/2, width/2+sin(radians(deg))*width/2, height/2-cos(radians(deg))*height/2);
}
void setHeadingMode() {
i2c.beginTransmission(0x21);
// command byte for writing to EEPROM
i2c.write(0x77);
// address of the output data control byte
i2c.write(0x4e);
// give us the plain heading
i2c.write(0x00);
i2c.endTransmission();
}
float getHeading() {
i2c.beginTransmission(0x21);
// command byte for reading the data
i2c.write(0x41);
byte[] in = i2c.read(2);
i2c.endTransmission();
// put bytes together to tenth of degrees
// & 0xff makes sure the byte is not interpreted as a negative value
int deg = (in[0] & 0xff) << 8 | (in[1] & 0xff);
// return degrees
return deg / 10.0;
}

View File

@@ -0,0 +1,27 @@
import processing.io.*;
I2C i2c;
// MCP4725 is a Digital-to-Analog converter using I2C
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf
void setup() {
//printArray(I2C.list());
i2c = new I2C(I2C.list()[0]);
}
void draw() {
background(map(mouseX, 0, width, 0, 255));
setAnalog(map(mouseX, 0, width, 0.0, 1.0));
}
// outputs voltages from 0V to the supply voltage
// (works with 3.3V and 5V)
void setAnalog(float fac) {
fac = constrain(fac, 0.0, 1.0);
// convert to 12 bit value
int val = int(4095 * fac);
i2c.beginTransmission(0x60);
i2c.write(val >> 8);
i2c.write(val & 255);
i2c.endTransmission();
}

View File

@@ -0,0 +1,12 @@
import processing.io.*;
MCP4725 dac;
void setup() {
//printArray(I2C.list());
dac = new MCP4725(I2C.list()[0], 0x60);
}
void draw() {
background(map(mouseX, 0, width, 0, 255));
dac.setAnalog(map(mouseX, 0, width, 0.0, 1.0));
}

View File

@@ -0,0 +1,27 @@
import processing.io.I2C;
// MCP4725 is a Digital-to-Analog converter using I2C
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf
class MCP4725 extends I2C {
int address;
// there can be more than one device connected to the bus
// as long as they have different addresses
MCP4725(String dev, int address) {
super(dev);
this.address = address;
}
// outputs voltages from 0V to the supply voltage
// (works with 3.3V and 5V)
void setAnalog(float fac) {
fac = constrain(fac, 0.0, 1.0);
// convert to 12 bit value
int val = int(4095 * fac);
beginTransmission(address);
write(val >> 8);
write(val & 255);
endTransmission();
}
}

View File

@@ -0,0 +1,25 @@
import processing.io.*;
color bgcolor = 0;
// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
// pin header, which is located on the fourth row, above one of
// the Ground pins
void setup() {
GPIO.pinMode(RPI.PIN7, GPIO.INPUT);
GPIO.attachInterrupt(RPI.PIN7, this, "pinEvent", GPIO.RISING);
}
void draw() {
background(bgcolor);
}
// this function will be called whenever pin 7 is brought from LOW to HIGH
void pinEvent(int pin) {
println("Received interrupt");
if (bgcolor == 0) {
bgcolor = color(255);
} else {
bgcolor = color(0);
}
}

View File

@@ -0,0 +1,39 @@
import processing.io.*;
LED leds[];
// the Raspberry Pi has two build-in LEDs we can control
// led0 (green) and led1 (red)
void setup() {
String available[] = LED.list();
print("Available: ");
println(available);
// create an object for each LED and store it in an array
leds = new LED[available.length];
for (int i=0; i < available.length; i++) {
leds[i] = new LED(available[i]);
}
frameRate(1);
}
void draw() {
// make the LEDs count in binary
for (int i=0; i < leds.length; i++) {
if ((frameCount & (1 << i)) != 0) {
leds[i].set(1.0);
} else {
leds[i].set(0.0);
}
}
println(frameCount);
}
void keyPressed() {
// cleanup
for (int i=0; i < leds.length; i++) {
leds[i].close();
}
exit();
}

View File

@@ -0,0 +1,21 @@
import processing.io.*;
SPI spi;
// MCP3001 is a Analog-to-Digital converter using SPI
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
void setup() {
//printArray(SPI.list());
spi = new SPI(SPI.list()[0]);
spi.settings(500000, SPI.MSBFIRST, SPI.MODE0);
}
void draw() {
// dummy write, actual values don't matter
byte[] out = { 0, 0 };
byte[] in = spi.transfer(out);
// some input bit shifting according to the datasheet p. 16
int val = ((in[0] & 0x1f) << 5) | ((in[1] & 0xf8) >> 3);
// val is between 0 and 1023
background(map(val, 0, 1023, 0, 255));
}

View File

@@ -0,0 +1,22 @@
import processing.io.SPI;
// MCP3001 is a Analog-to-Digital converter using SPI
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
class MCP3001 extends SPI {
MCP3001(String dev) {
super(dev);
super.settings(500000, SPI.MSBFIRST, SPI.MODE0);
}
float getAnalog() {
// dummy write, actual values don't matter
byte[] out = { 0, 0 };
byte[] in = super.transfer(out);
// some input bit shifting according to the datasheet p. 16
int val = ((in[0] & 0x1f) << 5) | ((in[1] & 0xf8) >> 3);
// val is between 0 and 1023
return val/1023.0;
}
}

View File

@@ -0,0 +1,11 @@
import processing.io.*;
MCP3001 adc;
void setup() {
//printArray(SPI.list());
adc = new MCP3001(SPI.list()[0]);
}
void draw() {
background(adc.getAnalog() * 255);
}

View File

@@ -0,0 +1,22 @@
import processing.io.*;
// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
// pin header, which is located on the fourth row, above one of
// the Ground pins
void setup() {
GPIO.pinMode(RPI.PIN7, GPIO.INPUT);
// this is equivalent to addressing the pin with its GPIO number:
// GPIO.pinMode(4, GPIO.INPUT);
}
void draw() {
// sense the input pin
if (GPIO.digitalRead(RPI.PIN7) == GPIO.HIGH) {
fill(255);
} else {
fill(204);
}
stroke(255);
ellipse(width/2, height/2, width*0.75, height*0.75);
}

View File

@@ -0,0 +1,27 @@
import processing.io.*;
boolean ledOn = false;
// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
// pin header, which is located on the fourth row, above one of
// the Ground pins
void setup() {
GPIO.pinMode(RPI.PIN7, GPIO.OUTPUT);
// this is equivalent to addressing the pin with its GPIO number:
// GPIO.pinMode(4, GPIO.OUTPUT);
frameRate(0.5);
}
void draw() {
// make the LED blink
ledOn = !ledOn;
if (ledOn) {
GPIO.digitalWrite(RPI.PIN7, GPIO.LOW);
fill(204);
} else {
GPIO.digitalWrite(RPI.PIN7, GPIO.HIGH);
fill(255);
}
stroke(255);
ellipse(width/2, height/2, width*0.75, height*0.75);
}

View File

@@ -0,0 +1,2 @@
name = I/O
version = 1

View File

@@ -0,0 +1 @@
name = I/O for Raspberry Pi and other Linux-based computers

View File

@@ -0,0 +1,17 @@
TARGET := libprocessing-io.so
OBJS := impl.o
CC := gcc
CFLAGS := -std=c99 -fPIC -g
CFLAGS += -I$(shell dirname $(shell realpath $(shell which javac)))/../include
CFLAGS += -I$(shell dirname $(shell realpath $(shell which javac)))/../include/linux
LDFLAGS := -shared
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
iface.h:
javah -classpath .. -o iface.h processing.io.NativeInterface
clean:
rm -f $(TARGET) $(OBJS)

View File

@@ -0,0 +1,85 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class processing_io_NativeInterface */
#ifndef _Included_processing_io_NativeInterface
#define _Included_processing_io_NativeInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: processing_io_NativeInterface
* Method: openDevice
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_openDevice
(JNIEnv *, jclass, jstring);
/*
* Class: processing_io_NativeInterface
* Method: getError
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_processing_io_NativeInterface_getError
(JNIEnv *, jclass, jint);
/*
* Class: processing_io_NativeInterface
* Method: closeDevice
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_closeDevice
(JNIEnv *, jclass, jint);
/*
* Class: processing_io_NativeInterface
* Method: readFile
* Signature: (Ljava/lang/String;[B)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_readFile
(JNIEnv *, jclass, jstring, jbyteArray);
/*
* Class: processing_io_NativeInterface
* Method: writeFile
* Signature: (Ljava/lang/String;[B)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile
(JNIEnv *, jclass, jstring, jbyteArray);
/*
* Class: processing_io_NativeInterface
* Method: pollDevice
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_pollDevice
(JNIEnv *, jclass, jstring, jint);
/*
* Class: processing_io_NativeInterface
* Method: transferI2c
* Signature: (II[B[B)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferI2c
(JNIEnv *, jclass, jint, jint, jbyteArray, jbyteArray);
/*
* Class: processing_io_NativeInterface
* Method: setSpiSettings
* Signature: (IIII)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_setSpiSettings
(JNIEnv *, jclass, jint, jint, jint, jint);
/*
* Class: processing_io_NativeInterface
* Method: transferSpi
* Signature: (I[B[B)I
*/
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferSpi
(JNIEnv *, jclass, jint, jbyteArray, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,241 @@
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
#include <errno.h>
#include <fcntl.h>
#include <jni.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/spi/spidev.h>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <unistd.h>
#include "iface.h"
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_openDevice
(JNIEnv *env, jclass cls, jstring _fn)
{
const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
int file = open(fn, O_RDWR);
(*env)->ReleaseStringUTFChars(env, _fn, fn);
if (file < 0) {
return -errno;
} else {
return file;
}
}
JNIEXPORT jstring JNICALL Java_processing_io_NativeInterface_getError
(JNIEnv *env, jclass cls, jint _errno)
{
char *msg = strerror(abs(_errno));
if (msg) {
return (*env)->NewStringUTF(env, msg);
} else {
return NULL;
}
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_closeDevice
(JNIEnv *env, jclass cls, jint handle)
{
if (close(handle) < 0) {
return -errno;
} else {
return 0;
}
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_readFile
(JNIEnv *env, jclass cls, jstring _fn, jbyteArray _in)
{
const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
int file = open(fn, O_RDONLY);
(*env)->ReleaseStringUTFChars(env, _fn, fn);
if (file < 0) {
return -errno;
}
jbyte *in = (*env)->GetByteArrayElements(env, _in, NULL);
int len = read(file, in, (*env)->GetArrayLength(env, _in));
if (len < 0) {
len = -errno;
}
(*env)->ReleaseByteArrayElements(env, _in, in, 0);
close(file);
return len;
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile
(JNIEnv *env, jclass cls, jstring _fn, jbyteArray _out)
{
const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
int file = open(fn, O_WRONLY);
(*env)->ReleaseStringUTFChars(env, _fn, fn);
if (file < 0) {
return -errno;
}
jbyte *out = (*env)->GetByteArrayElements(env, _out, JNI_FALSE);
int len = write(file, out, (*env)->GetArrayLength(env, _out));
if (len < 0) {
len = -errno;
}
(*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
close(file);
return len;
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_pollDevice
(JNIEnv *env, jclass cls, jstring _fn, jint timeout)
{
const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
int file = open(fn, O_RDONLY|O_NONBLOCK);
(*env)->ReleaseStringUTFChars(env, _fn, fn);
if (file < 0) {
return -errno;
}
// dummy read
char tmp;
while (0 < read(file, &tmp, 1));
struct pollfd fds[1];
memset(fds, 0, sizeof(fds));
fds[0].fd = file;
fds[0].events = POLLPRI|POLLERR;
// and wait
int ret = poll(fds, 1, timeout);
close(file);
if (ret < 0) {
return -errno;
} else if (ret == 0) {
// timeout
return 0;
} else if (fds[0].revents & POLLPRI) {
// interrupt
return 1;
} else {
// POLLERR?
return -ENOMSG;
}
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferI2c
(JNIEnv *env, jclass cls, jint handle, jint slave, jbyteArray _out, jbyteArray _in)
{
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg msgs[2];
jbyte *out, *in;
packets.msgs = msgs;
msgs[0].addr = slave;
msgs[0].flags = 0;
msgs[0].len = (*env)->GetArrayLength(env, _out);
out = (*env)->GetByteArrayElements(env, _out, NULL);
msgs[0].buf = out;
if (_in != NULL) {
in = (*env)->GetByteArrayElements(env, _in, NULL);
msgs[1].addr = slave;
msgs[1].flags = I2C_M_RD; // I2C_M_RECV_LEN is not supported
msgs[1].len = (*env)->GetArrayLength(env, _in);
msgs[1].buf = in;
packets.nmsgs = 2;
} else {
packets.nmsgs = 1;
}
int ret = ioctl(handle, I2C_RDWR, &packets);
if (ret < 0) {
ret = -errno;
}
(*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
if (_in != NULL) {
(*env)->ReleaseByteArrayElements(env, _in, in, 0);
}
return ret;
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_setSpiSettings
(JNIEnv *env, jclass cls, jint handle, jint _maxSpeed, jint dataOrder, jint mode)
{
uint8_t tmp;
uint32_t maxSpeed;
tmp = (uint8_t)mode;
int ret = ioctl(handle, SPI_IOC_WR_MODE, &tmp);
if (ret < 0) {
return ret;
}
tmp = (uint8_t)dataOrder;
ret = ioctl(handle, SPI_IOC_WR_LSB_FIRST, &tmp);
if (ret < 0) {
return ret;
}
maxSpeed = (uint32_t)_maxSpeed;
ret = ioctl(handle, SPI_IOC_WR_MAX_SPEED_HZ, &maxSpeed);
if (ret < 0) {
return ret;
}
return 0;
}
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferSpi
(JNIEnv *env, jclass cls, jint handle, jbyteArray _out, jbyteArray _in)
{
jbyte* out = (*env)->GetByteArrayElements(env, _out, NULL);
jbyte* in = (*env)->GetByteArrayElements(env, _in, NULL);
struct spi_ioc_transfer xfer = {
.tx_buf = (unsigned long)out,
.rx_buf = (unsigned long)in,
.len = MIN((*env)->GetArrayLength(env, _out), (*env)->GetArrayLength(env, _in)),
};
int ret = ioctl(handle, SPI_IOC_MESSAGE(1), &xfer);
(*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, _in, in, 0);
return ret;
}

View File

@@ -0,0 +1,545 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
import processing.core.*;
import processing.io.NativeInterface;
import java.lang.reflect.Method;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
public class GPIO {
// those constants are generally the same as in Arduino.h
public static final int INPUT = 0;
public static final int OUTPUT = 1;
public static final int INPUT_PULLUP = 2;
public static final int INPUT_PULLDOWN = 3;
public static final int LOW = 0;
public static final int HIGH = 1;
public static final int NONE = 0;
public static final int CHANGE = 1; // trigger when level changes
public static final int FALLING = 2; // trigger when level changes from high to low
public static final int RISING = 3; // trigger when level changes from low to high
protected static Map<Integer, Thread> irqThreads = new HashMap<Integer, Thread>();
protected static boolean serveInterrupts = true;
protected static BitSet values = new BitSet();
static {
NativeInterface.loadLibrary();
}
public static void analogWrite(int pin, int value) {
// currently this can't be done in a non-platform-specific way
// the best way forward would be implementing a generic, "soft"
// PWM in the kernel that uses high resolution timers, similiar
// to the patch Bill Gatliff posted, which unfortunately didn't
// get picked up, see
// https://dev.openwrt.org/browser/trunk/target/linux/generic/files/drivers/pwm/gpio-pwm.c?rev=35328
// additionally, there currently doesn't seem to be a way to link
// a PWM channel back to the GPIO pin it is associated with
// alternatively, this could be implemented in user-space to some
// degree
// see http://stackoverflow.com/a/13371570/3030124
// see http://raspberrypi.stackexchange.com/a/304
throw new RuntimeException("Not yet implemented");
}
/**
* Calls a function when the value of an INPUT pin changes
*
* Don't use enableInterrupt() and waitForInterrupt() in combination with
* this function, as they are orthogonal. The sketch method provided must
* accept a single integer (int) parameter, which is the number of the GPIO
* pin that the interrupt occured on.
* @param pin GPIO pin
* @param parent this
* @param method name of sketch method to call
* @param mode when to call: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
* @see noInterrupts
* @see interrupts
* @see releaseInterrupt
*/
public static void attachInterrupt(int pin, PApplet parent, String method, int mode) {
if (irqThreads.containsKey(pin)) {
throw new RuntimeException("You must call releaseInterrupt before attaching another interrupt on the same pin");
}
enableInterrupt(pin, mode);
final int irqPin = pin;
final PApplet irqObject = parent;
final Method irqMethod;
try {
irqMethod = parent.getClass().getMethod(method, int.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Method " + method + " does not exist");
}
// it might be worth checking how Java threads compare to pthreads in terms
// of latency
Thread t = new Thread(new Runnable() {
public void run() {
boolean gotInterrupt = false;
try {
do {
try {
if (waitForInterrupt(irqPin, 100)) {
gotInterrupt = true;
}
if (gotInterrupt && serveInterrupts) {
irqMethod.invoke(irqObject, irqPin);
gotInterrupt = false;
}
// if we received an interrupt while interrupts were disabled
// we still deliver it the next time interrupts get enabled
// not sure if everyone agrees with this logic though
} catch (RuntimeException e) {
// make sure we're not busy spinning on error
Thread.sleep(100);
}
} while (!Thread.currentThread().isInterrupted());
} catch (Exception e) {
// terminate the thread on any unexpected exception that might occur
System.err.println("Terminating interrupt handling for pin " + irqPin + " after catching: " + e.getMessage());
}
}
}, "GPIO" + pin + " IRQ");
t.setPriority(Thread.MAX_PRIORITY);
t.start();
irqThreads.put(pin, t);
}
/**
* Checks if the GPIO pin number can be valid
*
* Board-specific classes, such as RPI, assign -1 to pins that carry power,
* ground and the like.
* @param pin GPIO pin
*/
protected static void checkValidPin(int pin) {
if (pin < 0) {
throw new RuntimeException("Operation not supported on this pin");
}
}
/**
* Pauses the execution of the sketch
*
* Calling this function will have an influence on the framerate
* the sketch is going to achieve.
* @param ms milliseconds to pause
*/
protected static void delay(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
/**
* Pauses the execution of the sketch
*
* Note: Both the operating system, as well as Processing, are not what is
* called "hard real-time" systems. In other words: there are many factors,
* outside of the control of the programmer, which can influence the execution
* of the program in minute ways. Those are generally not an issue, or even
* noticeable using a desktop operating system, but they can be a factor, when
* the timing of a particular sequence of events is critical. For example, one
* might to wait a very specific number amount of time after receiving an
* interrupt before changing the state of an output pin. When programming with
* micro-controllers, as found on the Arduino Uno, there very little between
* your code and the actual hardware, and multiple executions of the same
* sketch will probably match each other almost to the very tick of a clock
* (which happens at the speed of 16 MHz). Systems running full-fledged
* desktop operating systems, such as Linux, are generally multi-tasking,
* which means that the operating system allocates small slices of time to
* the many different processes that run concurrently. The effect of this is
* often offset by the sheer clock speeds that such computers run. But
* regardless: if you require your sketch to adhere to a very specific timing,
* you might be disappointed.
* @param us microseconds to pause
*/
protected static void delayMicroseconds(int us) {
int ms = (int)(us / 1000);
int ns = (us - (ms * 1000)) * 1000;
try {
Thread.sleep(ms, ns);
} catch (InterruptedException e) {
}
}
/**
* Returns the value of an input pin
*
* You need to set the pin to INPUT by calling pinMode before calling
* this function.
* @param pin GPIO pin
* @return GPIO.HIGH (1) or GPIO.LOW (0)
* @see pinMode
* @see digitalWrite
*/
public static int digitalRead(int pin) {
checkValidPin(pin);
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
byte in[] = new byte[2];
int ret = NativeInterface.readFile(fn, in);
if (ret < 0) {
throw new RuntimeException(NativeInterface.getError(ret));
} else if (1 <= ret && in[0] == '0') {
return LOW;
} else if (1 <= ret && in[0] == '1') {
return HIGH;
} else {
System.err.print("Read " + ret + " bytes");
if (0 < ret) {
System.err.format(", first byte is 0x%02x" + in[0]);
}
System.err.println();
throw new RuntimeException("Unexpected value");
}
}
/**
* Sets an output pin to HIGH or LOW
*
* You need set the pin to OUTPUT by calling pinMode before calling this
* function. It is not possible to enable or disable internal pull-up
* resistors for inputs using this function, which is something that's
* supported on Arduino.
* @param pin GPIO pin
* @param value GPIO.HIGH or GPIO.LOW
* @see pinMode
* @see digitalRead
*/
public static void digitalWrite(int pin, int value) {
checkValidPin(pin);
String out;
if (value == LOW) {
// values are also stored in a bitmap to make it possible to set a
// default level per pin before enabling the output
values.clear(pin);
out = "0";
} else if (value == HIGH) {
values.set(pin);
out = "1";
} else {
System.err.println("Only GPIO.LOW and GPIO.HIGH, 0 and 1, or true and false, can be used.");
throw new IllegalArgumentException("Illegal value");
}
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
int ret = NativeInterface.writeFile(fn, out);
if (ret < 0) {
if (ret != -2) { // ENOENT, pin might not yet be exported
throw new RuntimeException(NativeInterface.getError(ret));
}
}
}
/**
* Sets an output pin to HIGH or LOW
*
* You need set the pin to OUTPUT by calling pinMode before calling this
* function. It is not possible to enable or disable internal pull-up
* resistors for inputs using this function, which is something that's
* supported on Arduino.
* @param pin GPIO pin
* @param value true or false
* @see pinMode
* @see digitalRead
*/
public static void digitalWrite(int pin, boolean value) {
if (value) {
digitalWrite(pin, HIGH);
} else {
digitalWrite(pin, LOW);
}
}
/**
* Disables an interrupt for an INPUT pin
*
* Use this function only in combination with enableInterrupt() and
* waitForInterrupt(). This should not be called when attachInterrupt()
* is being used.
* @param pin GPIO pin
* @see enableInterrupt
* @see waitForInterrupt
*/
public static void disableInterrupt(int pin) {
enableInterrupt(pin, NONE);
}
/**
* Enables an interrupt for an INPUT pin
*
* Use this function only when calling waitForInterrupt(). This should not
* be called when attachInterrupt() is being used.
* @param pin GPIO pin
* @param mode what to wait for: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
* @see waitForInterrupt
* @see disableInterrupt
*/
public static void enableInterrupt(int pin, int mode) {
checkValidPin(pin);
String out;
if (mode == NONE) {
out = "none";
} else if (mode == CHANGE) {
out = "both";
} else if (mode == FALLING) {
out = "falling";
} else if (mode == RISING) {
out = "rising";
} else {
throw new IllegalArgumentException("Unknown mode");
}
String fn = String.format("/sys/class/gpio/gpio%d/edge", pin);
int ret = NativeInterface.writeFile(fn, out);
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your called pinMode on the input pin");
}
throw new RuntimeException(NativeInterface.getError(ret));
}
}
/**
* Allows interrupts to happen
*
* You can use noInterrupts() and interrupts() in tandem to make sure no interrupts
* are occuring while your sketch is doing a particular task. This is only relevant
* when using attachInterrupt(), not for waitForInterrupt(). By default, interrupts
* are enabled.
* @see attachInterrupt
* @see noInterrupts
* @see releaseInterrupt
*/
public static void interrupts() {
serveInterrupts = true;
}
/**
* Prevents interrupts from happpening
*
* You can use noInterrupts() and interrupts() in tandem to make sure no interrupts
* are occuring while your sketch is doing a particular task. This is only relevant
* when using attachInterrupt(), not for waitForInterrupt(). By default, interrupts
* are enabled.
* @see attachInterrupt
* @see interrupts
* @see releaseInterrupt
*/
public static void noInterrupts() {
serveInterrupts = false;
}
/**
* Sets a pin to INPUT or OUTPUT
*
* While pins are implicitly set to input by default on Arduino, it is
* necessary to call this function for any pin you want to access later,
* including input pins.
* @param pin GPIO pin
* @param mode GPIO.INPUT or GPIO.OUTPUT
* @see digitalRead
* @see digitalWrite
* @see releasePin
*/
public static void pinMode(int pin, int mode) {
checkValidPin(pin);
// export pin through sysfs
String fn = "/sys/class/gpio/export";
int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your kernel is compiled with GPIO_SYSFS enabled");
}
if (ret == -22) { // EINVAL
System.err.println("GPIO pin " + pin + " does not seem to be available on your platform");
}
if (ret != -16) { // EBUSY, returned when the pin is already exported
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
// delay to give udev a chance to change the file permissions behind our back
// there should really be a cleaner way for this
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// set direction and default level for outputs
fn = String.format("/sys/class/gpio/gpio%d/direction", pin);
String out;
if (mode == INPUT) {
out = "in";
} else if (mode == OUTPUT) {
if (values.get(pin)) {
out = "high";
} else {
out = "low";
}
} else if (mode == INPUT_PULLUP || mode == INPUT_PULLDOWN) {
// currently this can't be done in a non-platform-specific way, see
// http://lists.infradead.org/pipermail/linux-rpi-kernel/2015-August/002146.html
throw new RuntimeException("Not yet implemented");
} else {
throw new IllegalArgumentException("Unknown mode");
}
ret = NativeInterface.writeFile(fn, out);
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
/**
* Stops listening for interrupts on an INPUT pin
*
* Use this function only in combination with attachInterrupt(). This should
* not be called when enableInterrupt() and waitForInterrupt() are being used.
* @param pin GPIO pin
* @see attachInterrupt
* @see noInterrupts
* @see interrupts
*/
public static void releaseInterrupt(int pin) {
Thread t = irqThreads.get(pin);
if (t == null) {
return;
}
t.interrupt();
try {
t.join();
} catch (InterruptedException e) {
System.err.println("Error joining thread in releaseInterrupt: " + e.getMessage());
}
t = null;
irqThreads.remove(pin);
disableInterrupt(pin);
}
/**
* Gives ownership of a pin back to the operating system
*
* Without calling this function the pin will remain in the current
* state even after the sketch has been closed.
* @param pin GPIO pin
* @see pinMode
*/
public static void releasePin(int pin) {
checkValidPin(pin);
String fn = "/sys/class/gpio/unexport";
int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your kernel is compiled with GPIO_SYSFS enabled");
}
// EINVAL is returned when trying to unexport pins that weren't exported to begin with, ignore this case
if (ret != -22) {
throw new RuntimeException(NativeInterface.getError(ret));
}
}
}
/**
* Waits for the value of an INPUT pin to change
*
* Make sure to setup the interrupt with enableInterrupt() before calling
* this function. A timeout value of -1 waits indefinitely.
* @param timeout don't wait more than timeout milliseconds
* @return true if the interrupt occured, false if the timeout occured
* @see enableInterrupt
* @see disableInterrupt
*/
public static boolean waitForInterrupt(int pin, int timeout) {
checkValidPin(pin);
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
int ret = NativeInterface.pollDevice(fn, timeout);
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your called pinMode on the input pin");
}
throw new RuntimeException(NativeInterface.getError(ret));
} else if (ret == 0) {
// timeout
return false;
} else {
// interrupt
return true;
}
}
/**
* Waits for the value of an INPUT pin to change
*
* Make sure to setup the interrupt with enableInterrupt() before calling
* this function. This function will wait indefinitely for an interrupt
* to occur.
* @see enableInterrupt
* @see disableInterrupt
*/
public static void waitForInterrupt(int pin) {
waitForInterrupt(pin, -1);
}
}

View File

@@ -0,0 +1,250 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
import processing.io.NativeInterface;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
public class I2C {
protected String dev;
protected int handle;
protected int slave;
protected byte[] out;
protected boolean transmitting;
/**
* Opens an I2C device as master
*
* @param dev device name
* @see list
*/
public I2C(String dev) {
NativeInterface.loadLibrary();
this.dev = dev;
handle = NativeInterface.openDevice("/dev/" + dev);
if (handle < 0) {
throw new RuntimeException(NativeInterface.getError(handle));
}
}
/**
* Close the I2C device
*/
public void close() {
NativeInterface.closeDevice(handle);
handle = 0;
}
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
/**
* Begins a transmission to an attached device
*
* I2C addresses consist of 7 bits plus one bit that indicates whether
* the device is being read from or written to. Some datasheets list
* the address in an 8 bit form (7 address bits + R/W bit), while others
* provide the address in a 7 bit form, with the address in the lower
* 7 bits. This function expects the address in the lower 7 bits, the
* same way as in Arduino's Wire library, and as shown in the output
* of the i2cdetect tool.
* If the address provided in a datasheet is greater than 127 (hex 0x7f)
* or there are separate addresses for read and write operations listed,
* which vary exactly by one, then you want to shif the this number by
* one bit to the right before passing it as an argument to this function.
* @param slave 7 bit address of slave device
* @see write
* @see read
* @see endTransmission
*/
public void beginTransmission(int slave) {
// addresses 120 (0x78) to 127 are additionally reserved
if (0x78 <= slave) {
System.err.println("beginTransmission expects a 7 bit address, try shifting one bit to the right");
throw new IllegalArgumentException("Illegal address");
}
this.slave = slave;
transmitting = true;
out = null;
}
/**
* Ends the current transmissions
*
* This executes any queued writes.
* @see beginTransmission
* @see write
*/
public void endTransmission() {
if (!transmitting) {
// silently ignore this case
return;
}
// implement these flags if needed: https://github.com/raspberrypi/linux/blob/rpi-patches/Documentation/i2c/i2c-protocol
int ret = NativeInterface.transferI2c(handle, slave, out, null);
transmitting = false;
out = null;
if (ret < 0) {
if (ret == -5) { // EIO
System.err.println("The device did not respond. Check the cabling and whether you are using the correct address.");
}
throw new RuntimeException(NativeInterface.getError(ret));
}
}
/**
* Lists all available I2C devices
* @return String array
*/
public static String[] list() {
ArrayList<String> devs = new ArrayList<String>();
File dir = new File("/dev");
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().startsWith("i2c-")) {
devs.add(file.getName());
}
}
}
// listFiles() does not guarantee ordering
String[] tmp = devs.toArray(new String[devs.size()]);
Arrays.sort(tmp);
return tmp;
}
/**
* Reads bytes from the attached device
*
* You must call beginTransmission() before calling this function. This function
* also ends the current transmisison and sends any data that was queued using
* write() before.
* @param len number of bytes to read
* @return bytes read from device
* @see beginTransmission
* @see write
* @see endTransmission
*/
public byte[] read(int len) {
if (!transmitting) {
throw new RuntimeException("beginTransmisson has not been called");
}
byte[] in = new byte[len];
int ret = NativeInterface.transferI2c(handle, slave, out, in);
transmitting = false;
out = null;
if (ret < 0) {
if (ret == -5) { // EIO
System.err.println("The device did not respond. Check the cabling and whether you are using the correct address.");
}
throw new RuntimeException(NativeInterface.getError(ret));
}
return in;
}
/**
* Adds bytes to be written to the device
*
* You must call beginTransmission() before calling this function.
* The actual writing takes part when read() or endTransmission() is being
* called.
* @param out bytes to be written
* @see beginTransmission
* @see read
* @see endTransmission
*/
public void write(byte[] out) {
if (!transmitting) {
throw new RuntimeException("beginTransmisson has not been called");
}
if (this.out == null) {
this.out = out;
} else {
byte[] tmp = new byte[this.out.length + out.length];
System.arraycopy(this.out, 0, tmp, 0, this.out.length);
System.arraycopy(out, 0, tmp, this.out.length, out.length);
this.out = tmp;
}
}
/**
* Adds bytes to be written to the device
*
* You must call beginTransmission() before calling this function.
* The actual writing takes part when read() or endTransmission() is being
* called.
* @param out string to be written
* @see beginTransmission
* @see read
* @see endTransmission
*/
public void write(String out) {
write(out.getBytes());
}
/**
* Adds a byte to be written to the device
*
* You must call beginTransmission() before calling this function.
* The actual writing takes part when read() or endTransmission() is being
* called.
* @param out single byte to be written (0-255)
* @see beginTransmission
* @see read
* @see endTransmission
*/
public void write(int out) {
if (out < 0 || 255 < out) {
System.err.println("The write function can only operate on a single byte at a time. Call it with a value from 0 to 255.");
throw new RuntimeException("Argument does not fit into a single byte");
}
byte[] tmp = new byte[1];
tmp[0] = (byte)out;
write(tmp);
}
}

View File

@@ -0,0 +1,155 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
import processing.io.NativeInterface;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
public class LED {
protected String dev;
protected int maxBrightness;
protected int prevBrightness;
protected String prevTrigger;
/**
* Opens a LED device
* @param dev device name
* @see list
*/
public LED(String dev) {
NativeInterface.loadLibrary();
this.dev = dev;
// read maximum brightness
try {
Path path = Paths.get("/sys/class/leds/" + dev + "/max_brightness");
String tmp = new String(Files.readAllBytes(path));
maxBrightness = Integer.parseInt(tmp.trim());
} catch (Exception e) {
System.err.println(e.getMessage());
throw new RuntimeException("Unable to read maximum brightness");
}
// read current trigger setting to be able to restore it later
try {
Path path = Paths.get("/sys/class/leds/" + dev + "/trigger");
String tmp = new String(Files.readAllBytes(path));
int start = tmp.indexOf('[');
int end = tmp.indexOf(']', start);
if (start != -1 && end != -1) {
prevTrigger = tmp.substring(start+1, end);
}
} catch (Exception e) {
System.err.println(e.getMessage());
throw new RuntimeException("Unable to read trigger setting");
}
// read current brightness to be able to restore it later
try {
Path path = Paths.get("/sys/class/leds/" + dev + "/brightness");
String tmp = new String(Files.readAllBytes(path));
prevBrightness = Integer.parseInt(tmp.trim());
} catch (Exception e) {
System.err.println(e.getMessage());
throw new RuntimeException("Unable to read current brightness");
}
// disable trigger
String fn = "/sys/class/leds/" + dev + "/trigger";
int ret = NativeInterface.writeFile(fn, "none");
if (ret < 0) {
if (ret == -13) { // EACCES
System.err.println("You might need to install a custom udev rule to allow regular users to modify /sys/class/leds/*.");
}
throw new RuntimeException(NativeInterface.getError(ret));
}
}
/**
* Restores the previous state
*
* Without calling this function the LED will remain in the current
* state even after the sketch has been closed.
*/
public void close() {
// restore previous settings
String fn = "/sys/class/leds/" + dev + "/brightness";
int ret = NativeInterface.writeFile(fn, Integer.toString(prevBrightness));
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
fn = "/sys/class/leds/" + dev + "/trigger";
ret = NativeInterface.writeFile(fn, prevTrigger);
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
/**
* Lists all available LED devices
* @return String array
*/
public static String[] list() {
ArrayList<String> devs = new ArrayList<String>();
File dir = new File("/sys/class/leds");
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
devs.add(file.getName());
}
}
// listFiles() does not guarantee ordering
String[] tmp = devs.toArray(new String[devs.size()]);
Arrays.sort(tmp);
return tmp;
}
/**
* Sets the brightness
* @param bright 0.0 (off) to 1.0 (maximum)
*/
public void set(float bright) {
String fn = "/sys/class/leds/" + dev + "/brightness";
if (bright < 0.0 || 1.0 < bright) {
System.err.println("Brightness must be between 0.0 and 1.0.");
throw new IllegalArgumentException("Illegal argument");
}
int ret = NativeInterface.writeFile(fn, Integer.toString((int)(bright * maxBrightness)));
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
}

View File

@@ -0,0 +1,60 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
public class NativeInterface {
protected static boolean loaded = false;
public static void loadLibrary() {
if (!loaded) {
if (!"Linux".equals(System.getProperty("os.name"))) {
throw new RuntimeException("The Processing I/O library is only supported on Linux");
}
System.loadLibrary("processing-io");
loaded = true;
}
}
public static native int openDevice(String fn);
public static native String getError(int errno);
public static native int closeDevice(int handle);
// the following two functions were done in native code to get access to the
// specifc error number (errno) that might occur
public static native int readFile(String fn, byte[] in);
public static native int writeFile(String fn, byte[] out);
public static int writeFile(String fn, String out) {
return writeFile(fn, out.getBytes());
}
/* GPIO */
public static native int pollDevice(String fn, int timeout);
/* I2C */
public static native int transferI2c(int handle, int slave, byte[] out, byte[] in);
/* SPI */
public static native int setSpiSettings(int handle, int maxSpeed, int dataOrder, int mode);
public static native int transferSpi(int handle, byte[] out, byte[] in);
}

View File

@@ -0,0 +1,191 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
import processing.io.NativeInterface;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
public class PWM {
int channel;
String chip;
/**
* Opens a PWM channel
* @param channel PWM channel
* @see list
*/
public PWM(String channel) {
NativeInterface.loadLibrary();
int pos = channel.indexOf("/pwm");
if (pos == -1) {
throw new IllegalArgumentException("Unsupported channel");
}
chip = channel.substring(0, pos);
this.channel = Integer.parseInt(channel.substring(pos+4));
// export channel through sysfs
String fn = "/sys/class/pwm/" + chip + "/export";
int ret = NativeInterface.writeFile(fn, Integer.toString(this.channel));
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your kernel is compiled with PWM_SYSFS enabled and you have the necessary PWM driver for your platform");
}
// XXX: check
if (ret == -22) { // EINVAL
System.err.println("PWM channel " + channel + " does not seem to be available on your platform");
}
// XXX: check
if (ret != -16) { // EBUSY, returned when the pin is already exported
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
// delay to give udev a chance to change the file permissions behind our back
// there should really be a cleaner way for this
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Disables the output
*/
public void clear() {
String fn = String.format("/sys/class/pwm/%s/gpio%d/enable", chip, channel);
int ret = NativeInterface.writeFile(fn, "0");
if (ret < 0) {
throw new RuntimeException(NativeInterface.getError(ret));
}
}
/**
* Gives ownership of a channel back to the operating system
*
* Without calling this function the channel will remain in the current
* state even after the sketch has been closed.
*/
public void close() {
// XXX: implicit clear()?
// XXX: also check GPIO
String fn = "/sys/class/pwm/" + chip + "/export";
int ret = NativeInterface.writeFile(fn, Integer.toString(channel));
if (ret < 0) {
if (ret == -2) { // ENOENT
System.err.println("Make sure your kernel is compiled with PWM_SYSFS enabled and you have the necessary PWM driver for your platform");
}
// XXX: check
// EINVAL is also returned when trying to unexport pins that weren't exported to begin with
throw new RuntimeException(NativeInterface.getError(ret));
}
}
/**
* Lists all available PWM channels
* @return String array
*/
public static String[] list() {
ArrayList<String> devs = new ArrayList<String>();
File dir = new File("/sys/class/pwm");
File[] chips = dir.listFiles();
if (chips != null) {
for (File chip : chips) {
// get the number of supported channels
try {
Path path = Paths.get("/sys/class/pwm/" + chip.getName() + "/npwm");
String tmp = new String(Files.readAllBytes(path));
int npwm = Integer.parseInt(tmp.trim());
for (int i=0; i < npwm; i++) {
devs.add(chip.getName() + "/pwm" + i);
}
} catch (Exception e) {
}
}
}
// listFiles() does not guarantee ordering
String[] tmp = devs.toArray(new String[devs.size()]);
Arrays.sort(tmp);
return tmp;
}
/**
* Enables the PWM output
* @param period cycle period in Hz
* @param duty duty cycle, 0.0 (always off) to 1.0 (always on)
*/
public void set(int period, float duty) {
// set period
String fn = fn = String.format("/sys/class/pwm/%s/gpio%d/period", chip, channel);
int ret = NativeInterface.writeFile(fn, String.format("%d", (int)(1000000000 / period)));
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
// set duty cycle
fn = fn = String.format("/sys/class/pwm/%s/gpio%d/duty", chip, channel);
if (duty < 0.0 || 1.0 < duty) {
System.err.println("Duty cycle must be between 0.0 and 1.0.");
throw new IllegalArgumentException("Illegal argument");
}
ret = NativeInterface.writeFile(fn, String.format("%d", (int)((1000000000 * duty) / period)));
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
// enable output
fn = String.format("/sys/class/pwm/%s/gpio%d/enable", chip, channel);
ret = NativeInterface.writeFile(fn, "1");
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
}
}
/**
* Enables the PWM output with a preset period of 1 kHz
*
* This period approximately matches the dedicated PWM pins on
* the Arduino Uno, which have a frequency of 980 Hz.
* It is recommended to use set(period, duty) instead.
* @param duty duty cycle, 0.0 (always off) to 1.0 (always on)
*/
public void set(float duty) {
set(1000, duty);
}
}

View File

@@ -0,0 +1,85 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
public class RPI {
/*
* The Raspberry Pi has a 2x20 pin header for connecting various peripherals.
* The following constants describe how the pins of this header correspond
* to the pin numbering used by the CPU and by software ("GPIO pin number").
*
* You can use this class to refer to a pin by its location on the header:
* e.g. GPIO.digitalWrite(RPI.PIN7, GPIO.HIGH)
*
* Alternatively, if you know a pins "true" pin number (GPIO number), you
* can use it directly. The following is equivalent to the example above:
* GPIO.digitalWrite(4, GPIO.HIGH)
*
* PIN1 is located on the "top left" of the column of pins, close to the two
* LEDs. PIN2 is next to it. PIN3 is below PIN1, and it goes on like this.
* See also: http://pi.gadgetoid.com/pinout
*/
public static final int PIN1 = -1; /* 3v3 Power, can source up to 50 mA */
public static final int PIN2 = -1; /* 5v Power, connected to input power */
public static final int PIN3 = 2; /* GPIO 2, also: I2C data */
public static final int PIN4 = -1; /* 5v Power, connected to input power */
public static final int PIN5 = 3; /* GPIO 3, also: I2C clock */
public static final int PIN6 = -1; /* Ground */
public static final int PIN7 = 4; /* GPIO 4 */
public static final int PIN8 = 14; /* GPIO 14, also: Serial TX */
public static final int PIN9 = -1; /* Ground */
public static final int PIN10 = 15; /* GPIO 15, also: Serial RX */
public static final int PIN11 = 17; /* GPIO 17 */
public static final int PIN12 = 18; /* GPIO 18 */
public static final int PIN13 = 27; /* GPIO 27 */
public static final int PIN14 = -1; /* Ground */
public static final int PIN15 = 22; /* GPIO 22 */
public static final int PIN16 = 23; /* GPIO 23 */
public static final int PIN17 = -1; /* 3v3 Power, can source up to 50 mA */
public static final int PIN18 = 24; /* GPIO 24 */
public static final int PIN19 = 10; /* GPIO 10, also: SPI MOSI */
public static final int PIN20 = -1; /* Ground */
public static final int PIN21 = 9; /* GPIO 9, also: SPI MISO */
public static final int PIN22 = 25; /* GPIO 25 */
public static final int PIN23 = 11; /* GPIO 11, also: SPI SCLK */
public static final int PIN24 = 8; /* GPIO 8, also: SPI Chip Select 0 */
public static final int PIN25 = -1; /* Ground */
public static final int PIN26 = 7; /* GPIO 7, also: SPI Chip Select 1 */
public static final int PIN27 = 0; /* GPIO 0, also: HAT I2C data, reserved [currenly not accessible] */
public static final int PIN28 = 1; /* GPIO 1, also HAT I2C data, reserved [currenly not accessible] */
public static final int PIN29 = 5; /* GPIO 5 */
public static final int PIN30 = -1; /* Ground */
public static final int PIN31 = 6; /* GPIO 6 */
public static final int PIN32 = 12; /* GPIO 12 */
public static final int PIN33 = 13; /* GPIO 13 */
public static final int PIN34 = -1; /* Ground */
public static final int PIN35 = 19; /* GPIO 19, also: SPI MISO [currenly not accessible] */
public static final int PIN36 = 16; /* GPIO 16 */
public static final int PIN37 = 26; /* GPIO 26 */
public static final int PIN38 = 20; /* GPIO 20, also: SPI MISO [currenly not accessible] */
public static final int PIN39 = -1; /* Ground */
public static final int PIN40 = 21; /* GPIO 21, also: SPI CLK [currenly not accessible] */
}

View File

@@ -0,0 +1,183 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Copyright (c) The Processing Foundation 2015
I/O library developed by Gottfried Haider as part of GSOC 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.io;
import processing.io.NativeInterface;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class SPI {
public static final int MODE0 = 0; // CPOL=0, CPHA=0, most common
public static final int MODE1 = 1; // CPOL=0, CPHA=1
public static final int MODE2 = 2; // CPOL=1, CPHA=0
public static final int MODE3 = 3; // CPOL=1, CPHA=1
public static final int MSBFIRST = 0; // most significant bit first, most common
public static final int LSBFIRST = 1; // least significant bit first
protected int dataOrder = 0;
protected String dev;
protected int handle;
protected int maxSpeed = 500000;
protected int mode = 0;
protected static Map<String, String> settings = new HashMap<String, String>();
/**
* Opens an SPI interface
* @param dev device name
* @see list
*/
public SPI(String dev) {
NativeInterface.loadLibrary();
this.dev = dev;
handle = NativeInterface.openDevice("/dev/" + dev);
if (handle < 0) {
throw new RuntimeException(NativeInterface.getError(handle));
}
}
/**
* Closes the I2C interface
*/
public void close() {
NativeInterface.closeDevice(handle);
handle = 0;
}
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
/**
* Lists all available SPI interfaces
* @return String array
*/
public static String[] list() {
ArrayList<String> devs = new ArrayList<String>();
File dir = new File("/dev");
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().startsWith("spidev")) {
devs.add(file.getName());
}
}
}
// listFiles() does not guarantee ordering
String[] tmp = devs.toArray(new String[devs.size()]);
Arrays.sort(tmp);
return tmp;
}
/**
* Configures the SPI interface
* @param maxSpeed maximum transmission rate in Hz, 500000 (500 kHz) is a resonable default
* @param dataOrder whether data is send with the first- or least significant bit first (SPI.MSBFIRST or SPI.LSBFIRST, the former is more common)
* @param mode SPI.MODE0 to SPI.MODE3 (see https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase)
*/
public void settings(int maxSpeed, int dataOrder, int mode) {
this.maxSpeed = maxSpeed;
this.dataOrder = dataOrder;
this.mode = mode;
}
/**
* Transfers data over the SPI bus
*
* With SPI, data is simultaneously being exchanged between the master device
* and the slave device. For every byte that is being sent out, there's also
* one byte being read in.
* @param out bytes to send
* @return bytes read in (array is the same length as out)
*/
public byte[] transfer(byte[] out) {
// track the current setting per device across multiple instances
String curSettings = maxSpeed + "-" + dataOrder + "-" + mode;
if (!curSettings.equals(settings.get(dev))) {
int ret = NativeInterface.setSpiSettings(handle, maxSpeed, dataOrder, mode);
if (ret < 0) {
System.err.println(NativeInterface.getError(handle));
throw new RuntimeException("Error updating device configuration");
}
settings.put(dev, curSettings);
}
byte[] in = new byte[out.length];
int transferred = NativeInterface.transferSpi(handle, out, in);
if (transferred < 0) {
throw new RuntimeException(NativeInterface.getError(transferred));
} else if (transferred < out.length) {
throw new RuntimeException("Fewer bytes transferred than requested: " + transferred);
}
return in;
}
/**
* Transfers data over the SPI bus
*
* With SPI, data is simultaneously being exchanged between the master device
* and the slave device. For every byte that is being sent out, there's also
* one byte being read in.
* @param out string to send
* @return bytes read in (array is the same length as out)
*/
public byte[] transfer(String out) {
return transfer(out.getBytes());
}
/**
* Transfers data over the SPI bus
*
* With SPI, data is simultaneously being exchanged between the master device
* and the slave device. For every byte that is being sent out, there's also
* one byte being read in.
* @param out single byte to send
* @return bytes read in (array is the same length as out)
*/
public byte[] transfer(int out) {
if (out < 0 || 255 < out) {
System.err.println("The transfer function can only operate on a single byte at a time. Call it with a value from 0 to 255.");
throw new RuntimeException("Argument does not fit into a single byte");
}
byte[] tmp = new byte[1];
tmp[0] = (byte)out;
return transfer(tmp);
}
}