diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java index c47c0bdc..20e410ff 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/Spi.java @@ -48,7 +48,10 @@ public interface Spi extends IO, AutoCloseable, IOD SpiChipSelect DEFAULT_CHIP_SELECT = SpiChipSelect.CS_0; /** Constant DEFAULT_BAUD=1000000 */ int DEFAULT_BAUD = 1000000; // 1MHz (range is 500kHz - 32MHz) - + /** Constant DEFAULT_WRITE_LSB_FIRST */ + int DEFAULT_WRITE_LSB_FIRST = 0; + /** Constant DEFAULT_READ_LSB_FIRST */ + int DEFAULT_READ_LSB_FIRST = 0; /** *

newConfigBuilder.

* diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java index c4fce94a..0f9a2d05 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfig.java @@ -44,6 +44,12 @@ public interface SpiConfig extends AddressConfig, IOConfig String MODE_KEY = "mode"; /** Constant FLAGS_KEY="flags" */ String FLAGS_KEY = "flags"; + /** Constant WRITE_LSB_KEY="baud" */ + String WRITE_LSB_KEY = "write_lsb"; + /** Constant READ_LSB_KEY="baud" */ + String READ_LSB_KEY = "read_lsb"; + + /** *

newBuilder.

@@ -68,6 +74,42 @@ static SpiConfigBuilder newBuilder(Context context) { */ default Integer getBaud() { return baud(); } + /** + *

ReadLsbFirst.

+ * In accordance with the flags parm, Read operations + * 0 is LSB bit shifted first, 1 MSB bit shifted first + * + * @return a {@link java.lang.Integer} object. + */ + Integer readLsbFirst(); + + /** + *

getreadLsbfISRT.

+ * + * @return a {@link java.lang.Integer} object. + */ + default Integer getReadLsbFirst() { return readLsbFirst(); } + + + + /** + *

WriteLsbFirst.

+ * In accordance with the flags parm, Write operations + * 0 is LSB bit shifted first, 1 MSB bit shifted first + * + * @return a {@link java.lang.Integer} object. + */ + Integer writeLsbFirst(); + + /** + *

getreadLsbfISRT.

+ * + * @return a {@link java.lang.Integer} object. + */ + default Integer getWriteLsbFirst() { return writeLsbFirst(); } + + + /** *

bus.

*

If the Bus value is configured, that SpiBus @@ -100,6 +142,36 @@ default boolean getBusUserProvided(){ return busUserProvided(); } + /** + *

writeLsbFirstserProvided.

+ * @return a boolean. + */ + boolean writeLsbFirstUserProvided(); + + /** + *

getWriteLsbFirstUserProvided.

+ * @return a {@link java.lang.Boolean} object. + */ + default boolean getWriteLsbFIrstUserProvided(){ + return writeLsbFirstUserProvided(); + } + + /** + *

writeLsbFirstserProvided.

+ * @return a boolean. + */ + boolean readLsbFirstUserProvided(); + + /** + *

getReadLsbFirstUserProvided.

+ * @return a {@link java.lang.Boolean} object. + */ + default boolean getReadLsbFIrstUserProvided(){ + return readLsbFirstUserProvided(); + } + + + /** *

mode.

diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java index 2e418b06..7b4959e2 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/SpiConfigBuilder.java @@ -47,6 +47,24 @@ static SpiConfigBuilder newInstance(Context context) { return DefaultSpiConfigBuilder.newInstance(context); } + /** + *

readLsbFirst.

+ * + * @param shift a {@link java.lang.Integer} object. + * @return a {@link com.pi4j.io.spi.SpiConfigBuilder} object. + */ + SpiConfigBuilder readLsbFirst(Integer shift); + + /** + *

writeLsbFirst.

+ * + * @param shift a {@link java.lang.Integer} object. + * @return a {@link com.pi4j.io.spi.SpiConfigBuilder} object. + */ + SpiConfigBuilder writeLsbFirst(Integer shift); + + + /** *

baud.

* @@ -55,6 +73,7 @@ static SpiConfigBuilder newInstance(Context context) { */ SpiConfigBuilder baud(Integer rate); + /** *

bus.

*

If the Bus value is configured, that SpiBus diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java index 60ae6e45..88106168 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfig.java @@ -48,6 +48,10 @@ public class DefaultSpiConfig protected final SpiBus bus; protected boolean busUserProvided = false; // indicate user supplied the value protected final Long flags; + protected final int readLsbFirst; + protected final int writeLsbFirst; + protected boolean readLsbFirstUserProvided; + protected boolean writeLsbFirstUserProvided; /** * PRIVATE CONSTRUCTOR @@ -73,6 +77,20 @@ protected DefaultSpiConfig(Map properties){ this.busUserProvided = false; } + if(properties.containsKey(WRITE_LSB_KEY)){ + this.writeLsbFirst = StringUtil.parseInteger(properties.get(WRITE_LSB_KEY),Spi.DEFAULT_WRITE_LSB_FIRST); + this.writeLsbFirstUserProvided = true; + }else { + this.writeLsbFirst = 0; + this.writeLsbFirstUserProvided = false; + } + if(properties.containsKey(READ_LSB_KEY)){ + this.readLsbFirst = StringUtil.parseInteger(properties.get(READ_LSB_KEY),Spi.DEFAULT_READ_LSB_FIRST); + this.readLsbFirstUserProvided = true; + }else { + this.readLsbFirst = 0; + this.readLsbFirstUserProvided = false; + } // load optional MODE from properties if(properties.containsKey(MODE_KEY)){ this.mode = SpiMode.parse(properties.get(MODE_KEY)); @@ -86,7 +104,7 @@ protected DefaultSpiConfig(Map properties){ if(properties.containsKey(FLAGS_KEY)){ this.flags = StringUtil.parseLong(properties.get(FLAGS_KEY), null); } else { - this.flags = 0L; // default flags (0) + this.flags = null; // set null, same as parseLong would } // define default property values if any are missing (based on the required address value) @@ -101,6 +119,16 @@ public Integer baud() { return this.baud; } + @Override + public Integer readLsbFirst() { + return this.readLsbFirst; + } + + @Override + public Integer writeLsbFirst() { + return this.writeLsbFirst; + } + /** {@inheritDoc} */ @Override public boolean busUserProvided() { @@ -108,6 +136,17 @@ public boolean busUserProvided() { } + /** {@inheritDoc} */ + @Override + public boolean writeLsbFirstUserProvided() { + return this.writeLsbFirstUserProvided; + } + /** {@inheritDoc} */ + @Override + public boolean readLsbFirstUserProvided() { + return this.readLsbFirstUserProvided; + } + /** {@inheritDoc} */ @Override diff --git a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java index cca1a4d5..085988bc 100644 --- a/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java +++ b/pi4j-core/src/main/java/com/pi4j/io/spi/impl/DefaultSpiConfigBuilder.java @@ -56,6 +56,18 @@ public static SpiConfigBuilder newInstance(Context context) { return new DefaultSpiConfigBuilder(context); } + @Override + public SpiConfigBuilder readLsbFirst(Integer shift) { + this.properties.put(SpiConfig.READ_LSB_KEY, shift.toString()); + return this; + } + + @Override + public SpiConfigBuilder writeLsbFirst(Integer shift) { + this.properties.put(SpiConfig.WRITE_LSB_KEY, shift.toString()); + return this; + } + /** {@inheritDoc} */ @Override public SpiConfigBuilder baud(Integer rate) { diff --git a/plugins/pi4j-plugin-linuxfs/pom.xml b/plugins/pi4j-plugin-linuxfs/pom.xml index 1374ed75..d9dc07d6 100644 --- a/plugins/pi4j-plugin-linuxfs/pom.xml +++ b/plugins/pi4j-plugin-linuxfs/pom.xml @@ -21,6 +21,11 @@ jsch ${jsch.version} + + net.java.dev.jna + jna + ${jna.version} + com.pi4j pi4j-library-linuxfs diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java index e4702db2..a21d2a37 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/LinuxFsPlugin.java @@ -37,6 +37,7 @@ import com.pi4j.plugin.linuxfs.provider.gpio.digital.LinuxFsDigitalOutputProvider; import com.pi4j.plugin.linuxfs.provider.pwm.LinuxFsPwmProvider; import com.pi4j.plugin.linuxfs.internal.LinuxPwm; +import com.pi4j.plugin.linuxfs.provider.spi.LinuxFsSpiProvider; import com.pi4j.provider.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,14 +101,15 @@ public class LinuxFsPlugin implements Plugin { public static final String I2C_PROVIDER_NAME = NAME + " I2C Provider"; public static final String I2C_PROVIDER_ID = ID + "-i2c"; -// // SPI Provider name and unique ID -// public static final String SPI_PROVIDER_NAME = NAME + " SPI Provider"; -// public static final String SPI_PROVIDER_ID = ID + "-spi"; -// + // // Serial Provider name and unique ID // public static final String SERIAL_PROVIDER_NAME = NAME + " Serial Provider"; // public static final String SERIAL_PROVIDER_ID = ID + "-serial"; + // SPI Provider name and unique ID + public static final String SPI_PROVIDER_NAME = NAME + " SPI Provider"; + public static final String SPI_PROVIDER_ID = ID + "-spi"; + public static String DEFAULT_GPIO_FILESYSTEM_PATH = LinuxGpio.DEFAULT_SYSTEM_PATH; public static String DEFAULT_PWM_FILESYSTEM_PATH = LinuxPwm.DEFAULT_SYSTEM_PATH; @@ -155,7 +157,8 @@ public void initialize(PluginService service) { LinuxFsDigitalInputProvider.newInstance(gpioFileSystemPath), LinuxFsDigitalOutputProvider.newInstance(gpioFileSystemPath), LinuxFsPwmProvider.newInstance(pwmFileSystemPath, pwmChip), - LinuxFsI2CProvider.newInstance() + LinuxFsI2CProvider.newInstance(), + LinuxFsSpiProvider.newInstance() }; // register the LinuxFS I/O Providers with the plugin service diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java new file mode 100644 index 00000000..fc246a52 --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/internal/LinuxLibC.java @@ -0,0 +1,84 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxLibC.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + +package com.pi4j.plugin.linuxfs.internal; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +/** + * C library functions. + * + * @author mpilone + * @since 10/3/24. + */ +public interface LinuxLibC extends Library { + + LinuxLibC INSTANCE = LinuxLibC.LibLoader.load(); + + class LibLoader { + static LinuxLibC load() { + return Native.load("c", LinuxLibC.class); + } + } + + /////////////////////////////////// + // fcntl.h + int O_WRONLY = 00000001; + int O_RDWR = 00000002; + int O_NONBLOCK = 00004000; + + /////////////////////////////////// + // ioctl.h + int _IOC_NRBITS = 8; + int _IOC_TYPEBITS = 8; + int _IOC_SIZEBITS = 14; + + int _IOC_NRSHIFT = 0; + int _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS); + int _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS); + int _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS); + + byte _IOC_NONE = 0; + byte _IOC_WRITE = 1; + byte _IOC_READ = 2; + + static int _IOC(byte dir, byte type, byte nr, int size) { + return (((dir) << _IOC_DIRSHIFT) | + ((type) << _IOC_TYPESHIFT) | + ((nr) << _IOC_NRSHIFT) | + ((size) << _IOC_SIZESHIFT)); + } + + int ioctl(int filedes, long op, Object... args); + + int open(String pathname, int flags); + + int close(int fd); +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java new file mode 100644 index 00000000..878cbdfe --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpi.java @@ -0,0 +1,359 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpi.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.io.exception.IOException; +import com.pi4j.io.spi.Spi; +import com.pi4j.io.spi.SpiBase; +import com.pi4j.io.spi.SpiConfig; +import com.pi4j.plugin.linuxfs.internal.LinuxLibC; +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.ptr.IntByReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.pi4j.plugin.linuxfs.internal.LinuxLibC.*; + +/** + * SPI implementation that uses JNA bindings to the Linux SPI device (i.e. /dev/spidev0.0). Only supports writing but + * it works to drive an SSD1306 OLED display. + * + * @author mpilone + * @see + * spi-c-ioctl + * @see spidev.h + * @see spi.h + * @see spidev_fdx.c + * @see SPI userspace API + * @since 10/3/24. + */ +public class LinuxFsSpi extends SpiBase implements Spi { + + private static final Logger LOG = LoggerFactory.getLogger(LinuxFsSpi.class); + + /////////////////////////////////// + // From spidev.h + private final static byte SPI_IOC_MAGIC = 'k'; + private final static byte SIZE_OF_BYTE = 1; + private final static byte SIZE_OF_INT = 4; + + private static int SPI_IOC_MESSAGE(int n) { + + // Even though we will pass the structure to ioctl as a pointer, the command needs to know + // the actual size of the structure (i.e. sizeof). Therefore, we use the ByValue interface + // when getting the struct size. + int structSize = Native.getNativeSize(spi_ioc_transfer.ByValue.class); + int msgSize = ((((n)*(structSize)) < (1 << _IOC_SIZEBITS)) + ? ((n)*(structSize)) : 0); + + return _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)0, msgSize); + } + + // These could be replaced with the specific values generated from the _IOC method (a macro in the native C), + // but I think it is useful to see where the values come from. + + /* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */ + private final static int SPI_IOC_RD_MODE = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)1, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_MODE = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)1, SIZE_OF_BYTE); + + /* Read / Write SPI bit justification */ + private final static int SPI_IOC_RD_LSB_FIRST = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)2, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_LSB_FIRST = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)2, SIZE_OF_BYTE); + + /* Read / Write SPI device word length (1..N) */ + private final static int SPI_IOC_RD_BITS_PER_WORD = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)3, SIZE_OF_BYTE); + private final static int SPI_IOC_WR_BITS_PER_WORD = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)3, SIZE_OF_BYTE); + + /* Read / Write SPI device default max speed hz */ + private final static int SPI_IOC_RD_MAX_SPEED_HZ = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)4, SIZE_OF_INT); + private final static int SPI_IOC_WR_MAX_SPEED_HZ = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)4, SIZE_OF_INT); + + /* Read / Write of the SPI mode field */ + private final static int SPI_IOC_RD_MODE32 = _IOC(_IOC_READ, SPI_IOC_MAGIC, (byte)5, SIZE_OF_INT); + private final static int SPI_IOC_WR_MODE32 = _IOC(_IOC_WRITE, SPI_IOC_MAGIC, (byte)5, SIZE_OF_INT); + + @Structure.FieldOrder({"tx_buf", "rx_buf", + "len", "speed_hz", + "delay_usecs", "bits_per_word", "cs_change", "tx_nbits", "rx_nbits", "word_delay_usecs", "pad"}) + public static class spi_ioc_transfer extends Structure { + public long tx_buf; + public long rx_buf; + + public int len; + public int speed_hz; + + public short delay_usecs; + public byte bits_per_word; + public byte cs_change; + public byte tx_nbits; + public byte rx_nbits; + public byte word_delay_usecs; + public byte pad; + + public static class ByValue extends spi_ioc_transfer implements Structure.ByValue {} + } + + /////////////////////////////////// + // spi.h + private final byte SPI_CPHA = 1; /* clock phase */ + private final byte SPI_CPOL = 1 << 1; /* clock polarity */ + + private final byte SPI_MODE_0 = (0|0); /* (original MicroWire) */ + private final byte SPI_MODE_1 = (0|SPI_CPHA); + private final byte SPI_MODE_2 = (SPI_CPOL|0); + private final byte SPI_MODE_3 = (SPI_CPOL|SPI_CPHA); + + + private final byte BITS8 = 8; + + private final static String SPI_DEVICE_BASE = "/dev/spidev"; + private final LinuxLibC libc = LinuxLibC.INSTANCE; + private int fd; + + public LinuxFsSpi(LinuxFsSpiProviderImpl provider, SpiConfig config) { + super(provider, config); + } + + @Override + public void open() { + super.open(); + + // From the docs (https://www.kernel.org/doc/html/v6.12/spi/spidev.html): + // For a SPI device with chipselect C on bus B, you should see: + // + // /dev/spidevB.C ... + // character special device, major number 153 with a dynamically chosen minor device number. + // This is the node that userspace programs will open, created by “udev” or “mdev”. + String spiDev = SPI_DEVICE_BASE + config().bus().getBus() + "." + config().getChipSelect().getChipSelect(); + fd = libc.open(spiDev, LinuxLibC.O_RDWR); + if (fd < 0) { + throw new RuntimeException("Failed to open SPI device " + spiDev); + } + + IntByReference intPtr = new IntByReference(); + int ret = libc.ioctl(fd, SPI_IOC_RD_MODE32, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not read SPI mode."); + } + + // if 'flags' were provided error + if(config().flags() != null){ + throw new IOException("Unsupported SPI Pi5 parameter flags" ); + } + switch (config().mode()) { + case MODE_0: + intPtr.setValue(intPtr.getValue() | SPI_MODE_0); + break; + case MODE_1: + intPtr.setValue(intPtr.getValue() | SPI_MODE_1); + break; + case MODE_2: + intPtr.setValue(intPtr.getValue() | SPI_MODE_2); + break; + case MODE_3: + intPtr.setValue(intPtr.getValue() | SPI_MODE_3); + break; + } + + ret = libc.ioctl(fd, SPI_IOC_WR_MODE32, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write SPI mode.."); + } + + intPtr.setValue(config().baud()); + ret = libc.ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not read the SPI max speed."); + } + + intPtr.setValue(config().baud()); + ret = libc.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI max speed."); + } + + // Bits per word + intPtr.setValue(BITS8); + ret = libc.ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI BITS per write."); + } + // Bits per word + intPtr.setValue(BITS8); + ret = libc.ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI BITS per read."); + } + + // BIT shift direction + intPtr.setValue(config().readLsbFirst()); + ret = libc.ioctl(fd, SPI_IOC_RD_LSB_FIRST, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI SHIFT read."); + } + intPtr.setValue(config().writeLsbFirst()); + ret = libc.ioctl(fd, SPI_IOC_WR_LSB_FIRST, intPtr); + if(ret != 0) { + libc.close(fd); + throw new RuntimeException("Could not write the SPI SHIFT write."); + } + + + } + + @Override + public void close() { + libc.close(fd); + + super.close(); + } + + @Override + public int transfer(byte[] write, int writeOffset, byte[] read, int readOffset, int numberOfBytes) { + PeerAccessibleMemory buf = new PeerAccessibleMemory(numberOfBytes); + buf.write(0, write, writeOffset, numberOfBytes); + + // According to the docs you can use the same buffer for tx/rx. + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = buf.getPeer(); + transfer.rx_buf = buf.getPeer(); + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = numberOfBytes; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPI message. ret {}, error: {}", ret, Native.getLastError()); + numberOfBytes = -1; + } + else { + buf.read(0, read, readOffset, numberOfBytes); + } + + buf.close(); + + return numberOfBytes; + } + + @Override + public int read() { + byte[] buf = new byte[1]; + if (read(buf, 0, 1) == 1) { + return buf[0]; + } + else { + return -1; + } + } + + @Override + public int read(byte[] read, int offset, int length) { + PeerAccessibleMemory buf = new PeerAccessibleMemory(length); + + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = 0; + transfer.rx_buf = buf.getPeer(); + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = length; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPIIOC message. ret {}, error: {}", ret, Native.getLastError()); + length = -1; + }else{ + buf.read(0, read, offset, length); + } + + buf.close(); + + return length; + } + + @Override + public int write(byte b) { + return write(new byte[] {b}, 0, 1); + } + + @Override + public int write(byte[] data, int offset, int length) { + // We could reuse this buffer for all requests as long as it is large enough. + // For now we'll alloc/free a new one on each request. + PeerAccessibleMemory buf = new PeerAccessibleMemory(length); + buf.write(0, data, offset, length); + + spi_ioc_transfer transfer = new spi_ioc_transfer(); + transfer.tx_buf = buf.getPeer(); + transfer.rx_buf = 0; + transfer.bits_per_word = BITS8; + transfer.speed_hz = config.baud(); + transfer.delay_usecs = 0; + transfer.len = length; + + int ret = libc.ioctl(fd, SPI_IOC_MESSAGE(1), transfer); + if (ret < 0) { + LOG.error("Could not write SPI message. ret {}, error: {}", ret, Native.getLastError()); + length = 0; + } + + buf.close(); + + return length; + } + + /** + * Extension of Memory to allow direct access to the native peer pointer. This is required because + * the {@link spi_ioc_transfer} structure uses a long for the tx_buf and rx_buf fields but a + * native pointer is normally only 32 bitsso we can't let JNA do the mapping for us. There may be a better + * way to do this with JNA. We fetch the peer and put it in a long in the struct to maintain proper struct + * alignment. + */ + private static class PeerAccessibleMemory extends Memory { + public PeerAccessibleMemory(long size) { + super(size); + } + + long getPeer() { + return peer; + } + } +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java new file mode 100644 index 00000000..c4835c7b --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProvider.java @@ -0,0 +1,62 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpiProvider.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.io.spi.SpiProvider; +import com.pi4j.plugin.linuxfs.LinuxFsPlugin; + +/** + *

LinuxFsSpiProvider interface.

+ * + * @author mpilone + * @since 10/4/24. + */ +public interface LinuxFsSpiProvider extends SpiProvider { + + + + + + /** {@link LinuxFsPlugin#SPI_PROVIDER_NAME} */ + String NAME = LinuxFsPlugin.SPI_PROVIDER_NAME; + /** {@link LinuxFsPlugin#SPI_PROVIDER_ID} */ + String ID = LinuxFsPlugin.SPI_PROVIDER_ID; + + /** + *

newInstance.

+ * + * @return a {@link LinuxFsSpiProviderImpl} object. + */ + static LinuxFsSpiProviderImpl newInstance() { + return new LinuxFsSpiProviderImpl(); + } + +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java new file mode 100644 index 00000000..5e064b5f --- /dev/null +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/spi/LinuxFsSpiProviderImpl.java @@ -0,0 +1,81 @@ +/* + * * Copyright (C) 2012 - 2024 Pi4J + * * %% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * - + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: EXTENSION + * FILENAME : LinuxFsSpiProviderImpl.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * %% + */ + + +package com.pi4j.plugin.linuxfs.provider.spi; + +import com.pi4j.boardinfo.util.BoardInfoHelper; +import com.pi4j.context.Context; +import com.pi4j.exception.ShutdownException; +import com.pi4j.io.spi.Spi; +import com.pi4j.io.spi.SpiConfig; +import com.pi4j.io.spi.SpiProvider; +import com.pi4j.io.spi.SpiProviderBase; + +/** + * @author mpilone + * @since 10/3/24. + */ +public class LinuxFsSpiProviderImpl extends SpiProviderBase + implements LinuxFsSpiProvider { + + public LinuxFsSpiProviderImpl() { + this.id = ID; + this.name = NAME; + } + + @Override + public int getPriority() { + // the linux FS driver should always be higher priority + return BoardInfoHelper.usesRP1() ? 150 : 50; + } + + @Override + public Spi create(SpiConfig config) { + Spi spi = new LinuxFsSpi(this, config); + + // Is this the right place to call open? Should we have a shared spi device like the I2CBus? + spi.open(); + + this.context.registry().add(spi); + return spi; + } + + @Override + public SpiProvider shutdown(Context context) throws ShutdownException { + + // Is this the right place to call close? + this.context.registry().allByType(LinuxFsSpi.class).values() + .forEach(LinuxFsSpi::close); + + return super.shutdown(context); + } + + +} \ No newline at end of file diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java b/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java index 365fd0ee..a9bcdde6 100644 --- a/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java +++ b/plugins/pi4j-plugin-linuxfs/src/main/java/module-info.java @@ -34,11 +34,13 @@ requires com.pi4j; requires com.pi4j.library.linuxfs; requires jsch; + requires com.sun.jna; exports com.pi4j.plugin.linuxfs; exports com.pi4j.plugin.linuxfs.provider.gpio.digital; exports com.pi4j.plugin.linuxfs.provider.pwm; exports com.pi4j.plugin.linuxfs.provider.i2c; + exports com.pi4j.plugin.linuxfs.provider.spi; provides com.pi4j.extension.Plugin with LinuxFsPlugin; diff --git a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java index 13fcad7e..95769280 100644 --- a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java +++ b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/spi/PiGpioSpi.java @@ -46,6 +46,8 @@ public class PiGpioSpi extends SpiBase implements Spi { protected final int handle; protected static int SPI_BUS_MASK = 0x0100; protected static int SPI_MODE_MASK = 0x0003; + protected static int SPI_WRITE_LSB_FIRST_MASK = 0x4000; + protected static int SPI_READ_LSB_FIRST_MASK = 0x8000; /** *

Constructor for PiGpioSpi.

@@ -119,6 +121,21 @@ public PiGpioSpi(PiGpio piGpio, SpiProvider provider, SpiConfig config) { throw new IOException("Unsupported SPI mode on AUX SPI BUS_1: mode=" + mode.toString()); } + if(config.writeLsbFirstUserProvided()) { // user provided, overwrite flags + if (config().getWriteLsbFirst() == 0) { + flags = (flags | (0xFFFFFFFF ^ SPI_WRITE_LSB_FIRST_MASK)); // clear bit + }else { + flags = (flags | (0xFFFFFFFF ^ SPI_WRITE_LSB_FIRST_MASK)) |SPI_WRITE_LSB_FIRST_MASK; // clear AUX bit + } + } + + if(config.readLsbFirstUserProvided()) { // user provided, overwrite flags + if (config().getReadLsbFirst() == 0) { + flags = (flags | (0xFFFFFFFF ^ SPI_READ_LSB_FIRST_MASK)); // clear bit + }else { + flags = (flags | (0xFFFFFFFF ^ SPI_READ_LSB_FIRST_MASK)) |SPI_READ_LSB_FIRST_MASK; // clear AUX bit + } + } if(config.busUserProvided()) { // user provided, overwrite flags // update flags value with BUS bit ('A' 0x0000=BUS0; 0x0100=BUS1) diff --git a/pom.xml b/pom.xml index 3e8717da..10888cf0 100644 --- a/pom.xml +++ b/pom.xml @@ -265,6 +265,7 @@ 5.10.2 2.0.12 2.10.4 + 5.15.0 3.2.0