SSPC-Tester/Driver/PCIE8586/Pcie8586Probe/Hardware/Pcie8586Digitizer.cs

407 lines
15 KiB
C#

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<IReadOnlyList<DeviceInfo>> EnumerateDevicesAsync(CancellationToken cancellationToken)
{
return await Task.Run<IReadOnlyList<DeviceInfo>>(() =>
{
var devices = new List<DeviceInfo>();
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<SampleBlock> 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);
}
}