From 0e9d8dc1f64567f9d56166fa982f3354ad485c63 Mon Sep 17 00:00:00 2001 From: Nate Meyer Date: Sun, 23 Aug 2026 00:22:21 -0400 Subject: [PATCH] Update Libtslitex API with new Calibration parameters Rework libtslitex driver to use Channel parameter API. Let libtslitex load the factory calibration from NVM. Retrieve beta hw flag from device info. Note: Testbench still uses Manual API for calibration routines. --- source/TS.NET.Engine/EngineManager.cs | 6 +- .../Singletons/Instruments.cs | 10 +- .../Drivers/Libtslitex/Interop/Interop.cs | 110 ++++- .../TS.NET/Drivers/Libtslitex/Thunderscope.cs | 455 +++++++++++------- .../TS.NET/Drivers/Simulation/Thunderscope.cs | 17 + source/TS.NET/IThunderscope.cs | 6 +- 6 files changed, 392 insertions(+), 212 deletions(-) diff --git a/source/TS.NET.Engine/EngineManager.cs b/source/TS.NET.Engine/EngineManager.cs index 466a75f..d94eff6 100644 --- a/source/TS.NET.Engine/EngineManager.cs +++ b/source/TS.NET.Engine/EngineManager.cs @@ -188,21 +188,25 @@ public bool TryStart(string settingsFile, string calibrationFile, string deviceS { logger?.LogInformation($"Calibration loaded from path: {calibrationFile}"); loadedCalibration = Calibration.FromJsonFile(calibrationFile); + ts.SetCalibration(loadedCalibration); } else if (ThunderscopeNonVolatileMemory.TryReadUserCalibration(ts, out var userCalibration)) { logger?.LogInformation($"Calibration loaded from user calibration memory"); loadedCalibration = userCalibration!; + ts.SetCalibration(loadedCalibration); } else if (ThunderscopeNonVolatileMemory.TryReadFactoryCalibration(ts, out var factoryCalibration)) { logger?.LogInformation($"Calibration loaded from factory calibration memory"); loadedCalibration = factoryCalibration!; + // Factory Calibration loaded by tslitex } else if (File.Exists("thunderscope-calibration.json")) { logger?.LogInformation($"Calibration loaded from thunderscope-calibration.json"); loadedCalibration = Calibration.FromJsonFile("thunderscope-calibration.json"); + ts.SetCalibration(loadedCalibration); } else { @@ -225,7 +229,7 @@ public bool TryStart(string settingsFile, string calibrationFile, string deviceS initialHardwareConfiguration.ExtSyncMode = ThunderscopeExtSyncMode.Disabled; initialHardwareConfiguration.RefClockMode = ThunderscopeRefClockMode.Disabled; initialHardwareConfiguration.RefClockFrequencyHz = 10_000_000; - ts.Configure(initialHardwareConfiguration, loadedCalibration, thunderscopeSettings.HardwareRevision); + ts.Configure(initialHardwareConfiguration); ts.StartMonitoring(); thunderscope = ts; break; diff --git a/source/TS.NET.Sequences/Singletons/Instruments.cs b/source/TS.NET.Sequences/Singletons/Instruments.cs index 0709250..55fa968 100644 --- a/source/TS.NET.Sequences/Singletons/Instruments.cs +++ b/source/TS.NET.Sequences/Singletons/Instruments.cs @@ -89,7 +89,7 @@ public string Initialise() hardwareConfig.Frontend[3] = ThunderscopeChannelFrontend.Default(); thunderScope = new Driver.Libtslitex.Thunderscope(loggerFactory, 1024 * 1024); thunderScope.Open(0); - thunderScope.Configure(hardwareConfig, Calibration.Default(), ""); + thunderScope.Configure(hardwareConfig); // Need to set manual control on all channels so that SetRate doesn't run normal logic var manualControl = new ThunderscopeChannelFrontendManualControl() { Coupling = ThunderscopeCoupling.DC, Termination = ThunderscopeTermination.OneMegaohm, Attenuator = 0, DAC = 2000, DPOT = 50, PgaLadderAttenuation = 0, PgaFilter = ThunderscopeBandwidth.Bw20M, PgaHighGain = 0 }; thunderScope.SetChannelManualControl(0, manualControl); @@ -144,10 +144,10 @@ public void SetThunderscopeChannel(int[] enabledChannelIndices) { if (!enabledChannelIndices.SequenceEqual(cachedChannelIndices)) { - thunderScope?.SetChannelEnable(0, enabledChannelIndices.Contains(0), updateFrontends: false); - thunderScope?.SetChannelEnable(1, enabledChannelIndices.Contains(1), updateFrontends: false); - thunderScope?.SetChannelEnable(2, enabledChannelIndices.Contains(2), updateFrontends: false); - thunderScope?.SetChannelEnable(3, enabledChannelIndices.Contains(3), updateFrontends: false); + thunderScope?.SetChannelEnable(0, enabledChannelIndices.Contains(0)); + thunderScope?.SetChannelEnable(1, enabledChannelIndices.Contains(1)); + thunderScope?.SetChannelEnable(2, enabledChannelIndices.Contains(2)); + thunderScope?.SetChannelEnable(3, enabledChannelIndices.Contains(3)); cachedSampleRateHz = 0; cachedChannelIndices = enabledChannelIndices; } diff --git a/source/TS.NET/Drivers/Libtslitex/Interop/Interop.cs b/source/TS.NET/Drivers/Libtslitex/Interop/Interop.cs index cbbb816..ea425ed 100644 --- a/source/TS.NET/Drivers/Libtslitex/Interop/Interop.cs +++ b/source/TS.NET/Drivers/Libtslitex/Interop/Interop.cs @@ -87,35 +87,99 @@ public struct tsScopeState_t public byte acq_power_good; } + [StructLayout(LayoutKind.Sequential)] + public struct tsAfePathCalibration_s + { + public double bufferInputVpp; + public double trimOffsetDacZeroC; + public double trimOffsetDacZeroM; + public double trimOffsetDacScale; + public uint trimDPot; + } + [StructLayout(LayoutKind.Sequential)] public struct tsChannelCalibration_t { - public int buffer_uV; - public int bias_uV; - public int attenuatorGain1M_mdB; - public int attenuatorGain50_mdB; - public int bufferGain_mdB; - public int trimRheostat_range; - public int preampLowGainError_mdB; - public int preampHighGainError_mdB; + public double attenuatorScale; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 11)] + public tsAfePathCalibration_s[] highPgaPathCal; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 11)] + public tsAfePathCalibration_s[] lowPgaPathCal; + + public tsChannelCalibration_t() { + highPgaPathCal = new tsAfePathCalibration_s[11]; + lowPgaPathCal = new tsAfePathCalibration_s[11]; } + } - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] - public int[] preampAttenuatorGain_mdB; - public int preampOutputGainError_mdB; - public int preampLowOffset_uV; - public int preampHighOffset_uV; - public int preampInputBias_uA; + [StructLayout(LayoutKind.Sequential)] + public struct tsAdcLoad_t + { + public uint rate; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public double[] scale; - public tsChannelCalibration_t() { preampAttenuatorGain_mdB = new int[11]; } + public tsAdcLoad_t() { scale = new double[4];} } - + [StructLayout(LayoutKind.Sequential)] - public struct tsAdcCalibration_t + public struct tsAdcLoadCal_t + { + public uint channels; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 8)] + public tsAdcLoad_t[] conf; + + public tsAdcLoadCal_t() { + conf = new tsAdcLoad_t[8]; + for (var cal = 0; cal < 8; cal++) + { + conf[cal] = new tsAdcLoad_t(); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct tsAdcGain_t { + public uint rate; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] branchFineGain; + public byte[] gain; - public tsAdcCalibration_t() { branchFineGain = new byte[8]; } + public tsAdcGain_t() { gain = new byte[8];} + } + + [StructLayout(LayoutKind.Sequential)] + public struct tsAdcGainCal_t + { + public uint channels; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 8)] + public tsAdcGain_t[] conf; + + public tsAdcGainCal_t() { + conf = new tsAdcGain_t[8]; + for (var cal = 0; cal < 8; cal++) + { + conf[cal] = new tsAdcGain_t(); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct tsAdcCalibration_t + { + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 11)] + public tsAdcLoadCal_t[] loadCal; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 11)] + public tsAdcGainCal_t[] branchFineGain; + + public tsAdcCalibration_t() { + loadCal = new tsAdcLoadCal_t[11]; + branchFineGain = new tsAdcGainCal_t[11]; + for (var cal = 0; cal < 11; cal++) + { + loadCal[cal] = new tsAdcLoadCal_t(); + branchFineGain[cal] = new tsAdcGainCal_t(); + } + } } public enum tsSampleFormat_t @@ -171,7 +235,10 @@ public enum tsSampleFormat_t public static unsafe partial int UserDataWrite(nint ts, byte* buffer, uint offset, uint writeLen); [DllImport(library, EntryPoint = "thunderscopeChanCalibrationSet")] // Use runtime marshalling for now. Custom marshalling later. - public static extern int SetCalibration(nint ts, uint channel, in tsChannelCalibration_t cal); + public static extern int SetAFECalibration(nint ts, uint channel, in tsChannelCalibration_t cal); + + [DllImport(library, EntryPoint = "thunderscopeAdcCalibrationGet")] // Use runtime marshalling for now. Custom marshalling later. + public static extern int GetAFECalibration(nint ts, uint channel, out tsChannelCalibration_t cal); [DllImport(library, EntryPoint = "thunderscopeAdcCalibrationSet")] // Use runtime marshalling for now. Custom marshalling later. public static extern int SetAdcCalibration(nint ts, in tsAdcCalibration_t cal); @@ -194,6 +261,9 @@ public struct tsChannelCtrl_t [LibraryImport(library, EntryPoint = "thunderscopeCalibrationManualCtrl")] public static unsafe partial int SetChannelManualControl(nint ts, uint channel, in tsChannelCtrl_t ctrl); + + [LibraryImport(library, EntryPoint = "thunderscopeCalibrationManualAdcFineGain")] + public static unsafe partial int SetAdcManualFineGain(nint ts, in byte[] ctrl); public enum tsEventType_t { diff --git a/source/TS.NET/Drivers/Libtslitex/Thunderscope.cs b/source/TS.NET/Drivers/Libtslitex/Thunderscope.cs index 8bf9b0e..97b2ac2 100644 --- a/source/TS.NET/Drivers/Libtslitex/Thunderscope.cs +++ b/source/TS.NET/Drivers/Libtslitex/Thunderscope.cs @@ -2,7 +2,17 @@ namespace TS.NET.Driver.Libtslitex; -public record ThunderscopeLiteXDevice(uint DeviceID, uint HardwareRev, uint GatewareRev, uint LitexRev, string DevicePath, string Identity, string Serial, string BuildConfiguration, string BuildDate, string ManufacturingSignature); +public record ThunderscopeLiteXDevice + (uint DeviceID, + uint HardwareRev, + uint GatewareRev, + uint LitexRev, + string DevicePath, + string Identity, + string Serial, + string BuildConfiguration, + string BuildDate, + string ManufacturingSignature); public class Thunderscope : IThunderscope { @@ -16,6 +26,7 @@ public class Thunderscope : IThunderscope private bool[] channelManualOverride; private Calibration calibration; private ThunderscopeChannelFrontend[] channelFrontend; + private ThunderscopeLiteXDevice tsInfo; uint cachedSampleRateHz = 1_000_000_000; AdcResolution cachedSampleResolution = AdcResolution.EightBit; @@ -50,12 +61,19 @@ public Thunderscope(ILoggerFactory loggerFactory, int dmaBufferSize) channelManualOverride = new bool[4]; calibration = new Calibration(); channelFrontend = new ThunderscopeChannelFrontend[4]; + tsInfo = new ThunderscopeLiteXDevice(0, 0, 0, 0, "", "", "", "", "", ""); } public void Open(uint devIndex) { if (open) Close(); + + if (0 == Interop.ListDevices(devIndex, out var devInfo)) + { + tsInfo = new ThunderscopeLiteXDevice(devInfo.deviceID, devInfo.hw_id, devInfo.gw_id, devInfo.litex, devInfo.devicePath, devInfo.identity, devInfo.serialNumber, devInfo.buildConfig, devInfo.buildDate, devInfo.mfgSignature); + beta = (devInfo.hw_id & (1 << 9)) == 0; // ID Valid in bit [9] is not set for Beta hardware builds + } tsHandle = Interop.Open(devIndex, false); @@ -64,47 +82,36 @@ public void Open(uint devIndex) open = true; } - public void Configure(ThunderscopeHardwareConfig initialHardwareConfiguration, Calibration calibration, string hardwareRevision) + public void SetCalibration(Calibration calibration) { CheckOpen(); - if (hardwareRevision.Equals("Rev4.1", StringComparison.InvariantCultureIgnoreCase)) - beta = true; - - //SetAdcCalibration(initialHardwareConfiguration.AdcCalibration); - //for (int chan = 0; chan < 4; chan++) - //{ - // SetChannelCalibration(chan, initialHardwareConfiguration.Calibration[chan]); - //} + SetAdcCalibration(calibration.Adc); + for (int chan = 0; chan < 4; chan++) + { + SetChannelCalibration(chan, calibration.Frontend[chan]); + } this.calibration = calibration; + } + public void Configure(ThunderscopeHardwareConfig initialHardwareConfiguration) + { + CheckOpen(); + GetStatus(); for (int chan = 0; chan < 4; chan++) { channelFrontend[chan] = initialHardwareConfiguration.Frontend[chan]; var chanEnabled = ((initialHardwareConfiguration.Acquisition.EnabledChannels >> chan) & 0x01) > 0; - SetChannelEnable(chan, chanEnabled, updateFrontends: false); + SetChannelEnable(chan, chanEnabled); } - SetSampleMode(initialHardwareConfiguration.Acquisition.SampleRateHz, initialHardwareConfiguration.Acquisition.Resolution, updateFrontends: false); + SetSampleMode(initialHardwareConfiguration.Acquisition.SampleRateHz, initialHardwareConfiguration.Acquisition.Resolution); UpdateFrontends(); SetExtSyncMode(initialHardwareConfiguration.ExtSyncMode); SetRefClockMode(initialHardwareConfiguration.RefClockMode); SetRefClockFrequency(initialHardwareConfiguration.RefClockFrequencyHz); - var temperature = GetStatus().FpgaTemp; - var frontend = calibration.Frontend[0]; - foreach (var path in frontend.Path) - { - Frontend.CalculateAllowableOffsetRangeV(logger, frontend, path, attenuator: false, temperature, out var minOffsetV, out var maxOffsetV); - logger.LogDebug($"Input range: {path.BufferInputVpp:F3}Vpp, input offset: {minOffsetV:F3}V to {maxOffsetV:F3}V [{path.PgaPreampGain}, {path.PgaLadder}, attenuator off]"); - } - - foreach (var path in frontend.Path) - { - Frontend.CalculateAllowableOffsetRangeV(logger, frontend, path, attenuator: true, temperature, out var minOffsetV, out var maxOffsetV); - logger.LogDebug($"Input range: {path.BufferInputVpp * frontend.AttenuatorScale:F3}Vpp, input offset: {minOffsetV:F3}V to {maxOffsetV:F3}V [{path.PgaPreampGain}, {path.PgaLadder}, attenuator on]"); - } } public void Close() @@ -243,42 +250,29 @@ public ThunderscopeChannelFrontend GetChannelFrontend(int channelIndex) { CheckOpen(); - //var channel = new ThunderscopeChannelFrontend(); - //var tsChannel = new Interop.tsChannelParam_t(); - - //var retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); - //if (retVal < 0) - // throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); - - //channel.RequestedVoltFullScale = requestedChannelVoltScale[channelIndex]; - //channel.ActualVoltFullScale = (double)tsChannel.volt_scale_uV / 1000000.0; - //channel.RequestedVoltOffset = requestedChannelVoltOffset[channelIndex]; - //channel.ActualVoltOffset = (double)tsChannel.volt_offset_uV / 1000000.0; - //channel.Coupling = (tsChannel.coupling == 1) ? ThunderscopeCoupling.AC : ThunderscopeCoupling.DC; - //channel.Termination = (tsChannel.term == 1) ? ThunderscopeTermination.FiftyOhm : ThunderscopeTermination.OneMegaohm; - //channel.Bandwidth = (tsChannel.bandwidth == 750) ? ThunderscopeBandwidth.Bw750M : - // (tsChannel.bandwidth == 650) ? ThunderscopeBandwidth.Bw650M : - // (tsChannel.bandwidth == 350) ? ThunderscopeBandwidth.Bw350M : - // (tsChannel.bandwidth == 200) ? ThunderscopeBandwidth.Bw200M : - // (tsChannel.bandwidth == 100) ? ThunderscopeBandwidth.Bw100M : - // (tsChannel.bandwidth == 20) ? ThunderscopeBandwidth.Bw20M : - // ThunderscopeBandwidth.BwFull; - - //return channel; - - - var channel = channelFrontend[channelIndex]; - //var tsChannel = new Interop.tsChannelParam_t(); - - //var retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); - //if (retVal < 0) - // throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); - //channel.Termination = tsChannel.term switch - //{ - // 0 => ThunderscopeTermination.OneMegaohm, - // 1 => ThunderscopeTermination.FiftyOhm, - // _ => throw new NotImplementedException() - //}; + var channel = new ThunderscopeChannelFrontend(); + var tsChannel = new Interop.tsChannelParam_t(); + + var retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); + if (retVal < 0) + throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); + + channel.RequestedVoltFullScale = channelFrontend[channelIndex].RequestedVoltFullScale; + channel.ActualVoltFullScale = (double)tsChannel.volt_scale_uV / 1000000.0; + channel.RequestedVoltOffset = channelFrontend[channelIndex].RequestedVoltOffset; + channel.ActualVoltOffset = (double)tsChannel.volt_offset_uV / 1000000.0; + channel.Coupling = (tsChannel.coupling == 1) ? ThunderscopeCoupling.AC : ThunderscopeCoupling.DC; + channel.RequestedTermination = channelFrontend[channelIndex].RequestedTermination; + channel.ActualTermination = (tsChannel.term == 1) ? ThunderscopeTermination.FiftyOhm : ThunderscopeTermination.OneMegaohm; + channel.Bandwidth = (tsChannel.bandwidth == 750) ? ThunderscopeBandwidth.Bw750M : + (tsChannel.bandwidth == 650) ? ThunderscopeBandwidth.Bw650M : + (tsChannel.bandwidth == 350) ? ThunderscopeBandwidth.Bw350M : + (tsChannel.bandwidth == 200) ? ThunderscopeBandwidth.Bw200M : + (tsChannel.bandwidth == 100) ? ThunderscopeBandwidth.Bw100M : + (tsChannel.bandwidth == 20) ? ThunderscopeBandwidth.Bw20M : + ThunderscopeBandwidth.BwFull; + + return channel; } @@ -327,15 +321,42 @@ private ThunderscopeAcquisitionConfig GetAcquisitionConfig() return acquisitionConfig; } - //public ThunderscopeChannelCalibration GetChannelCalibration(int channelIndex) - //{ - // CheckOpen(); + public FrontendCalibration GetChannelCalibration(int channelIndex) + { + CheckOpen(); + + var afeCalibration = FrontendCalibration.Default(channelIndex); + var tsCal = new Interop.tsChannelCalibration_t(); - // if (channelIndex >= 4 || channelIndex < 0) - // throw new ThunderscopeException($"Invalid Channel Index {channelIndex}"); + if (channelIndex >= 4 || channelIndex < 0) + throw new ThunderscopeException($"Invalid Channel Index {channelIndex}"); + + var retVal = Interop.GetAFECalibration(tsHandle, (uint)channelIndex, out tsCal); + + if (retVal < 0) + throw new ThunderscopeException($"Failed to get libtslitex AFE{channelIndex} Calibration ({GetLibraryReturnString(retVal)})"); - // return channelCalibration[channelIndex]; - //} + for (int i = 0; i < 11; i++) + { + afeCalibration.Path[i].PgaPreampGain = PgaPreampGain.High; + afeCalibration.Path[i].PgaLadder = (byte)i; + afeCalibration.Path[i].TrimDPot = (byte)tsCal.highPgaPathCal[i].trimDPot; + afeCalibration.Path[i].TrimDacScale = tsCal.highPgaPathCal[i].trimOffsetDacScale; + afeCalibration.Path[i].TrimDacZeroC = tsCal.highPgaPathCal[i].trimOffsetDacZeroC; + afeCalibration.Path[i].TrimDacZeroM = tsCal.highPgaPathCal[i].trimOffsetDacZeroM; + afeCalibration.Path[i].BufferInputVpp = tsCal.highPgaPathCal[i].bufferInputVpp; + + afeCalibration.Path[11 + i].PgaPreampGain = PgaPreampGain.Low; + afeCalibration.Path[11 + i].PgaLadder = (byte)i; + afeCalibration.Path[11 + i].TrimDPot = (byte)tsCal.lowPgaPathCal[i].trimDPot; + afeCalibration.Path[11 + i].TrimDacScale = tsCal.lowPgaPathCal[i].trimOffsetDacScale; + afeCalibration.Path[11 + i].TrimDacZeroC = tsCal.lowPgaPathCal[i].trimOffsetDacZeroC; + afeCalibration.Path[11 + i].TrimDacZeroM = tsCal.lowPgaPathCal[i].trimOffsetDacZeroM; + afeCalibration.Path[11 + i].BufferInputVpp = tsCal.lowPgaPathCal[i].bufferInputVpp; + } + afeCalibration.AttenuatorScale = tsCal.attenuatorScale; + return afeCalibration; + } public ThunderscopeLiteXStatus GetStatus() { @@ -365,7 +386,7 @@ public ThunderscopeLiteXStatus GetStatus() public void SetRate(ulong sampleRateHz) { - SetSampleMode(sampleRateHz, cachedSampleResolution, updateFrontends: true); + SetSampleMode(sampleRateHz, cachedSampleResolution); } public void SetResolution(AdcResolution resolution) @@ -374,108 +395,203 @@ public void SetResolution(AdcResolution resolution) if (resolution == AdcResolution.TwelveBit && sampleRateHz > 660_000_000) sampleRateHz = 660_000_000; - SetSampleMode(sampleRateHz, resolution, updateFrontends: true); + SetSampleMode(sampleRateHz, resolution); } public void SetChannelFrontend(int channelIndex, ThunderscopeChannelFrontend channel) { CheckOpen(); - //var tsChannel = new Interop.tsChannelParam_t(); - //var retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); + var tsChannel = new Interop.tsChannelParam_t(); + var retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); - //if (retVal < 0) - // throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); + if (retVal < 0) + throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); - //tsChannel.volt_scale_uV = (uint)(channel.RequestedVoltFullScale * 1000000); - //tsChannel.volt_offset_uV = (int)(channel.RequestedVoltOffset * 1000000); - //tsChannel.coupling = (channel.Coupling == ThunderscopeCoupling.DC) ? (byte)0 : (byte)1; - //tsChannel.term = (channel.Termination == ThunderscopeTermination.OneMegaohm) ? (byte)0 : (byte)1; - //tsChannel.bandwidth = channel.Bandwidth switch - //{ - // ThunderscopeBandwidth.BwFull => 900, - // ThunderscopeBandwidth.Bw750M => 750, - // ThunderscopeBandwidth.Bw650M => 650, - // ThunderscopeBandwidth.Bw350M => 350, - // ThunderscopeBandwidth.Bw200M => 200, - // ThunderscopeBandwidth.Bw100M => 100, - // ThunderscopeBandwidth.Bw20M => 20, - // _ => throw new NotImplementedException() - //}; + tsChannel.volt_scale_uV = (uint)(channel.RequestedVoltFullScale * 1000000); + tsChannel.volt_offset_uV = (int)(channel.RequestedVoltOffset * 1000000); + tsChannel.coupling = (channel.Coupling == ThunderscopeCoupling.DC) ? (byte)0 : (byte)1; + tsChannel.term = (channel.RequestedTermination == ThunderscopeTermination.OneMegaohm) ? (byte)0 : (byte)1; + tsChannel.bandwidth = channel.Bandwidth switch + { + ThunderscopeBandwidth.BwFull => 900, + ThunderscopeBandwidth.Bw750M => 750, + ThunderscopeBandwidth.Bw650M => 650, + ThunderscopeBandwidth.Bw350M => 350, + ThunderscopeBandwidth.Bw200M => 200, + ThunderscopeBandwidth.Bw100M => 100, + ThunderscopeBandwidth.Bw20M => 20, + _ => throw new NotImplementedException() + }; - //retVal = Interop.SetChannelConfig(tsHandle, (uint)channelIndex, in tsChannel); + logger.LogInformation($"Configure channel {channelIndex}: scale {tsChannel.volt_scale_uV}uVpp, offset {tsChannel.volt_offset_uV}uV, term {tsChannel.term}"); - //if (retVal < 0) - // throw new ThunderscopeException($"Failed to set channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); + retVal = Interop.SetChannelConfig(tsHandle, (uint)channelIndex, in tsChannel); - //requestedChannelVoltScale[channelIndex] = channel.RequestedVoltFullScale; - //requestedChannelVoltOffset[channelIndex] = channel.RequestedVoltOffset; + if (retVal < 0) + throw new ThunderscopeException($"Failed to set channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); - var loadScales = Frontend.GetAllLoadScales(calibration, channelsEnabled, cachedSampleRateHz); - SetChannelFrontend(channelIndex, channel, loadScales[channelIndex]); + + retVal = Interop.GetChannelConfig(tsHandle, (uint)channelIndex, out tsChannel); + + if (retVal < 0) + throw new ThunderscopeException($"Failed to get channel {channelIndex} config ({GetLibraryReturnString(retVal)})"); + + channelFrontend[channelIndex].ActualTermination = (tsChannel.term == 0) ? ThunderscopeTermination.OneMegaohm : ThunderscopeTermination.FiftyOhm; + channelFrontend[channelIndex].ActualVoltFullScale = tsChannel.volt_scale_uV * 1000000.0; + channelFrontend[channelIndex].ActualVoltOffset = tsChannel.volt_offset_uV * 1000000.0; + channelFrontend[channelIndex].RequestedVoltFullScale = channel.RequestedVoltFullScale; + channelFrontend[channelIndex].RequestedVoltOffset = channel.RequestedVoltOffset; + channelFrontend[channelIndex].RequestedTermination = channel.RequestedTermination; + channelFrontend[channelIndex].Bandwidth = channel.Bandwidth; + channelFrontend[channelIndex].Coupling = channel.Coupling; + + channelManualOverride[channelIndex] = false; // SetChannelManualControl sets to true, so immediately set to false } public void SetAdcBranchGainManualControl(byte[] branchGain) { CheckOpen(); - var tsCal = new Interop.tsAdcCalibration_t(); - tsCal.branchFineGain[0] = branchGain[0]; - tsCal.branchFineGain[1] = branchGain[1]; - tsCal.branchFineGain[2] = branchGain[2]; - tsCal.branchFineGain[3] = branchGain[3]; - tsCal.branchFineGain[4] = branchGain[4]; - tsCal.branchFineGain[5] = branchGain[5]; - tsCal.branchFineGain[6] = branchGain[6]; - tsCal.branchFineGain[7] = branchGain[7]; - - Interop.SetAdcCalibration(tsHandle, in tsCal); - } - - //public void SetChannelCalibration(int channelIndex, ThunderscopeChannelCalibration channelCalibration) - //{ - // CheckOpen(); - - // if (channelIndex >= 4 || channelIndex < 0) - // throw new ThunderscopeException($"Invalid Channel Index {channelIndex}"); - - // var tsCal = new Interop.tsChannelCalibration_t - // { - // //buffer_uV = (int)(channelCalibration.BufferOffset * 1000000), - // //bias_uV = (int)(channelCalibration.BiasVoltage * 1000000), - // //attenuatorGain1M_mdB = (int)(channelCalibration.AttenuatorGain1MOhm * 1000), - // //attenuatorGain50_mdB = (int)(channelCalibration.AttenuatorGain50Ohm * 1000), - // //bufferGain_mdB = (int)(channelCalibration.BufferGain * 1000), - // //trimRheostat_range = (int)channelCalibration.TrimResistorOhms, - // //preampLowGainError_mdB = (int)(channelCalibration.PgaLowGainError * 1000), - // //preampHighGainError_mdB = (int)(channelCalibration.PgaHighGainError * 1000), - // //preampLowOffset_uV = (int)(channelCalibration.PgaLowOffsetVoltage * 1000000), - // //preampHighOffset_uV = (int)(channelCalibration.PgaHighOffsetVoltage * 1000000), - // //preampOutputGainError_mdB = (int)(channelCalibration.PgaOutputGainError * 1000), - // //preampInputBias_uA = (int)channelCalibration.PgaInputBiasCurrent, - // }; - // //tsCal.preampAttenuatorGain_mdB[0] = (int)channelCalibration.PgaAttenuatorGain0; - // //tsCal.preampAttenuatorGain_mdB[1] = (int)channelCalibration.PgaAttenuatorGain1; - // //tsCal.preampAttenuatorGain_mdB[2] = (int)channelCalibration.PgaAttenuatorGain2; - // //tsCal.preampAttenuatorGain_mdB[3] = (int)channelCalibration.PgaAttenuatorGain3; - // //tsCal.preampAttenuatorGain_mdB[4] = (int)channelCalibration.PgaAttenuatorGain4; - // //tsCal.preampAttenuatorGain_mdB[5] = (int)channelCalibration.PgaAttenuatorGain5; - // //tsCal.preampAttenuatorGain_mdB[6] = (int)channelCalibration.PgaAttenuatorGain6; - // //tsCal.preampAttenuatorGain_mdB[7] = (int)channelCalibration.PgaAttenuatorGain7; - // //tsCal.preampAttenuatorGain_mdB[8] = (int)channelCalibration.PgaAttenuatorGain8; - // //tsCal.preampAttenuatorGain_mdB[9] = (int)channelCalibration.PgaAttenuatorGain9; - // //tsCal.preampAttenuatorGain_mdB[10] = (int)channelCalibration.PgaAttenuatorGain10; - - // this.channelCalibration[channelIndex] = channelCalibration; - // Interop.SetCalibration(tsHandle, (uint)channelIndex, in tsCal); - //} - - public void SetChannelEnable(int channelIndex, bool enabled) => SetChannelEnable(channelIndex, enabled, updateFrontends: true); + var tsCal = new byte[8]; + tsCal[0] = branchGain[0]; + tsCal[1] = branchGain[1]; + tsCal[2] = branchGain[2]; + tsCal[3] = branchGain[3]; + tsCal[4] = branchGain[4]; + tsCal[5] = branchGain[5]; + tsCal[6] = branchGain[6]; + tsCal[7] = branchGain[7]; + + Interop.SetAdcManualFineGain(tsHandle, in tsCal); + } + + public void SetChannelCalibration(int channelIndex, FrontendCalibration channelCalibration) + { + CheckOpen(); + + if (channelIndex >= 4 || channelIndex < 0) + throw new ThunderscopeException($"Invalid Channel Index {channelIndex}"); + + var tsCal = new Interop.tsChannelCalibration_t(); + + if (channelIndex >= 4 || channelIndex < 0) + throw new ThunderscopeException($"Invalid Channel Index {channelIndex}"); + + + + foreach (var path in channelCalibration.Path) + { + if (path.PgaPreampGain == PgaPreampGain.High) + { + tsCal.highPgaPathCal[path.PgaLadder].trimDPot = path.TrimDPot; + tsCal.highPgaPathCal[path.PgaLadder].trimOffsetDacZeroC = path.TrimDacZeroC; + tsCal.highPgaPathCal[path.PgaLadder].trimOffsetDacZeroM = path.TrimDacZeroM; + tsCal.highPgaPathCal[path.PgaLadder].trimOffsetDacScale = path.TrimDacScale; + tsCal.highPgaPathCal[path.PgaLadder].bufferInputVpp = path.BufferInputVpp; + } + else + { + tsCal.lowPgaPathCal[path.PgaLadder].trimDPot = path.TrimDPot; + tsCal.lowPgaPathCal[path.PgaLadder].trimOffsetDacZeroC = path.TrimDacZeroC; + tsCal.lowPgaPathCal[path.PgaLadder].trimOffsetDacZeroM = path.TrimDacZeroM; + tsCal.lowPgaPathCal[path.PgaLadder].trimOffsetDacScale = path.TrimDacScale; + tsCal.lowPgaPathCal[path.PgaLadder].bufferInputVpp = path.BufferInputVpp; + } + } + + tsCal.attenuatorScale = channelCalibration.AttenuatorScale; + + var retVal = Interop.SetAFECalibration(tsHandle, (uint)channelIndex, in tsCal); + if (retVal < 0) + throw new ThunderscopeException($"Failed to set libtslitex AFE{channelIndex} Calibration ({GetLibraryReturnString(retVal)})"); + + } + + public void SetAdcCalibration(AdcCalibration adcCalibration) + { + CheckOpen(); + + var tsAdcCal = new Interop.tsAdcCalibration_t(); + for (int i = 0; i < adcCalibration.LoadScale.Length; i++) + { + tsAdcCal.loadCal[i].channels = (uint)adcCalibration.LoadScale[i].Channel.Aggregate(0, (current, chan) => 1 << chan); + for (int j = 0; j < adcCalibration.LoadScale[i].RateScale.Length; j++) + { + tsAdcCal.loadCal[i].conf[j].rate = adcCalibration.LoadScale[i].RateScale[j].Rate; + for (int k = 0; k < adcCalibration.LoadScale[i].RateScale[j].Scale.Length; k++) + tsAdcCal.loadCal[i].conf[j].scale[k] = adcCalibration.LoadScale[i].RateScale[j].Scale[k]; + } + } + for (int i = 0; i < adcCalibration.BranchGain.Length; i++) + { + tsAdcCal.branchFineGain[i].channels = (uint)adcCalibration.BranchGain[i].Channel.Aggregate(0, (current, chan) => 1 << chan); + for (int j = 0; j < adcCalibration.BranchGain[i].RateGain.Length; j++) + { + tsAdcCal.branchFineGain[i].conf[j].rate = adcCalibration.BranchGain[i].RateGain[j].Rate; + for (int k = 0; k < adcCalibration.BranchGain[i].RateGain[j].Gain.Length; k++) + tsAdcCal.branchFineGain[i].conf[j].gain[k] = (byte)adcCalibration.BranchGain[i].RateGain[j].Gain[k]; + } + } + } + + public AdcCalibration GetAdcCalibration() + { + var adcCal = AdcCalibration.Default(); + var tsAdcCal = new Interop.tsAdcCalibration_t(); + if (0 == Interop.GetAdcCalibration(tsHandle, out tsAdcCal)) + { + // Convert Load Scale calibration + for (int load = 0; load < 11; load++) + { + var channelCount = 0; + for (int i = 0; i < 4; i++) + { + if ((tsAdcCal.loadCal[load].channels & (1 << i)) != 0) + { + adcCal.LoadScale[load].Channel[channelCount] = i; + channelCount++; + } + } + for (int rateIdx = 0; rateIdx < 8; rateIdx++) + { + adcCal.LoadScale[load].RateScale[rateIdx].Rate = tsAdcCal.loadCal[load].conf[rateIdx].rate; + for (int i = 0; i < channelCount; i++) + { + adcCal.LoadScale[load].RateScale[rateIdx].Scale[i] = tsAdcCal.loadCal[load].conf[rateIdx].scale[i]; + } + } + } + // Convert Branch Fine Gain calibration + for (int gain = 0; gain < 11; gain++) + { + var channelCount = 0; + for (int i = 0; i < 4; i++) + { + if ((tsAdcCal.branchFineGain[gain].channels & (1 << i)) != 0) + { + adcCal.BranchGain[gain].Channel[channelCount] = i; + channelCount++; + } + } + for (int rateIdx = 0; rateIdx < 8; rateIdx++) + { + adcCal.BranchGain[gain].RateGain[rateIdx].Rate = tsAdcCal.branchFineGain[gain].conf[rateIdx].rate; + for (int i = 0; i < 8; i++) + { + adcCal.BranchGain[gain].RateGain[rateIdx].Gain[i] = tsAdcCal.branchFineGain[gain].conf[rateIdx].gain[i]; + } + } + } + } + + return adcCal; + } /// /// Intended for use by testbench/calibration routines that use SetChannelManualControl /// - public void SetChannelEnable(int channelIndex, bool enabled, bool updateFrontends) + public void SetChannelEnable(int channelIndex, bool enabled) { CheckOpen(); @@ -505,10 +621,6 @@ public void SetChannelEnable(int channelIndex, bool enabled, bool updateFrontend channelsEnabled = channelsEnabled.Where(c => c != channelIndex).ToArray(); } - // There is a channel-count vs. scale relationship so update frontends - if (updateFrontends) - UpdateFrontends(); - if (restart) Start(); @@ -669,35 +781,16 @@ public int FactoryDataRead(uint tag, Span buffer) } } - private void SetChannelFrontend(int channelIndex, ThunderscopeChannelFrontend channel, double channelLoadScale) - { - CheckOpen(); - - lastFrontendUpdateTemp = GetStatus().FpgaTemp; - var frontend = calibration.Frontend[channelIndex]; - - var calculatedChannel = Frontend.CalculateFrontend(logger, channelIndex, channel, frontend, channelLoadScale, cachedSampleRateHz, lastFrontendUpdateTemp, beta, out var manualControl); - SetChannelManualControl(channelIndex, manualControl); - channelManualOverride[channelIndex] = false; // SetChannelManualControl sets to true, so immediately set to false - channelFrontend[channelIndex] = calculatedChannel; - } - private void UpdateFrontends() { - GetStatus(); // Update cachedSampleRateHz & cachedSampleResolution - - var loadScales = Frontend.GetAllLoadScales(calibration, channelsEnabled, cachedSampleRateHz); - for (int i = 0; i < channelFrontend.Length; i++) + for (int channelIndex = 0; channelIndex < 4; channelIndex++) { // Update all frontends that aren't under manual override, even for disabled channels (so relays actuate when expected) - if (!channelManualOverride[i]) + if (!channelManualOverride[channelIndex]) { - //Console.WriteLine($"Updating channel {i} frontend with load scale {loadScales[i]:F3}"); - SetChannelFrontend(i, channelFrontend[i], loadScales[i]); + SetChannelFrontend(channelIndex, channelFrontend[channelIndex]); } } - var branchGains = Frontend.GetAdcBranchGain(calibration, channelsEnabled, cachedSampleRateHz); - SetAdcBranchGainManualControl(branchGains); } private void CheckOpen() @@ -720,7 +813,7 @@ private static string GetLibraryReturnString(int retValue) }; } - private void SetSampleMode(ulong sampleRateHz, AdcResolution resolution, bool updateFrontends) + private void SetSampleMode(ulong sampleRateHz, AdcResolution resolution) { CheckOpen(); @@ -737,9 +830,6 @@ private void SetSampleMode(ulong sampleRateHz, AdcResolution resolution, bool up }; var retVal = Interop.SetSampleMode(tsHandle, (uint)sampleRateHz, format); - // There is a rate vs. scale relationship so update frontends - if (updateFrontends) - UpdateFrontends(); if (retVal == -2) logger.LogTrace($"Failed to set sample rate ({sampleRateHz}): {GetLibraryReturnString(retVal)}"); @@ -770,11 +860,8 @@ private void MonitoringLoop(ILogger logger, CancellationToken cancelToken) if (diff > tempDelta) { - for (int channelIndex = 0; channelIndex < 4; channelIndex++) - { - var frontend = GetChannelFrontend(channelIndex); - SetChannelFrontend(channelIndex, frontend); // Updates lastFrontendUpdateTemp - } + UpdateFrontends(); + lastFrontendUpdateTemp = currentTemp; } } diff --git a/source/TS.NET/Drivers/Simulation/Thunderscope.cs b/source/TS.NET/Drivers/Simulation/Thunderscope.cs index 75ee107..35b0c78 100644 --- a/source/TS.NET/Drivers/Simulation/Thunderscope.cs +++ b/source/TS.NET/Drivers/Simulation/Thunderscope.cs @@ -167,5 +167,22 @@ public void SetPeriodicEventSync(uint period_us) { throw new NotImplementedException(); } + public FrontendCalibration GetChannelCalibration(int channelIndex) + { + throw new NotImplementedException(); + } + public void SetChannelCalibration(int channelIndex, FrontendCalibration channelCalibration) + { + throw new NotImplementedException(); + } + + public AdcCalibration GetAdcCalibration() + { + throw new NotImplementedException(); + } + public void SetAdcCalibration(AdcCalibration adcCalibration) + { + throw new NotImplementedException(); + } } } diff --git a/source/TS.NET/IThunderscope.cs b/source/TS.NET/IThunderscope.cs index 27de239..d6c9fd9 100644 --- a/source/TS.NET/IThunderscope.cs +++ b/source/TS.NET/IThunderscope.cs @@ -6,10 +6,12 @@ public interface IThunderscope void Stop(); bool Running(); ThunderscopeChannelFrontend GetChannelFrontend(int channelIndex); - //ThunderscopeChannelCalibration GetChannelCalibration(int channelIndex); + FrontendCalibration GetChannelCalibration(int channelIndex); + AdcCalibration GetAdcCalibration(); void SetChannelEnable(int channelIndex, bool enabled); void SetChannelFrontend(int channelIndex, ThunderscopeChannelFrontend channel); - //void SetChannelCalibration(int channelIndex, ThunderscopeChannelCalibration channelCalibration); + void SetChannelCalibration(int channelIndex, FrontendCalibration channelCalibration); + void SetAdcCalibration(AdcCalibration adcCalibration); void Read(ThunderscopeMemory data); bool TryRead(Span data, out ulong sampleStartIndex, out int sampleLengthPerChannel); ThunderscopeHardwareConfig GetConfiguration();