All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.freeutils.scrollphat.Device Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2016 Amichai Rothman
 *
 *  This file is part of JScrollPhat - the Java Scroll pHAT package.
 *
 *  JScrollPhat is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JScrollPhat 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JScrollPhat.  If not, see .
 *
 *  For additional info see http://www.freeutils.net/source/jscrollphat/
 */

package net.freeutils.scrollphat;

import java.io.Closeable;
import java.io.IOException;

/**
 * Provides access to the raw functionality of
 * ISSI's IS31FL3730 matrix LED driver chip.
 * 

* The device must be {@link #open opened} and {@link #configure configured} * before being used (the {@link #init} method does both with default values). *

* The device must be {@link #close closed} when done, in order to clear the * display and release all resources. The {@link #closeOnShutdown method} can * be called to enable a shutdown hook that will close the device automatically * when the JVM exits, which is convenient in most simple use cases. *

* The LED matrix data is written via the {@link #setDisplay} methods. Data is * expressed as a single byte per column, where each bit represents the state * of a single led on that column. Columns are indexed from left to right, and * the rows within a column are indexed from top (lowest bit) to bottom (highest bit). *

* The written matrix data is stored by the device in temporary registers, and * becomes visible only when the {@link #update} method is invoked to apply the * changes. *

* The LED brightness can be set as a value in the range 0 (off) to 128 (brightest). * The brightness setting applies to the entire matrix and cannot be set for * each LED individually. *

* This class is not thread-safe, and should only be accessed by one thread * at a time. * * @see IS31FL3730 Datasheet */ public abstract class Device implements Closeable { // IS31FL3730 I2C addresses (according to how the address pin is connected) public static final int ADDR_GND = 0x60, // address pin connected to GND ADDR_SCL = 0x61, // address pin connected to SCL ADDR_SDA = 0x62, // address pin connected to SDA ADDR_VCC = 0x63; // address pin connected to VCC // IS31FL3730 registers public static final byte REG_CONFIG = 0x00, REG_MATRIX1_DATA_START = 0x01, REG_MATRIX2_DATA_START = 0x0e, REG_UPDATE = 0x0c, REG_BRIGHTNESS_CONFIG = 0x0d, REG_BRIGHTNESS_PWM = 0x19, REG_RESET = (byte)0xff; // IS31FL3730 configuration options (bitmask) public static final byte CONFIG_MATRIX_8X8 = 0x00, CONFIG_MATRIX_7X9 = 0x01, CONFIG_MATRIX_6X10 = 0x02, CONFIG_MATRIX_5X11 = 0x03, CONFIG_AUDIO_INPUT = 0x04, CONFIG_DISPLAY_MATRIX_1 = 0x00, CONFIG_DISPLAY_MATRIX_2 = 0x08, CONFIG_DISPLAY_MATRIX_1_AND_2 = 0x18, CONFIG_SOFTWARE_SHUTDOWN = (byte)0x80; protected int errors; protected int warnErrorCount = 10; protected int throwErrorCount = 1000; protected int width; protected int height; protected Thread shutdownHook; /** * Returns a new Device instance. *

* The implementation name can be specified in the "scrollphat.impl" * system property, and if none exists, a default is used. * * @return a new Device instance * @throws IllegalArgumentException if the implementation name is invalid */ public static Device newInstance() { return newInstance(null); } /** * Returns a new Device instance using the given implementation. *

* The implementation name can be one of "pi4j", "jnadev" or "mock". * If null, the implementation name is taken from the "scrollphat.impl" * system property, and if none exists, a default is used. * * @param impl the implementation name * @return a new Device instance * @throws IllegalArgumentException if the implementation name is invalid */ public static Device newInstance(String impl) { if (impl == null) impl = System.getProperty("scrollphat.impl", "pi4j"); impl = impl.toLowerCase(); if (impl.equals("pi4j")) return new Pi4JDevice(); if (impl.equals("jnadev")) return new JNADevDevice(); if (impl.equals("mock")) return new MockDevice(); throw new IllegalArgumentException("unknown device implementation: " + impl); } /** * Returns the configured device width. * * @return the configured device width */ public int getWidth() { return width; } /** * Returns the configured device height. * * @return the configured device height */ public int getHeight() { return height; } /** * Handles errors that occur while writing data to the device. *

* The error may be rethrown, swallowed, or a warning printed, * depending on the error handling configuration and current * accumulated error count. * * @param ioe the error that occurred * @throws IOException if the error is to be propagated */ protected void handleError(IOException ioe) throws IOException { errors++; if (warnErrorCount > 0 && errors % warnErrorCount == 0) System.err.println("warning: accumulated " + errors + " errors, check your connections (last error: " + ioe + ")"); if (throwErrorCount > 0 && errors % throwErrorCount == 0) throw ioe; } /** * Opens the connection to I2C bus number 1 * and to the device at the given address. * * @param address the I2C device address * @return this device * @throws IOException if an error occurs * @throws IllegalArgumentException if the address is invalid */ public Device open(int address) throws IOException { return open(1, address); } /** * Opens the connection to the I2C bus and * to the device at the given address. * * @param busNumber the I2C bus number to use * @param address the I2C device address * @return this device * @throws IOException if an error occurs * @throws IllegalArgumentException if the address is invalid */ public Device open(int busNumber, int address) throws IOException { if (busNumber < 0) throw new IllegalArgumentException("invalid bus number: " + busNumber); if (address < ADDR_GND || address > ADDR_VCC) throw new IllegalArgumentException("invalid address: " + address); return openImpl(busNumber, address); } /** * Opens the connection to the I2C bus and * to the device at the given address. * * @param busNumber the I2C bus number to use * @param address the I2C device address * @return this device * @throws IOException if an error occurs * @throws IllegalArgumentException if the address is invalid */ protected abstract Device openImpl(int busNumber, int address) throws IOException; /** * Closes the I2C bus and device. *

* This method may be called multiple times, or * without a successful call to {@link #open} before it. * * @throws IOException if an error occurs */ public void close() throws IOException { closeImpl(); } /** * Closes the I2C bus and device. *

* This method may be called multiple times, or * without a successful call to {@link #open} before it. * * @throws IOException if an error occurs */ protected abstract void closeImpl() throws IOException; /** * Writes a single byte of data to the given register. * * @param register the register to write to * @param data the data to write (only the lower 8 bits are written) * @throws IOException if an error occurs */ public void write(byte register, int data) throws IOException { try { writeImpl(register, data); } catch (IOException ioe) { handleError(ioe); } } /** * Writes a single byte of data to the given register. * * @param register the register to write to * @param data the data to write (only the lower 8 bits are written) * @throws IOException if an error occurs */ protected void writeImpl(byte register, int data) throws IOException { write(register, new byte[] { (byte)data }, 0, 1); } /** * Writes a series of bytes of data starting at the given register. * The register number is incremented by one after each written byte. * * @param register the register to write to * @param data an array containing the data to write * @param offset the offset within the array of the first byte to write * @param length the number of bytes to write * @throws IOException if an error occurs */ public void write(byte register, byte[] data, int offset, int length) throws IOException { try { writeImpl(register, data, offset, length); } catch (IOException ioe) { handleError(ioe); } } /** * Writes a series of bytes of data starting at the given register. * The register number is incremented by one after each written byte. * * @param register the register to write to * @param data an array containing the data to write * @param offset the offset within the array of the first byte to write * @param length the number of bytes to write * @throws IOException if an error occurs */ protected abstract void writeImpl(byte register, byte[] data, int offset, int length) throws IOException; /** * Specifies whether to set a system shutdown * hook to close this device when the JVM exits. *

* If disabled, the caller is responsible for performing a graceful * shutdown, e.g. by calling {@link #close} in a finally block. * * @param enable specifies whether to enable or disable * close on shutdown * @return this device */ public Device closeOnShutdown(boolean enable) { if (enable && shutdownHook == null) { shutdownHook = new Thread() { @Override public void run() { try { close(); } catch (IOException ignore) {} } }; Runtime.getRuntime().addShutdownHook(shutdownHook); } else if (!enable && shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(shutdownHook); shutdownHook = null; } return this; } /** * Configures the device. * * @param config the bitmask of configuration options * @return this device * @throws IOException if an error occurs */ public Device configure(int config) throws IOException { reset(); if ((config & ~0xff) != 0) throw new IllegalArgumentException("invalid config bitmask: " + config); write(REG_CONFIG, config); this.width = 8 + (config & 3); this.height = 16 - width; return this; } /** * Initializes the device. *

* This is a convenience method that calls {@link #closeOnShutdown}, * {@link #open} and {@link #configure} with default options. * * @return this device * @throws IOException if an error occurs */ public Device init() throws IOException { closeOnShutdown(true); return open(ADDR_GND).configure((int)CONFIG_MATRIX_5X11); } /** * Resets the device (by writing the reset command to it). * * @throws IOException if an error occurs */ public void reset() throws IOException { write(REG_RESET, 0); } /** * Sets the display brightness. * * @param value a brightness value in the range 0-128 * @throws IOException if an error occurs */ public void setBrightness(int value) throws IOException { write(REG_BRIGHTNESS_PWM, value); } /** * Updates the display. This method must be called after modifying * the display data in order for the changes to be applied. * * @throws IOException if an error occurs */ public void update() throws IOException { write(REG_UPDATE, 0); } /** * Writes the given display data and updates the display. * * @param columns the consecutive column values to write * @throws IOException if an error occurs */ public void update(byte[] columns) throws IOException { setDisplay(columns); update(); } /** * Sets the value of the given column. * * @param column the column number * @param value the column value * @throws IOException if an error occurs */ public void setDisplay(int column, int value) throws IOException { if (column < 0 || column >= width) throw new IndexOutOfBoundsException("column " + column); write((byte)(REG_MATRIX1_DATA_START + column), value); } /** * Sets the values of a consecutive sequence of columns. * * @param startColumn the column to start writing at * @param columns an array containing the consecutive column values to write * @param offset the offset within the array of the first column's value * @param length the number of consecutive column values to write * @throws IOException if an error occurs */ public void setDisplay(int startColumn, byte[] columns, int offset, int length) throws IOException { // clip to boundaries if (startColumn < 0) { offset -= startColumn; length += startColumn; startColumn = 0; } length = Math.min(length, Math.min(width - startColumn, columns.length - offset)); if (length > 0) write((byte)(REG_MATRIX1_DATA_START + startColumn), columns, offset, length); } /** * Sets the values of a consecutive sequence of columns. * * @param startColumn the column to start writing at * @param columns the consecutive column values to write * @throws IOException if an error occurs */ public void setDisplay(int startColumn, byte[] columns) throws IOException { setDisplay(startColumn, columns, 0, width); } /** * Sets the values of a consecutive sequence of columns, * starting at the first column. * * @param columns the consecutive column values to write * @throws IOException if an error occurs */ public void setDisplay(byte[] columns) throws IOException { setDisplay(0, columns, 0, width); } /** * Displays test patterns. This functionality is implemented * in software and not by the device itself, but is useful * in verifying that the device is working properly. * * @throws IOException if an error occurs * @throws InterruptedException if the thread is interrupted */ public void displayTestPatterns() throws IOException, InterruptedException { int width = getWidth(); byte[] matrix = new byte[width]; for (int round = 0; round < 4; round++) { setBrightness(1); for (int i = 0; i < 2 * width; i++) { if (i == width) { for (int j = 0, levels = 60; j < levels; j++) { setBrightness(1 + 20 * Math.min(j, levels - j) / levels); Thread.sleep(800 / levels); } } matrix[i % width] = i < width ? (byte)0xff : 0; // turn column on/off update(matrix); Thread.sleep(800 / width); } } } /** * The main command-line utility entry point. * * @param args the arguments * @throws IOException if an error occurs * @throws InterruptedException if the thread is interrupted */ public static void main(String[] args) throws IOException, InterruptedException { newInstance().init().displayTestPatterns(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy