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

com.diozero.devices.mcp23xxx.MCP23xxx Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
package com.diozero.devices.mcp23xxx;

/*-
 * #%L
 * Organisation: diozero
 * Project:      Device I/O Zero - Core
 * Filename:     MCP23xxx.java  
 * 
 * This file is part of the diozero project. More information about this project
 * can be found at http://www.diozero.com/
 * %%
 * Copyright (C) 2016 - 2021 diozero
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import org.tinylog.Logger;

import com.diozero.api.DeviceMode;
import com.diozero.api.DigitalInputDevice;
import com.diozero.api.DigitalInputEvent;
import com.diozero.api.GpioEventTrigger;
import com.diozero.api.GpioPullUpDown;
import com.diozero.api.PinInfo;
import com.diozero.api.RuntimeIOException;
import com.diozero.api.function.DeviceEventConsumer;
import com.diozero.devices.GpioExpander;
import com.diozero.internal.SoftwarePwmOutputDevice;
import com.diozero.internal.spi.AbstractDeviceFactory;
import com.diozero.internal.spi.GpioDeviceFactoryInterface;
import com.diozero.internal.spi.GpioDigitalInputDeviceInterface;
import com.diozero.internal.spi.GpioDigitalInputOutputDeviceInterface;
import com.diozero.internal.spi.GpioDigitalOutputDeviceInterface;
import com.diozero.internal.spi.PwmOutputDeviceFactoryInterface;
import com.diozero.internal.spi.PwmOutputDeviceInterface;
import com.diozero.util.BitManipulation;
import com.diozero.util.MutableByte;

/**
 * Support for both MCP23008 and MCP23017 GPIO expansion boards.
 */
