using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Pcie8586Probe.Acquisition; using Pcie8586Probe.Models; namespace Pcie8586Probe.Hardware; public sealed class Pcie8586Digitizer : IDigitizer { private const int InvalidHandle = -1; private const int MaxChannels = 8; private const int ReadBufferWords = 256 * 1024; private IntPtr _handle; private AcquisitionConfig _config = AcquisitionConfig.Default; private bool _started; public bool IsOpen => IsValidHandle(_handle); public async ValueTask> EnumerateDevicesAsync(CancellationToken cancellationToken) { return await Task.Run>(() => { var devices = new List(); IntPtr probe = IntPtr.Zero; try { probe = Native.ACTS1000_CreateDevice(0); if (!IsValidHandle(probe)) { return devices; } var count = Native.ACTS1000_GetDeviceCount(probe); for (var logicalId = 0; logicalId < Math.Max(0, count); logicalId++) { cancellationToken.ThrowIfCancellationRequested(); IntPtr device = IntPtr.Zero; try { device = Native.ACTS1000_CreateDevice(logicalId); if (!IsValidHandle(device)) { continue; } var info = default(Native.ACTS1000_AD_MAIN_INFO); var description = "ACTS1000 digitizer"; if (Native.ACTS1000_GetMainInfo(device, ref info)) { description = FormatDeviceName(info.nDeviceType); } int? physicalId = null; var lgc = 0; var phys = 0; if (Native.ACTS1000_GetDeviceCurrentID(device, ref lgc, ref phys)) { physicalId = phys; } devices.Add(new DeviceInfo(logicalId, physicalId, description, false)); } finally { if (IsValidHandle(device)) { Native.ACTS1000_ReleaseDevice(device); } } } } catch (DllNotFoundException) { return devices; } catch (BadImageFormatException) { return devices; } catch (EntryPointNotFoundException) { return devices; } finally { if (IsValidHandle(probe)) { Native.ACTS1000_ReleaseDevice(probe); } } return devices; }, cancellationToken); } public ValueTask OpenAsync(DeviceInfo device, CancellationToken cancellationToken) { CloseAsync().GetAwaiter().GetResult(); _handle = Native.ACTS1000_CreateDevice(device.LogicalId); if (!IsValidHandle(_handle)) { _handle = IntPtr.Zero; throw new InvalidOperationException($"ACTS1000_CreateDevice({device.LogicalId}) failed."); } return ValueTask.CompletedTask; } public ValueTask ConfigureAsync(AcquisitionConfig config, CancellationToken cancellationToken) { config.Validate(); EnsureOpen(); _config = config; var mainInfo = default(Native.ACTS1000_AD_MAIN_INFO); if (!Native.ACTS1000_GetMainInfo(_handle, ref mainInfo)) { throw new Win32Exception("ACTS1000_GetMainInfo failed."); } var ad = new Native.ACTS1000_PARA_AD { bChannelArray = new int[MaxChannels], InputRange = new int[MaxChannels], CouplingType = new int[MaxChannels], InputImped = new int[MaxChannels], FreqDivision = config.ClockDivider, SampleMode = config.Mode == AcquisitionMode.Continuous ? Native.ACTS1000_SAMPMODE_CONTINUOUS : Native.ACTS1000_SAMPMODE_FINITE, M_Length = 0, N_Length = config.Mode == AcquisitionMode.Finite ? checked((uint)config.FiniteSamplesPerChannel) : 32 * 1024, PFISel = Native.ACTS1000_PFISEL_TRIG_IN, TriggerMode = Native.ACTS1000_TRIGMODE_POST, TriggerSource = Native.ACTS1000_TRIGMODE_SOFT, TriggerDir = Native.ACTS1000_TRIGDIR_NEGATIVE, TrigLevelVolt = 0, TrigWindow = 0, TrigCount = 1, ReferenceClock = Native.ACTS1000_RECLK_ONBOARD, TimeBaseClock = Native.ACTS1000_TBCLK_IN, bMasterEn = 0, SyncTrigSignal = Native.ACTS1000_STS_TRIGGER0, bClkOutEn = 0, ClkOutSel = Native.ACTS1000_CLKOUT_REFERENCE, bTrigOutEn = 0, TrigOutPolarity = Native.ACTS1000_TOP_POSITIVE, TrigOutWidth = 50, bSaveFile = 0, chFileName = string.Empty }; var range = config.InputRange == InputRange.PlusMinus5V ? Native.ACTS1000_INPUT_N5000_P5000mV : Native.ACTS1000_INPUT_N1000_P1000mV; for (var channel = 0; channel < MaxChannels; channel++) { ad.bChannelArray[channel] = channel < config.ChannelCount ? 1 : 0; ad.InputRange[channel] = range; ad.CouplingType[channel] = Native.ACTS1000_COUPLING_DC; ad.InputImped[channel] = Native.ACTS1000_IMPED_1M; } if (!Native.ACTS1000_InitDeviceAD(_handle, ref ad)) { throw new Win32Exception("ACTS1000_InitDeviceAD failed."); } return ValueTask.CompletedTask; } public async IAsyncEnumerable StartAcquisitionAsync([EnumeratorCancellation] CancellationToken cancellationToken) { EnsureOpen(); if (!Native.ACTS1000_StartDeviceAD(_handle)) { throw new Win32Exception("ACTS1000_StartDeviceAD failed."); } _started = true; if (!Native.ACTS1000_SetDeviceTrigAD(_handle)) { throw new Win32Exception("ACTS1000_SetDeviceTrigAD failed."); } var buffer = new ushort[ReadBufferWords]; var totalSamplesPerChannel = 0L; var remainingWords = _config.Mode == AcquisitionMode.Finite ? _config.FiniteSamplesPerChannel * _config.ChannelCount : long.MaxValue; while (!cancellationToken.IsCancellationRequested && remainingWords > 0) { var wordsToRead = (uint)Math.Min(buffer.Length, remainingWords); uint wordsRead = 0; uint available = 0; var ok = await Task.Run(() => Native.ACTS1000_ReadDeviceAD(_handle, buffer, wordsToRead, ref wordsRead, ref available, 1.0), cancellationToken); if (!ok) { throw new Win32Exception("ACTS1000_ReadDeviceAD failed."); } if (wordsRead == 0) { await Task.Delay(1, cancellationToken); continue; } var alignedWords = (int)(wordsRead / _config.ChannelCount * _config.ChannelCount); if (alignedWords == 0) { continue; } var raw = new ushort[alignedWords]; Array.Copy(buffer, raw, alignedWords); var channels = CodeConverter.Deinterleave(raw, _config.ChannelCount, _config.InputRange); var block = new SampleBlock(channels, _config.SampleRateHz, totalSamplesPerChannel, DateTimeOffset.Now); totalSamplesPerChannel += block.SamplesPerChannel; remainingWords -= alignedWords; yield return block; } } public ValueTask StopAsync() { if (IsOpen && _started) { Native.ACTS1000_StopDeviceAD(_handle); Native.ACTS1000_ReleaseDeviceAD(_handle); _started = false; } return ValueTask.CompletedTask; } public async ValueTask CloseAsync() { await StopAsync(); if (IsOpen) { Native.ACTS1000_ReleaseDevice(_handle); _handle = IntPtr.Zero; } } public void Dispose() { CloseAsync().GetAwaiter().GetResult(); } private static bool IsValidHandle(IntPtr handle) { return handle != IntPtr.Zero && handle.ToInt64() != InvalidHandle; } private static string FormatDeviceName(int deviceType) { var prefix = (deviceType >> 16) switch { 0x2012 => "PXIE", 0x2111 => "PCIE", _ => "ACTS1000-" }; return $"{prefix}{deviceType & 0xFFFF:X4}"; } private void EnsureOpen() { if (!IsOpen) { throw new InvalidOperationException("Device is not open."); } } private static class Native { public const int ACTS1000_INPUT_N5000_P5000mV = 0x00; public const int ACTS1000_INPUT_N1000_P1000mV = 0x01; public const int ACTS1000_COUPLING_DC = 0x00; public const int ACTS1000_IMPED_1M = 0x00; public const int ACTS1000_SAMPMODE_FINITE = 0x00; public const int ACTS1000_SAMPMODE_CONTINUOUS = 0x01; public const int ACTS1000_PFISEL_TRIG_IN = 0x01; public const int ACTS1000_TRIGMODE_POST = 0x01; public const int ACTS1000_TRIGMODE_SOFT = 0x00; public const int ACTS1000_TRIGDIR_NEGATIVE = 0x00; public const int ACTS1000_RECLK_ONBOARD = 0x00; public const int ACTS1000_TBCLK_IN = 0x00; public const int ACTS1000_STS_TRIGGER0 = 0x00; public const int ACTS1000_CLKOUT_REFERENCE = 0x00; public const int ACTS1000_TOP_POSITIVE = 0x01; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct ACTS1000_PARA_AD { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public int[] bChannelArray; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public int[] InputRange; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public int[] CouplingType; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public int[] InputImped; public int FreqDivision; public int SampleMode; public uint M_Length; public uint N_Length; public int PFISel; public int TriggerMode; public int TriggerSource; public int TriggerDir; public int TrigLevelVolt; public int TrigWindow; public uint TrigCount; public int ReferenceClock; public int TimeBaseClock; public int bMasterEn; public int SyncTrigSignal; public int bClkOutEn; public int ClkOutSel; public int bTrigOutEn; public int TrigOutPolarity; public int TrigOutWidth; public int bSaveFile; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string chFileName; } [StructLayout(LayoutKind.Sequential)] public struct ACTS1000_AD_MAIN_INFO { public int nDeviceType; public int nChannelCount; public int nDepthOfMemory; public int nSampResolution; public int nSampCodeCount; public int nTrigLvlResolution; public int nTrigLvlCodeCount; public int nBaseRate; public int nMaxRate; public int nMinFreqDivision; public int nSupportImped; public int nSupportPFI; public int nSupportExtClk; public int nSupportPXIE100M; public int nSupportClkOut; public int nReserved0; public int nReserved1; public int nReserved2; public int nReserved3; } [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] public static extern IntPtr ACTS1000_CreateDevice(int DeviceID); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_ReleaseDevice(IntPtr hDevice); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] public static extern int ACTS1000_GetDeviceCount(IntPtr hDevice); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_GetDeviceCurrentID(IntPtr hDevice, ref int DeviceLgcID, ref int DevicePhysID); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_GetMainInfo(IntPtr hDevice, ref ACTS1000_AD_MAIN_INFO pMainInfo); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_InitDeviceAD(IntPtr hDevice, ref ACTS1000_PARA_AD pADPara); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_StartDeviceAD(IntPtr hDevice); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_SetDeviceTrigAD(IntPtr hDevice); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_ReadDeviceAD( IntPtr hDevice, [Out] ushort[] pADBuffer, uint nReadSizeWords, ref uint nRetSizeWords, ref uint pAvailSampsPoints, double fTimeout); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_StopDeviceAD(IntPtr hDevice); [DllImport("ACTS1000_64.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ACTS1000_ReleaseDeviceAD(IntPtr hDevice); } }