// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Device.Gpio; using System.Device.Spi; using System.IO; using System.Runtime.InteropServices; using System.Threading; using Iot.Device.Ft232H; using Iot.Device.Board; using Iot.Device.Ft2232H; using Iot.Device.Ft4232H; namespace Iot.Device.FtCommon { /// /// FTx32H base Device /// public class Ftx232HDevice : FtDevice, IDisposable { private const int PinNumberFT2x = 16; private const int PinNumberFT4x = 8; /// /// Number of pins /// internal const int PinCountConst = 16; // Commands are available in: // - AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf // - AN_113_FTDI_Hi_Speed_USB_To_I2C_Example.pdf // To understand the signal need for I2C communication: https://training.ti.com/sites/default/files/docs/slides-i2c-protocol.pdf private const uint I2cMasterFrequencyKbps = 400; // Direction values private const byte I2cDirSDAinSCLin = 0x00; private const byte I2cDirSDAinSCLout = 0x01; private const byte I2cDirSDAoutSCLin = 0x02; private const byte I2cDirSDAoutSCLout = 0x03; // Data values private const byte I2cDataSDAloSCLhi = 0x01; private const byte I2cDataSDAhiSCLhi = 0x03; private const byte I2cDataSDAloSCLlo = 0x00; private const byte I2cDataSDAhiSCLlo = 0x02; private const byte NumberCycles = 6; private const byte MaskGpio = 0xF8; private SafeFtHandle _ftHandle = null!; // This is used by FT232H and others to track the GPIO states internal byte GpioLowData = 0; internal byte GpioLowDir = 0; internal byte GpioHighData = 0; internal byte GpioHighDir = 0; internal bool[] PinOpen = new bool[PinCountConst]; internal PinMode[] GpioDirections = new PinMode[PinCountConst]; internal bool UsesMpseeForGpio = true; internal List ConnectionSettings = new List(); /// /// Gets all the FTx232H connected /// /// A list of FTx232H public static List GetFtx232H() { List ft232s = new List(); var devices = FtCommon.GetDevices(new FtDeviceType[] { FtDeviceType.Ft232H, FtDeviceType.Ft2232H, FtDeviceType.Ft4232H, FtDeviceType.Ft2232 }); foreach (var device in devices) { var dev = device.Type switch { FtDeviceType.Ft2232 or FtDeviceType.Ft2232H => new Ft2232HDevice(device), FtDeviceType.Ft232H => new Ft232HDevice(device), FtDeviceType.Ft4232H => new Ft4232HDevice(device), _ => new Ftx232HDevice(device), }; ft232s.Add(dev); } return ft232s; } /// /// Instantiates a FTx232H device object. /// /// Indicates device state. /// Indicates the device type. /// The Vendor ID and Product ID of the device. /// The physical location identifier of the device. /// The device serial number. /// The device description. public Ftx232HDevice(FtFlag flags, FtDeviceType type, uint id, uint locId, string serialNumber, string description) : base(flags, type, id, locId, serialNumber, description) { } /// /// Instantiates a FTx232H device object. /// /// a FT Device public Ftx232HDevice(FtDevice ftDevice) : base(ftDevice.Flags, ftDevice.Type, ftDevice.Id, ftDevice.LocId, ftDevice.SerialNumber, ftDevice.Description) { } /// /// Gets the channel. /// public FtChannel Channel { get { return SerialNumber switch { "A" => FtChannel.A, "B" => FtChannel.B, "C" => FtChannel.C, "D" => FtChannel.D, _ => FtChannel.A, }; } } /// /// Gets the number of pins for this specific FT device. /// public int PinCount => Type == FtDeviceType.Ft4232H ? PinNumberFT4x : PinNumberFT2x; /// /// Gets or sets the I2C Bus frequency. Default value is 400 KHz. /// public uint I2cBusFrequencyKbps { get; set; } = I2cMasterFrequencyKbps; /// public override int GetDefaultI2cBusNumber() { return 0; } /// public override int[] GetDefaultPinAssignmentForI2c(int busId) { return new[] { 0, 1 }; } /// public override int[] GetDefaultPinAssignmentForSpi(SpiConnectionSettings connectionSettings) { return new[] { 0, 1, 2, 3 }; } /// protected override I2cBusManager CreateI2cBusCore(int busNumber, int[]? pins) { if (busNumber != 0) { throw new ArgumentOutOfRangeException(nameof(busNumber)); } return new I2cBusManager(this, busNumber, pins, new Ft232HI2cBus(this)); } /// /// Creates SPI device related to this device /// /// The SPI settings /// The pins to use /// a SPI device /// You can create either an I2C, either an SPI device. /// You can create multiple SPI devices, the first one will be the one used for the clock frequency. /// They all have to have different Chip Select. You can use any of the 3 to 15 pin for this function. protected override SpiDevice CreateSimpleSpiDevice(SpiConnectionSettings settings, int[] pins) => new Ft232HSpi(settings, this); /// /// Creates the controller /// /// A new GPIO driver protected override GpioDriver? TryCreateBestGpioDriver() { return new Ft232HGpio(this); } internal bool IsI2cModeEnabled { get; set; } internal bool IsSpiModeEnabled { get; set; } internal void GetHandle() { if ((_ftHandle == null) || _ftHandle.IsClosed) { // Open device if not opened var ftStatus = FtFunction.FT_OpenEx(LocId, FtOpenType.OpenByLocation, out _ftHandle); if (ftStatus != FtStatus.Ok) { throw new IOException($"Failed to open device {Description}, status: {ftStatus}"); } } } /// /// Resets the device. /// public void Reset() { GetHandle(); FtFunction.FT_ResetDevice(_ftHandle); } #region I2C internal void I2cInitialize() { GetHandle(); // check is any of the first 3 GPIO are open if (PinOpen[0] || PinOpen[1] || PinOpen[2]) { throw new IOException("Can't open I2C if GPIO 0, 1 or 2 are open"); } if (IsSpiModeEnabled) { throw new IOException("Can't open I2C if SPI mode is used"); } var ftStatus = FtFunction.FT_SetTimeouts(_ftHandle, 5000, 5000); ftStatus |= FtFunction.FT_SetLatencyTimer(_ftHandle, 16); ftStatus |= FtFunction.FT_SetFlowControl(_ftHandle, (ushort)FtFlowControl.FT_FLOW_RTS_CTS, 0x00, 0x00); ftStatus |= FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.ResetIoBitMode); ftStatus |= FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.Mpsee); if (ftStatus != FtStatus.Ok) { throw new IOException($"Failed to setup device {Description}, status: {ftStatus} in MPSSE mode"); } IsI2cModeEnabled = true; DiscardInput(); SetupMpsseMode(); // Now setup the clock and other elements Span toSend = stackalloc byte[13]; int idx = 0; // Disable clock divide by 5 for 60Mhz master clock toSend[idx++] = (byte)FtOpcode.DisableClockDivideBy5; // Turn off adaptive clocking toSend[idx++] = (byte)FtOpcode.TurnOffAdaptativeClocking; // Enable 3 phase data clock, used by I2C to allow data on both clock edges toSend[idx++] = (byte)FtOpcode.Enable3PhaseDataClocking; // The SK clock frequency can be worked out by below algorithm with divide by 5 set as off // TCK period = 60MHz / (( 1 + [ (0xValueH * 256) OR 0xValueL] ) * 2) // Command to set clock divisor toSend[idx++] = (byte)FtOpcode.SetClockDivisor; uint clockDivisor = 60000 / (I2cBusFrequencyKbps * 2) - 1; toSend[idx++] = (byte)(clockDivisor & 0x00FF); toSend[idx++] = (byte)((clockDivisor >> 8) & 0x00FF); // loopback off toSend[idx++] = (byte)FtOpcode.DisconnectTDItoTDOforLoopback; // Enable the FT232H's drive-zero mode with the following enable mask toSend[idx++] = (byte)FtOpcode.SetIOOnlyDriveOn0AndTristateOn1; // Low byte (ADx) enables - bits 0, 1 and 2 toSend[idx++] = 0x07; // High byte (ACx) enables - all off toSend[idx++] = 0x00; // Command to set directions of lower 8 pins and force value on bits set as output toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; if (Type == FtDeviceType.Ft232H) { // SDA and SCL both output high(open drain) GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } else { // SDA and SCL set low but as input to mimic open drain GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLin | (GpioLowDir & MaskGpio)); } toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; Write(toSend); } /// /// Multi-Protocol Synchronous Serial Engine (MPSSE). The purpose of the MPSSE command processor is to /// communicate with devices which use synchronous protocols (such as JTAG or SPI) in an efficient manner. /// internal void SetupMpsseMode() { // Seems that we have to send a wrong command to get the MPSSE mode working // First with 0xAA Span toSend = stackalloc byte[1]; toSend[0] = 0xAA; Write(toSend); Span toRead = stackalloc byte[2]; Read(toRead); if (!((toRead[0] == 0xFA) && (toRead[1] == 0xAA))) { throw new IOException($"Failed to setup device {Description} in MPSSE mode using magic 0xAA sync"); } // Second with 0xAB toSend[0] = 0xAB; Write(toSend); Read(toRead); if (!((toRead[0] == 0xFA) && (toRead[1] == 0xAB))) { throw new IOException($"Failed to setup device {Description}, status in MPSSE mode using magic 0xAB sync"); } } internal void I2cStart() { int count; int idx = 0; // SDA high, SCL high // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } else { GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLin | (GpioLowDir & MaskGpio)); } Span toSend = stackalloc byte[NumberCycles * 3 * 3 + 3]; for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // SDA lo, SCL high // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); } else { GpioLowDir = (byte)(I2cDirSDAoutSCLin | (GpioLowDir & MaskGpio)); } for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // SDA lo, SCL lo // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); } else { GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // Release SDA // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); } else { GpioLowDir = (byte)(I2cDirSDAinSCLout | (GpioLowDir & MaskGpio)); } toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; Write(toSend); } internal void I2cStop() { int count; int idx = 0; // SDA low, SCL low GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); Span toSend = stackalloc byte[NumberCycles * 3 * 3]; for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // SDA low, SCL high // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } else { GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLin | (GpioLowDir & MaskGpio)); } for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // SDA high, SCL high // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } else { GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLin | (GpioLowDir & MaskGpio)); } for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } Write(toSend); } internal void I2cLineIdle() { int idx = 0; // SDA low, SCL low // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); } else { GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLin | (GpioLowDir & MaskGpio)); } Span toSend = stackalloc byte[3]; toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; Write(toSend); IsI2cModeEnabled = false; } internal bool I2cSendByteAndCheckACK(byte data) { int idx = 0; Span toSend = stackalloc byte[Type == FtDeviceType.Ft232H ? 10 : 13]; Span toRead = stackalloc byte[1]; // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { // Just clock with one byte (0 = 1 byte) toSend[idx++] = (byte)FtOpcode.ClockDataBytesOutOnMinusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = 0; toSend[idx++] = data; // Put line back to idle (data released, clock pulled low) GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; // Clock in (0 = 1 byte) toSend[idx++] = (byte)FtOpcode.ClockDataBitsInOnPlusVeClockMSBFirst; toSend[idx++] = 0; } else { // Set directions and clock data GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; // Just clock with one byte (0 = 1 byte) toSend[idx++] = (byte)FtOpcode.ClockDataBytesOutOnMinusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = 0; toSend[idx++] = data; // Put line back to idle (data released, clock pulled low) // Set directions and clock data GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; // Clock in (0 = 1 byte) toSend[idx++] = (byte)FtOpcode.ClockDataBitsInOnPlusVeClockMSBFirst; toSend[idx++] = 0; } // And ask it right away toSend[idx++] = (byte)FtOpcode.SendImmediate; Write(toSend); Read(toRead); // Bit 0 equivalent to acknoledge, otherwise nack return (toRead[0] & 0x01) == 0; } internal bool I2cSendDeviceAddrAndCheckACK(byte Address, bool Read) { // Set address for read or write Address <<= 1; if (Read == true) { Address |= 0x01; } return I2cSendByteAndCheckACK(Address); } internal byte I2CReadByte(bool ack) { int idx = 0; Span toSend = stackalloc byte[Type == FtDeviceType.Ft232H ? 10 : 16]; Span toRead = stackalloc byte[1]; // The behavior is a bit different for FT232H and FT2232H/FT4232H if (Type == FtDeviceType.Ft232H) { // Read one byte toSend[idx++] = (byte)FtOpcode.ClockDataBytesInOnPlusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = 0; // Send out either ack either nak toSend[idx++] = (byte)FtOpcode.ClockDataBitsOutOnMinusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = (byte)(ack ? 0x00 : 0xFF); // I2C lines back to idle state toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } else { // Make sure no open gain GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; // Read one byte toSend[idx++] = (byte)FtOpcode.ClockDataBytesInOnPlusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = 0; // Change direction GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; // Send out either ack either nak toSend[idx++] = (byte)FtOpcode.ClockDataBitsOutOnMinusVeClockMSBFirst; toSend[idx++] = 0; toSend[idx++] = (byte)(ack ? 0x00 : 0xFF); // I2C lines back to idle state toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); GpioLowDir = (byte)(I2cDirSDAinSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } // And ask it right away toSend[idx++] = (byte)FtOpcode.SendImmediate; Write(toSend); Read(toRead); return toRead[0]; } #endregion #region gpio internal void InitializeGpio() { // Check if the chip supports MPSEE or not. So far only channel C and D of FT4232H does not if ((Type == FtDeviceType.Ft4232H) && ((Channel == FtChannel.C) || (Channel == FtChannel.D))) { UsesMpseeForGpio = false; } else { UsesMpseeForGpio = true; } if (UsesMpseeForGpio) { if (IsI2cModeEnabled || IsSpiModeEnabled) { return; } // Reset var ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.ResetIoBitMode); // Enable MPSSE mode ftStatus |= FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.Mpsee); if (ftStatus != FtStatus.Ok) { throw new IOException($"Failed to setup device {Description}, status: {ftStatus} in MPSSE mode"); } // 50 ms according to thr doc for all USB to complete Thread.Sleep(50); DiscardInput(); SetupMpsseMode(); } else { var ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.ResetIoBitMode); // Enable asynchronous bit bang mode, thise does allow to have different pin modes, put all pins as input ftStatus |= FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.AsynchronousBitBang); if (ftStatus != FtStatus.Ok) { throw new IOException($"Failed to setup device {Description}, status: {ftStatus} in MPSSE mode"); } DiscardInput(); } } internal byte GetGpioValuesLow() { if (UsesMpseeForGpio) { Span toSend = stackalloc byte[2]; Span toRead = stackalloc byte[1]; toSend[0] = (byte)FtOpcode.ReadDataBitsLowByte; toSend[1] = (byte)FtOpcode.SendImmediate; Write(toSend); Read(toRead); return toRead[0]; } else { // Just read the instantaneous value byte values = 0; FtFunction.FT_GetBitMode(_ftHandle, ref values); return values; } } internal void SetGpioValuesLow() { if (UsesMpseeForGpio) { Span toSend = stackalloc byte[3]; toSend[0] = (byte)FtOpcode.SetDataBitsLowByte; toSend[1] = GpioLowData; toSend[2] = GpioLowDir; Write(toSend); } else { // Make sure we always have the right direction for the pins FtFunction.FT_SetBitMode(_ftHandle, GpioLowDir, FtBitMode.AsynchronousBitBang); // Write the values Write(stackalloc byte[1] { GpioLowData }); } } internal byte GetGpioValuesHigh() { Span toSend = stackalloc byte[2]; Span toRead = stackalloc byte[1]; toSend[0] = (byte)FtOpcode.ReadDataBitsHighByte; toSend[1] = (byte)FtOpcode.SendImmediate; Write(toSend); Read(toRead); return toRead[0]; } internal void SetGpioValuesHigh() { Span toSend = stackalloc byte[3]; toSend[0] = (byte)FtOpcode.SetDataBitsHighByte; toSend[1] = GpioHighData; toSend[2] = GpioHighDir; Write(toSend); } #endregion #region SPI internal void SpiInitialize() { // Do we already have SPI setup? if (IsSpiModeEnabled) { // No need to initialize everything return; } GetHandle(); IsSpiModeEnabled = true; var ftStatus = FtFunction.FT_SetLatencyTimer(_ftHandle, 1); ftStatus = FtFunction.FT_SetUSBParameters(_ftHandle, 65535, 65535); ftStatus = FtFunction.FT_SetChars(_ftHandle, 0, 0, 0, 0); ftStatus = FtFunction.FT_SetTimeouts(_ftHandle, 3000, 3000); ftStatus = FtFunction.FT_SetLatencyTimer(_ftHandle, 1); // Reset ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.ResetIoBitMode); // Enable MPSSE mode ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, FtBitMode.Mpsee); if (ftStatus != FtStatus.Ok) { throw new IOException($"Failed to setup device {Description}, status: {ftStatus} in MPSSE mode"); } // 50 ms according to thr doc for all USB to complete Thread.Sleep(50); DiscardInput(); SetupMpsseMode(); int idx = 0; Span toSend = stackalloc byte[10]; toSend[idx++] = (byte)FtOpcode.DisableClockDivideBy5; toSend[idx++] = (byte)FtOpcode.TurnOffAdaptativeClocking; toSend[idx++] = (byte)FtOpcode.Disable3PhaseDataClocking; toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; // Pin clock output, MISO output, MOSI input GpioLowDir = (byte)((GpioLowDir & MaskGpio) | 0x03); // clock, MOSI and MISO to 0 GpioLowData = (byte)(GpioLowData & MaskGpio); toSend[idx++] = GpioLowDir; toSend[idx++] = GpioLowData; // The SK clock frequency can be worked out by below algorithm with divide by 5 set as off // TCK period = 60MHz / (( 1 + [ (0xValueH * 256) OR 0xValueL] ) * 2) // Command to set clock divisor toSend[idx++] = (byte)FtOpcode.SetClockDivisor; uint clockDivisor = (uint)(60000 / ((ConnectionSettings[0].ClockFrequency / 1000) * 2) - 1); toSend[idx++] = (byte)(clockDivisor & 0xFF); toSend[idx++] = (byte)(clockDivisor >> 8); // loopback off toSend[idx++] = (byte)FtOpcode.DisconnectTDItoTDOforLoopback; Write(toSend); // Delay as in the documentation Thread.Sleep(30); } internal void SpiDeinitialize() { if (ConnectionSettings.Count == 0) { IsSpiModeEnabled = false; } } internal void SpiWrite(SpiConnectionSettings settings, ReadOnlySpan buffer) { if (buffer.Length > 65535) { throw new ArgumentException("Buffer too large, maximum size if 65535"); } byte clock; switch (settings.Mode) { default: case SpiMode.Mode3: case SpiMode.Mode0: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesOutOnMinusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesOutOnMinusVeClockLSBFirst; } break; case SpiMode.Mode2: case SpiMode.Mode1: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesOutOnPlusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesOutOnPlusVeClockLSBFirst; } break; } SpiChipSelectEnable(settings.ChipSelectLine, settings.ChipSelectLineActiveState, true); int idx = 0; Span toSend = stackalloc byte[3 + buffer.Length]; toSend[idx++] = clock; toSend[idx++] = (byte)((buffer.Length - 1) & 0xFF); toSend[idx++] = (byte)((buffer.Length - 1) >> 8); buffer.CopyTo(toSend.Slice(3)); Write(toSend); SpiChipSelectEnable(settings.ChipSelectLine, settings.ChipSelectLineActiveState, false); } internal void SpiRead(SpiConnectionSettings settings, Span buffer) { if (buffer.Length > 65535) { throw new ArgumentException("Buffer too large, maximum size if 65535"); } byte clock; switch (settings.Mode) { default: case SpiMode.Mode3: case SpiMode.Mode0: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesInOnPlusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesInOnPlusVeClockLSBFirst; } break; case SpiMode.Mode2: case SpiMode.Mode1: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesInOnMinusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesInOnMinusVeClockLSBFirst; } break; } SpiChipSelectEnable((byte)settings.ChipSelectLine, settings.ChipSelectLineActiveState, true); int idx = 0; Span toSend = stackalloc byte[3]; toSend[idx++] = clock; toSend[idx++] = (byte)((buffer.Length - 1) & 0xFF); toSend[idx++] = (byte)((buffer.Length - 1) >> 8); Write(toSend); Read(buffer); SpiChipSelectEnable((byte)settings.ChipSelectLine, settings.ChipSelectLineActiveState, false); } internal void SpiWriteRead(SpiConnectionSettings settings, ReadOnlySpan bufferWrite, Span bufferRead) { if ((bufferRead.Length > 65535) || (bufferWrite.Length > 65535)) { throw new ArgumentException("Buffer too large, maximum size if 65535"); } byte clock; switch (settings.Mode) { default: case SpiMode.Mode3: case SpiMode.Mode0: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesOutOnMinusBytesInOnPlusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesOutOnMinusBytesInOnPlusVeClockLSBFirst; } break; case SpiMode.Mode2: case SpiMode.Mode1: if (settings.DataFlow == DataFlow.MsbFirst) { clock = (byte)FtOpcode.ClockDataBytesOutOnPlusBytesInOnMinusVeClockMSBFirst; } else { clock = (byte)FtOpcode.ClockDataBytesOutOnPlusBytesInOnMinusVeClockLSBFirst; } break; } SpiChipSelectEnable(settings.ChipSelectLine, settings.ChipSelectLineActiveState, true); int idx = 0; Span toSend = stackalloc byte[3 + bufferWrite.Length]; toSend[idx++] = clock; toSend[idx++] = (byte)((bufferWrite.Length - 1) & 0xFF); toSend[idx++] = (byte)((bufferWrite.Length - 1) >> 8); bufferWrite.CopyTo(toSend.Slice(3)); Write(toSend); Read(bufferRead); SpiChipSelectEnable(settings.ChipSelectLine, settings.ChipSelectLineActiveState, false); } internal void SpiChipSelectEnable(int chipSelect, PinValue csPinValue, bool enable) { if (chipSelect < 0) { return; } var value = enable ? csPinValue : !csPinValue; Span toSend = stackalloc byte[NumberCycles * 3]; int idx = 0; if (chipSelect < 8) { GpioLowDir |= (byte)(1 << chipSelect); if (value == PinValue.High) { GpioLowData |= (byte)(1 << chipSelect); } else { byte mask = 0xFF; mask &= (byte)(~(1 << chipSelect)); GpioLowData &= mask; } for (int i = 0; i < NumberCycles; i++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; } } else { GpioHighDir |= (byte)(1 << (chipSelect - 8)); if (value == PinValue.High) { GpioHighData |= (byte)(1 << (chipSelect - 8)); } else { byte mask = 0xFF; mask &= (byte)(~(1 << (chipSelect - 8))); GpioHighData &= mask; } for (int i = 0; i < NumberCycles; i++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsHighByte; toSend[idx++] = GpioHighData; toSend[idx++] = GpioHighDir; } } Write(toSend); } #endregion #region Read Write internal void Write(ReadOnlySpan buffer) { uint numBytesWritten = 0; var ftStatus = FtFunction.FT_Write(_ftHandle, in MemoryMarshal.GetReference(buffer), (ushort)buffer.Length, ref numBytesWritten); if ((ftStatus != FtStatus.Ok) || (buffer.Length != numBytesWritten)) { throw new IOException($"Can't write to the device"); } } internal int Read(Span buffer) { CancellationToken token = new CancellationTokenSource(1000).Token; int totalBytesRead = 0; uint bytesToRead = 0; uint numBytesRead = 0; FtStatus ftStatus; while ((totalBytesRead < buffer.Length) && (!token.IsCancellationRequested)) { bytesToRead = GetAvailableBytes(); if (bytesToRead > 0) { ftStatus = FtFunction.FT_Read(_ftHandle, in buffer[totalBytesRead], bytesToRead, ref numBytesRead); if ((ftStatus != FtStatus.Ok) && (bytesToRead != numBytesRead)) { throw new IOException("Can't read device"); } totalBytesRead += (int)numBytesRead; } } return totalBytesRead; } /// /// Clears all the input data to get an empty buffer. /// /// True for success private bool DiscardInput() { var availableBytes = GetAvailableBytes(); if (availableBytes > 0) { byte[] toRead = new byte[availableBytes]; uint bytesRead = 0; var ftStatus = FtFunction.FT_Read(_ftHandle, in toRead[0], availableBytes, ref bytesRead); return ftStatus == FtStatus.Ok; } return true; } private uint GetAvailableBytes() { uint availableBytes = 0; var ftStatus = FtFunction.FT_GetQueueStatus(_ftHandle, ref availableBytes); if (ftStatus != FtStatus.Ok) { throw new IOException($"Can't get available bytes"); } return availableBytes; } #endregion /// /// Dispose FTx232H device. /// protected override void Dispose(bool disposing) { if (disposing) { _ftHandle.Dispose(); _ftHandle = null!; } base.Dispose(disposing); } } }