diff --git a/build/build.xml b/build/build.xml index 839ec6f169..1c70cf6fe9 100644 --- a/build/build.xml +++ b/build/build.xml @@ -45,6 +45,7 @@ + @@ -70,11 +71,11 @@ value="../../processing-docs/content/examples" /> - - + + - + @@ -798,11 +799,13 @@ + + - - diff --git a/core/src/processing/opengl/PGL.java b/core/src/processing/opengl/PGL.java index 73e519871a..ecfbb1715c 100644 --- a/core/src/processing/opengl/PGL.java +++ b/core/src/processing/opengl/PGL.java @@ -1287,9 +1287,9 @@ protected PGL initTex2DShader() { PGL ppgl = primaryPGL ? this : graphics.getPrimaryPGL(); if (!ppgl.loadedTex2DShader || ppgl.tex2DShaderContext != ppgl.glContext) { - String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion()); + String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion(), getGLSLVersionSuffix()); String vertSource = PApplet.join(preprocVertSrc, "\n"); - String[] preprocFragSrc = preprocessFragmentSource(tex2DFragShaderSource, getGLSLVersion()); + String[] preprocFragSrc = preprocessFragmentSource(tex2DFragShaderSource, getGLSLVersion(), getGLSLVersionSuffix()); String fragSource = PApplet.join(preprocFragSrc, "\n"); ppgl.tex2DVertShader = createShader(VERTEX_SHADER, vertSource); ppgl.tex2DFragShader = createShader(FRAGMENT_SHADER, fragSource); @@ -1419,9 +1419,9 @@ protected PGL initTexRectShader() { PGL ppgl = primaryPGL ? this : graphics.getPrimaryPGL(); if (!ppgl.loadedTexRectShader || ppgl.texRectShaderContext != ppgl.glContext) { - String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion()); + String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion(), getGLSLVersionSuffix()); String vertSource = PApplet.join(preprocVertSrc, "\n"); - String[] preprocFragSrc = preprocessFragmentSource(texRectFragShaderSource, getGLSLVersion()); + String[] preprocFragSrc = preprocessFragmentSource(texRectFragShaderSource, getGLSLVersion(), getGLSLVersionSuffix()); String fragSource = PApplet.join(preprocFragSrc, "\n"); ppgl.texRectVertShader = createShader(VERTEX_SHADER, vertSource); ppgl.texRectFragShader = createShader(FRAGMENT_SHADER, fragSource); @@ -1848,6 +1848,7 @@ protected static int qualityToSamples(int quality) { abstract protected int getGLSLVersion(); + abstract protected String getGLSLVersionSuffix(); protected String[] loadVertexShader(String filename) { @@ -1880,28 +1881,29 @@ protected String[] loadVertexShader(URL url) { } - protected String[] loadVertexShader(String filename, int version) { + protected String[] loadVertexShader(String filename, int version, String versionSuffix) { return loadVertexShader(filename); } - protected String[] loadFragmentShader(String filename, int version) { + protected String[] loadFragmentShader(String filename, int version, String versionSuffix) { return loadFragmentShader(filename); } - protected String[] loadFragmentShader(URL url, int version) { + protected String[] loadFragmentShader(URL url, int version, String versionSuffix) { return loadFragmentShader(url); } - protected String[] loadVertexShader(URL url, int version) { + protected String[] loadVertexShader(URL url, int version, String versionSuffix) { return loadVertexShader(url); } protected static String[] preprocessFragmentSource(String[] fragSrc0, - int version) { + int version, + String versionSuffix) { if (containsVersionDirective(fragSrc0)) { // The user knows what she or he is doing return fragSrc0; @@ -1915,7 +1917,7 @@ protected static String[] preprocessFragmentSource(String[] fragSrc0, int offset = 1; fragSrc = preprocessShaderSource(fragSrc0, search, replace, offset); - fragSrc[0] = "#version " + version; + fragSrc[0] = "#version " + version + versionSuffix; } else { // We need to replace 'texture' uniform by 'texMap' uniform and // 'textureXXX()' functions by 'texture()' functions. Order of these @@ -1932,15 +1934,20 @@ protected static String[] preprocessFragmentSource(String[] fragSrc0, int offset = 2; fragSrc = preprocessShaderSource(fragSrc0, search, replace, offset); - fragSrc[0] = "#version " + version; - fragSrc[1] = "out vec4 _fragColor;"; + fragSrc[0] = "#version " + version + versionSuffix; + if (" es".equals(versionSuffix)) { + fragSrc[1] = "out mediump vec4 _fragColor;"; + } else { + fragSrc[1] = "out vec4 _fragColor;"; + } } return fragSrc; } protected static String[] preprocessVertexSource(String[] vertSrc0, - int version) { + int version, + String versionSuffix) { if (containsVersionDirective(vertSrc0)) { // The user knows what she or he is doing return vertSrc0; @@ -1954,7 +1961,7 @@ protected static String[] preprocessVertexSource(String[] vertSrc0, int offset = 1; vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); - vertSrc[0] = "#version " + version; + vertSrc[0] = "#version " + version + versionSuffix; } else { // We need to replace 'texture' uniform by 'texMap' uniform and // 'textureXXX()' functions by 'texture()' functions. Order of these @@ -1971,7 +1978,7 @@ protected static String[] preprocessVertexSource(String[] vertSrc0, int offset = 1; vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); - vertSrc[0] = "#version " + version; + vertSrc[0] = "#version " + version + versionSuffix; } return vertSrc; @@ -2225,7 +2232,7 @@ protected boolean hasPackedDepthStencilSupport() { protected boolean hasAnisoSamplingSupport() { int major = getGLVersion()[0]; - if (major < 3) { + if (isES() || major < 3) { String ext = getString(EXTENSIONS); return -1 < ext.indexOf("_texture_filter_anisotropic"); } else { diff --git a/core/src/processing/opengl/PGraphicsOpenGL.java b/core/src/processing/opengl/PGraphicsOpenGL.java index f2e64514af..6b0f42c635 100644 --- a/core/src/processing/opengl/PGraphicsOpenGL.java +++ b/core/src/processing/opengl/PGraphicsOpenGL.java @@ -1561,13 +1561,14 @@ protected void restoreGL() { } pgl.depthFunc(PGL.LEQUAL); - if (OPENGL_RENDERER.equals("VideoCore IV HW")) { - // Broadcom's VC IV driver is unhappy with either of these - // ignore for now + if (pgl.isES()) { + // neither GL_MULTISAMPLE nor GL_POLYGON_SMOOTH are part of GLES2 or GLES3 } else if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); } else if (1 <= smooth) { pgl.enable(PGL.MULTISAMPLE); + } + if (!pgl.isES()) { pgl.disable(PGL.POLYGON_SMOOTH); } @@ -6775,16 +6776,14 @@ protected void setGLSettings() { // quality = temp; // } } - if (OPENGL_RENDERER.equals("VideoCore IV HW")) { - // Broadcom's VC IV driver is unhappy with either of these - // ignore for now + if (pgl.isES()) { + // neither GL_MULTISAMPLE nor GL_POLYGON_SMOOTH are part of GLES2 or GLES3 } else if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); } else if (1 <= smooth) { pgl.enable(PGL.MULTISAMPLE); } - // work around runtime exceptions in Broadcom's VC IV driver - if (false == OPENGL_RENDERER.equals("VideoCore IV HW")) { + if (!pgl.isES()) { pgl.disable(PGL.POLYGON_SMOOTH); } @@ -6895,8 +6894,12 @@ protected void getGLParameters() { // overwrite the default shaders with vendor specific versions // if needed - if (OPENGL_RENDERER.equals("VideoCore IV HW") || // Broadcom's binary driver for Raspberry Pi - OPENGL_RENDERER.contains("VC4")) { // Mesa driver for same hardware + if (OPENGL_RENDERER.equals("VideoCore IV HW")) { // Broadcom's binary driver for Raspberry Pi + defLightShaderVertURL = + PGraphicsOpenGL.class.getResource("/processing/opengl/shaders/LightVert-brcm.glsl"); + defTexlightShaderVertURL = + PGraphicsOpenGL.class.getResource("/processing/opengl/shaders/TexLightVert-brcm.glsl"); + } else if (OPENGL_RENDERER.contains("VC4")) { // Mesa driver for same hardware defLightShaderVertURL = PGraphicsOpenGL.class.getResource("/processing/opengl/shaders/LightVert-vc4.glsl"); defTexlightShaderVertURL = diff --git a/core/src/processing/opengl/PJOGL.java b/core/src/processing/opengl/PJOGL.java index 58f7618d9d..31b5669f73 100644 --- a/core/src/processing/opengl/PJOGL.java +++ b/core/src/processing/opengl/PJOGL.java @@ -509,50 +509,59 @@ protected int getGLSLVersion() { return vn.getMajor() * 100 + vn.getMinor(); } + protected String getGLSLVersionSuffix() { + VersionNumber vn = context.getGLSLVersionNumber(); + if (context.isGLESProfile() && 1 < vn.getMajor()) { + return " es"; + } else { + return ""; + } + } + @Override protected String[] loadVertexShader(String filename) { - return loadVertexShader(filename, getGLSLVersion()); + return loadVertexShader(filename, getGLSLVersion(), getGLSLVersionSuffix()); } @Override protected String[] loadFragmentShader(String filename) { - return loadFragmentShader(filename, getGLSLVersion()); + return loadFragmentShader(filename, getGLSLVersion(), getGLSLVersionSuffix()); } @Override protected String[] loadVertexShader(URL url) { - return loadVertexShader(url, getGLSLVersion()); + return loadVertexShader(url, getGLSLVersion(), getGLSLVersionSuffix()); } @Override protected String[] loadFragmentShader(URL url) { - return loadFragmentShader(url, getGLSLVersion()); + return loadFragmentShader(url, getGLSLVersion(), getGLSLVersionSuffix()); } @Override - protected String[] loadFragmentShader(String filename, int version) { + protected String[] loadFragmentShader(String filename, int version, String versionSuffix) { String[] fragSrc0 = sketch.loadStrings(filename); - return preprocessFragmentSource(fragSrc0, version); + return preprocessFragmentSource(fragSrc0, version, versionSuffix); } @Override - protected String[] loadVertexShader(String filename, int version) { + protected String[] loadVertexShader(String filename, int version, String versionSuffix) { String[] vertSrc0 = sketch.loadStrings(filename); - return preprocessVertexSource(vertSrc0, version); + return preprocessVertexSource(vertSrc0, version, versionSuffix); } @Override - protected String[] loadFragmentShader(URL url, int version) { + protected String[] loadFragmentShader(URL url, int version, String versionSuffix) { try { String[] fragSrc0 = PApplet.loadStrings(url.openStream()); - return preprocessFragmentSource(fragSrc0, version); + return preprocessFragmentSource(fragSrc0, version, versionSuffix); } catch (IOException e) { PGraphics.showException("Cannot load fragment shader " + url.getFile()); } @@ -561,10 +570,10 @@ protected String[] loadFragmentShader(URL url, int version) { @Override - protected String[] loadVertexShader(URL url, int version) { + protected String[] loadVertexShader(URL url, int version, String versionSuffix) { try { String[] vertSrc0 = PApplet.loadStrings(url.openStream()); - return preprocessVertexSource(vertSrc0, version); + return preprocessVertexSource(vertSrc0, version, versionSuffix); } catch (IOException e) { PGraphics.showException("Cannot load vertex shader " + url.getFile()); } @@ -1926,6 +1935,8 @@ public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX gl2x.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } else if (gl3 != null) { gl3.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } else if (gl3es3 != null) { + gl3es3.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } else { throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glBlitFramebuffer()")); } @@ -1937,6 +1948,8 @@ public void renderbufferStorageMultisample(int target, int samples, int format, gl2x.glRenderbufferStorageMultisample(target, samples, format, width, height); } else if (gl3 != null) { gl3.glRenderbufferStorageMultisample(target, samples, format, width, height); + } else if (gl3es3 != null) { + gl3es3.glRenderbufferStorageMultisample(target, samples, format, width, height); } else { throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glRenderbufferStorageMultisample()")); } @@ -1948,6 +1961,8 @@ public void readBuffer(int buf) { gl2x.glReadBuffer(buf); } else if (gl3 != null) { gl3.glReadBuffer(buf); + } else if (gl3es3 != null) { + gl3es3.glReadBuffer(buf); } else { throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glReadBuffer()")); } @@ -1959,6 +1974,11 @@ public void drawBuffer(int buf) { gl2x.glDrawBuffer(buf); } else if (gl3 != null) { gl3.glDrawBuffer(buf); + } else if (gl3es3 != null) { + IntBuffer intBuffer = IntBuffer.allocate(1); + intBuffer.put(buf); + intBuffer.rewind(); + gl3es3.glDrawBuffers(1, intBuffer); } else { throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glDrawBuffer()")); } diff --git a/core/src/processing/opengl/shaders/LightVert-brcm.glsl b/core/src/processing/opengl/shaders/LightVert-brcm.glsl new file mode 100644 index 0000000000..b96caa4b3b --- /dev/null +++ b/core/src/processing/opengl/shaders/LightVert-brcm.glsl @@ -0,0 +1,154 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + 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, version 2.1. + + 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 +*/ + +uniform mat4 modelviewMatrix; +uniform mat4 transformMatrix; +uniform mat3 normalMatrix; + +uniform int lightCount; +uniform vec4 lightPosition[8]; +uniform vec3 lightNormal[8]; +uniform vec3 lightAmbient[8]; +uniform vec3 lightDiffuse[8]; +uniform vec3 lightSpecular[8]; +uniform vec3 lightFalloff[8]; +uniform vec2 lightSpot[8]; + +attribute vec4 position; +attribute vec4 color; +attribute vec3 normal; + +attribute vec4 ambient; +attribute vec4 specular; +attribute vec4 emissive; +attribute float shininess; + +varying vec4 vertColor; +varying vec4 backVertColor; + +const float zero_float = 0.0; +const float one_float = 1.0; +const vec3 zero_vec3 = vec3(0.0); +const vec3 minus_one_vec3 = vec3(0.0-1.0); + +float falloffFactor(vec3 lightPos, vec3 vertPos, vec3 coeff) { + vec3 lpv = lightPos - vertPos; + vec3 dist = vec3(one_float); + dist.z = dot(lpv, lpv); + dist.y = sqrt(dist.z); + return one_float / dot(dist, coeff); +} + +float spotFactor(vec3 lightPos, vec3 vertPos, vec3 lightNorm, float minCos, float spotExp) { + vec3 lpv = normalize(lightPos - vertPos); + vec3 nln = minus_one_vec3 * lightNorm; + float spotCos = dot(nln, lpv); + return spotCos <= minCos ? zero_float : pow(spotCos, spotExp); +} + +float lambertFactor(vec3 lightDir, vec3 vecNormal) { + return max(zero_float, dot(lightDir, vecNormal)); +} + +float blinnPhongFactor(vec3 lightDir, vec3 vertPos, vec3 vecNormal, float shine) { + vec3 np = normalize(vertPos); + vec3 ldp = normalize(lightDir - np); + return pow(max(zero_float, dot(ldp, vecNormal)), shine); +} + +void main() { + // Vertex in clip coordinates + gl_Position = transformMatrix * position; + + // Vertex in eye coordinates + vec3 ecVertex = vec3(modelviewMatrix * position); + + // Normal vector in eye coordinates + vec3 ecNormal = normalize(normalMatrix * normal); + vec3 ecNormalInv = ecNormal * minus_one_vec3; + + // Light calculations + vec3 totalAmbient = vec3(0, 0, 0); + + vec3 totalFrontDiffuse = vec3(0, 0, 0); + vec3 totalFrontSpecular = vec3(0, 0, 0); + + vec3 totalBackDiffuse = vec3(0, 0, 0); + vec3 totalBackSpecular = vec3(0, 0, 0); + + // prevent register allocation failure by limiting ourselves to + // two lights for now + for (int i = 0; i < 2; i++) { + if (lightCount == i) break; + + vec3 lightPos = lightPosition[i].xyz; + bool isDir = lightPosition[i].w < one_float; + float spotCos = lightSpot[i].x; + float spotExp = lightSpot[i].y; + + vec3 lightDir; + float falloff; + float spotf; + + if (isDir) { + falloff = one_float; + lightDir = minus_one_vec3 * lightNormal[i]; + } else { + falloff = falloffFactor(lightPos, ecVertex, lightFalloff[i]); + lightDir = normalize(lightPos - ecVertex); + } + + spotf = spotExp > zero_float ? spotFactor(lightPos, ecVertex, lightNormal[i], + spotCos, spotExp) + : one_float; + + if (any(greaterThan(lightAmbient[i], zero_vec3))) { + totalAmbient += lightAmbient[i] * falloff; + } + + if (any(greaterThan(lightDiffuse[i], zero_vec3))) { + totalFrontDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormal); + totalBackDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormalInv); + } + + if (any(greaterThan(lightSpecular[i], zero_vec3))) { + totalFrontSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormal, shininess); + totalBackSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormalInv, shininess); + } + } + + // Calculating final color as result of all lights (plus emissive term). + // Transparency is determined exclusively by the diffuse component. + vertColor = vec4(totalAmbient, 0) * ambient + + vec4(totalFrontDiffuse, 1) * color + + vec4(totalFrontSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + backVertColor = vec4(totalAmbient, 0) * ambient + + vec4(totalBackDiffuse, 1) * color + + vec4(totalBackSpecular, 0) * specular + + vec4(emissive.rgb, 0); +} \ No newline at end of file diff --git a/core/src/processing/opengl/shaders/LightVert-vc4.glsl b/core/src/processing/opengl/shaders/LightVert-vc4.glsl index b96caa4b3b..ba79726767 100644 --- a/core/src/processing/opengl/shaders/LightVert-vc4.glsl +++ b/core/src/processing/opengl/shaders/LightVert-vc4.glsl @@ -96,8 +96,8 @@ void main() { vec3 totalBackSpecular = vec3(0, 0, 0); // prevent register allocation failure by limiting ourselves to - // two lights for now - for (int i = 0; i < 2; i++) { + // four lights for now + for (int i = 0; i < 4; i++) { if (lightCount == i) break; vec3 lightPos = lightPosition[i].xyz; diff --git a/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl b/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl new file mode 100644 index 0000000000..51e88ab056 --- /dev/null +++ b/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl @@ -0,0 +1,160 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + 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, version 2.1. + + 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 +*/ + +uniform mat4 modelviewMatrix; +uniform mat4 transformMatrix; +uniform mat3 normalMatrix; +uniform mat4 texMatrix; + +uniform int lightCount; +uniform vec4 lightPosition[8]; +uniform vec3 lightNormal[8]; +uniform vec3 lightAmbient[8]; +uniform vec3 lightDiffuse[8]; +uniform vec3 lightSpecular[8]; +uniform vec3 lightFalloff[8]; +uniform vec2 lightSpot[8]; + +attribute vec4 position; +attribute vec4 color; +attribute vec3 normal; +attribute vec2 texCoord; + +attribute vec4 ambient; +attribute vec4 specular; +attribute vec4 emissive; +attribute float shininess; + +varying vec4 vertColor; +varying vec4 backVertColor; +varying vec4 vertTexCoord; + +const float zero_float = 0.0; +const float one_float = 1.0; +const vec3 zero_vec3 = vec3(0.0); +const vec3 minus_one_vec3 = vec3(0.0-1.0); + +float falloffFactor(vec3 lightPos, vec3 vertPos, vec3 coeff) { + vec3 lpv = lightPos - vertPos; + vec3 dist = vec3(one_float); + dist.z = dot(lpv, lpv); + dist.y = sqrt(dist.z); + return one_float / dot(dist, coeff); +} + +float spotFactor(vec3 lightPos, vec3 vertPos, vec3 lightNorm, float minCos, float spotExp) { + vec3 lpv = normalize(lightPos - vertPos); + vec3 nln = minus_one_vec3 * lightNorm; + float spotCos = dot(nln, lpv); + return spotCos <= minCos ? zero_float : pow(spotCos, spotExp); +} + +float lambertFactor(vec3 lightDir, vec3 vecNormal) { + return max(zero_float, dot(lightDir, vecNormal)); +} + +float blinnPhongFactor(vec3 lightDir, vec3 vertPos, vec3 vecNormal, float shine) { + vec3 np = normalize(vertPos); + vec3 ldp = normalize(lightDir - np); + return pow(max(zero_float, dot(ldp, vecNormal)), shine); +} + +void main() { + // Vertex in clip coordinates + gl_Position = transformMatrix * position; + + // Vertex in eye coordinates + vec3 ecVertex = vec3(modelviewMatrix * position); + + // Normal vector in eye coordinates + vec3 ecNormal = normalize(normalMatrix * normal); + vec3 ecNormalInv = ecNormal * minus_one_vec3; + + // Light calculations + vec3 totalAmbient = vec3(0, 0, 0); + + vec3 totalFrontDiffuse = vec3(0, 0, 0); + vec3 totalFrontSpecular = vec3(0, 0, 0); + + vec3 totalBackDiffuse = vec3(0, 0, 0); + vec3 totalBackSpecular = vec3(0, 0, 0); + + // prevent register allocation failure by limiting ourselves to + // two lights for now + for (int i = 0; i < 2; i++) { + if (lightCount == i) break; + + vec3 lightPos = lightPosition[i].xyz; + bool isDir = lightPosition[i].w < one_float; + float spotCos = lightSpot[i].x; + float spotExp = lightSpot[i].y; + + vec3 lightDir; + float falloff; + float spotf; + + if (isDir) { + falloff = one_float; + lightDir = minus_one_vec3 * lightNormal[i]; + } else { + falloff = falloffFactor(lightPos, ecVertex, lightFalloff[i]); + lightDir = normalize(lightPos - ecVertex); + } + + spotf = spotExp > zero_float ? spotFactor(lightPos, ecVertex, lightNormal[i], + spotCos, spotExp) + : one_float; + + if (any(greaterThan(lightAmbient[i], zero_vec3))) { + totalAmbient += lightAmbient[i] * falloff; + } + + if (any(greaterThan(lightDiffuse[i], zero_vec3))) { + totalFrontDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormal); + totalBackDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormalInv); + } + + if (any(greaterThan(lightSpecular[i], zero_vec3))) { + totalFrontSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormal, shininess); + totalBackSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormalInv, shininess); + } + } + + // Calculating final color as result of all lights (plus emissive term). + // Transparency is determined exclusively by the diffuse component. + vertColor = vec4(totalAmbient, 0) * ambient + + vec4(totalFrontDiffuse, 1) * color + + vec4(totalFrontSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + backVertColor = vec4(totalAmbient, 0) * ambient + + vec4(totalBackDiffuse, 1) * color + + vec4(totalBackSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + // Calculating texture coordinates, with r and q set both to one + vertTexCoord = texMatrix * vec4(texCoord, 1.0, 1.0); +} diff --git a/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl b/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl index 51e88ab056..f54277e812 100644 --- a/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl +++ b/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl @@ -99,8 +99,8 @@ void main() { vec3 totalBackSpecular = vec3(0, 0, 0); // prevent register allocation failure by limiting ourselves to - // two lights for now - for (int i = 0; i < 2; i++) { + // four lights for now + for (int i = 0; i < 4; i++) { if (lightCount == i) break; vec3 lightPos = lightPosition[i].xyz; diff --git a/java/application/launch4j/bin/ld-linux-armv6hf b/java/application/launch4j/bin/ld-linux-armv6hf new file mode 100755 index 0000000000..032cd917a8 Binary files /dev/null and b/java/application/launch4j/bin/ld-linux-armv6hf differ diff --git a/java/application/launch4j/bin/windres-linux-armv6hf b/java/application/launch4j/bin/windres-linux-armv6hf new file mode 100755 index 0000000000..41b2223800 Binary files /dev/null and b/java/application/launch4j/bin/windres-linux-armv6hf differ diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP/SPIAnalogDigitalOOP.pde b/java/libraries/io/examples/AnalogDigital_SPI_MCP3001/AnalogDigital_SPI_MCP3001.pde similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP/SPIAnalogDigitalOOP.pde rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3001/AnalogDigital_SPI_MCP3001.pde diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP/MCP3001.pde b/java/libraries/io/examples/AnalogDigital_SPI_MCP3001/MCP3001.pde similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP/MCP3001.pde rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3001/MCP3001.pde diff --git a/java/libraries/io/examples/SPIAnalogDigital/setup.png b/java/libraries/io/examples/AnalogDigital_SPI_MCP3001/setup.png similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigital/setup.png rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3001/setup.png diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP8/SPIAnalogDigitalOOP8.pde b/java/libraries/io/examples/AnalogDigital_SPI_MCP3008/AnalogDigital_SPI_MCP3008.pde similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP8/SPIAnalogDigitalOOP8.pde rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3008/AnalogDigital_SPI_MCP3008.pde diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP8/MCP3008.pde b/java/libraries/io/examples/AnalogDigital_SPI_MCP3008/MCP3008.pde similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP8/MCP3008.pde rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3008/MCP3008.pde diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP8/setup.png b/java/libraries/io/examples/AnalogDigital_SPI_MCP3008/setup.png similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP8/setup.png rename to java/libraries/io/examples/AnalogDigital_SPI_MCP3008/setup.png diff --git a/java/libraries/io/examples/Compass_I2C_HMC6352/Compass_I2C_HMC6352.pde b/java/libraries/io/examples/Compass_I2C_HMC6352/Compass_I2C_HMC6352.pde new file mode 100644 index 0000000000..921fe47cb4 --- /dev/null +++ b/java/libraries/io/examples/Compass_I2C_HMC6352/Compass_I2C_HMC6352.pde @@ -0,0 +1,19 @@ +import processing.io.*; +HMC6352 compass; + +// see setup.png in the sketch folder for wiring details + +void setup() { + // the module's I2C address can be changed by modifying values in its EEPROM + // 0x21 is however the default address + + //printArray(I2C.list()); + compass = new HMC6352("i2c-1", 0x21); +} + +void draw() { + background(255); + float deg = compass.heading(); + println(deg + " degrees"); + line(width/2, height/2, width/2+sin(radians(deg))*width/2, height/2-cos(radians(deg))*height/2); +} diff --git a/java/libraries/io/examples/Compass_I2C_HMC6352/HMC6352.pde b/java/libraries/io/examples/Compass_I2C_HMC6352/HMC6352.pde new file mode 100644 index 0000000000..ff925ac3a5 --- /dev/null +++ b/java/libraries/io/examples/Compass_I2C_HMC6352/HMC6352.pde @@ -0,0 +1,38 @@ +import processing.io.I2C; + +// HMC6352 is a digital compass using I2C +// datasheet: https://www.sparkfun.com/datasheets/Components/HMC6352.pdf + +class HMC6352 extends I2C { + int address; + + HMC6352(String dev, int address) { + super(dev); + this.address = address; + setHeadingMode(); + } + + void setHeadingMode() { + beginTransmission(address); + // command byte for writing to EEPROM + write(0x77); + // address of the output data control byte + write(0x4e); + // give us the plain heading + write(0x00); + endTransmission(); + } + + float heading() { + beginTransmission(address); + // command byte for reading the data + write(0x41); + byte[] in = read(2); + 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; + } +} diff --git a/java/libraries/io/examples/I2CCompass/setup.png b/java/libraries/io/examples/Compass_I2C_HMC6352/setup.png similarity index 100% rename from java/libraries/io/examples/I2CCompass/setup.png rename to java/libraries/io/examples/Compass_I2C_HMC6352/setup.png diff --git a/java/libraries/io/examples/I2CDigitalAnalogOOP/I2CDigitalAnalogOOP.pde b/java/libraries/io/examples/DigitalAnalog_I2C_MCP4725/DigitalAnalog_I2C_MCP4725.pde similarity index 100% rename from java/libraries/io/examples/I2CDigitalAnalogOOP/I2CDigitalAnalogOOP.pde rename to java/libraries/io/examples/DigitalAnalog_I2C_MCP4725/DigitalAnalog_I2C_MCP4725.pde diff --git a/java/libraries/io/examples/I2CDigitalAnalogOOP/MCP4725.pde b/java/libraries/io/examples/DigitalAnalog_I2C_MCP4725/MCP4725.pde similarity index 100% rename from java/libraries/io/examples/I2CDigitalAnalogOOP/MCP4725.pde rename to java/libraries/io/examples/DigitalAnalog_I2C_MCP4725/MCP4725.pde diff --git a/java/libraries/io/examples/I2CScreen/I2CScreen.pde b/java/libraries/io/examples/Display_I2C_SSD1306/Display_I2C_SSD1306.pde similarity index 100% rename from java/libraries/io/examples/I2CScreen/I2CScreen.pde rename to java/libraries/io/examples/Display_I2C_SSD1306/Display_I2C_SSD1306.pde diff --git a/java/libraries/io/examples/I2CScreen/SSD1306.pde b/java/libraries/io/examples/Display_I2C_SSD1306/SSD1306.pde similarity index 100% rename from java/libraries/io/examples/I2CScreen/SSD1306.pde rename to java/libraries/io/examples/Display_I2C_SSD1306/SSD1306.pde diff --git a/java/libraries/io/examples/Environment_I2C_BME280/BME280.pde b/java/libraries/io/examples/Environment_I2C_BME280/BME280.pde new file mode 100644 index 0000000000..572e6d7013 --- /dev/null +++ b/java/libraries/io/examples/Environment_I2C_BME280/BME280.pde @@ -0,0 +1,407 @@ +import processing.io.I2C; + +// BME280 is an integrated environmental sensor +// It can measure temperature, pressure and humidity +// datasheet: https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf +// code contributed by @OlivierLD + +public class BME280 extends I2C { + + public final static int BME280_I2CADDR = 0x77; // this is the default I2C address + public final static int DEFAULT_ADDR = BME280_I2CADDR; + + // Operating Modes + public final static int BME280_OSAMPLE_1 = 1; + public final static int BME280_OSAMPLE_2 = 2; + public final static int BME280_OSAMPLE_4 = 3; + public final static int BME280_OSAMPLE_8 = 4; + public final static int BME280_OSAMPLE_16 = 5; + + // BME280 Registers + public final static int BME280_REGISTER_DIG_T1 = 0x88; // Trimming parameter registers + public final static int BME280_REGISTER_DIG_T2 = 0x8A; + public final static int BME280_REGISTER_DIG_T3 = 0x8C; + + public final static int BME280_REGISTER_DIG_P1 = 0x8E; + public final static int BME280_REGISTER_DIG_P2 = 0x90; + public final static int BME280_REGISTER_DIG_P3 = 0x92; + public final static int BME280_REGISTER_DIG_P4 = 0x94; + public final static int BME280_REGISTER_DIG_P5 = 0x96; + public final static int BME280_REGISTER_DIG_P6 = 0x98; + public final static int BME280_REGISTER_DIG_P7 = 0x9A; + public final static int BME280_REGISTER_DIG_P8 = 0x9C; + public final static int BME280_REGISTER_DIG_P9 = 0x9E; + + public final static int BME280_REGISTER_DIG_H1 = 0xA1; + public final static int BME280_REGISTER_DIG_H2 = 0xE1; + public final static int BME280_REGISTER_DIG_H3 = 0xE3; + public final static int BME280_REGISTER_DIG_H4 = 0xE4; + public final static int BME280_REGISTER_DIG_H5 = 0xE5; + public final static int BME280_REGISTER_DIG_H6 = 0xE6; + public final static int BME280_REGISTER_DIG_H7 = 0xE7; + + public final static int BME280_REGISTER_CHIPID = 0xD0; + public final static int BME280_REGISTER_VERSION = 0xD1; + public final static int BME280_REGISTER_SOFTRESET = 0xE0; + + public final static int BME280_REGISTER_CONTROL_HUM = 0xF2; + public final static int BME280_REGISTER_CONTROL = 0xF4; + public final static int BME280_REGISTER_CONFIG = 0xF5; + public final static int BME280_REGISTER_PRESSURE_DATA = 0xF7; + public final static int BME280_REGISTER_TEMP_DATA = 0xFA; + public final static int BME280_REGISTER_HUMIDITY_DATA = 0xFD; + + private int dig_T1 = 0; + private int dig_T2 = 0; + private int dig_T3 = 0; + + private int dig_P1 = 0; + private int dig_P2 = 0; + private int dig_P3 = 0; + private int dig_P4 = 0; + private int dig_P5 = 0; + private int dig_P6 = 0; + private int dig_P7 = 0; + private int dig_P8 = 0; + private int dig_P9 = 0; + + private int dig_H1 = 0; + private int dig_H2 = 0; + private int dig_H3 = 0; + private int dig_H4 = 0; + private int dig_H5 = 0; + private int dig_H6 = 0; + + private float tFine = 0.0f; + + private int address; + private int mode = BME280_OSAMPLE_8; + private float standardSeaLevelPressure = 101325.0f; // in Pa (1013.25 hPa) + + protected float temp = 0.0f; // most recent sensor readings, set by update() + protected float press = 0.0f; + protected float hum = 0.0f; + + + public BME280(String dev) { + this(dev, DEFAULT_ADDR); + } + + public BME280(String dev, int address) { + super(dev); + this.address = address; + + // Soft reset + command(BME280_REGISTER_SOFTRESET, (byte)0xB6); + // Wait for the chip to wake up + delay(300); + + try { + readCalibrationData(); + // showCalibrationData(); + } catch (Exception ex) { + ex.printStackTrace(); + } + + command(BME280_REGISTER_CONTROL, (byte)0x3F); + tFine = 0.0f; + } + + + /** + * Read and update all sensors values + */ + public void update() { + // The order used to read the data is important! + // 1.temperature, 2.pressure (analog to altitude), 3.humidity. + + try { + temp = readTemperature(); + } catch (Exception ex) { + System.err.println(ex.getMessage()); + ex.printStackTrace(); + } + + try { + press = readPressure(); + } catch (Exception ex) { + System.err.println(ex.getMessage()); + ex.printStackTrace(); + } + + try { + hum = readHumidity(); + } catch (Exception ex) { + System.err.println(ex.getMessage()); + ex.printStackTrace(); + } + } + + /** + * Returns the temperature in degrees celsius + */ + public float temperature() { + return temp; + } + + /** + * Returns the pressure in Pa + */ + public float pressure() { + return press; + } + + /** + * Returns the altitude in meters + * @param pressure as returned by pressure() + */ + public float altitude(float pressure) { + double altitude = 0.0; + if (standardSeaLevelPressure != 0) { + altitude = 44330.0 * (1.0 - Math.pow(pressure / standardSeaLevelPressure, 0.1903)); + } + return (float)altitude; + } + + /** + * Returns the altitude in meters + * @param pressure as returned by pressure() in Pa + * @param temperature as returned by temperature() in Celcius + */ + public float altitude(float pressure, float temperature) { + double altitude = 0.0; + if (standardSeaLevelPressure != 0) { + altitude = ((Math.pow(standardSeaLevelPressure / pressure, 1 / 5.257) - 1) * (temperature + 273.25)) / 0.0065; + } + return (float)altitude; + } + + /** + * Returns the humidity in percent + */ + public float humidity() { + return hum; + } + + /** + * Set the standard sea level pressure used for calculating altitude() + * Defaults to 101325 Pa (1013.25 hPa) + */ + public void setStandardSeaLevelPressure(float pressure) { + standardSeaLevelPressure = pressure; + } + + + protected float readTemperature() { + // Returns the compensated temperature in degrees celcius + float UT = readRawTemp(); + float var1 = 0.0f; + float var2 = 0.0f; + float temp = 0.0f; + + // Read raw temp before aligning it with the calibration values + var1 = (UT / 16384.0f - dig_T1 / 1024.0f) * (float) dig_T2; + var2 = ((UT / 131072.0f - dig_T1 / 8192.0f) * (UT / 131072.0f - dig_T1 / 8192.0f)) * (float) dig_T3; + tFine = (int) (var1 + var2); + temp = (var1 + var2) / 5120.0f; + // println("DBG: Calibrated temperature = " + temp + " C"); + return temp; + } + + protected float readPressure() { + // Returns the compensated pressure in Pascal + int adc = readRawPressure(); + // println("ADC:" + adc + ", tFine:" + tFine); + float var1 = (tFine / 2.0f) - 64000.0f; + float var2 = var1 * var1 * (dig_P6 / 32768.0f); + var2 = var2 + var1 * dig_P5 * 2.0f; + var2 = (var2 / 4.0f) + (dig_P4 * 65536.0f); + var1 = (dig_P3 * var1 * var1 / 524288.0f + dig_P2 * var1) / 524288.0f; + var1 = (1.0f + var1 / 32768.0f) * dig_P1; + if (var1 == 0f) { + return 0.0f; + } + float p = 1048576.0f - adc; + p = ((p - var2 / 4096.0f) * 6250.0f) / var1; + var1 = dig_P9 * p * p / 2147483648.0f; + var2 = p * dig_P8 / 32768.0f; + p = p + (var1 + var2 + dig_P7) / 16.0f; + // println("DBG: Pressure = " + p + " Pa"); + return p; + } + + protected float readHumidity() { + // Returns the compensated humidity in percent + int adc = readRawHumidity(); + float h = tFine - 76800.0f; + h = (adc - (dig_H4 * 64.0f + dig_H5 / 16384.8f * h)) * + (dig_H2 / 65536.0f * (1.0f + dig_H6 / 67108864.0f * h * (1.0f + dig_H3 / 67108864.0f * h))); + h = h * (1.0f - dig_H1 * h / 524288.0f); + if (h > 100) { + h = 100; + } else if (h < 0) { + h = 0; + } + // println("DBG: Humidity = " + h); + return h; + } + + + private void readCalibrationData() { + // Reads the calibration data from the IC + dig_T1 = readU16LE(BME280_REGISTER_DIG_T1); + dig_T2 = readS16LE(BME280_REGISTER_DIG_T2); + dig_T3 = readS16LE(BME280_REGISTER_DIG_T3); + + dig_P1 = readU16LE(BME280_REGISTER_DIG_P1); + dig_P2 = readS16LE(BME280_REGISTER_DIG_P2); + dig_P3 = readS16LE(BME280_REGISTER_DIG_P3); + dig_P4 = readS16LE(BME280_REGISTER_DIG_P4); + dig_P5 = readS16LE(BME280_REGISTER_DIG_P5); + dig_P6 = readS16LE(BME280_REGISTER_DIG_P6); + dig_P7 = readS16LE(BME280_REGISTER_DIG_P7); + dig_P8 = readS16LE(BME280_REGISTER_DIG_P8); + dig_P9 = readS16LE(BME280_REGISTER_DIG_P9); + + dig_H1 = readU8(BME280_REGISTER_DIG_H1); + dig_H2 = readS16LE(BME280_REGISTER_DIG_H2); + dig_H3 = readU8(BME280_REGISTER_DIG_H3); + dig_H6 = readS8(BME280_REGISTER_DIG_H7); + + int h4 = readS8(BME280_REGISTER_DIG_H4); + h4 = (h4 << 24) >> 20; + dig_H4 = h4 | (readU8(BME280_REGISTER_DIG_H5) & 0x0F); + + int h5 = readS8(BME280_REGISTER_DIG_H6); + h5 = (h5 << 24) >> 20; + dig_H5 = h5 | (readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F); + } + + private String displayRegister(int reg) { + return String.format("0x%s (%d)", lpad(Integer.toHexString(reg & 0xFFFF).toUpperCase(), 4, "0"), reg); + } + + private void showCalibrationData() { + // Displays the calibration values for debugging purposes + println("======================"); + println("DBG: T1 = " + displayRegister(dig_T1)); + println("DBG: T2 = " + displayRegister(dig_T2)); + println("DBG: T3 = " + displayRegister(dig_T3)); + println("----------------------"); + println("DBG: P1 = " + displayRegister(dig_P1)); + println("DBG: P2 = " + displayRegister(dig_P2)); + println("DBG: P3 = " + displayRegister(dig_P3)); + println("DBG: P4 = " + displayRegister(dig_P4)); + println("DBG: P5 = " + displayRegister(dig_P5)); + println("DBG: P6 = " + displayRegister(dig_P6)); + println("DBG: P7 = " + displayRegister(dig_P7)); + println("DBG: P8 = " + displayRegister(dig_P8)); + println("DBG: P9 = " + displayRegister(dig_P9)); + println("----------------------"); + println("DBG: H1 = " + displayRegister(dig_H1)); + println("DBG: H2 = " + displayRegister(dig_H2)); + println("DBG: H3 = " + displayRegister(dig_H3)); + println("DBG: H4 = " + displayRegister(dig_H4)); + println("DBG: H5 = " + displayRegister(dig_H5)); + println("DBG: H6 = " + displayRegister(dig_H6)); + println("======================"); + } + + private void command(int reg, byte val) { + super.beginTransmission(address); + super.write(reg); + super.write(val); + super.endTransmission(); + } + + private int readRawTemp() { + // Returns the raw (uncompensated) temperature + int meas = mode; + // println(String.format("readRawTemp: 1 - meas=%d", meas)); + command(BME280_REGISTER_CONTROL_HUM, (byte) meas); // HUM ? + meas = mode << 5 | mode << 2 | 1; + // println(String.format("readRawTemp: 2 - meas=%d", meas)); + command(BME280_REGISTER_CONTROL, (byte) meas); + + double sleepTime = 0.00125 + 0.0023 * (1 << mode); + sleepTime = sleepTime + 0.0023 * (1 << mode) + 0.000575; + sleepTime = sleepTime + 0.0023 * (1 << mode) + 0.000575; + delay((int)Math.round(sleepTime * 1000)); + int msb = readU8(BME280_REGISTER_TEMP_DATA); + int lsb = readU8(BME280_REGISTER_TEMP_DATA + 1); + int xlsb = readU8(BME280_REGISTER_TEMP_DATA + 2); + int raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4; + // println("DBG: Raw Temp: " + (raw & 0xFFFF) + ", " + raw + String.format(", msb: 0x%04X lsb: 0x%04X xlsb: 0x%04X", msb, lsb, xlsb)); + return raw; + } + + private int readRawPressure() { + // Returns the raw (uncompensated) pressure + int msb = readU8(BME280_REGISTER_PRESSURE_DATA); + int lsb = readU8(BME280_REGISTER_PRESSURE_DATA + 1); + int xlsb = readU8(BME280_REGISTER_PRESSURE_DATA + 2); + int raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4; + // println("DBG: Raw Press: " + (raw & 0xFFFF) + ", " + raw + String.format(", msb: 0x%04X lsb: 0x%04X xlsb: 0x%04X", msb, lsb, xlsb)); + return raw; + } + + private int readRawHumidity() { + // Returns the raw (uncompensated) humidity + int msb = readU8(BME280_REGISTER_HUMIDITY_DATA); + int lsb = readU8(BME280_REGISTER_HUMIDITY_DATA + 1); + int raw = (msb << 8) | lsb; + return raw; + } + + private int readU16LE(int register) { + super.beginTransmission(address); + super.write((byte)register); + byte[] ba = super.read(2); + super.endTransmission(); + return ((ba[1] & 0xFF) << 8) + (ba[0] & 0xFF); // Little Endian + } + + private int readS16LE(int register) { + super.beginTransmission(address); + super.write((byte)register); + byte[] ba = super.read(2); + super.endTransmission(); + + int lo = ba[0] & 0xFF; + int hi = ba[1] & 0xFF; + if (hi > 127) + hi -= 256; + return (hi << 8) + lo; // Little Endian + } + + private int readU8(int register) { + super.beginTransmission(address); + super.write(register); + byte[] ba = super.read(1); + super.endTransmission(); + return (int)(ba[0] & 0xFF); + } + + private int readS8(int register) { + int val = readU8(register); + if (val > 127) + val -= 256; + return val; + } + + private String rpad(String s, int len, String pad) { + String str = s; + while (str.length() < len) { + str += pad; + } + return str; + } + + private String lpad(String s, int len, String pad) { + String str = s; + while (str.length() < len) { + str = pad + str; + } + return str; + } +} diff --git a/java/libraries/io/examples/Environment_I2C_BME280/Environment_I2C_BME280.pde b/java/libraries/io/examples/Environment_I2C_BME280/Environment_I2C_BME280.pde new file mode 100644 index 0000000000..a5ec80602c --- /dev/null +++ b/java/libraries/io/examples/Environment_I2C_BME280/Environment_I2C_BME280.pde @@ -0,0 +1,29 @@ +import processing.io.*; +BME280 bme280; + +// see setup.png in the sketch folder for wiring details + +void setup() { + size(720, 320); + textSize(72); + + //printArray(I2C.list()); + bme280 = new BME280("i2c-1", 0x77); +} + +void draw() { + background(0); + stroke(255); + + bme280.update(); + float temp = bme280.temperature(); + float hum = bme280.humidity(); + float press = bme280.pressure(); + text(String.format("Temp: %.02f\272C", temp), 10, 75); + text(String.format("Hum: %.02f %%", hum), 10, 150); + text(String.format("Press: %.02f hPa", press / 100f), 10, 225); + + // pressure can be used to calculate the altitude like so + float alt = bme280.altitude(press, temp); + text(String.format("Alt: %.02f m", alt), 10, 300); +} diff --git a/java/libraries/io/examples/Environment_I2C_BME280/setup.png b/java/libraries/io/examples/Environment_I2C_BME280/setup.png new file mode 100644 index 0000000000..cb37a3a60e Binary files /dev/null and b/java/libraries/io/examples/Environment_I2C_BME280/setup.png differ diff --git a/java/libraries/io/examples/I2CCompass/I2CCompass.pde b/java/libraries/io/examples/I2CCompass/I2CCompass.pde deleted file mode 100644 index 079a7bcc61..0000000000 --- a/java/libraries/io/examples/I2CCompass/I2CCompass.pde +++ /dev/null @@ -1,43 +0,0 @@ -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; -} diff --git a/java/libraries/io/examples/Light_I2C_TSL2561/Light_I2C_TSL2561.pde b/java/libraries/io/examples/Light_I2C_TSL2561/Light_I2C_TSL2561.pde new file mode 100644 index 0000000000..c6c9a28ec7 --- /dev/null +++ b/java/libraries/io/examples/Light_I2C_TSL2561/Light_I2C_TSL2561.pde @@ -0,0 +1,27 @@ +import processing.io.*; +TSL2561 sensor; + +// see setup.png in the sketch folder for wiring details + +// this variable will contain the measured brightness +// Lux (lx) is the unit of illuminance +float lux; + +void setup() { + size(700, 100); + textSize(72); + //printArray(I2C.list()); + sensor = new TSL2561("i2c-1", 0x39); +} + +void draw() { + background(0); + stroke(255); + lux = sensor.lux(); + text(String.format("Light: %.02f Lux", lux), 10, 75); +} + +void dispose() { + // turn the sensor off + sensor.stop(); +} diff --git a/java/libraries/io/examples/Light_I2C_TSL2561/TSL2561.pde b/java/libraries/io/examples/Light_I2C_TSL2561/TSL2561.pde new file mode 100644 index 0000000000..775b9d02ca --- /dev/null +++ b/java/libraries/io/examples/Light_I2C_TSL2561/TSL2561.pde @@ -0,0 +1,187 @@ +import processing.io.I2C; + +// TSL2561 is light sensor using I2C +// datasheet: https://cdn-shop.adafruit.com/datasheets/TSL2561.pdf +// code contributed by @OlivierLD + +public class TSL2561 extends I2C { + + public final static int TSL2561_ADDRESS = 0x39; + + public final static int TSL2561_ADDRESS_LOW = 0x29; + public final static int TSL2561_ADDRESS_FLOAT = 0x39; + public final static int TSL2561_ADDRESS_HIGH = 0x49; + + public final static int TSL2561_COMMAND_BIT = 0x80; + public final static int TSL2561_WORD_BIT = 0x20; + public final static int TSL2561_CONTROL_POWERON = 0x03; + public final static int TSL2561_CONTROL_POWEROFF = 0x00; + + public final static int TSL2561_REGISTER_CONTROL = 0x00; + public final static int TSL2561_REGISTER_TIMING = 0x01; + public final static int TSL2561_REGISTER_CHAN0_LOW = 0x0C; + public final static int TSL2561_REGISTER_CHAN0_HIGH = 0x0D; + public final static int TSL2561_REGISTER_CHAN1_LOW = 0x0E; + public final static int TSL2561_REGISTER_CHAN1_HIGH = 0x0F; + public final static int TSL2561_REGISTER_ID = 0x0A; + + public final static int TSL2561_GAIN_1X = 0x00; + public final static int TSL2561_GAIN_16X = 0x10; + + public final static int TSL2561_INTEGRATIONTIME_13MS = 0x00; // rather 13.7ms + public final static int TSL2561_INTEGRATIONTIME_101MS = 0x01; + public final static int TSL2561_INTEGRATIONTIME_402MS = 0x02; + + public final static double TSL2561_LUX_K1C = 0.130; // (0x0043) // 0.130 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B1C = 0.0315; // (0x0204) // 0.0315 * 2^LUX_SCALE + public final static double TSL2561_LUX_M1C = 0.0262; // (0x01ad) // 0.0262 * 2^LUX_SCALE + public final static double TSL2561_LUX_K2C = 0.260; // (0x0085) // 0.260 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B2C = 0.0337; // (0x0228) // 0.0337 * 2^LUX_SCALE + public final static double TSL2561_LUX_M2C = 0.0430; // (0x02c1) // 0.0430 * 2^LUX_SCALE + public final static double TSL2561_LUX_K3C = 0.390; // (0x00c8) // 0.390 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B3C = 0.0363; // (0x0253) // 0.0363 * 2^LUX_SCALE + public final static double TSL2561_LUX_M3C = 0.0529; // (0x0363) // 0.0529 * 2^LUX_SCALE + public final static double TSL2561_LUX_K4C = 0.520; // (0x010a) // 0.520 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B4C = 0.0392; // (0x0282) // 0.0392 * 2^LUX_SCALE + public final static double TSL2561_LUX_M4C = 0.0605; // (0x03df) // 0.0605 * 2^LUX_SCALE + public final static double TSL2561_LUX_K5C = 0.65; // (0x014d) // 0.65 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B5C = 0.0229; // (0x0177) // 0.0229 * 2^LUX_SCALE + public final static double TSL2561_LUX_M5C = 0.0291; // (0x01dd) // 0.0291 * 2^LUX_SCALE + public final static double TSL2561_LUX_K6C = 0.80; // (0x019a) // 0.80 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B6C = 0.0157; // (0x0101) // 0.0157 * 2^LUX_SCALE + public final static double TSL2561_LUX_M6C = 0.0180; // (0x0127) // 0.0180 * 2^LUX_SCALE + public final static double TSL2561_LUX_K7C = 1.3; // (0x029a) // 1.3 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B7C = 0.00338; // (0x0037) // 0.00338 * 2^LUX_SCALE + public final static double TSL2561_LUX_M7C = 0.00260; // (0x002b) // 0.00260 * 2^LUX_SCALE + public final static double TSL2561_LUX_K8C = 1.3; // (0x029a) // 1.3 * 2^RATIO_SCALE + public final static double TSL2561_LUX_B8C = 0.000; // (0x0000) // 0.000 * 2^LUX_SCALE + public final static double TSL2561_LUX_M8C = 0.000; // (0x0000) // 0.000 * 2^LUX_SCALE + + private int gain = TSL2561_GAIN_1X; + private int integration = TSL2561_INTEGRATIONTIME_402MS; + private int pause = 800; + + private int address; + + + public TSL2561(String dev) { + this(dev, TSL2561_ADDRESS); + } + + public TSL2561(String dev, int address) { + super(dev); + this.address = address; + start(); + } + + public void start() { + command(TSL2561_COMMAND_BIT, (byte) TSL2561_CONTROL_POWERON); + } + + public void stop() { + command(TSL2561_COMMAND_BIT, (byte) TSL2561_CONTROL_POWEROFF); + } + + public void setGain() { + setGain(TSL2561_GAIN_1X); + } + + public void setGain(int gain) { + setGain(gain, TSL2561_INTEGRATIONTIME_402MS); + } + + public void setGain(int gain, int integration) { + if (gain != TSL2561_GAIN_1X && gain != TSL2561_GAIN_16X) { + throw new IllegalArgumentException("Invalid gain value"); + } + if (gain != this.gain || integration != this.integration) { + command(TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, (byte) (gain | integration)); + //println("Setting low gain"); + this.gain = gain; + this.integration = integration; + delay(pause); // pause for integration (pause must be bigger than integration time) + } + } + + /** + * Read visible+IR diode from the I2C device + */ + public int readFull() { + int reg = TSL2561_COMMAND_BIT | TSL2561_REGISTER_CHAN0_LOW; + return readU16(reg); + } + + /** + * Read IR only diode from the I2C device + */ + public int readIR() { + int reg = TSL2561_COMMAND_BIT | TSL2561_REGISTER_CHAN1_LOW; + return readU16(reg); + } + + /** + * Device lux range 0.1 - 40,000+ + * see https://learn.adafruit.com/tsl2561/overview + */ + public float lux() { + int ambient = this.readFull(); + int ir = this.readIR(); + + //println("IR Result: " + ir); + //println("Ambient Result: " + ambient); + + if (ambient >= 0xffff || ir >= 0xffff) { + throw new RuntimeException("Gain too high, values exceed range"); + } + double ratio = (ir / (float) ambient); + + /* + * For the values below, see https://github.com/adafruit/_TSL2561/blob/master/_TSL2561_U.h + */ + float lux = 0.0f; + if ((ratio >= 0) && (ratio <= TSL2561_LUX_K4C)) { + lux = (float)((TSL2561_LUX_B1C * ambient) - (0.0593 * ambient * (Math.pow(ratio, 1.4)))); + } else if (ratio <= TSL2561_LUX_K5C) { + lux = (float)((TSL2561_LUX_B5C * ambient) - (TSL2561_LUX_M5C * ir)); + } else if (ratio <= TSL2561_LUX_K6C) { + lux = (float)((TSL2561_LUX_B6C * ambient) - (TSL2561_LUX_M6C * ir)); + } else if (ratio <= TSL2561_LUX_K7C) { + lux = (float)((TSL2561_LUX_B7C * ambient) - (TSL2561_LUX_M7C * ir)); + } else if (ratio > TSL2561_LUX_K8C) { + lux = 0.0f; + } + return lux; + } + + + private void command(int register, byte value) { + beginTransmission(address); + write(register); + write(value); + endTransmission(); + } + + private int readU8(int register) { + beginTransmission(this.address); + write(register); + byte[] ba = read(1); + endTransmission(); + return (int)(ba[0] & 0xFF); + } + + private int readU16(int register) { + int lo = readU8(register); + int hi = readU8(register + 1); + int result = (hi << 8) + lo; // Big Endian + //println("(U16) I2C: Device " + toHex(TSL2561_ADDRESS) + " returned " + toHex(result) + " from reg " + toHex(register)); + return result; + } + + private String toHex(int i) { + String s = Integer.toString(i, 16).toUpperCase(); + while (s.length() % 2 != 0) { + s = "0" + s; + } + return "0x" + s; + } +} diff --git a/java/libraries/io/examples/Light_I2C_TSL2561/setup.png b/java/libraries/io/examples/Light_I2C_TSL2561/setup.png new file mode 100644 index 0000000000..a0b33fefbe Binary files /dev/null and b/java/libraries/io/examples/Light_I2C_TSL2561/setup.png differ diff --git a/java/libraries/io/examples/Servo_I2C_PCA9685/PCA9685.pde b/java/libraries/io/examples/Servo_I2C_PCA9685/PCA9685.pde new file mode 100644 index 0000000000..67ea815a4f --- /dev/null +++ b/java/libraries/io/examples/Servo_I2C_PCA9685/PCA9685.pde @@ -0,0 +1,148 @@ +import processing.io.I2C; + +// PCA9685 is a 16-channel servo/PWM driver +// datasheet: https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf +// code contributed by @OlivierLD + +public class PCA9685 extends I2C { + public final static int PCA9685_ADDRESS = 0x40; + + // registers used + public final static int MODE1 = 0x00; + public final static int PRESCALE = 0xFE; + public final static int LED0_ON_L = 0x06; + public final static int LED0_ON_H = 0x07; + public final static int LED0_OFF_L = 0x08; + public final static int LED0_OFF_H = 0x09; + + private int address; + private int freq = 200; // 200 Hz default frequency (after power-up) + private boolean hasFreqSet = false; // whether a different frequency has been set + private int minPulses[] = new int[16]; + private int maxPulses[] = new int[16]; + + + public PCA9685(String dev) { + this(dev, PCA9685_ADDRESS); + } + public PCA9685(String dev, int address) { + super(dev); + this.address = address; + // reset device + command(MODE1, (byte) 0x00); + } + + + public void attach(int channel) { + // same as on Arduino + attach(channel, 544, 2400); + } + + public void attach(int channel, int minPulse, int maxPulse) { + if (channel < 0 || 15 < channel) { + throw new IllegalArgumentException("Channel must be between 0 and 15"); + } + minPulses[channel] = minPulse; + maxPulses[channel] = maxPulse; + + // set the PWM frequency to be the same as on Arduino + if (!hasFreqSet) { + frequency(50); + } + } + + public void write(int channel, float angle) { + if (channel < 0 || 15 < channel) { + throw new IllegalArgumentException("Channel must be between 0 and 15"); + } + if (angle < 0 || 180 < angle) { + throw new IllegalArgumentException("Angle must be between 0 and 180"); + } + int us = (int)(minPulses[channel] + (angle/180.0) * (maxPulses[channel]-minPulses[channel])); + + double pulseLength = 1000000; // 1s = 1,000,000 us per pulse + pulseLength /= freq; // 40..1000 Hz + pulseLength /= 4096; // 12 bits of resolution + int pulse = us; + pulse /= pulseLength; + // println(pulseLength + " us per bit, pulse:" + pulse); + pwm(channel, 0, pulse); + } + + public boolean attached(int channel) { + if (channel < 0 || 15 < channel) { + return false; + } + return (maxPulses[channel] != 0) ? true : false; + } + + public void detach(int channel) { + pwm(channel, 0, 0); + minPulses[channel] = 0; + maxPulses[channel] = 0; + } + + + /** + * @param freq 40..1000 Hz + */ + public void frequency(int freq) { + this.freq = freq; + float preScaleVal = 25000000.0f; // 25MHz + preScaleVal /= 4096.0; // 4096: 12-bit + preScaleVal /= freq; + preScaleVal -= 1.0; + // println("Setting PWM frequency to " + freq + " Hz"); + // println("Estimated pre-scale: " + preScaleVal); + double preScale = Math.floor(preScaleVal + 0.5); + // println("Final pre-scale: " + preScale); + byte oldmode = (byte) readU8(MODE1); + byte newmode = (byte) ((oldmode & 0x7F) | 0x10); // sleep + command(MODE1, newmode); // go to sleep + command(PRESCALE, (byte) (Math.floor(preScale))); + command(MODE1, oldmode); + delay(5); + command(MODE1, (byte) (oldmode | 0x80)); + hasFreqSet = true; + } + + /** + * @param channel 0..15 + * @param on cycle offset to turn output on (0..4095) + * @param off cycle offset to turn output off again (0..4095) + */ + public void pwm(int channel, int on, int off) { + if (channel < 0 || 15 < channel) { + throw new IllegalArgumentException("Channel must be between 0 and 15"); + } + if (on < 0 || 4095 < on) { + throw new IllegalArgumentException("On must be between 0 and 4095"); + } + if (off < 0 || 4095 < off) { + throw new IllegalArgumentException("Off must be between 0 and 4095"); + } + if (off < on) { + throw new IllegalArgumentException("Off must be greater than On"); + } + command(LED0_ON_L + 4 * channel, (byte) (on & 0xFF)); + command(LED0_ON_H + 4 * channel, (byte) (on >> 8)); + command(LED0_OFF_L + 4 * channel, (byte) (off & 0xFF)); + command(LED0_OFF_H + 4 * channel, (byte) (off >> 8)); + } + + + private void command(int register, byte value) { + beginTransmission(address); + write(register); + write(value); + endTransmission(); + } + + private byte readU8(int register) { + beginTransmission(address); + write(register); + byte[] ba = read(1); + endTransmission(); + return (byte)(ba[0] & 0xFF); + } +} diff --git a/java/libraries/io/examples/Servo_I2C_PCA9685/Servo_I2C_PCA9685.pde b/java/libraries/io/examples/Servo_I2C_PCA9685/Servo_I2C_PCA9685.pde new file mode 100644 index 0000000000..ede995bc5e --- /dev/null +++ b/java/libraries/io/examples/Servo_I2C_PCA9685/Servo_I2C_PCA9685.pde @@ -0,0 +1,41 @@ +import processing.io.*; +PCA9685 servos; + +// see setup.png in the sketch folder for wiring details + +void setup() { + size(400, 300); + //printArray(I2C.list()); + servos = new PCA9685("i2c-1", 0x40); + + // different servo motors will vary in the pulse width they expect + // the lines below set the pulse width for 0 degrees to 544 microseconds (μs) + // and the pulse width for 180 degrees to 2400 microseconds + // these values match the defaults of the Servo library on Arduino + // but you might need to modify this for your particular servo still + servos.attach(0, 544, 2400); + servos.attach(1, 544, 2400); +} + +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; + servos.write(0, angle); + float y = map(angle, 0, 180, 0, height); + line(0, y, width/2, y); + + angle = 90 + cos(frameCount / 100.0)*85; + servos.write(1, 90 + cos(frameCount / 100.0)*85); + y = map(angle, 0, 180, 0, height); + line(width/2, y, width, y); +} + +void dispose() { + servos.detach(0); + servos.detach(1); +} diff --git a/java/libraries/io/examples/Servo_I2C_PCA9685/setup.png b/java/libraries/io/examples/Servo_I2C_PCA9685/setup.png new file mode 100644 index 0000000000..77b0b74bf0 Binary files /dev/null and b/java/libraries/io/examples/Servo_I2C_PCA9685/setup.png differ diff --git a/java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde b/java/libraries/io/examples/SimpleI2C/SimpleI2C.pde similarity index 86% rename from java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde rename to java/libraries/io/examples/SimpleI2C/SimpleI2C.pde index 66f034f61e..75a5bacccf 100644 --- a/java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde +++ b/java/libraries/io/examples/SimpleI2C/SimpleI2C.pde @@ -4,6 +4,9 @@ I2C i2c; // MCP4725 is a Digital-to-Analog converter using I2C // datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf +// also see DigitalAnalog_I2C_MCP4725 for how to write the +// same sketch in an object-oriented way + void setup() { //printArray(I2C.list()); i2c = new I2C(I2C.list()[0]); diff --git a/java/libraries/io/examples/SimpleInput/SimpleInput.pde b/java/libraries/io/examples/SimpleInput/SimpleInput.pde index d7a420ce12..5b6f5687a6 100644 --- a/java/libraries/io/examples/SimpleInput/SimpleInput.pde +++ b/java/libraries/io/examples/SimpleInput/SimpleInput.pde @@ -5,14 +5,18 @@ import processing.io.*; // see setup.png in the sketch folder for wiring details void setup() { - GPIO.pinMode(4, GPIO.INPUT); + // INPUT_PULLUP enables the built-in pull-up resistor for this pin + // left alone, the pin will read as HIGH + // connected to ground (via e.g. a button or switch) it will read LOW + GPIO.pinMode(4, GPIO.INPUT_PULLUP); } void draw() { - // sense the input pin - if (GPIO.digitalRead(4) == GPIO.HIGH) { + if (GPIO.digitalRead(4) == GPIO.LOW) { + // button is pressed fill(255); } else { + // button is not pressed fill(204); } stroke(255); diff --git a/java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde b/java/libraries/io/examples/SimpleSPI/SimpleSPI.pde similarity index 86% rename from java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde rename to java/libraries/io/examples/SimpleSPI/SimpleSPI.pde index bb1311adaf..a1b4e2b08d 100644 --- a/java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde +++ b/java/libraries/io/examples/SimpleSPI/SimpleSPI.pde @@ -5,6 +5,9 @@ SPI spi; // datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf // see setup.png in the sketch folder for wiring details +// also see AnalogDigital_SPI_MCP3001 for how to write the +// same sketch in an object-oriented way + void setup() { //printArray(SPI.list()); spi = new SPI(SPI.list()[0]); diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP/setup.png b/java/libraries/io/examples/SimpleSPI/setup.png similarity index 100% rename from java/libraries/io/examples/SPIAnalogDigitalOOP/setup.png rename to java/libraries/io/examples/SimpleSPI/setup.png diff --git a/java/libraries/io/examples/ServoSweep/ServoSweep.pde b/java/libraries/io/examples/SoftwareServoSweep/SoftwareServoSweep.pde similarity index 100% rename from java/libraries/io/examples/ServoSweep/ServoSweep.pde rename to java/libraries/io/examples/SoftwareServoSweep/SoftwareServoSweep.pde diff --git a/java/libraries/io/examples/ServoSweep/setup.png b/java/libraries/io/examples/SoftwareServoSweep/setup.png similarity index 100% rename from java/libraries/io/examples/ServoSweep/setup.png rename to java/libraries/io/examples/SoftwareServoSweep/setup.png diff --git a/java/libraries/io/examples/ServoSweep/setup_better.png b/java/libraries/io/examples/SoftwareServoSweep/setup_better.png similarity index 100% rename from java/libraries/io/examples/ServoSweep/setup_better.png rename to java/libraries/io/examples/SoftwareServoSweep/setup_better.png diff --git a/java/libraries/io/examples/Touch_I2C_MPR121/MPR121.pde b/java/libraries/io/examples/Touch_I2C_MPR121/MPR121.pde new file mode 100644 index 0000000000..0ef023820b --- /dev/null +++ b/java/libraries/io/examples/Touch_I2C_MPR121/MPR121.pde @@ -0,0 +1,112 @@ +import processing.io.I2C; + +// MPR121 is a capacitive-touch sensor controller with 12 channels +// datasheet: https://www.nxp.com/docs/en/data-sheet/MPR121.pdf + +class MPR121 extends I2C { + int address; + int touched; + + // registers used (there are more) + static final int EFD0LB = 0x04; // ELE0 Electrode Filtered Data LSB + static final int E0TTH = 0x41; // ELE0 Touch Threshold + static final int E0RTH = 0x42; // ELE0 Release Threshold + static final int E0BV = 0x1e; // ELE0 Baseline Value + static final int MHDR = 0x2b; // MHD Rising + static final int NHDR = 0x2c; // NHD Amount Rising + static final int NCLR = 0x2d; // NCL Rising + static final int MHDF = 0x2f; // MHD Falling + static final int NHDF = 0x30; // NHD Amount Falling + static final int NCLF = 0x31; // NCL Falling + static final int CDT = 0x5d; // Filter/Global CDT Configuration + static final int ECR = 0x5e; // Electrode Configuration + static final int SRST = 0x80; // Soft Reset + + // there can be more than one device connected to the bus + // as long as they have different addresses + // possible addresses: 0x5a (default) - 0x5d + MPR121(String dev, int address) { + super(dev); + this.address = address; + reset(); + } + + void update() { + beginTransmission(address); + write(0x00); + byte[] in = read(2); + // & 0xff makes sure the byte is not interpreted as a negative value + touched = (in[1] & 0xff) << 8 | (in[0] & 0xff); + } + + boolean touched(int channel) { + if (channel < 0 || 11 < channel) { + return false; + } + if ((touched & (1 << channel)) != 0) { + return true; + } else { + return false; + } + } + + void threshold(int touch, int release) { + for (int i=0; i < 12; i++) { + threshold(touch, release, i); + } + } + + void threshold(int touch, int release, int channel) { + if (channel < 0 || 11 < channel) { + return; + } + touch = constrain(touch, 0, 255); + release = constrain(release, 0, 255); + writeRegister(E0TTH + 2*channel, touch); + writeRegister(E0RTH + 2*channel, release); + } + + int analogRead(int channel) { + if (channel < 0 || 11 < channel) { + return 0; + } + beginTransmission(address); + write(EFD0LB + 2*channel); + byte[] in = read(2); + return (in[1] & 0xff) << 8 | (in[0] & 0xff); + } + + int analogReadBaseline(int channel) { + if (channel < 0 || 11 < channel) { + return 0; + } + beginTransmission(address); + write(E0BV + channel); + byte[] in = read(1); + return (in[0] & 0xff) << 2; + } + + void reset() { + writeRegister(SRST, 0x63); + delay(1); + threshold(12, 6); + // set baseline filtering control registers (see p. 12) + writeRegister(MHDR, 0x01); + writeRegister(NHDR, 0x01); + writeRegister(NCLR, 0x0e); + writeRegister(MHDF, 0x01); + writeRegister(NHDF, 0x05); + writeRegister(NCLF, 0x01); + // change sample interval to 1ms period from default 16ms + writeRegister(CDT, 0x20); + // start sampling + writeRegister(ECR, 0x8f); + } + + void writeRegister(int register, int value) { + beginTransmission(address); + write(register); + write(value); + endTransmission(); + } +} diff --git a/java/libraries/io/examples/Touch_I2C_MPR121/Touch_I2C_MPR121.pde b/java/libraries/io/examples/Touch_I2C_MPR121/Touch_I2C_MPR121.pde new file mode 100644 index 0000000000..58f69ad1c1 --- /dev/null +++ b/java/libraries/io/examples/Touch_I2C_MPR121/Touch_I2C_MPR121.pde @@ -0,0 +1,26 @@ +import processing.io.*; +MPR121 touch; + +// see setup.png in the sketch folder for wiring details + +void setup() { + size(600, 200); + //printArray(I2C.list()); + touch = new MPR121("i2c-1", 0x5a); +} + +void draw() { + background(204); + noStroke(); + + touch.update(); + + for (int i=0; i < 12; i++) { + if (touch.touched(i)) { + fill(255, 0, 0); + } else { + fill(255, 255, 255); + } + ellipse((width/12) * (i+0.5), height/2, 20, 20); + } +} diff --git a/java/libraries/io/examples/Touch_I2C_MPR121/setup.png b/java/libraries/io/examples/Touch_I2C_MPR121/setup.png new file mode 100644 index 0000000000..65bb7a04b2 Binary files /dev/null and b/java/libraries/io/examples/Touch_I2C_MPR121/setup.png differ diff --git a/java/libraries/io/library/linux-arm64/libprocessing-io.so b/java/libraries/io/library/linux-arm64/libprocessing-io.so index d180991a48..9659c68af3 100755 Binary files a/java/libraries/io/library/linux-arm64/libprocessing-io.so and b/java/libraries/io/library/linux-arm64/libprocessing-io.so differ diff --git a/java/libraries/io/library/linux-armv6hf/libprocessing-io.so b/java/libraries/io/library/linux-armv6hf/libprocessing-io.so index 94ed162d63..9d272c4d86 100755 Binary files a/java/libraries/io/library/linux-armv6hf/libprocessing-io.so and b/java/libraries/io/library/linux-armv6hf/libprocessing-io.so differ diff --git a/java/libraries/io/library/linux32/libprocessing-io.so b/java/libraries/io/library/linux32/libprocessing-io.so index 32a231170c..acc2bd6032 100755 Binary files a/java/libraries/io/library/linux32/libprocessing-io.so and b/java/libraries/io/library/linux32/libprocessing-io.so differ diff --git a/java/libraries/io/library/linux64/libprocessing-io.so b/java/libraries/io/library/linux64/libprocessing-io.so index 2979766a83..4c2d715886 100755 Binary files a/java/libraries/io/library/linux64/libprocessing-io.so and b/java/libraries/io/library/linux64/libprocessing-io.so differ diff --git a/java/libraries/io/src/native/iface.h b/java/libraries/io/src/native/iface.h index 32e235e1e1..74decc1852 100644 --- a/java/libraries/io/src/native/iface.h +++ b/java/libraries/io/src/native/iface.h @@ -47,6 +47,30 @@ JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_readFile JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile (JNIEnv *, jclass, jstring, jbyteArray); +/* + * Class: processing_io_NativeInterface + * Method: raspbianGpioMemRead + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemRead + (JNIEnv *, jclass, jint); + +/* + * Class: processing_io_NativeInterface + * Method: raspbianGpioMemWrite + * Signature: (III)I + */ +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemWrite + (JNIEnv *, jclass, jint, jint, jint); + +/* + * Class: processing_io_NativeInterface + * Method: raspbianGpioMemWrite + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemSetPinBias + (JNIEnv *, jclass, jint, jint); + /* * Class: processing_io_NativeInterface * Method: pollDevice diff --git a/java/libraries/io/src/native/impl.c b/java/libraries/io/src/native/impl.c index 0f9402ab92..522aa310fa 100644 --- a/java/libraries/io/src/native/impl.c +++ b/java/libraries/io/src/native/impl.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,143 @@ JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile } +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemRead + (JNIEnv *env, jclass cls, jint offset) +{ + // validate offset + if (4096 <= offset) { + return -EINVAL; + } + + int file = open("/dev/gpiomem", O_RDWR|O_SYNC); + if (file < 0) { + return -errno; + } + + uint32_t *mem = mmap(NULL, 4096, PROT_READ, MAP_SHARED, file, 0); + if (mem == MAP_FAILED) { + close(file); + return -errno; + } + + uint32_t value = mem[offset]; + + munmap(mem, 4096); + close(file); + return value; +} + + +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemWrite + (JNIEnv *env, jclass cls, jint offset, jint mask, jint value) +{ + // validate offset + if (4096 <= offset) { + return -EINVAL; + } + + int file = open("/dev/gpiomem", O_RDWR|O_SYNC); + if (file < 0) { + return -errno; + } + + uint32_t *mem = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, file, 0); + if (mem == MAP_FAILED) { + close(file); + return -errno; + } + + mem[offset] = (mem[offset] & ~mask) | (value & mask); + + munmap(mem, 4096); + close(file); + return 1; // number of bytes written +} + + +#define BCM2835_GPPUD_OFFSET (0x94 >> 2) +#define BCM2835_GPPUDCLK0_OFFSET (0x98 >> 2) +#define BCM2835_GPPUDCLK1_OFFSET (0x9c >> 2) + +JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_raspbianGpioMemSetPinBias + (JNIEnv *env, jclass cls, jint gpio, jint mode) +{ + int ret = 0; // success + + int file = open("/dev/gpiomem", O_RDWR|O_SYNC); + if (file < 0) { + return -errno; + } + + uint32_t *mem = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, file, 0); + if (mem == MAP_FAILED) { + close(file); + return -errno; + } + + // validate arguments + if (gpio < 0 || 53 < gpio) { + ret = -EINVAL; + goto out; + } + + // see BCM2835 datasheet, p. 101 + uint32_t pud; + if (mode == 0) { + pud = 0; // floating + } else if (mode == 2) { + pud = 2; // pull-up + } else if (mode == 3) { + pud = 1; // pull-down + } else { + ret = -EINVAL; + goto out; + } + + /* + * From the BCM2835 datasheet, p. 101: + * + * The following sequence of events is required: + * 1. Write to GPPUD to set the required control signal (i.e. Pull-up or + * Pull-Down or neither to remove the current Pull-up/down) + * 2. Wait 150 cycles – this provides the required set-up time for the + * control signal + * 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads + * you wish to modify – NOTE only the pads which receive a clock will + * be modified, all others will retain their previous state. + * 4. Wait 150 cycles – this provides the required hold time for the + * control signal + * 5. Write to GPPUD to remove the control signal + * 6. Write to GPPUDCLK0/1 to remove the clock + */ + + // python-gpiozero uses a delay of 214 ns, so we do the same + struct timespec wait; + wait.tv_sec = 0; + wait.tv_nsec = 214; + + mem[BCM2835_GPPUD_OFFSET] = pud; + nanosleep(&wait, NULL); + if (gpio < 32) { + mem[BCM2835_GPPUDCLK0_OFFSET] = 1 << gpio; + } else { + mem[BCM2835_GPPUDCLK1_OFFSET] = 1 << (gpio-32); + } + nanosleep(&wait, NULL); + mem[BCM2835_GPPUD_OFFSET] = 0; + if (gpio < 32) { + mem[BCM2835_GPPUDCLK0_OFFSET] = 0; + } else { + mem[BCM2835_GPPUDCLK1_OFFSET] = 0; + } + +out: + munmap(mem, 4096); + close(file); + return ret; +} + + JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_pollDevice (JNIEnv *env, jclass cls, jstring _fn, jint timeout) { @@ -166,29 +304,36 @@ JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferI2c 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; + packets.nmsgs = 0; + + if (_out != NULL) { + msgs[packets.nmsgs].addr = slave; + msgs[packets.nmsgs].flags = 0; + msgs[packets.nmsgs].len = (*env)->GetArrayLength(env, _out); + out = (*env)->GetByteArrayElements(env, _out, NULL); + msgs[packets.nmsgs].buf = out; + packets.nmsgs++; + } if (_in != NULL) { + msgs[packets.nmsgs].addr = slave; + msgs[packets.nmsgs].flags = I2C_M_RD; // I2C_M_RECV_LEN is not supported + msgs[packets.nmsgs].len = (*env)->GetArrayLength(env, _in); 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; + msgs[packets.nmsgs].buf = in; + packets.nmsgs++; } + // set the timeout to 100ms - this helps slow devices such as the + // Arduino Uno to keep up + ioctl(handle, I2C_TIMEOUT, 10); int ret = ioctl(handle, I2C_RDWR, &packets); if (ret < 0) { ret = -errno; } - (*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT); + if (_out != NULL) { + (*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT); + } if (_in != NULL) { (*env)->ReleaseByteArrayElements(env, _in, in, 0); } diff --git a/java/libraries/io/src/processing/io/GPIO.java b/java/libraries/io/src/processing/io/GPIO.java index b4e0d476bf..711aafd808 100644 --- a/java/libraries/io/src/processing/io/GPIO.java +++ b/java/libraries/io/src/processing/io/GPIO.java @@ -328,7 +328,7 @@ public static void noInterrupts() { /** * Configures a pin to act either as input or output * @param pin GPIO pin - * @param mode GPIO.INPUT or GPIO.OUTPUT + * @param mode GPIO.INPUT, GPIO.INPUT_PULLUP, GPIO.INPUT_PULLDOWN, or GPIO.OUTPUT * @see digitalRead * @see digitalWrite * @see releasePin @@ -356,19 +356,15 @@ public static void pinMode(int pin, int mode) { } } - // 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"; + + // attempt to disable any pre-set pullups on the Raspberry Pi + NativeInterface.raspbianGpioMemSetPinBias(pin, mode); + } else if (mode == OUTPUT) { if (values.get(pin)) { out = "high"; @@ -376,13 +372,32 @@ public static void pinMode(int pin, int mode) { out = "low"; } } else if (mode == INPUT_PULLUP || mode == INPUT_PULLDOWN) { + out = "in"; + + // attempt to set pullups on the Raspberry Pi + ret = NativeInterface.raspbianGpioMemSetPinBias(pin, mode); + if (ret == -2) { // NOENT + System.err.println("Setting pullup or pulldown resistors is currently only supported on the Raspberry Pi running Raspbian. Continuing without."); + } else if (ret < 0) { + System.err.println("Error setting pullup or pulldown resistors: " + NativeInterface.getError(ret) + ". Continuing without."); + } // 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); + + // we need to give udev some time to change the file permissions behind our back + // retry for 500ms when writing to the file fails with -EACCES + long start = System.currentTimeMillis(); + do { + ret = NativeInterface.writeFile(fn, out); + if (ret == -13) { + Thread.yield(); + } + } while (ret == -13 && System.currentTimeMillis()-start < 500); + if (ret < 0) { throw new RuntimeException(fn + ": " + NativeInterface.getError(ret)); } diff --git a/java/libraries/io/src/processing/io/I2C.java b/java/libraries/io/src/processing/io/I2C.java index 1d0bfac31b..2e5dedb4e5 100644 --- a/java/libraries/io/src/processing/io/I2C.java +++ b/java/libraries/io/src/processing/io/I2C.java @@ -125,7 +125,7 @@ public void endTransmission() { transmitting = false; out = null; if (ret < 0) { - if (ret == -5) { // EIO + if (ret == -5 | ret == -121) { // EIO | EREMOTEIO 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)); @@ -185,7 +185,7 @@ public byte[] read(int len) { transmitting = false; out = null; if (ret < 0) { - if (ret == -5) { // EIO + if (ret == -5 | ret == -121) { // EIO | EREMOTEIO 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)); diff --git a/java/libraries/io/src/processing/io/NativeInterface.java b/java/libraries/io/src/processing/io/NativeInterface.java index 3b1b3e0c32..a05a71ecd5 100644 --- a/java/libraries/io/src/processing/io/NativeInterface.java +++ b/java/libraries/io/src/processing/io/NativeInterface.java @@ -62,6 +62,9 @@ public static int writeFile(String fn, String out) { } /* GPIO */ + public static native int raspbianGpioMemRead(int offset); + public static native int raspbianGpioMemWrite(int offset, int mask, int value); + public static native int raspbianGpioMemSetPinBias(int gpio, int mode); public static native int pollDevice(String fn, int timeout); /* I2C */ public static native int transferI2c(int handle, int slave, byte[] out, byte[] in); diff --git a/java/libraries/io/src/processing/io/PWM.java b/java/libraries/io/src/processing/io/PWM.java index 2a8fe19f22..c33c9107cb 100644 --- a/java/libraries/io/src/processing/io/PWM.java +++ b/java/libraries/io/src/processing/io/PWM.java @@ -206,7 +206,6 @@ public void set(int period, float duty) { /** * 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) { diff --git a/java/libraries/serial/library/jssc.jar b/java/libraries/serial/library/jssc.jar index 93269cfabe..bf5640670c 100644 Binary files a/java/libraries/serial/library/jssc.jar and b/java/libraries/serial/library/jssc.jar differ diff --git a/todo.txt b/todo.txt old mode 100755 new mode 100644