407 lines
15 KiB
C#
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);
|
|
}
|
|
}
|