Initial Commit
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="Processing Hardware 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.8"
|
||||
target="1.8"
|
||||
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>
|
||||
|
||||
<target name="dist" depends="build" description="Package standalone library">
|
||||
<!-- set revision number as library version -->
|
||||
<loadfile srcfile="../../../todo.txt" property="revision">
|
||||
<filterchain>
|
||||
<headfilter lines="1"/>
|
||||
<tokenfilter>
|
||||
<stringtokenizer suppressdelims="true"/>
|
||||
<!-- grab the thing from the first line that's 4 digits -->
|
||||
<containsregex pattern="(\d\d\d\d)" />
|
||||
</tokenfilter>
|
||||
</filterchain>
|
||||
</loadfile>
|
||||
<replaceregexp file="library.properties" match="version = .*" replace="version = ${revision}" flags="g" />
|
||||
<replaceregexp file="library.properties" match="prettyVersion = .*" replace="prettyVersion = ${revision}" flags="g" />
|
||||
|
||||
<get src="http://download.processing.org/reference.zip"
|
||||
dest="reference.zip"
|
||||
usetimestamp="true" />
|
||||
<mkdir dir="reference" />
|
||||
<unzip dest="."
|
||||
src="reference.zip"
|
||||
overwrite="true">
|
||||
<patternset>
|
||||
<include name="reference/css/**" />
|
||||
<include name="reference/img/**" />
|
||||
<include name="reference/javascript/**" />
|
||||
<include name="reference/libraries/io/**" />
|
||||
</patternset>
|
||||
</unzip>
|
||||
<delete file="reference.zip" />
|
||||
<echo file="reference/index.html" message="<html><head><meta http-equiv='refresh' content='0; url=libraries/io/index.html'></head><body></body></html>" />
|
||||
|
||||
<zip destfile="../io.zip">
|
||||
<zipfileset dir="." prefix="io">
|
||||
<exclude name="bin/**"/>
|
||||
</zipfileset>
|
||||
</zip>
|
||||
|
||||
<copy file="library.properties"
|
||||
toFile="../io.txt"/>
|
||||
</target>
|
||||
</project>
|
||||
@@ -0,0 +1,43 @@
|
||||
import processing.io.*;
|
||||
I2C i2c;
|
||||
|
||||
// HMC6352 is a digital compass module using I2C
|
||||
// datasheet: https://www.sparkfun.com/datasheets/Components/HMC6352.pdf
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 194 KiB |
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import processing.io.*;
|
||||
|
||||
// 0.96" 128x64 OLED display ("SKU 346540")
|
||||
SSD1306 oled;
|
||||
|
||||
void setup() {
|
||||
size(128, 64);
|
||||
|
||||
// the display can be set to one of these two addresses: 0x3c (default) or 0x3d
|
||||
// (they might be listed as 0x7a and 0x7b on the circuit board)
|
||||
|
||||
// you might need to use a different interface on other SBCs
|
||||
oled = new SSD1306("i2c-1", 0x3c);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(0);
|
||||
stroke(255);
|
||||
line(0, 0, 127, 63);
|
||||
line(0, 63, 127, 0);
|
||||
oled.sendImage(get());
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import processing.io.I2C;
|
||||
|
||||
// SSD1306 is a small, inexpensive 128x64 pixels monochrome OLED display
|
||||
// available online as "0.96" 128x64 OLED display", SKU 346540
|
||||
// or from Adafruit
|
||||
// datasheet: https://www.adafruit.com/datasheets/SSD1306.pdf
|
||||
|
||||
class SSD1306 extends I2C {
|
||||
int address;
|
||||
|
||||
// there can be more than one device connected to the bus
|
||||
// as long as they have different addresses
|
||||
SSD1306(String dev, int address) {
|
||||
super(dev);
|
||||
this.address = address;
|
||||
init();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
writeCommand(0xae); // turn display off
|
||||
writeCommand(0xa8, 0x3f); // set multiplex ratio to the highest setting
|
||||
writeCommand(0x8d, 0x14); // enable charge pump
|
||||
writeCommand(0x20, 0x00); // set memory addressing mode to horizontal
|
||||
writeCommand(0xd5, 0x80); // set display clock divide ratio & oscillator frequency to default
|
||||
writeCommand(0xd3, 0x00); // no display offset
|
||||
writeCommand(0x40 | 0x00); // set default display start line
|
||||
|
||||
// use the following two lines to flip the display
|
||||
writeCommand(0xa0 | 0x01); // set segment re-map
|
||||
writeCommand(0xc8); // set COM output scan direction
|
||||
|
||||
writeCommand(0xda, 0x12); // set COM pins hardware configuration
|
||||
writeCommand(0xd9, 0xf1); // set pre-charge period to 241x DCLK
|
||||
writeCommand(0xdB, 0x40); // set VCOMH deselect level
|
||||
writeCommand(0xa4); // display RAM content (not all-on)
|
||||
writeCommand(0xa6); // set normal (not-inverted) display
|
||||
|
||||
// set this since we don't have access to the OLED's reset pins (?)
|
||||
writeCommand(0x21, 0, 127); // set column address
|
||||
writeCommand(0x22, 0, 7); // set page address
|
||||
|
||||
writeCommand(0x81, 0xcf); // set contrast
|
||||
writeCommand(0x2e); // deactivate scroll
|
||||
writeCommand(0xaf); // turn display on
|
||||
}
|
||||
|
||||
void invert(boolean inverted) {
|
||||
if (inverted) {
|
||||
writeCommand(0xa7);
|
||||
} else {
|
||||
writeCommand(0xa6);
|
||||
}
|
||||
}
|
||||
|
||||
void sendImage(PImage img) {
|
||||
sendImage(img, 0, 0);
|
||||
}
|
||||
|
||||
void sendImage(PImage img, int startX, int startY) {
|
||||
byte[] frame = new byte[1024];
|
||||
img.loadPixels();
|
||||
for (int y=startY; y < height && y-startY < 64; y++) {
|
||||
for (int x=startX; x < width && x-startX < 128; x++) {
|
||||
if (128 <= brightness(img.pixels[y*img.width+x])) {
|
||||
// this isn't the normal (scanline) mapping, but 8 pixels below each other at a time
|
||||
// white pixels have their bit turned on
|
||||
frame[x + (y/8)*128] |= (1 << (y % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
sendFramebuffer(frame);
|
||||
}
|
||||
|
||||
void sendFramebuffer(byte[] buf) {
|
||||
if (buf.length != 1024) {
|
||||
System.err.println("The framebuffer should be 1024 bytes long, with one bit per pixel");
|
||||
throw new IllegalArgumentException("Unexpected buffer size");
|
||||
}
|
||||
|
||||
writeCommand(0x00 | 0x0); // set start address
|
||||
writeCommand(0x10 | 0x0); // set higher column start address
|
||||
writeCommand(0x40 | 0x0); // set start line
|
||||
|
||||
// send the frame buffer as 16 byte long packets
|
||||
for (int i=0; i < buf.length/16; i++) {
|
||||
super.beginTransmission(address);
|
||||
super.write(0x40); // indicates data write
|
||||
for (int j=0; j < 16; j++) {
|
||||
super.write(buf[i*16+j]);
|
||||
}
|
||||
super.endTransmission();
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeCommand(int arg1) {
|
||||
super.beginTransmission(address);
|
||||
super.write(0x00); // indicates command write
|
||||
super.write(arg1);
|
||||
super.endTransmission();
|
||||
}
|
||||
|
||||
protected void writeCommand(int arg1, int arg2) {
|
||||
super.beginTransmission(address);
|
||||
super.write(0x00);
|
||||
super.write(arg1);
|
||||
super.write(arg2);
|
||||
super.endTransmission();
|
||||
}
|
||||
|
||||
protected void writeCommand(int arg1, int arg2, int arg3) {
|
||||
super.beginTransmission(address);
|
||||
super.write(0x00);
|
||||
super.write(arg1);
|
||||
super.write(arg2);
|
||||
super.write(arg3);
|
||||
super.endTransmission();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import processing.io.*;
|
||||
color bgcolor = 0;
|
||||
|
||||
// GPIO numbers refer to different phyiscal pins on various boards
|
||||
// On the Raspberry Pi GPIO 4 is physical pin 7 on the header
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
void setup() {
|
||||
GPIO.pinMode(4, GPIO.INPUT);
|
||||
GPIO.attachInterrupt(4, this, "pinEvent", GPIO.RISING);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(bgcolor);
|
||||
}
|
||||
|
||||
// this function will be called whenever GPIO 4 is brought from LOW to HIGH
|
||||
void pinEvent(int pin) {
|
||||
println("Received interrupt");
|
||||
if (bgcolor == 0) {
|
||||
bgcolor = color(255);
|
||||
} else {
|
||||
bgcolor = color(0);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 193 KiB |
@@ -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].brightness(1.0);
|
||||
} else {
|
||||
leds[i].brightness(0.0);
|
||||
}
|
||||
}
|
||||
println(frameCount);
|
||||
}
|
||||
|
||||
void keyPressed() {
|
||||
// cleanup
|
||||
for (int i=0; i < leds.length; i++) {
|
||||
leds[i].close();
|
||||
}
|
||||
exit();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import processing.io.*;
|
||||
SPI spi;
|
||||
|
||||
// MCP3001 is a Analog-to-Digital converter using SPI
|
||||
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
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));
|
||||
}
|
||||
|
After Width: | Height: | Size: 66 KiB |
@@ -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);
|
||||
settings(500000, SPI.MSBFIRST, SPI.MODE0);
|
||||
}
|
||||
|
||||
float getAnalog() {
|
||||
// dummy write, actual values don't matter
|
||||
byte[] out = { 0, 0 };
|
||||
byte[] in = 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import processing.io.*;
|
||||
MCP3001 adc;
|
||||
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
void setup() {
|
||||
//printArray(SPI.list());
|
||||
adc = new MCP3001(SPI.list()[0]);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(adc.getAnalog() * 255);
|
||||
}
|
||||
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,27 @@
|
||||
import processing.io.SPI;
|
||||
|
||||
// MCP3008 is a Analog-to-Digital converter using SPI
|
||||
// other than the MCP3001, this has 8 input channels
|
||||
// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf
|
||||
|
||||
class MCP3008 extends SPI {
|
||||
|
||||
MCP3008(String dev) {
|
||||
super(dev);
|
||||
settings(500000, SPI.MSBFIRST, SPI.MODE0);
|
||||
}
|
||||
|
||||
float getAnalog(int channel) {
|
||||
if (channel < 0 || 7 < channel) {
|
||||
System.err.println("The channel needs to be from 0 to 7");
|
||||
throw new IllegalArgumentException("Unexpected channel");
|
||||
}
|
||||
byte[] out = { 0, 0, 0 };
|
||||
// encode the channel number in the first byte
|
||||
out[0] = (byte)(0x18 | channel);
|
||||
byte[] in = transfer(out);
|
||||
int val = ((in[1] & 0x03) << 8) | (in[2] & 0xff);
|
||||
// val is between 0 and 1023
|
||||
return val/1023.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import processing.io.*;
|
||||
MCP3008 adc;
|
||||
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
void setup() {
|
||||
//printArray(SPI.list());
|
||||
adc = new MCP3008(SPI.list()[0]);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(adc.getAnalog(0) * 255);
|
||||
fill(adc.getAnalog(1) * 255);
|
||||
ellipse(width/2, height/2, width * 0.75, width * 0.75);
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,34 @@
|
||||
import processing.io.*;
|
||||
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
// for more reliable operation it is recommended to power
|
||||
// the servo from an external power source, see setup_better.png
|
||||
|
||||
SoftwareServo servo1;
|
||||
SoftwareServo servo2;
|
||||
|
||||
void setup() {
|
||||
size(400, 300);
|
||||
servo1 = new SoftwareServo(this);
|
||||
servo1.attach(17);
|
||||
servo2 = new SoftwareServo(this);
|
||||
servo2.attach(4);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(0);
|
||||
stroke(255);
|
||||
strokeWeight(3);
|
||||
|
||||
// we don't go right to the edge to prevent
|
||||
// making the servo unhappy
|
||||
float angle = 90 + sin(frameCount / 100.0)*85;
|
||||
servo1.write(angle);
|
||||
float y = map(angle, 0, 180, 0, height);
|
||||
line(0, y, width/2, y);
|
||||
|
||||
angle = 90 + cos(frameCount / 100.0)*85;
|
||||
servo2.write(90 + cos(frameCount / 100.0)*85);
|
||||
y = map(angle, 0, 180, 0, height);
|
||||
line(width/2, y, width, y);
|
||||
}
|
||||
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,20 @@
|
||||
import processing.io.*;
|
||||
|
||||
// GPIO numbers refer to different phyiscal pins on various boards
|
||||
// On the Raspberry Pi GPIO 4 is physical pin 7 on the header
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
void setup() {
|
||||
GPIO.pinMode(4, GPIO.INPUT);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
// sense the input pin
|
||||
if (GPIO.digitalRead(4) == GPIO.HIGH) {
|
||||
fill(255);
|
||||
} else {
|
||||
fill(204);
|
||||
}
|
||||
stroke(255);
|
||||
ellipse(width/2, height/2, width*0.75, height*0.75);
|
||||
}
|
||||
|
After Width: | Height: | Size: 193 KiB |
@@ -0,0 +1,25 @@
|
||||
import processing.io.*;
|
||||
boolean ledOn = false;
|
||||
|
||||
// GPIO numbers refer to different phyiscal pins on various boards
|
||||
// On the Raspberry Pi GPIO 4 is physical pin 7 on the header
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
void setup() {
|
||||
GPIO.pinMode(4, GPIO.OUTPUT);
|
||||
frameRate(0.5);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
// make the LED blink
|
||||
ledOn = !ledOn;
|
||||
if (ledOn) {
|
||||
GPIO.digitalWrite(4, GPIO.LOW);
|
||||
fill(204);
|
||||
} else {
|
||||
GPIO.digitalWrite(4, GPIO.HIGH);
|
||||
fill(255);
|
||||
}
|
||||
stroke(255);
|
||||
ellipse(width/2, height/2, width*0.75, height*0.75);
|
||||
}
|
||||
|
After Width: | Height: | Size: 194 KiB |
@@ -0,0 +1,56 @@
|
||||
import processing.io.*;
|
||||
|
||||
// using a capacitor that gets charged and discharged, while
|
||||
// measuring the time it takes, is an inexpensive way to
|
||||
// read the value of an (analog) resistive sensor, such as
|
||||
// a photocell
|
||||
// kudos to ladyada for the original tutorial
|
||||
|
||||
// see setup.png in the sketch folder for wiring details
|
||||
|
||||
int max = 0;
|
||||
int min = 9999;
|
||||
|
||||
void setup() {
|
||||
}
|
||||
|
||||
void draw() {
|
||||
int val = sensorRead(4);
|
||||
println(val);
|
||||
|
||||
// track largest and smallest reading, to get a sense
|
||||
// how we compare
|
||||
if (max < val) {
|
||||
max = val;
|
||||
}
|
||||
if (val < min) {
|
||||
min = val;
|
||||
}
|
||||
|
||||
// convert current reading into a number between 0.0 and 1.0
|
||||
float frac = map(val, min, max, 0.0, 1.0);
|
||||
|
||||
background(255 * frac);
|
||||
}
|
||||
|
||||
int sensorRead(int pin) {
|
||||
// discharge the capacitor
|
||||
GPIO.pinMode(pin, GPIO.OUTPUT);
|
||||
GPIO.digitalWrite(pin, GPIO.LOW);
|
||||
delay(100);
|
||||
// now the capacitor should be empty
|
||||
|
||||
// measure the time takes to fill it
|
||||
// up to ~ 1.4V again
|
||||
GPIO.pinMode(pin, GPIO.INPUT);
|
||||
int start = millis();
|
||||
while (GPIO.digitalRead(pin) == GPIO.LOW) {
|
||||
// wait
|
||||
}
|
||||
|
||||
// return the time elapsed
|
||||
// this will vary based on the value of the
|
||||
// resistive sensor (lower resistance will
|
||||
// make the capacitor charge faster)
|
||||
return millis() - start;
|
||||
}
|
||||
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,9 @@
|
||||
name = Hardware I/O
|
||||
authors = The Processing Foundation
|
||||
url = http://processing.org/reference/libraries/io/index.html
|
||||
categories = Hardware
|
||||
sentence = Access peripherals on the Raspberry Pi and other Linux-based computers.
|
||||
paragraph = For other platforms, this is solely provided in order to build and export sketches that require processing.io.
|
||||
version = 1
|
||||
prettyVersion = 1
|
||||
minRevision = 247
|
||||
@@ -0,0 +1 @@
|
||||
name = Hardware I/O for Raspberry Pi and other Linux-based computers
|
||||
@@ -0,0 +1,20 @@
|
||||
TARGET := libprocessing-io.so
|
||||
OBJS := impl.o
|
||||
CC := gcc
|
||||
|
||||
# prefix with -m32 to compile for linux32
|
||||
CFLAGS := -std=gnu99 -fPIC -g -ffast-math
|
||||
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)
|
||||
|
||||
.PHONY: iface.h clean
|
||||
@@ -0,0 +1,109 @@
|
||||
/* 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: servoStartThread
|
||||
* Signature: (III)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_processing_io_NativeInterface_servoStartThread
|
||||
(JNIEnv *, jclass, jint, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: processing_io_NativeInterface
|
||||
* Method: servoUpdateThread
|
||||
* Signature: (JII)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_servoUpdateThread
|
||||
(JNIEnv *, jclass, jlong, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: processing_io_NativeInterface
|
||||
* Method: servoStopThread
|
||||
* Signature: (J)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_servoStopThread
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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 <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/param.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "iface.h"
|
||||
|
||||
|
||||
static const int servo_pulse_oversleep = 35; // amount of uS to account for when sleeping
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
pthread_t thread;
|
||||
int pulse;
|
||||
int period;
|
||||
} SERVO_STATE_T;
|
||||
|
||||
|
||||
static void* servoThread(void *ptr) {
|
||||
SERVO_STATE_T *state = (SERVO_STATE_T*)ptr;
|
||||
struct timespec on, off;
|
||||
on.tv_sec = 0;
|
||||
off.tv_sec = 0;
|
||||
|
||||
do {
|
||||
write(state->fd, "1", 1);
|
||||
|
||||
on.tv_nsec = state->pulse * 1000;
|
||||
nanosleep(&on, NULL);
|
||||
|
||||
write(state->fd, "0", 1);
|
||||
|
||||
off.tv_nsec = (state->period - state->pulse) * 1000;
|
||||
nanosleep(&off, NULL);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_processing_io_NativeInterface_servoStartThread
|
||||
(JNIEnv *env, jclass cls, jint gpio, jint pulse, jint period)
|
||||
{
|
||||
char path[26 + 19 + 1];
|
||||
int fd;
|
||||
pthread_t thread;
|
||||
|
||||
// setup struct holding our state
|
||||
SERVO_STATE_T *state = malloc(sizeof(SERVO_STATE_T));
|
||||
if (!state) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->pulse = (pulse - servo_pulse_oversleep > 0) ? pulse - servo_pulse_oversleep : 0;
|
||||
// we're obviously also oversleeping in the general period case
|
||||
// but other than the pulse, this doesn't seem to be crucial with servos
|
||||
state->period = period;
|
||||
|
||||
// open gpio
|
||||
sprintf(path, "/sys/class/gpio/gpio%d/value", gpio);
|
||||
state->fd = open(path, O_WRONLY);
|
||||
if (state->fd < 0) {
|
||||
free(state);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
// start thread
|
||||
int ret = pthread_create(&state->thread, NULL, servoThread, state);
|
||||
if (ret != 0) {
|
||||
free(state);
|
||||
return -ret;
|
||||
}
|
||||
|
||||
// set scheduling policy and priority
|
||||
struct sched_param param;
|
||||
param.sched_priority = 75;
|
||||
ret = pthread_setschedparam(state->thread, SCHED_FIFO, ¶m);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error setting thread policy: %s\n", strerror(ret));
|
||||
}
|
||||
|
||||
return (intptr_t)state;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_servoUpdateThread
|
||||
(JNIEnv *env, jclass cls, jlong handle, jint pulse, jint period)
|
||||
{
|
||||
SERVO_STATE_T *state = (SERVO_STATE_T*)(intptr_t)handle;
|
||||
state->pulse = (pulse - servo_pulse_oversleep > 0) ? pulse - servo_pulse_oversleep : 0;
|
||||
state->period = period;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_servoStopThread
|
||||
(JNIEnv *env, jclass cls, jlong handle)
|
||||
{
|
||||
SERVO_STATE_T *state = (SERVO_STATE_T*)(intptr_t)handle;
|
||||
|
||||
// signal thread to stop
|
||||
pthread_cancel(state->thread);
|
||||
pthread_join(state->thread, NULL);
|
||||
|
||||
close(state->fd);
|
||||
free(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* trigger when level changes
|
||||
*/
|
||||
public static final int CHANGE = 1;
|
||||
/**
|
||||
* trigger when level changes from high to low
|
||||
*/
|
||||
public static final int FALLING = 2;
|
||||
/**
|
||||
* trigger when level changes from low to high
|
||||
*/
|
||||
public static final int RISING = 3;
|
||||
|
||||
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
|
||||
* @param pin GPIO pin
|
||||
* @param parent typically use "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
|
||||
* @webref
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of an input pin
|
||||
* @param pin GPIO pin
|
||||
* @return GPIO.HIGH (1) or GPIO.LOW (0)
|
||||
* @see pinMode
|
||||
* @see digitalWrite
|
||||
* @webref
|
||||
*/
|
||||
public static int digitalRead(int pin) {
|
||||
checkValidPin(pin);
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return LOW;
|
||||
}
|
||||
|
||||
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 be either high or low
|
||||
* @param pin GPIO pin
|
||||
* @param value GPIO.HIGH (1) or GPIO.LOW (0)
|
||||
* @see pinMode
|
||||
* @see digitalRead
|
||||
* @webref
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param value true or false
|
||||
*/
|
||||
public static void digitalWrite(int pin, boolean value) {
|
||||
if (value) {
|
||||
digitalWrite(pin, HIGH);
|
||||
} else {
|
||||
digitalWrite(pin, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disables an interrupt for an input pin
|
||||
* @param pin GPIO pin
|
||||
* @see enableInterrupt
|
||||
* @see waitForInterrupt
|
||||
*/
|
||||
protected static void disableInterrupt(int pin) {
|
||||
enableInterrupt(pin, NONE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enables an interrupt for an input pin
|
||||
* @param pin GPIO pin
|
||||
* @param mode what to wait for: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
|
||||
* @see waitForInterrupt
|
||||
* @see disableInterrupt
|
||||
*/
|
||||
protected 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");
|
||||
}
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
* @see attachInterrupt
|
||||
* @see noInterrupts
|
||||
* @see releaseInterrupt
|
||||
* @webref
|
||||
*/
|
||||
public static void interrupts() {
|
||||
serveInterrupts = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents interrupts from happpening
|
||||
* @see attachInterrupt
|
||||
* @see interrupts
|
||||
* @see releaseInterrupt
|
||||
* @webref
|
||||
*/
|
||||
public static void noInterrupts() {
|
||||
serveInterrupts = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configures a pin to act either as input or output
|
||||
* @param pin GPIO pin
|
||||
* @param mode GPIO.INPUT or GPIO.OUTPUT
|
||||
* @see digitalRead
|
||||
* @see digitalWrite
|
||||
* @see releasePin
|
||||
* @webref
|
||||
*/
|
||||
public static void pinMode(int pin, int mode) {
|
||||
checkValidPin(pin);
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
* @param pin GPIO pin
|
||||
* @see attachInterrupt
|
||||
* @see noInterrupts
|
||||
* @see interrupts
|
||||
* @webref
|
||||
*/
|
||||
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
|
||||
* @param pin GPIO pin
|
||||
* @see pinMode
|
||||
* @webref
|
||||
*/
|
||||
public static void releasePin(int pin) {
|
||||
checkValidPin(pin);
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
* @param pin GPIO pin
|
||||
* @param mode what to wait for: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
|
||||
* @webref
|
||||
*/
|
||||
public static void waitFor(int pin, int mode) {
|
||||
waitFor(pin, mode, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the value of an input pin to change
|
||||
*
|
||||
* This function will throw a RuntimeException in case of a timeout.
|
||||
* @param timeout don't wait more than timeout milliseconds
|
||||
* @webref
|
||||
*/
|
||||
public static void waitFor(int pin, int mode, int timeout) {
|
||||
enableInterrupt(pin, mode);
|
||||
if (waitForInterrupt(pin, timeout) == false) {
|
||||
throw new RuntimeException("Timeout occurred");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean waitForInterrupt(int pin, int mode, int timeout) {
|
||||
throw new RuntimeException("The waitForInterrupt function has been renamed to waitFor. Please update your sketch accordingly.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 pin GPIO pin
|
||||
* @param timeout don't wait more than timeout milliseconds
|
||||
* @return true if the interrupt occured, false if the timeout occured
|
||||
* @see enableInterrupt
|
||||
* @see disableInterrupt
|
||||
*/
|
||||
protected static boolean waitForInterrupt(int pin, int timeout) {
|
||||
checkValidPin(pin);
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
// pretend the interrupt happens after 200ms
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
public class I2C {
|
||||
|
||||
protected String dev;
|
||||
protected int handle;
|
||||
protected int slave;
|
||||
protected byte[] out;
|
||||
protected boolean transmitting;
|
||||
|
||||
|
||||
/**
|
||||
* Opens an I2C interface as master
|
||||
* @param dev interface name
|
||||
* @see list
|
||||
* @webref
|
||||
*/
|
||||
public I2C(String dev) {
|
||||
NativeInterface.loadLibrary();
|
||||
this.dev = dev;
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
handle = NativeInterface.openDevice("/dev/" + dev);
|
||||
if (handle < 0) {
|
||||
throw new RuntimeException(NativeInterface.getError(handle));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begins a transmission to an attached device
|
||||
* @see write
|
||||
* @see read
|
||||
* @see endTransmission
|
||||
* @webref
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the I2C device
|
||||
* @webref
|
||||
*/
|
||||
public void close() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeInterface.closeDevice(handle);
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ends the current transmissions
|
||||
* @see beginTransmission
|
||||
* @see write
|
||||
* @webref
|
||||
*/
|
||||
public void endTransmission() {
|
||||
if (!transmitting) {
|
||||
// silently ignore this case
|
||||
return;
|
||||
}
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
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 interfaces
|
||||
* @return String array
|
||||
* @webref
|
||||
*/
|
||||
public static String[] list() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
// as on the Raspberry Pi
|
||||
return new String[]{ "i2c-1" };
|
||||
}
|
||||
|
||||
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
|
||||
* @param len number of bytes to read
|
||||
* @return bytes read from device
|
||||
* @see beginTransmission
|
||||
* @see write
|
||||
* @see endTransmission
|
||||
* @webref
|
||||
*/
|
||||
public byte[] read(int len) {
|
||||
if (!transmitting) {
|
||||
throw new RuntimeException("beginTransmisson has not been called");
|
||||
}
|
||||
|
||||
byte[] in = new byte[len];
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return in;
|
||||
}
|
||||
|
||||
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
|
||||
* @param out bytes to be written
|
||||
* @see beginTransmission
|
||||
* @see read
|
||||
* @see endTransmission
|
||||
* @webref
|
||||
*/
|
||||
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 attached device
|
||||
* @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 attached device
|
||||
* @param out single byte to be written, e.g. numeric literal (0 to 255, or -128 to 127)
|
||||
* @see beginTransmission
|
||||
* @see read
|
||||
* @see endTransmission
|
||||
*/
|
||||
public void write(int out) {
|
||||
if (out < -128 || 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, or -128 to 127.");
|
||||
throw new RuntimeException("Argument does not fit into a single byte");
|
||||
}
|
||||
byte[] tmp = new byte[1];
|
||||
tmp[0] = (byte)out;
|
||||
write(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a byte to be written to the attached device
|
||||
* @param out single byte to be written
|
||||
* @see beginTransmission
|
||||
* @see read
|
||||
* @see endTransmission
|
||||
*/
|
||||
public void write(byte out) {
|
||||
// cast to (unsigned) int
|
||||
write(out & 0xff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
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
|
||||
* @webref
|
||||
*/
|
||||
public LED(String dev) {
|
||||
NativeInterface.loadLibrary();
|
||||
this.dev = dev;
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the brightness
|
||||
* @param bright 0.0 (off) to 1.0 (maximum)
|
||||
* @webref
|
||||
*/
|
||||
public void brightness(float bright) {
|
||||
if (bright < 0.0 || 1.0 < bright) {
|
||||
System.err.println("Brightness must be between 0.0 and 1.0.");
|
||||
throw new IllegalArgumentException("Illegal argument");
|
||||
}
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String fn = "/sys/class/leds/" + dev + "/brightness";
|
||||
int ret = NativeInterface.writeFile(fn, Integer.toString((int)(bright * maxBrightness)));
|
||||
if (ret < 0) {
|
||||
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores the previous state
|
||||
* @webref
|
||||
*/
|
||||
public void close() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
* @webref
|
||||
*/
|
||||
public static String[] list() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
// as on the Raspberry Pi
|
||||
return new String[]{ "led0", "led1" };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
protected static boolean alwaysSimulate = false;
|
||||
|
||||
public static void loadLibrary() {
|
||||
if (!loaded) {
|
||||
if (isSimulated()) {
|
||||
System.err.println("The Processing I/O library is not supported on this platform. Instead of values from actual hardware ports, your sketch will only receive stand-in values that allow you to test the remainder of its functionality.");
|
||||
} else {
|
||||
System.loadLibrary("processing-io");
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void alwaysSimulate() {
|
||||
alwaysSimulate = true;
|
||||
}
|
||||
|
||||
public static boolean isSimulated() {
|
||||
return alwaysSimulate ||
|
||||
!"Linux".equals(System.getProperty("os.name"));
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
/* SoftwareServo */
|
||||
public static native long servoStartThread(int gpio, int pulse, int period);
|
||||
public static native int servoUpdateThread(long handle, int pulse, int period);
|
||||
public static native int servoStopThread(long handle);
|
||||
/* 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);
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
public class PWM {
|
||||
|
||||
int channel;
|
||||
String chip;
|
||||
|
||||
|
||||
/**
|
||||
* Opens a PWM channel
|
||||
* @param channel PWM channel
|
||||
* @see list
|
||||
* @webref
|
||||
*/
|
||||
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));
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 PWM output
|
||||
* @webref
|
||||
*/
|
||||
public void clear() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String fn = String.format("/sys/class/pwm/%s/pwm%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
|
||||
* @webref
|
||||
*/
|
||||
public void close() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: implicit clear()?
|
||||
// XXX: also check GPIO
|
||||
|
||||
String fn = "/sys/class/pwm/" + chip + "/unexport";
|
||||
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
|
||||
* @webref
|
||||
*/
|
||||
public static String[] list() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return new String[]{ "pwmchip0/pwm0", "pwmchip0/pwm1" };
|
||||
}
|
||||
|
||||
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)
|
||||
* @webref
|
||||
*/
|
||||
public void set(int period, float duty) {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set period
|
||||
String fn = fn = String.format("/sys/class/pwm/%s/pwm%d/period", chip, channel);
|
||||
// convert to nanoseconds
|
||||
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/pwm%d/duty_cycle", 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");
|
||||
}
|
||||
// convert to nanoseconds
|
||||
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/pwm%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
|
||||
* @param duty duty cycle, 0.0 (always off) to 1.0 (always on)
|
||||
* @webref
|
||||
*/
|
||||
public void set(float duty) {
|
||||
set(1000, duty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware 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;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
public class SPI {
|
||||
|
||||
/**
|
||||
* CPOL=0, CPHA=0, most common
|
||||
*/
|
||||
public static final int MODE0 = 0;
|
||||
/**
|
||||
* CPOL=0, CPHA=1
|
||||
*/
|
||||
public static final int MODE1 = 1;
|
||||
/**
|
||||
* CPOL=1, CPHA=0
|
||||
*/
|
||||
public static final int MODE2 = 2;
|
||||
/**
|
||||
* CPOL=1, CPHA=1
|
||||
*/
|
||||
public static final int MODE3 = 3;
|
||||
/**
|
||||
* most significant bit first, most common
|
||||
*/
|
||||
public static final int MSBFIRST = 0;
|
||||
/**
|
||||
* least significant bit first
|
||||
*/
|
||||
public static final int LSBFIRST = 1;
|
||||
|
||||
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 as master
|
||||
* @param dev device name
|
||||
* @see list
|
||||
* @webref
|
||||
*/
|
||||
public SPI(String dev) {
|
||||
NativeInterface.loadLibrary();
|
||||
this.dev = dev;
|
||||
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
handle = NativeInterface.openDevice("/dev/" + dev);
|
||||
if (handle < 0) {
|
||||
throw new RuntimeException(NativeInterface.getError(handle));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the SPI interface
|
||||
* @webref
|
||||
*/
|
||||
public void close() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeInterface.closeDevice(handle);
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lists all available SPI interfaces
|
||||
* @return String array
|
||||
* @webref
|
||||
*/
|
||||
public static String[] list() {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
// as on the Raspberry Pi
|
||||
return new String[]{ "spidev0.0", "spidev0.1" };
|
||||
}
|
||||
|
||||
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 <a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase">SPI.MODE0 to SPI.MODE3</a>
|
||||
* @webref
|
||||
*/
|
||||
public void settings(int maxSpeed, int dataOrder, int mode) {
|
||||
this.maxSpeed = maxSpeed;
|
||||
this.dataOrder = dataOrder;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transfers data over the SPI bus
|
||||
* @param out bytes to send
|
||||
* @return bytes read in (array is the same length as out)
|
||||
* @webref
|
||||
*/
|
||||
public byte[] transfer(byte[] out) {
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return new byte[out.length];
|
||||
}
|
||||
|
||||
// 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
|
||||
* @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
|
||||
* @param out single byte to send, e.g. numeric literal (0 to 255, or -128 to 127)
|
||||
* @return bytes read in (array is the same length as out)
|
||||
*/
|
||||
public byte[] transfer(int out) {
|
||||
if (out < -128 || 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, or -128 to 127.");
|
||||
throw new RuntimeException("Argument does not fit into a single byte");
|
||||
}
|
||||
byte[] tmp = new byte[1];
|
||||
tmp[0] = (byte)out;
|
||||
return transfer(tmp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transfers data over the SPI bus
|
||||
* @param out single byte to send
|
||||
* @return bytes read in (array is the same length as out)
|
||||
*/
|
||||
public byte[] transfer(byte out) {
|
||||
// cast to (unsigned) int
|
||||
return transfer(out & 0xff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Copyright (c) The Processing Foundation 2015
|
||||
Hardware I/O library developed by Gottfried Haider
|
||||
|
||||
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.*;
|
||||
|
||||
|
||||
/**
|
||||
* @webref
|
||||
*/
|
||||
public class SoftwareServo {
|
||||
|
||||
public static final int DEFAULT_MIN_PULSE = 544;
|
||||
public static final int DEFAULT_MAX_PULSE = 2400;
|
||||
|
||||
protected int pin = -1; // gpio number (-1 .. not attached)
|
||||
protected long handle = -1; // native thread id (<0 .. not started)
|
||||
protected int period = 20000; // 20 ms (50 Hz)
|
||||
protected int minPulse = 0; // minimum pulse width in microseconds
|
||||
protected int maxPulse = 0; // maximum pulse width in microseconds
|
||||
protected int pulse = 0; // current pulse in microseconds
|
||||
|
||||
|
||||
/**
|
||||
* Opens a servo motor
|
||||
* @param parent typically use "this"
|
||||
* @webref
|
||||
*/
|
||||
public SoftwareServo(PApplet parent) {
|
||||
NativeInterface.loadLibrary();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes a servo motor
|
||||
* @webref
|
||||
*/
|
||||
public void close() {
|
||||
detach();
|
||||
}
|
||||
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attaches a servo motor to a GPIO pin
|
||||
* @param pin GPIO pin
|
||||
* @webref
|
||||
*/
|
||||
public void attach(int pin) {
|
||||
detach();
|
||||
this.pin = pin;
|
||||
this.minPulse = DEFAULT_MIN_PULSE;
|
||||
this.maxPulse = DEFAULT_MAX_PULSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attaches a servo motor to a GPIO pin using custom pulse widths
|
||||
* @param minPulse minimum pulse width in microseconds (default: 544, same as on Arduino)
|
||||
* @param maxPulse maximum pulse width in microseconds (default: 2400, same as on Arduino)
|
||||
* @webref
|
||||
*/
|
||||
public void attach(int pin, int minPulse, int maxPulse) {
|
||||
detach();
|
||||
this.pin = pin;
|
||||
this.minPulse = minPulse;
|
||||
this.maxPulse = maxPulse;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves a servo motor to a given orientation
|
||||
* @param angle angle in degrees (controls speed and direction on continuous-rotation servos)
|
||||
* @webref
|
||||
*/
|
||||
public void write(float angle) {
|
||||
if (attached() == false) {
|
||||
System.err.println("You need to call attach(pin) before write(angle).");
|
||||
throw new RuntimeException("Servo is not attached");
|
||||
}
|
||||
|
||||
if (angle < 0 || 180 < angle) {
|
||||
System.err.println("Only degree values between 0 and 180 can be used.");
|
||||
throw new IllegalArgumentException("Illegal value");
|
||||
}
|
||||
pulse = (int)(minPulse + (angle/180.0) * (maxPulse-minPulse));
|
||||
|
||||
if (handle < 0) {
|
||||
// start a new thread
|
||||
GPIO.pinMode(pin, GPIO.OUTPUT);
|
||||
if (NativeInterface.isSimulated()) {
|
||||
return;
|
||||
}
|
||||
handle = NativeInterface.servoStartThread(pin, pulse, period);
|
||||
if (handle < 0) {
|
||||
throw new RuntimeException(NativeInterface.getError((int)handle));
|
||||
}
|
||||
} else {
|
||||
// thread already running
|
||||
int ret = NativeInterface.servoUpdateThread(handle, pulse, period);
|
||||
if (ret < 0) {
|
||||
throw new RuntimeException(NativeInterface.getError(ret));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a servo motor is attached to a pin
|
||||
* @return true if attached, false is not
|
||||
* @webref
|
||||
*/
|
||||
public boolean attached() {
|
||||
return (pin != -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detatches a servo motor from a GPIO pin
|
||||
* @webref
|
||||
*/
|
||||
public void detach() {
|
||||
if (0 <= handle) {
|
||||
// stop thread
|
||||
int ret = NativeInterface.servoStopThread(handle);
|
||||
GPIO.pinMode(pin, GPIO.INPUT);
|
||||
handle = -1;
|
||||
pin = -1;
|
||||
if (ret < 0) {
|
||||
throw new RuntimeException(NativeInterface.getError(ret));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||