From 3a7373a788507605b260b65fbea17403bb9566bb Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Mon, 26 Dec 2022 14:43:17 +0100 Subject: [PATCH 1/6] adjusting --- src/devices/Ft232H/Ft232HDevice.cs | 49 ++++++++++++++------------- src/devices/Ft232H/Ft232HI2cBus.cs | 8 +++++ src/devices/Ft232H/samples/Program.cs | 4 +-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/devices/Ft232H/Ft232HDevice.cs b/src/devices/Ft232H/Ft232HDevice.cs index 72f2b927b3..8521887cb4 100644 --- a/src/devices/Ft232H/Ft232HDevice.cs +++ b/src/devices/Ft232H/Ft232HDevice.cs @@ -30,11 +30,13 @@ public class Ft232HDevice : FtDevice, IDisposable // 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; private const byte I2cDirSDAoutSCLout = 0x03; + private const byte I2cDirSDAinSCLout = 0x11; private const byte I2cDataSDAloSCLhi = 0x01; private const byte I2cDataSDAhiSCLhi = 0x03; private const byte I2cDataSDAloSCLlo = 0x00; private const byte I2cDataSDAhiSCLlo = 0x02; - private const byte NumberCycles = 5; + private const byte NumberCycles = 6; + private const byte MaskGpio = 0xF8; private SafeFtHandle _ftHandle = null!; @@ -304,8 +306,8 @@ internal void I2cInitialize() // Command to set directions of lower 8 pins and force value on bits set as output toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; // SDA and SCL both output high (open drain) - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; Write(toSend); @@ -344,8 +346,8 @@ internal void I2cStart() int count; int idx = 0; // SDA high, SCL high - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); Span toSend = stackalloc byte[NumberCycles * 3 * 3 + 3]; for (count = 0; count < NumberCycles; count++) { @@ -355,7 +357,8 @@ internal void I2cStart() } // SDA lo, SCL high - GpioLowData = (byte)(0x00 | I2cDataSDAloSCLhi | (GpioLowData & 0xF8)); + GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; @@ -364,7 +367,7 @@ internal void I2cStart() } // SDA lo, SCL lo - GpioLowData = (byte)(0x00 | I2cDataSDAloSCLlo | (GpioLowData & 0xF8)); + GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; @@ -373,7 +376,7 @@ internal void I2cStart() } // Release SDA - GpioLowData = (byte)(0x00 | I2cDataSDAhiSCLlo | (GpioLowData & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; @@ -386,8 +389,8 @@ internal void I2cStop() int count; int idx = 0; // SDA low, SCL low - GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); Span toSend = stackalloc byte[NumberCycles * 3 * 3]; for (count = 0; count < NumberCycles; count++) { @@ -397,8 +400,8 @@ internal void I2cStop() } // SDA low, SCL high - GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; @@ -407,8 +410,8 @@ internal void I2cStop() } // SDA high, SCL high - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); for (count = 0; count < NumberCycles; count++) { toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; @@ -423,8 +426,8 @@ internal void I2cLineIdle() { int idx = 0; // SDA low, SCL low - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); Span toSend = stackalloc byte[3]; toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; @@ -444,8 +447,8 @@ internal bool I2cSendByteAndCheckACK(byte data) toSend[idx++] = 0; toSend[idx++] = data; // Put line back to idle (data released, clock pulled low) - GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & 0xF8)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); + GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; toSend[idx++] = GpioLowData; toSend[idx++] = GpioLowDir; @@ -487,8 +490,8 @@ internal byte I2CReadByte(bool ack) toSend[idx++] = (byte)(ack ? 0x00 : 0xFF); // I2C lines back to idle state toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & 0xF8)); - GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & 0xF8)); + GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); + GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & MaskGpio)); toSend[idx++] = GpioLowDir; toSend[idx++] = GpioLowData; // And ask it right away @@ -532,7 +535,7 @@ internal byte GetGpioValuesLow() toSend[1] = (byte)FtOpcode.SendImmediate; Write(toSend); Read(toRead); - return (byte)(toRead[0] & 0xF8); + return (byte)(toRead[0] & MaskGpio); } internal void SetGpioValuesLow() @@ -605,9 +608,9 @@ internal void SpiInitialize() toSend[idx++] = (byte)FtOpcode.Disable3PhaseDataClocking; toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; // Pin clock output, MISO output, MOSI input - GpioLowDir = (byte)((GpioLowDir & 0xF8) | 0x03); + GpioLowDir = (byte)((GpioLowDir & MaskGpio) | 0x03); // clock, MOSI and MISO to 0 - GpioLowData = (byte)(GpioLowData & 0xF8); + 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 diff --git a/src/devices/Ft232H/Ft232HI2cBus.cs b/src/devices/Ft232H/Ft232HI2cBus.cs index 4d38aa8bb7..146917a4a0 100644 --- a/src/devices/Ft232H/Ft232HI2cBus.cs +++ b/src/devices/Ft232H/Ft232HI2cBus.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Device.I2c; using System.IO; +using System.Net.Http.Headers; namespace Iot.Device.Ft232H { @@ -75,11 +76,18 @@ internal void Read(int deviceAddress, Span buffer) internal void Write(int deviceAddress, ReadOnlySpan buffer) { + var retry = 0; + Retry: DeviceInformation.I2cStart(); var ack = DeviceInformation.I2cSendDeviceAddrAndCheckACK((byte)deviceAddress, false); if (!ack) { DeviceInformation.I2cStop(); + if (retry++ < 5) + { + goto Retry; + } + throw new IOException($"Error writing device while setting up address"); } diff --git a/src/devices/Ft232H/samples/Program.cs b/src/devices/Ft232H/samples/Program.cs index 9b27d0ae5e..7ae3a37f7d 100644 --- a/src/devices/Ft232H/samples/Program.cs +++ b/src/devices/Ft232H/samples/Program.cs @@ -35,7 +35,7 @@ Ft232HDevice ft232h = Ft232HDevice.GetFt232H()[0]; // Uncomment the test you want to run // TestSpi(ft232h); -TestGpio(ft232h); +// TestGpio(ft232h); TestI2c(ft232h); void TestSpi(Ft232HDevice ft232h) @@ -91,7 +91,7 @@ void TestI2c(Ft232HDevice ft232h) Pressure defaultSeaLevelPressure = WeatherHelper.MeanSeaLevel; Length stationHeight = Length.FromMeters(640); // Elevation of the sensor var ftI2cBus = ft232h.CreateOrGetI2cBus(ft232h.GetDefaultI2cBusNumber()); - var i2cDevice = ftI2cBus.CreateDevice(Bmp280.SecondaryI2cAddress); + var i2cDevice = ftI2cBus.CreateDevice(Bmp280.DefaultI2cAddress); using var i2CBmp280 = new Bmp280(i2cDevice); while (true) From 39a95e182bbf44d1c783711bd05e6ca423baacda Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Fri, 30 Dec 2022 18:34:03 -0400 Subject: [PATCH 2/6] adding sample --- src/devices/Ft232H/Ft232HI2cBus.cs | 7 ------- src/devices/Ft232H/samples/Program.cs | 27 ++++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/devices/Ft232H/Ft232HI2cBus.cs b/src/devices/Ft232H/Ft232HI2cBus.cs index 146917a4a0..7a53ea35be 100644 --- a/src/devices/Ft232H/Ft232HI2cBus.cs +++ b/src/devices/Ft232H/Ft232HI2cBus.cs @@ -76,18 +76,11 @@ internal void Read(int deviceAddress, Span buffer) internal void Write(int deviceAddress, ReadOnlySpan buffer) { - var retry = 0; - Retry: DeviceInformation.I2cStart(); var ack = DeviceInformation.I2cSendDeviceAddrAndCheckACK((byte)deviceAddress, false); if (!ack) { DeviceInformation.I2cStop(); - if (retry++ < 5) - { - goto Retry; - } - throw new IOException($"Error writing device while setting up address"); } diff --git a/src/devices/Ft232H/samples/Program.cs b/src/devices/Ft232H/samples/Program.cs index 7ae3a37f7d..7508295624 100644 --- a/src/devices/Ft232H/samples/Program.cs +++ b/src/devices/Ft232H/samples/Program.cs @@ -3,11 +3,12 @@ using System; using System.Collections.Generic; +using System.Device.Gpio; using System.Device.Spi; using System.Threading; -using System.Device.Gpio; using Iot.Device.Bmxx80; using Iot.Device.Bmxx80.FilteringMode; +using Iot.Device.Board; using Iot.Device.Common; using Iot.Device.Ft232H; using Iot.Device.FtCommon; @@ -36,6 +37,7 @@ // Uncomment the test you want to run // TestSpi(ft232h); // TestGpio(ft232h); +I2cScan(ft232h); TestI2c(ft232h); void TestSpi(Ft232HDevice ft232h) @@ -56,16 +58,18 @@ void TestSpi(Ft232HDevice ft232h) void TestGpio(Ft232HDevice ft232h) { // Should transform it into 5 + const string PinNumber = "D5"; + // It's possible to use this function to convert the board names you find in various // implementation into the pin number - int gpio5 = Ft232HDevice.GetPinNumberFromString("D5"); + int gpio5 = Ft232HDevice.GetPinNumberFromString(PinNumber); var gpioController = ft232h.CreateGpioController(); // Opening GPIO2 gpioController.OpenPin(gpio5); gpioController.SetPinMode(gpio5, PinMode.Output); - Console.WriteLine("Blinking GPIO5 (D5)"); + Console.WriteLine($"Blinking GPIO{gpio5} ({PinNumber})"); while (!Console.KeyAvailable) { gpioController.Write(gpio5, PinValue.High); @@ -85,6 +89,23 @@ void TestGpio(Ft232HDevice ft232h) } } +void I2cScan(Ft232HDevice ft232h) +{ + Console.WriteLine("Hello I2C scanner!"); + var i2cBus = ft232h.CreateOrGetI2cBus(ft232h.GetDefaultI2cBusNumber()); + // First 8 I2C addresses are reserved, last one is 0x7F + List validAddress = i2cBus.PerformBusScan(3, 0x7F); + Console.WriteLine($"Found {validAddress.Count} device(s)."); + + foreach (var valid in validAddress) + { + Console.WriteLine($"Address: 0x{valid:X}"); + } + + // Let'zs clean all + i2cBus.Dispose(); +} + void TestI2c(Ft232HDevice ft232h) { // set this to the current sea level pressure in the area for correct altitude readings From 4f40d2b3930f76af8720c05d5694b00ee11f4089 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Fri, 20 Jan 2023 18:01:56 +0100 Subject: [PATCH 3/6] Adding support for FT2232H and FT4232H --- src/devices/Ft232H/Ft2232HDevice.cs | 165 +++ src/devices/Ft232H/Ft232H.csproj | 3 + src/devices/Ft232H/Ft232H.sln | 19 +- src/devices/Ft232H/Ft232HDevice.cs | 802 +----------- src/devices/Ft232H/Ft232HGpio.cs | 27 +- src/devices/Ft232H/Ft232HI2cBus.cs | 5 +- src/devices/Ft232H/Ft232HSpi.cs | 9 +- src/devices/Ft232H/Ft4232HDevice.cs | 141 +++ src/devices/Ft232H/Ftx232HDevice.cs | 1074 +++++++++++++++++ src/devices/Ft232H/README.md | 114 +- .../Ft232H/samples/Ft232H.sample.csproj | 3 + src/devices/Ft232H/samples/Program.cs | 59 +- src/devices/FtCommon/FtBitMode.cs | 29 + src/devices/FtCommon/FtChannel.cs | 23 + src/devices/FtCommon/FtCommon.projitems | 2 + src/devices/FtCommon/FtFunction.cs | 19 +- 16 files changed, 1667 insertions(+), 827 deletions(-) create mode 100644 src/devices/Ft232H/Ft2232HDevice.cs create mode 100644 src/devices/Ft232H/Ft4232HDevice.cs create mode 100644 src/devices/Ft232H/Ftx232HDevice.cs create mode 100644 src/devices/FtCommon/FtBitMode.cs create mode 100644 src/devices/FtCommon/FtChannel.cs diff --git a/src/devices/Ft232H/Ft2232HDevice.cs b/src/devices/Ft232H/Ft2232HDevice.cs new file mode 100644 index 0000000000..a66813c83c --- /dev/null +++ b/src/devices/Ft232H/Ft2232HDevice.cs @@ -0,0 +1,165 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Iot.Device.Common; +using Iot.Device.FtCommon; + +namespace Iot.Device.Ft2232H +{ + /// + /// FT232H Device + /// + public class Ft2232HDevice : Ftx232HDevice + { + /// + /// Gets all the FT232H connected + /// + /// A list of FT232H + public static List GetFt2232H() + { + List ft2232s = new List(); + var devices = FtCommon.FtCommon.GetDevices(new FtDeviceType[] { FtDeviceType.Ft2232H }); + foreach (var device in devices) + { + ft2232s.Add(new Ft2232HDevice(device)); + } + + return ft2232s; + } + + /// + /// Gets the pin number from a string + /// + /// A string + /// The pin number, -1 in case it's not found. + /// Valid pins are ADBUS0 to 7, D0 to 7, ACBUS0 to 7, C0 to 7, + /// TCK, SK, CLK, MOSI, MISO, SDA, TDI, DI, TDO, DO, + /// TMS, CS, GPIOL0 to 3, GPIOH0 to 7 + public static int GetPinNumberFromString(string pin) + { + pin = pin.ToUpper(); + switch (pin) + { + case "ADBUS0": + case "BDBUS0": + case "D0": + case "TCK": + case "SK": + case "CLK": + case "SDL": + return 0; + case "ADBUS1": + case "BDBUS1": + case "D1": + case "DO": + case "TDI": + case "SDA": + case "MOSI": + return 1; + case "ADBUS2": + case "BDBUS2": + case "D2": + case "DI": + case "TDO": + case "MISO": + return 2; + case "ADBUS3": + case "BDBUS3": + case "D3": + case "TMS": + case "CS": + return 3; + case "ADBUS4": + case "BDBUS4": + case "D4": + case "GPIOL0": + return 4; + case "ADBUS5": + case "BDBUS5": + case "D5": + case "GPIOL1": + return 5; + case "ADBUS6": + case "BDBUS6": + case "D6": + case "GPIOL2": + return 6; + case "ADBUS7": + case "BDBUS7": + case "D7": + case "GPIOL3": + return 7; + case "ACBUS0": + case "BCBUS0": + case "C0": + case "GPIOH0": + return 8; + case "ACBUS1": + case "BCBUS1": + case "C1": + case "GPIOH1": + return 9; + case "ACBUS2": + case "BCBUS2": + case "C2": + case "GPIOH2": + return 10; + case "ACBUS3": + case "BCBUS3": + case "C3": + case "GPIOH3": + return 11; + case "ACBUS4": + case "BCBUS4": + case "C4": + case "GPIOH4": + return 12; + case "ACBUS5": + case "BCBUS5": + case "C5": + case "GPIOH5": + return 13; + case "ACBUS6": + case "BCBUS6": + case "C6": + case "GPIOH6": + return 14; + case "ACBUS7": + case "BCBUS7": + case "C7": + case "GPIOH7": + return 15; + default: + return -1; + } + } + + /// + /// Instantiates a FT2232H 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 Ft2232HDevice(FtFlag flags, FtDeviceType type, uint id, uint locId, string serialNumber, string description) + : base(flags, type, id, locId, serialNumber, description) + { + } + + /// + /// Instantiates a FT2232H device object. + /// + /// a FT Device + public Ft2232HDevice(FtDevice ftDevice) + : base(ftDevice.Flags, ftDevice.Type, ftDevice.Id, ftDevice.LocId, ftDevice.SerialNumber, ftDevice.Description) + { + } + } +} diff --git a/src/devices/Ft232H/Ft232H.csproj b/src/devices/Ft232H/Ft232H.csproj index 09bc76ebab..0cfcbde535 100644 --- a/src/devices/Ft232H/Ft232H.csproj +++ b/src/devices/Ft232H/Ft232H.csproj @@ -6,11 +6,14 @@ + + + diff --git a/src/devices/Ft232H/Ft232H.sln b/src/devices/Ft232H/Ft232H.sln index d3e8a84162..a1b3fd3fdf 100644 --- a/src/devices/Ft232H/Ft232H.sln +++ b/src/devices/Ft232H/Ft232H.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31515.178 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ft232H", "Ft232H.csproj", "{41A5516C-6799-4642-B20D-BEC14CF0B742}" EndProject @@ -17,11 +17,9 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "FtCommon", "..\FtCommon\FtC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Board", "..\Board\Board.csproj", "{DF7F5D81-1F96-46A6-AB39-977A8522F6FA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tsl256x", "..\Tsl256x\Tsl256x.csproj", "{72A779D8-444D-4273-A759-652B71F76B60}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ..\FtCommon\FtCommon.projitems*{2f022801-b558-4395-a995-ef10628447b4}*SharedItemsImports = 13 - ..\FtCommon\FtCommon.projitems*{41a5516c-6799-4642-b20d-bec14cf0b742}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -43,6 +41,10 @@ Global {DF7F5D81-1F96-46A6-AB39-977A8522F6FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF7F5D81-1F96-46A6-AB39-977A8522F6FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF7F5D81-1F96-46A6-AB39-977A8522F6FA}.Release|Any CPU.Build.0 = Release|Any CPU + {72A779D8-444D-4273-A759-652B71F76B60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72A779D8-444D-4273-A759-652B71F76B60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72A779D8-444D-4273-A759-652B71F76B60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72A779D8-444D-4273-A759-652B71F76B60}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -52,8 +54,13 @@ Global {7CC23CB9-7276-418B-8085-AA06AD7AC63B} = {D476679D-EBC5-406A-B293-93A65C8B58ED} {2F022801-B558-4395-A995-EF10628447B4} = {07E992C0-1091-4A40-B1A0-71878D58140F} {DF7F5D81-1F96-46A6-AB39-977A8522F6FA} = {07E992C0-1091-4A40-B1A0-71878D58140F} + {72A779D8-444D-4273-A759-652B71F76B60} = {D476679D-EBC5-406A-B293-93A65C8B58ED} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECE390DE-F924-422E-8D21-ABB498BA0D20} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\FtCommon\FtCommon.projitems*{2f022801-b558-4395-a995-ef10628447b4}*SharedItemsImports = 13 + ..\FtCommon\FtCommon.projitems*{41a5516c-6799-4642-b20d-bec14cf0b742}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/devices/Ft232H/Ft232HDevice.cs b/src/devices/Ft232H/Ft232HDevice.cs index bc9927cabb..e7db5ac6fc 100644 --- a/src/devices/Ft232H/Ft232HDevice.cs +++ b/src/devices/Ft232H/Ft232HDevice.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Threading; using Iot.Device.Board; +using Iot.Device.Common; using Iot.Device.FtCommon; namespace Iot.Device.Ft232H @@ -17,39 +18,23 @@ namespace Iot.Device.Ft232H /// /// FT232H Device /// - public class Ft232HDevice : FtDevice, IDisposable + public class Ft232HDevice : Ftx232HDevice, IDisposable { /// - /// Number of pins + /// Gets all the FT232H connected /// - 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; - private const byte I2cDirSDAoutSCLout = 0x03; - private const byte I2cDirSDAinSCLout = 0x11; - 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]; + /// A list of FT232H + public static List GetFt232H() + { + List ft232s = new List(); + var devices = FtCommon.FtCommon.GetDevices(new FtDeviceType[] { FtDeviceType.Ft232H }); + foreach (var device in devices) + { + ft232s.Add(new Ft232HDevice(device)); + } - internal List ConnectionSettings = new List(); + return ft232s; + } /// /// Gets the pin number from a string @@ -142,22 +127,6 @@ public static int GetPinNumberFromString(string pin) } } - /// - /// Gets all the FT232H connected - /// - /// A list of FT232H - public static List GetFt232H() - { - List ft232s = new List(); - var devices = FtCommon.FtCommon.GetDevices(new FtDeviceType[] { FtDeviceType.Ft232H }); - foreach (var device in devices) - { - ft232s.Add(new Ft232HDevice(device)); - } - - return ft232s; - } - /// /// Instantiates a FT232H device object. /// @@ -180,748 +149,5 @@ public Ft232HDevice(FtDevice ftDevice) : base(ftDevice.Flags, ftDevice.Type, ftDevice.Id, ftDevice.LocId, ftDevice.SerialNumber, ftDevice.Description) { } - - /// - 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); - } - - /// - 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 }; - } - - 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}"); - } - } - } - - #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, 0x00); - ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, 0x02); - - 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 / (I2cMasterFrequencyKbps * 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; - // SDA and SCL both output high (open drain) - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (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 - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (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 - GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); - for (count = 0; count < NumberCycles; count++) - { - toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; - toSend[idx++] = GpioLowData; - toSend[idx++] = GpioLowDir; - } - - // SDA lo, SCL lo - GpioLowData = (byte)(I2cDataSDAloSCLlo | (GpioLowData & MaskGpio)); - for (count = 0; count < NumberCycles; count++) - { - toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; - toSend[idx++] = GpioLowData; - toSend[idx++] = GpioLowDir; - } - - // Release SDA - GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & 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 - GpioLowData = (byte)(I2cDataSDAloSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); - for (count = 0; count < NumberCycles; count++) - { - toSend[idx++] = (byte)FtOpcode.SetDataBitsLowByte; - toSend[idx++] = GpioLowData; - toSend[idx++] = GpioLowDir; - } - - // SDA high, SCL high - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (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 - GpioLowData = (byte)(I2cDataSDAhiSCLhi | (GpioLowData & MaskGpio)); - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (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[10]; - Span toRead = stackalloc byte[1]; - // 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; - // 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[10]; - Span toRead = stackalloc byte[1]; - // 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; - GpioLowDir = (byte)(I2cDirSDAoutSCLout | (GpioLowDir & MaskGpio)); - GpioLowData = (byte)(I2cDataSDAhiSCLlo | (GpioLowData & 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() - { - if (IsI2cModeEnabled || IsSpiModeEnabled) - { - return; - } - - // Reset - var ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, 0x00); - // Enable MPSSE mode - ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, 0x02); - 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(); - } - - internal byte GetGpioValuesLow() - { - 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 (byte)(toRead[0] & MaskGpio); - } - - internal void SetGpioValuesLow() - { - Span toSend = stackalloc byte[3]; - toSend[0] = (byte)FtOpcode.SetDataBitsLowByte; - toSend[1] = GpioLowData; - toSend[2] = GpioLowDir; - Write(toSend); - } - - 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, 0x00); - // Enable MPSSE mode - ftStatus = FtFunction.FT_SetBitMode(_ftHandle, 0x00, 0x02); - 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 FT323H - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _ftHandle.Dispose(); - _ftHandle = null!; - } - - base.Dispose(disposing); - } } } diff --git a/src/devices/Ft232H/Ft232HGpio.cs b/src/devices/Ft232H/Ft232HGpio.cs index 7fe5cd3a03..3b4ca52938 100644 --- a/src/devices/Ft232H/Ft232HGpio.cs +++ b/src/devices/Ft232H/Ft232HGpio.cs @@ -6,6 +6,7 @@ using System.Device.Gpio; using System.Linq; using System.Threading; +using Iot.Device.FtCommon; namespace Iot.Device.Ft232H { @@ -19,16 +20,16 @@ public class Ft232HGpio : GpioDriver /// /// Store the FTDI Device Information /// - public Ft232HDevice DeviceInformation { get; private set; } + public Ftx232HDevice DeviceInformation { get; private set; } /// - protected override int PinCount => Ft232HDevice.PinCountConst; + protected override int PinCount => DeviceInformation.PinCount; /// /// Creates a GPIO Driver /// /// The FT232H device - internal Ft232HGpio(Ft232HDevice deviceInformation) + internal Ft232HGpio(Ftx232HDevice deviceInformation) { DeviceInformation = deviceInformation; // Open device @@ -80,6 +81,11 @@ protected override void ClosePin(int pinNumber) /// protected override void SetPinMode(int pinNumber, PinMode mode) { + if ((pinNumber < 0) || (pinNumber >= PinCount)) + { + throw new ArgumentException($"Pin number can only be between 0 and {PinCount - 1}"); + } + if (pinNumber < 8) { if (mode != PinMode.Output) @@ -115,6 +121,11 @@ protected override void SetPinMode(int pinNumber, PinMode mode) /// protected override PinMode GetPinMode(int pinNumber) { + if ((pinNumber < 0) || (pinNumber >= PinCount)) + { + throw new ArgumentException($"Pin number can only be between 0 and {PinCount - 1}"); + } + if (pinNumber < 8) { return ((DeviceInformation.GpioLowDir >> pinNumber) & 0x01) == 0x01 ? PinMode.Output : PinMode.Input; @@ -132,6 +143,11 @@ protected override bool IsPinModeSupported(int pinNumber, PinMode mode) /// protected override PinValue Read(int pinNumber) { + if ((pinNumber < 0) || (pinNumber >= PinCount)) + { + throw new ArgumentException($"Pin number can only be between 0 and {PinCount - 1}"); + } + if (pinNumber < 8) { var val = DeviceInformation.GetGpioValuesLow(); @@ -152,6 +168,11 @@ protected override PinValue Read(int pinNumber) /// protected override void Write(int pinNumber, PinValue value) { + if ((pinNumber < 0) || (pinNumber >= PinCount)) + { + throw new ArgumentException($"Pin number can only be between 0 and {PinCount - 1}"); + } + if (pinNumber < 8) { if (value == PinValue.High) diff --git a/src/devices/Ft232H/Ft232HI2cBus.cs b/src/devices/Ft232H/Ft232HI2cBus.cs index 7a53ea35be..49a110a77b 100644 --- a/src/devices/Ft232H/Ft232HI2cBus.cs +++ b/src/devices/Ft232H/Ft232HI2cBus.cs @@ -6,6 +6,7 @@ using System.Device.I2c; using System.IO; using System.Net.Http.Headers; +using Iot.Device.FtCommon; namespace Iot.Device.Ft232H { @@ -19,13 +20,13 @@ internal class Ft232HI2cBus : I2cBus /// /// Store the FTDI Device Information /// - public Ft232HDevice DeviceInformation { get; private set; } + public Ftx232HDevice DeviceInformation { get; private set; } /// /// Creates anI2C Bus /// /// a FT232H device - public Ft232HI2cBus(Ft232HDevice deviceInformation) + public Ft232HI2cBus(Ftx232HDevice deviceInformation) { DeviceInformation = deviceInformation; DeviceInformation.I2cInitialize(); diff --git a/src/devices/Ft232H/Ft232HSpi.cs b/src/devices/Ft232H/Ft232HSpi.cs index 3f3eda448c..9168b3dbc6 100644 --- a/src/devices/Ft232H/Ft232HSpi.cs +++ b/src/devices/Ft232H/Ft232HSpi.cs @@ -4,6 +4,7 @@ using System; using System.Device.Spi; using System.Linq; +using Iot.Device.FtCommon; namespace Iot.Device.Ft232H { @@ -20,15 +21,15 @@ public class Ft232HSpi : SpiDevice /// /// Store the FTDI Device Information /// - public Ft232HDevice DeviceInformation { get; internal set; } + public Ftx232HDevice DeviceInformation { get; internal set; } - internal Ft232HSpi(SpiConnectionSettings settings, Ft232HDevice deviceInformation) + internal Ft232HSpi(SpiConnectionSettings settings, Ftx232HDevice deviceInformation) { DeviceInformation = deviceInformation; _settings = settings; - if ((_settings.ChipSelectLine < 3) || (_settings.ChipSelectLine > Ft232HDevice.PinCountConst)) + if ((_settings.ChipSelectLine < 3) || (_settings.ChipSelectLine > DeviceInformation.PinCount)) { - throw new ArgumentException($"Chip Select line has to be between 3 and {Ft232HDevice.PinCountConst - 1}"); + throw new ArgumentException($"Chip Select line has to be between 3 and {DeviceInformation.PinCount - 1}"); } if (DeviceInformation.ConnectionSettings.Where(m => m.ChipSelectLine == _settings.ChipSelectLine).Any()) diff --git a/src/devices/Ft232H/Ft4232HDevice.cs b/src/devices/Ft232H/Ft4232HDevice.cs new file mode 100644 index 0000000000..24dd061288 --- /dev/null +++ b/src/devices/Ft232H/Ft4232HDevice.cs @@ -0,0 +1,141 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Iot.Device.Common; +using Iot.Device.FtCommon; + +namespace Iot.Device.Ft4232H +{ + /// + /// FT232H Device + /// + public class Ft4232HDevice : Ftx232HDevice + { + /// + /// Gets all the FT232H connected + /// + /// A list of FT232H + public static List GetFt2232H() + { + List ft4232s = new List(); + var devices = FtCommon.FtCommon.GetDevices(new FtDeviceType[] { FtDeviceType.Ft4232H }); + foreach (var device in devices) + { + ft4232s.Add(new Ft4232HDevice(device)); + } + + return ft4232s; + } + + /// + /// Gets the pin number from a string + /// + /// A string + /// The pin number, -1 in case it's not found. + /// Valid pins are ADBUS0 to 7, D0 to 7, ACBUS0 to 7, C0 to 7, + /// TCK, SK, CLK, MOSI, MISO, SDA, TDI, DI, TDO, DO, + /// TMS, CS, GPIOL0 to 3, GPIOH0 to 7 + public static int GetPinNumberFromString(string pin) + { + pin = pin.ToUpper(); + switch (pin) + { + case "ADBUS0": + case "BDBUS0": + case "CDBUS0": + case "DDBUS0": + case "D0": + case "TCK": + case "SK": + case "CLK": + case "SDL": + return 0; + case "ADBUS1": + case "BDBUS1": + case "CDBUS1": + case "DDBUS1": + case "D1": + case "DO": + case "TDI": + case "SDA": + case "MOSI": + return 1; + case "ADBUS2": + case "BDBUS2": + case "CDBUS2": + case "DDBUS2": + case "D2": + case "DI": + case "TDO": + case "MISO": + return 2; + case "ADBUS3": + case "BDBUS3": + case "CDBUS3": + case "DDBUS3": + case "D3": + case "TMS": + case "CS": + return 3; + case "ADBUS4": + case "BDBUS4": + case "CDBUS4": + case "DDBUS4": + case "D4": + case "GPIOL0": + return 4; + case "ADBUS5": + case "BDBUS5": + case "CDBUS5": + case "DDBUS5": + case "D5": + case "GPIOL1": + return 5; + case "ADBUS6": + case "BDBUS6": + case "CDBUS6": + case "DDBUS6": + case "D6": + case "GPIOL2": + return 6; + case "ADBUS7": + case "BDBUS7": + case "CDBUS7": + case "DDBUS7": + case "D7": + case "GPIOL3": + return 7; + default: + return -1; + } + } + + /// + /// Instantiates a FT2232H 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 Ft4232HDevice(FtFlag flags, FtDeviceType type, uint id, uint locId, string serialNumber, string description) + : base(flags, type, id, locId, serialNumber, description) + { + } + + /// + /// Instantiates a FT2232H device object. + /// + /// a FT Device + public Ft4232HDevice(FtDevice ftDevice) + : base(ftDevice.Flags, ftDevice.Type, ftDevice.Id, ftDevice.LocId, ftDevice.SerialNumber, ftDevice.Description) + { + } + } +} diff --git a/src/devices/Ft232H/Ftx232HDevice.cs b/src/devices/Ft232H/Ftx232HDevice.cs new file mode 100644 index 0000000000..2545c2f87f --- /dev/null +++ b/src/devices/Ft232H/Ftx232HDevice.cs @@ -0,0 +1,1074 @@ +// 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 FT232H connected + /// + /// A list of FT232H + 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 FT232H 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 FT232H 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 ? PinNumberFT2x : PinNumberFT4x; + + /// + /// 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); + } + } +} diff --git a/src/devices/Ft232H/README.md b/src/devices/Ft232H/README.md index a0196e16ea..9ea61345c2 100644 --- a/src/devices/Ft232H/README.md +++ b/src/devices/Ft232H/README.md @@ -1,14 +1,18 @@ -# SPI, GPIO and I2C drivers for FT232H +# SPI, GPIO and I2C drivers for FT232H, FT2232H, FT4232H -This project support SPI, GPIO and I2C into a normal Windows 64 bits or Windows 32 bits environment thru FT232H chipset. MacOS and Linux can be added as well. +This project support SPI, GPIO and I2C into a normal Windows (32 or 64 bits), Linux and MacOS environments thru FT232H, FT2232H, FT4232H chipsets. ## Documentation -The product datasheet can be found [here](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf). +The product datasheets can be found here: + +- [FT232H](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf). +- [FT2232H](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf). +- [FT4232H](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT4232H.pdf). You can find for example this chipset on an [Adafruit board](https://www.adafruit.com/product/2264). -[FTDI](https://www.ftdichip.com/) has multiple chip that may look the same. You will find the implementation of 2 of those chip, [FT4222](../Ft4222/README.md) and FT232H. +[FTDI](https://www.ftdichip.com/) has multiple chip that may look the same. You will find another implementation with the [FT4222](../Ft4222/README.md). ## Requirements @@ -16,6 +20,8 @@ Once plugged into your Windows, MacOS or Linux machine, the [D2xx drivers](https This implementation uses the 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. +Note that the FT4232H also uses MPSEE but only for 2 of the sub devices (A and B), the C and D only supports GPIO and uses the Asynchronous Bit Band mode. This allows to use GPIO as you would do normally and is handled in a fully transparent way. + ## Usage You can get the list of FTDI devices like this: @@ -40,11 +46,17 @@ if (devices.Count == 0) } ``` -You should see a FT232H device listed. Once you see the device, it means it's properly plugged kin and the driver is properly installed as well. +You should see a FT232H, FT2232H or FT4232H device listed. Once you see the device, it means it's properly plugged kin and the driver is properly installed as well. ### Easy pin numbering -This chipset come into various boards using different naming. You can try to use the `GetPinNumberFromString` function from `Ft232HDevice` +This chipset come into various boards using different naming. You can try to use the `GetPinNumberFromString` function from `Ft232HDevice` for example. That does exist for the 3 chipsets. + +> Important notes: +> +> - FT232H has a total of 16 pins. +> - FT2232H has 2 sub device, A and B. A and B have the exact same pin numbering and are both 16 pins. +> - FT4232H has 4 sub devices, A, B, C and D; each of 8 pins. ```csharp int Gpio5 = Ft232HDevice.GetPinNumberFromString("D5"); @@ -65,6 +77,12 @@ var i2cDevice = ftI2cBus.CreateDevice(Bmp280.SecondaryI2cAddress); using var i2CBmp280 = new Bmp280(i2cDevice); ``` +> Important notes: +> +> - FT232H uses pin 0 for clock and pin 1 and 2 must be connected for the data pin. +> - FT2232H uses pin 0 for clock and pin 1 and 2 must be connected for the data pin on both channels A and B. +> - FT4232H uses pin 0 for clock and pin 1 and 2 must be connected for the data pin on both channels A and B. + ### Creating a SPI Device Let's assume your device is the first one, you'll be able to create a SPI Device like this: @@ -76,9 +94,14 @@ var spi = new Ft232HDevice(devices[0]).CreateSpiDevice(settings); In this case the pin 3 is used as chip select. +> Important notes: +> +> - FT232H only has 1 SPI +> - FT2232H and FT4232H have 2 SPI, one on channel A and one on channel B. + ### Important notes on I2C and SPI -You can either create an SPI device, either an I2C device. FT232H does **not** allow you to create both at the same time. +You can either create an SPI device, either an I2C device on the same channel for FT232H and FT4232H. FT232H does **not** allow you to create both at the same time. You can for example create an I2C device on channel A of FT4232H and SPI on channel B. For both, the clock pin is 0, called D0, also called ADBUS0. @@ -100,4 +123,79 @@ var gpio = ft232h.CreateGpioDriver(); GpioController controller = new(PinNumberingScheme.Board, gpio); ``` -Note that then you can open any pin that hasn't been open or used by the SPI Device or I2C. In other words, you can use all the 16 pins if you're not using SPI or I2C. +> Important notes: +> +> - You can open any pin that hasn't been open or used by the SPI Device or I2C. In other words, you can use all the pins if you're not using SPI or I2C. +> + +### Differences between FT232H, FT2232H and FT4232H + +As seen before in some specificities, they can have multiple channels: + +- FT232H only has 1 channel and a total of 16 pins. + - You can create either SPI or I2C + - You can use any GPIO pin if you're not using SPI or I2C + - The device will appear like this: + + ```text + FT232H + Flags: HiSpeedMode + Id: 67330068 + LocId: 8739 + Serial number: FTL35PIN + Type: Ft232H + ``` + +- FT2232H has 2 channels A and B. + - Each channel has a total of 16 pins + - Each channel acts like an FT232H device + - The device will appear like this: + + ```text + Dual RS232-HS A + Flags: HiSpeedMode + Id: 67330064 + LocId: 139809 + Serial number: A + Type: Ft2232H + Dual RS232-HS B + Flags: HiSpeedMode + Id: 67330064 + LocId: 139810 + Serial number: B + Type: Ft2232H + ``` + +- FT4232H has 4 channels A, B, C and D. + - Each channel has a total of 8 pins + - Channel A and B can create either an I2C or SPI device on the same channel + - Remaining pins can be used for traditional GPIO + - Channel C and D can only be used for GPIO. All 8 pins can be set as input, output read and wrote. + - The device will appear like this: + + ```text + Quad RS232-HS A + Flags: HiSpeedMode + Id: 67330065 + LocId: 139825 + Serial number: A + Type: Ft4232H + Quad RS232-HS B + Flags: HiSpeedMode + Id: 67330065 + LocId: 139826 + Serial number: B + Type: Ft4232H + Quad RS232-HS C + Flags: HiSpeedMode + Id: 67330065 + LocId: 139827 + Serial number: C + Type: Ft4232H + Quad RS232-HS D + Flags: HiSpeedMode + Id: 67330065 + LocId: 139828 + Serial number: D + Type: Ft4232H + ``` diff --git a/src/devices/Ft232H/samples/Ft232H.sample.csproj b/src/devices/Ft232H/samples/Ft232H.sample.csproj index ae4cca9b8a..c2750fea4d 100644 --- a/src/devices/Ft232H/samples/Ft232H.sample.csproj +++ b/src/devices/Ft232H/samples/Ft232H.sample.csproj @@ -3,11 +3,14 @@ Exe $(DefaultSampleTfms) + false + + diff --git a/src/devices/Ft232H/samples/Program.cs b/src/devices/Ft232H/samples/Program.cs index 7508295624..7e8082fdc5 100644 --- a/src/devices/Ft232H/samples/Program.cs +++ b/src/devices/Ft232H/samples/Program.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Device.Gpio; +using System.Device.I2c; using System.Device.Spi; using System.Threading; using Iot.Device.Bmxx80; @@ -11,10 +12,12 @@ using Iot.Device.Board; using Iot.Device.Common; using Iot.Device.Ft232H; +using Iot.Device.Ft4232H; using Iot.Device.FtCommon; +using Iot.Device.Tsl256x; using UnitsNet; -Console.WriteLine("Hello FT232H"); +Console.WriteLine("Hello FTx232H"); var devices = FtCommon.GetDevices(); Console.WriteLine($"{devices.Count} available device(s)"); foreach (var device in devices) @@ -33,14 +36,17 @@ return; } -Ft232HDevice ft232h = Ft232HDevice.GetFt232H()[0]; +Ftx232HDevice ft232h = Ftx232HDevice.GetFtx232H()[0]; +// Optional, you can make sure the device is rest +ft232h.Reset(); // Uncomment the test you want to run -// TestSpi(ft232h); -// TestGpio(ft232h); -I2cScan(ft232h); -TestI2c(ft232h); +////TestSpi(ft232h); +TestGpio(ft232h); +////I2cScan(ft232h); +////TestI2c(ft232h); +////TestI2cTsl2561(ft232h); -void TestSpi(Ft232HDevice ft232h) +void TestSpi(Ftx232HDevice ft232h) { SpiConnectionSettings settings = new(0, 3) { ClockFrequency = 1_000_000, DataBitLength = 8, ChipSelectLineActiveState = PinValue.Low }; var spi = ft232h.CreateSpiDevice(settings); @@ -55,14 +61,14 @@ void TestSpi(Ft232HDevice ft232h) Console.WriteLine(); } -void TestGpio(Ft232HDevice ft232h) +void TestGpio(Ftx232HDevice ft232h) { - // Should transform it into 5 - const string PinNumber = "D5"; + // Should transform it into 5 on an FT4232H + const string PinNumber = "CDBUS5"; //// uses "D5" on a FT232H for example // It's possible to use this function to convert the board names you find in various - // implementation into the pin number - int gpio5 = Ft232HDevice.GetPinNumberFromString(PinNumber); + // implementation into the pin number assuming it's a FT4232H + int gpio5 = Ft4232HDevice.GetPinNumberFromString(PinNumber); var gpioController = ft232h.CreateGpioController(); // Opening GPIO2 @@ -89,7 +95,7 @@ void TestGpio(Ft232HDevice ft232h) } } -void I2cScan(Ft232HDevice ft232h) +void I2cScan(Ftx232HDevice ft232h) { Console.WriteLine("Hello I2C scanner!"); var i2cBus = ft232h.CreateOrGetI2cBus(ft232h.GetDefaultI2cBusNumber()); @@ -99,14 +105,14 @@ void I2cScan(Ft232HDevice ft232h) foreach (var valid in validAddress) { - Console.WriteLine($"Address: 0x{valid:X}"); + Console.WriteLine($"Address: 0x{valid:X2}"); } // Let'zs clean all i2cBus.Dispose(); } -void TestI2c(Ft232HDevice ft232h) +void TestI2c(Ftx232HDevice ft232h) { // set this to the current sea level pressure in the area for correct altitude readings Pressure defaultSeaLevelPressure = WeatherHelper.MeanSeaLevel; @@ -167,3 +173,26 @@ void TestI2c(Ft232HDevice ft232h) Thread.Sleep(5000); } } + +void TestI2cTsl2561(Ftx232HDevice ft232H) +{ + var ftI2cBus = ft232h.CreateOrGetI2cBus(ft232h.GetDefaultI2cBusNumber()); + var i2cDevice = ftI2cBus.CreateDevice(Tsl256x.DefaultI2cAddress); + Tsl256x tsl256X = new(i2cDevice, PackageType.Other); + + var ver = tsl256X.Version; + string msg = (ver.Major & 0x01) == 0x01 ? $"This is a TSL2561, version {ver}" : $"This is a TSL2560, version {ver}"; + Console.WriteLine(msg); + + tsl256X.IntegrationTime = IntegrationTime.Integration402Milliseconds; + tsl256X.Gain = Gain.Normal; + var lux = tsl256X.MeasureAndGetIlluminance(); + Console.WriteLine($"Illuminance is {lux.Lux} Lux"); + + Console.WriteLine("This will use a manual integration for 2 seconds"); + tsl256X.StartManualIntegration(); + Thread.Sleep(2000); + tsl256X.StopManualIntegration(); + tsl256X.GetRawChannels(out ushort ch0, out ushort ch1); + Console.WriteLine($"Raw data channel 0 {ch0}, channel 1 {ch1}"); +} diff --git a/src/devices/FtCommon/FtBitMode.cs b/src/devices/FtCommon/FtBitMode.cs new file mode 100644 index 0000000000..bbc0351684 --- /dev/null +++ b/src/devices/FtCommon/FtBitMode.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.FtCommon +{ + /// + /// Bit Mode used for pins. + /// + public enum FtBitMode : byte + { + /// Reset the IO Bit Mode. + ResetIoBitMode = 0x00, + + /// Asynchronous Bit Bang Mode. + AsynchronousBitBang = 0x01, + + /// MPSSE. + Mpsee = 0x02, + + /// Synchronous Bit Bang Mode. + SynchronousBitBang = 0x04, + + /// MCU Host Bus Emulation. + McuHostBusEmulation = 0x08, + + /// Fast Serial For Opto-Isolation. + FastSerialForOptoIsolation = 0x10, + } +} diff --git a/src/devices/FtCommon/FtChannel.cs b/src/devices/FtCommon/FtChannel.cs new file mode 100644 index 0000000000..bd3620a0c8 --- /dev/null +++ b/src/devices/FtCommon/FtChannel.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.FtCommon +{ + /// + /// The channel used when multi channels are used (like in FT2232H, FT4232H). + /// + public enum FtChannel + { + /// Channel A. + A, + + /// Channel B. + B, + + /// Channel C. + C, + + /// Channel A. + D, + } +} diff --git a/src/devices/FtCommon/FtCommon.projitems b/src/devices/FtCommon/FtCommon.projitems index fe9a08e6ed..2a3bbcf29c 100644 --- a/src/devices/FtCommon/FtCommon.projitems +++ b/src/devices/FtCommon/FtCommon.projitems @@ -9,6 +9,8 @@ FtCommon + + diff --git a/src/devices/FtCommon/FtFunction.cs b/src/devices/FtCommon/FtFunction.cs index 54b4093543..16d64443b6 100644 --- a/src/devices/FtCommon/FtFunction.cs +++ b/src/devices/FtCommon/FtFunction.cs @@ -52,6 +52,14 @@ internal class FtFunction [DllImport("ftd2xx")] public static extern FtStatus FT_Close(IntPtr ftHandle); + /// + /// Reset the device. + /// + /// The device handle + /// The status + [DllImport("ftd2xx")] + public static extern FtStatus FT_ResetDevice(SafeFtHandle ftHandle); + /// /// Sets timeouts. /// @@ -90,7 +98,16 @@ internal class FtFunction /// bit mode /// [DllImport("ftd2xx")] - public static extern FtStatus FT_SetBitMode(SafeFtHandle ftHandle, byte ucMask, byte ucMode); + public static extern FtStatus FT_SetBitMode(SafeFtHandle ftHandle, byte ucMask, FtBitMode ucMode); + + /// + /// Get bit mode. + /// + /// The handle of the open device + /// The actual value of each pin regarless if they are input or output. + /// + [DllImport("ftd2xx")] + public static extern FtStatus FT_GetBitMode(SafeFtHandle ftHandle, ref byte ucMode); /// /// Get queued status, this is used only to read the status of number of bytes to write. From 274aab30afbf9e32e36c99ef7ff56d772155fa4b Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Thu, 2 Feb 2023 09:05:36 +0100 Subject: [PATCH 4/6] fix doc nit --- src/devices/Ft232H/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/Ft232H/README.md b/src/devices/Ft232H/README.md index 9ea61345c2..9fc83ddb3c 100644 --- a/src/devices/Ft232H/README.md +++ b/src/devices/Ft232H/README.md @@ -67,7 +67,7 @@ int Gpio5 = Ft232HDevice.GetPinNumberFromString("D5"); Let's assume your device is the first one, you'll be able to create an I2C bus like this: ```csharp - var ftI2cBus = new Ft232HDevice(devices[0]).CreateI2cBus(); + var ftI2cBus = new Ft232HDevice(devices[0]).CreateOrGetI2cBus(); ``` From this bus, like for any other device, you can create an `I2cDevice`, in the following example for a BMP280. From e28e9904a78b07161b9c7ef790311a5eac60e907 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Thu, 2 Feb 2023 16:05:47 +0100 Subject: [PATCH 5/6] adjusting comments --- src/devices/Ft232H/Ft2232HDevice.cs | 4 ++-- src/devices/Ft232H/Ft4232HDevice.cs | 10 +++++----- src/devices/Ft232H/Ftx232HDevice.cs | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/devices/Ft232H/Ft2232HDevice.cs b/src/devices/Ft232H/Ft2232HDevice.cs index a66813c83c..4d8b87369c 100644 --- a/src/devices/Ft232H/Ft2232HDevice.cs +++ b/src/devices/Ft232H/Ft2232HDevice.cs @@ -17,9 +17,9 @@ namespace Iot.Device.Ft2232H public class Ft2232HDevice : Ftx232HDevice { /// - /// Gets all the FT232H connected + /// Gets all the FT2232H connected /// - /// A list of FT232H + /// A list of FT2232H public static List GetFt2232H() { List ft2232s = new List(); diff --git a/src/devices/Ft232H/Ft4232HDevice.cs b/src/devices/Ft232H/Ft4232HDevice.cs index 24dd061288..d8dcfd3b2b 100644 --- a/src/devices/Ft232H/Ft4232HDevice.cs +++ b/src/devices/Ft232H/Ft4232HDevice.cs @@ -12,14 +12,14 @@ namespace Iot.Device.Ft4232H { /// - /// FT232H Device + /// FT4232H Device /// public class Ft4232HDevice : Ftx232HDevice { /// - /// Gets all the FT232H connected + /// Gets all the FT4232H connected /// - /// A list of FT232H + /// A list of FT4232H public static List GetFt2232H() { List ft4232s = new List(); @@ -116,7 +116,7 @@ public static int GetPinNumberFromString(string pin) } /// - /// Instantiates a FT2232H device object. + /// Instantiates a FT4232H device object. /// /// Indicates device state. /// Indicates the device type. @@ -130,7 +130,7 @@ public Ft4232HDevice(FtFlag flags, FtDeviceType type, uint id, uint locId, strin } /// - /// Instantiates a FT2232H device object. + /// Instantiates a FT4232H device object. /// /// a FT Device public Ft4232HDevice(FtDevice ftDevice) diff --git a/src/devices/Ft232H/Ftx232HDevice.cs b/src/devices/Ft232H/Ftx232HDevice.cs index 2545c2f87f..ab2a69c80d 100644 --- a/src/devices/Ft232H/Ftx232HDevice.cs +++ b/src/devices/Ft232H/Ftx232HDevice.cs @@ -61,9 +61,9 @@ public class Ftx232HDevice : FtDevice, IDisposable internal List ConnectionSettings = new List(); /// - /// Gets all the FT232H connected + /// Gets all the FTx232H connected /// - /// A list of FT232H + /// A list of FTx232H public static List GetFtx232H() { List ft232s = new List(); @@ -85,7 +85,7 @@ public static List GetFtx232H() } /// - /// Instantiates a FT232H device object. + /// Instantiates a FTx232H device object. /// /// Indicates device state. /// Indicates the device type. @@ -99,7 +99,7 @@ public Ftx232HDevice(FtFlag flags, FtDeviceType type, uint id, uint locId, strin } /// - /// Instantiates a FT232H device object. + /// Instantiates a FTx232H device object. /// /// a FT Device public Ftx232HDevice(FtDevice ftDevice) From 0b05135b4ce3cb13f3e3f34e8350f4a71dfd90c5 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Fri, 3 Feb 2023 09:41:32 +0100 Subject: [PATCH 6/6] distinct elements in FtCommon only --- src/devices/FtCommon/FtCommon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/FtCommon/FtCommon.cs b/src/devices/FtCommon/FtCommon.cs index be974cdb54..511f0a9d4b 100644 --- a/src/devices/FtCommon/FtCommon.cs +++ b/src/devices/FtCommon/FtCommon.cs @@ -73,7 +73,7 @@ internal static List GetDevices(FtDeviceType[] ftDeviceTypes) { List ftdevices = new List(); var devices = GetDevices(); - foreach (var deviceType in ftDeviceTypes) + foreach (var deviceType in ftDeviceTypes.Distinct()) { ftdevices.AddRange(devices.Where(m => m.Type == deviceType)); }