Merge pull request #4384 from gohai/arm-next

Add a few IO library examples
This commit is contained in:
Ben Fry
2016-05-06 20:03:11 -04:00
14 changed files with 435 additions and 12 deletions
+42
View File
@@ -29,4 +29,46 @@
<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="&lt;html&gt;&lt;head&gt;&lt;meta http-equiv='refresh' content='0; url=libraries/io/index.html'&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;" />
<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,18 @@
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)
oled = new SSD1306(I2C.list()[0], 0x3c);
}
void draw() {
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 (brightness(img.pixels[y*img.width+x]) < 128) {
// 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();
}
}
@@ -22,9 +22,9 @@ 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);
leds[i].brightness(1.0);
} else {
leds[i].set(0.0);
leds[i].brightness(0.0);
}
}
println(frameCount);
@@ -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);
super.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 = super.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);
}
@@ -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;
}
+7
View File
@@ -1,2 +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
@@ -177,6 +177,10 @@ public class GPIO {
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);
@@ -222,6 +226,10 @@ public class GPIO {
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) {
@@ -278,6 +286,10 @@ public class GPIO {
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) {
@@ -325,6 +337,10 @@ public class GPIO {
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));
@@ -409,6 +425,10 @@ public class GPIO {
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) {
@@ -451,6 +471,14 @@ public class GPIO {
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) {
+37 -3
View File
@@ -50,6 +50,11 @@ public class I2C {
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));
@@ -81,6 +86,10 @@ public class I2C {
* @webref
*/
public void close() {
if (NativeInterface.isSimulated()) {
return;
}
NativeInterface.closeDevice(handle);
handle = 0;
}
@@ -107,6 +116,10 @@ public class I2C {
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;
@@ -126,6 +139,11 @@ public class I2C {
* @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();
@@ -159,6 +177,10 @@ public class I2C {
byte[] in = new byte[len];
if (NativeInterface.isSimulated()) {
return in;
}
int ret = NativeInterface.transferI2c(handle, slave, out, in);
transmitting = false;
out = null;
@@ -211,18 +233,30 @@ public class I2C {
/**
* Adds a byte to be written to the attached device
* @param out single byte to be written (0-255)
* @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 < 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.");
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);
}
}
+19 -1
View File
@@ -53,6 +53,10 @@ public class LED {
NativeInterface.loadLibrary();
this.dev = dev;
if (NativeInterface.isSimulated()) {
return;
}
// read maximum brightness
try {
Path path = Paths.get("/sys/class/leds/" + dev + "/max_brightness");
@@ -105,11 +109,16 @@ public class LED {
* @webref
*/
public void brightness(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");
}
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));
@@ -122,6 +131,10 @@ public class LED {
* @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));
@@ -143,6 +156,11 @@ public class LED {
* @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();
@@ -26,17 +26,28 @@ package processing.io;
public class NativeInterface {
protected static boolean loaded = false;
protected static boolean alwaysSimulate = 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");
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");
}
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);
@@ -57,6 +57,10 @@ public class PWM {
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));
@@ -89,6 +93,10 @@ public class PWM {
* @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) {
@@ -102,6 +110,10 @@ public class PWM {
* @webref
*/
public void close() {
if (NativeInterface.isSimulated()) {
return;
}
// XXX: implicit clear()?
// XXX: also check GPIO
@@ -124,6 +136,10 @@ public class PWM {
* @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();
@@ -155,6 +171,10 @@ public class PWM {
* @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
+32 -3
View File
@@ -78,6 +78,11 @@ public class SPI {
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));
@@ -90,6 +95,10 @@ public class SPI {
* @webref
*/
public void close() {
if (NativeInterface.isSimulated()) {
return;
}
NativeInterface.closeDevice(handle);
handle = 0;
}
@@ -110,6 +119,11 @@ public class SPI {
* @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();
@@ -148,6 +162,10 @@ public class SPI {
* @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))) {
@@ -182,16 +200,27 @@ public class SPI {
/**
* Transfers data over the SPI bus
* @param out single byte to send
* @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 < 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.");
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);
}
}