public abstract class MCP23xxx extends AbstractDeviceFactory implements GpioDeviceFactoryInterface,
		PwmOutputDeviceFactoryInterface, DeviceEventConsumer, GpioExpander {
	private static enum InterruptMode {
		DISABLED, BANK_A_ONLY, BANK_B_ONLY, BANK_A_AND_B, MIRRORED;
	}
	
	/** Controls how the registers are addressed
	 * 1 = The registers associated with each port are separated into different banks
	 * 0 = The registers are in the same bank (addresses are sequential) */
	private static final byte IOCON_BANK_BIT = 7;
	/** INT Pins Mirror bit
	 * 1 = The INT pins are internally connected
	 * 0 = The INT pins are not connected. INTA is associated with PortA and INTB is associated with PortB */
	private static final byte IOCON_MIRROR_BIT = 6;
	/** Sequential Operation mode bit
	 * 1 = Sequential operation disabled, address pointer does not increment.
	 * 0 = Sequential operation enabled, address pointer increments */
	private static final byte IOCON_SEQOP_BIT = 5;
	/** Slew Rate control bit for SDA output
	 * 1 = Slew rate disabled.
	 * 0 = Slew rate enabled */
	private static final byte IOCON_DISSLW_BIT = 4;
	/** Hardware Address Enable bit (MCP23S17 only). Address pins are always enabled on MCP23017
	 * 1 = Enables the MCP23S17 address pins.
	 * 0 = Disables the MCP23S17 address pins */
	private static final byte IOCON_HAEN_BIT = 3;
	/** This bit configures the INT pin as an open-drain output
	 * 1 = Open-drain output (overrides the INTPOL bit).
	 * 0 = Active driver output (INTPOL bit sets the polarity) */
	private static final byte IOCON_ODR_BIT = 2;
	/** This bit sets the polarity of the INT output pin.
	 * 1 = Active-high.
	 * 0 = Active-low */
	private static final byte IOCON_INTPOL_BIT = 1;
	
	private static final int GPIOS_PER_PORT = 8;
	public static final int INTERRUPT_GPIO_NOT_SET = -1;
	private static final int DEFAULT_PWM_FREQUENCY = 50;

	private String deviceName;
	private DigitalInputDevice[] interruptGpios;
	private MutableByte[] directions = { new MutableByte(), new MutableByte() };
	private MutableByte[] pullUps = { new MutableByte(), new MutableByte() };
	private MutableByte[] interruptOnChangeFlags = { new MutableByte(), new MutableByte() };
	private MutableByte[] defaultValues = { new MutableByte(), new MutableByte() };
	private MutableByte[] interruptCompareFlags = { new MutableByte(), new MutableByte() };
	private InterruptMode interruptMode = InterruptMode.DISABLED;
	private int numPorts;
	private int numGpios;

	public MCP23xxx(int numPorts, String deviceName) throws RuntimeIOException {
		this(numPorts, deviceName, INTERRUPT_GPIO_NOT_SET, INTERRUPT_GPIO_NOT_SET);
	}

	public MCP23xxx(int numPorts, String deviceName, int interruptGpio) throws RuntimeIOException {
		this(numPorts, deviceName, interruptGpio, interruptGpio);
	}

	public MCP23xxx(int numPorts, String deviceName, int interruptGpioA, int interruptGpioB) throws RuntimeIOException {
		super(deviceName);
		
		this.numPorts = numPorts;
		numGpios = numPorts*GPIOS_PER_PORT;
		this.deviceName = deviceName;
		
		interruptGpios = new DigitalInputDevice[numPorts];
		if (interruptGpioA != INTERRUPT_GPIO_NOT_SET) {
			interruptGpios[0] = new DigitalInputDevice(interruptGpioA, GpioPullUpDown.NONE, GpioEventTrigger.RISING);
			
			if (interruptGpioA == interruptGpioB) {
				interruptMode = InterruptMode.MIRRORED;
			} else {
				interruptMode = InterruptMode.BANK_A_ONLY;
			}
		}
		
		// There can only be one interrupt GPIO (A) if there is only one bank of GPIOs
		if (numPorts > 1 && interruptMode != InterruptMode.MIRRORED
				&& interruptGpioB != INTERRUPT_GPIO_NOT_SET) {
			interruptGpios[1] = new DigitalInputDevice(interruptGpioB, GpioPullUpDown.NONE, GpioEventTrigger.RISING);
			
			if (interruptMode == InterruptMode.BANK_A_ONLY) {
				interruptMode = InterruptMode.BANK_A_AND_B;
			} else {
				interruptMode = InterruptMode.BANK_B_ONLY;
			}
		}
	}
	
	@Override
	public final String getName() {
		return deviceName;
	}
	
	protected final void initialise() {
		// Initialise
		// Read the I/O configuration value
		byte start_iocon = readByte(getIOConReg(0));
		Logger.debug("Default power-on values for IOCON: 0x{}", Integer.toHexString(start_iocon));
		if (numPorts > 1) {
			// Is there an IOCONB value?
			Logger.debug("IOCONB: 0x{}", Integer.toHexString(readByte(getIOConReg(1))));
		}
		
		// Configure interrupts
		MutableByte iocon = new MutableByte(start_iocon);
		if (interruptMode == InterruptMode.MIRRORED) {
			// Enable interrupt mirroring
			iocon.setBit(IOCON_MIRROR_BIT);
			iocon.setBit(IOCON_INTPOL_BIT);
		} else if (interruptMode != InterruptMode.DISABLED) {
			// Disable interrupt mirroring
			iocon.unsetBit(IOCON_MIRROR_BIT);
			iocon.setBit(IOCON_INTPOL_BIT);
		}
		iocon.unsetBit(IOCON_BANK_BIT);
		iocon.setBit(IOCON_SEQOP_BIT);
		iocon.unsetBit(IOCON_DISSLW_BIT);
		iocon.setBit(IOCON_HAEN_BIT);
		iocon.unsetBit(IOCON_ODR_BIT);
		if (! iocon.equals(start_iocon)) {
			writeByte(getIOConReg(0), iocon.getValue());
		}
	
		for (int port=0; port= numGpios) {
			throw new IllegalArgumentException("Invalid GPIO: " + gpio + ". "
					+ deviceName + " has " + numGpios + " GPIOs; must be 0.." + (numGpios - 1));
		}
		
		byte bit = (byte) (gpio % GPIOS_PER_PORT);
		int port = gpio / GPIOS_PER_PORT;
		
		byte states = readByte(getGPIOReg(port));
		
		return BitManipulation.isBitSet(states, bit);
	}

	public void setValue(int gpio, boolean value) throws RuntimeIOException {
		if (gpio < 0 || gpio >= numGpios) {
			throw new IllegalArgumentException("Invalid GPIO: " + gpio + ". "
					+ deviceName + " has " + numGpios + " GPIOs; must be 0.." + (numGpios - 1));
		}
		
		byte bit = (byte)(gpio % GPIOS_PER_PORT);
		int port = gpio / GPIOS_PER_PORT;
		
		// Check the direction of the GPIO - can't set the output value for input GPIOs (direction bit is set)
		if (directions[port].isBitSet(bit)) {
			throw new IllegalStateException("Can't set value for input GPIO: " + gpio);
		}
		// Read the current state of this bank of GPIOs
		byte old_val = readByte(getGPIOReg(port));
		byte new_val = BitManipulation.setBitValue(old_val, value, bit);
		writeByte(getOLatReg(port), new_val);
	}
	
	@Override
	public void close() throws RuntimeIOException {
		Logger.trace("close()");
		// Close the interrupt GPIOs
		for (DigitalInputDevice interrupt_gpio : interruptGpios) {
			if (interrupt_gpio != null) { interrupt_gpio.close(); }
		}
		// Close all open GPIOs before closing the I2C device itself
		super.close();
	}

	public void closeGpio(int gpio) throws RuntimeIOException {
		Logger.trace("closeGpio({})", Integer.valueOf(gpio));
		
		if (gpio < 0 || gpio >= numGpios) {
			throw new IllegalArgumentException("Invalid GPIO: " + gpio + ". "
					+ deviceName + " has " + numGpios + " GPIOs; must be 0.." + (numGpios - 1));
		}
		
		byte bit = (byte)(gpio % GPIOS_PER_PORT);
		int port = gpio / GPIOS_PER_PORT;
		
		// Clean-up this GPIO only
		
		if (interruptOnChangeFlags[port].isBitSet(bit)) {
			interruptOnChangeFlags[port].unsetBit(bit);
			writeByte(getGPIntEnReg(port), interruptOnChangeFlags[port].getValue());
		}
		if (defaultValues[port].isBitSet(bit)) {
			defaultValues[port].unsetBit(bit);
			writeByte(getDefValReg(port), defaultValues[port].getValue());
		}
		if (interruptCompareFlags[port].isBitSet(bit)) {
			interruptCompareFlags[port].unsetBit(bit);
			writeByte(getIntConReg(port), interruptCompareFlags[port].getValue());
		}
		if (pullUps[port].isBitSet(bit)) {
			pullUps[port].unsetBit(bit);
			writeByte(getGPPullUpReg(port), pullUps[port].getValue());
		}
		// Default GPIO to input
		if (! directions[port].isBitSet(bit)) {
			directions[port].setBit(bit);
			writeByte(getIODirReg(port), directions[port].getValue());
		}
	}

	@Override
	public void accept(DigitalInputEvent event) {
		Logger.debug("accept({})", event);
		
		if (! event.getValue()) {
			Logger.info("value was false - ignoring");
			return;
		}
		
		// Check the event is for one of the interrupt gpios
		boolean process_event = false;
		for (DigitalInputDevice interrupt_gpio : interruptGpios) {
			if (interrupt_gpio != null && event.getGpio() == interrupt_gpio.getGpio()) {
				process_event = true;
				break;
			}
		}
		if (! process_event) {
			Logger.error("Unexpected interrupt event on gpio {}", Integer.valueOf(event.getGpio()));
			return;
		}
		
		synchronized (this) {
			try {
				byte[] intf = new byte[2];
				byte[] intcap = new byte[2];
				if (interruptMode == InterruptMode.MIRRORED) {
					intf[0] = readByte(getIntFReg(0));
					intcap[0] = readByte(getIntCapReg(0));
					intf[1] = readByte(getIntFReg(1));
					intcap[1] = readByte(getIntCapReg(1));
				} else if (interruptMode != InterruptMode.DISABLED) {
					if (interruptGpios[0] != null && event.getGpio() == interruptGpios[0].getGpio()) {
						intf[0] = readByte(getIntFReg(0));
						intcap[0] = readByte(getIntCapReg(0));
					} else {
						intf[1] = readByte(getIntFReg(1));
						intcap[1] = readByte(getIntCapReg(1));
					}
				}
				for (int port=0; port




© 2015 - 2024 Weber Informatics LLC | Privacy Policy