public WatchdogTimerTask(SequenceData sequence, int masterFrequency, NationalInstruments.DAQmx.Task taskToWatch, double watchDogThresholdTime) { this.taskToWatch = taskToWatch; TimestepTimebaseSegmentCollection segments = sequence.generateVariableTimebaseSegments(SequenceData.VariableTimebaseTypes.AnalogGroupControlledVariableFrequencyClock, 1.0 / ((double)masterFrequency)); int currentSequenceSampleNumber = 0; long currentMasterSampleNumber=0; watchdogTimerSegments = new List<WatchdogTimerSegment>(); int thresholdTime = (int)(masterFrequency*watchDogThresholdTime); for (int i = 0; i < sequence.TimeSteps.Count; i++) { TimeStep step = sequence.TimeSteps[i]; if (step.StepEnabled) { bool stepUsed = false; if (segments.ContainsKey(step)) { List<SequenceData.VariableTimebaseSegment> segmentList = segments[step]; for (int j = 0; j < segmentList.Count; j++) { SequenceData.VariableTimebaseSegment currentSegment = segmentList[j]; if (currentSegment.MasterSamplesPerSegmentSample >= thresholdTime) { if (!stepUsed) { WatchdogTimerSegment wseg = new WatchdogTimerSegment(); wseg.timeStepName = step.StepName; wseg.timeStepID = i; wseg.sequenceSampleNumber = currentSequenceSampleNumber + 1; wseg.masterSampleNumber = currentMasterSampleNumber + thresholdTime / 2; stepUsed = true; watchdogTimerSegments.Add(wseg); } } currentSequenceSampleNumber += currentSegment.NSegmentSamples; currentMasterSampleNumber += currentSegment.NSegmentSamples * currentSegment.MasterSamplesPerSegmentSample; } } } } }
public FpgaTimebaseTask(DeviceSettings deviceSettings, okCFrontPanel opalKellyDevice, SequenceData sequence, double masterClockPeriod, out int nSegments, bool useRfModulation, bool assymetric) : base() { com.opalkelly.frontpanel.okCFrontPanel.ErrorCode errorCode; this.opalKellyDevice = opalKellyDevice; this.masterClockPeriod = masterClockPeriod; TimestepTimebaseSegmentCollection segments = sequence.generateVariableTimebaseSegments(SequenceData.VariableTimebaseTypes.AnalogGroupControlledVariableFrequencyClock, masterClockPeriod); this.max_elapsedtime_ms = (UInt32)((sequence.SequenceDuration * 1000.0) + 100); byte[] data = FpgaTimebaseTask.createByteArray(segments, sequence, out nSegments, masterClockPeriod, assymetric ); // Send the device an abort trigger. errorCode = opalKellyDevice.ActivateTriggerIn(0x40, 1); if (errorCode != okCFrontPanel.ErrorCode.NoError) { throw new Exception("Unable to set abort trigger to FPGA device. Error code " + errorCode.ToString()); } UInt16 wireInValue = 0; if (deviceSettings.StartTriggerType != DeviceSettings.TriggerType.SoftwareTrigger) { wireInValue += 1; } if (useRfModulation) { wireInValue += 2; } setWireInValue(0x00, wireInValue); setWireInValue(0x01, deviceSettings.RetriggerDebounceSamples); opalKellyDevice.UpdateWireIns(); // pipe the byte stream to the device int xfered = opalKellyDevice.WriteToPipeIn(0x80, data.Length, data); if (xfered != data.Length) { throw new Exception("Error when piping clock data to FPGA device. Sent " + xfered + " bytes instead of " + data.Length + "bytes."); } }
/// <summary> /// Creates a task for a variable timebase output. Consumes the entire port (8 bits) that the timebase is on. (ie outputs the /// signal on all 8 bits /// </summary> /// <param name="channelName"></param> /// <param name="masterFrequency"></param> /// <param name="sequenceData"></param> /// <param name="timebaseType"></param> /// <returns></returns> public static Task createDaqMxVariableTimebaseSource(string channelName, int masterFrequency, SequenceData sequenceData, SequenceData.VariableTimebaseTypes timebaseType, ServerSettings serverSettings, DeviceSettings deviceSettings) { Task task = new Task("Variable timebase output task"); TimestepTimebaseSegmentCollection timebaseSegments = sequenceData.generateVariableTimebaseSegments(timebaseType, 1.0 / (double)masterFrequency); bool [] buffer = sequenceData.getVariableTimebaseClock(timebaseSegments); string timebaseDeviceName = HardwareChannel.parseDeviceNameStringFromPhysicalChannelString(channelName); string timebasePort = HardwareChannel.parsePortStringFromChannelString(channelName); task.DOChannels.CreateChannel(timebasePort, "", ChannelLineGrouping.OneChannelForAllLines); task.Timing.ConfigureSampleClock("", (double)masterFrequency, deviceSettings.ClockEdge, SampleQuantityMode.FiniteSamples, buffer.Length); if (serverSettings.VariableTimebaseTriggerInput != "") { task.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger(serverSettings.VariableTimebaseTriggerInput, DigitalEdgeStartTriggerEdge.Rising); } DigitalSingleChannelWriter writer = new DigitalSingleChannelWriter(task.Stream); byte[] byteBuffer = new byte[buffer.Length]; for (int j = 0; j < buffer.Length; j++) { if (buffer[j]) { byteBuffer[j] = 255; } } writer.WriteMultiSamplePort(false, byteBuffer); return task; }
/// <summary> /// This method is a sort of combination of createDaqMxVariableTimebaseSource and createDaqMxTask. It is intended /// for use to create a digital output task that has both a variable timebase source on it, without having to discard /// all of the other channels on that port (and possibly on its neighboring port). /// /// NOTE: No longer true. This function can not create the variable timebase outputs at the same time as /// normal outputs. If you attempt to use digital outputs on the same half of the card as your variable timebase output, /// this method will complain. /// </summary> /// <param name="channelName"> /// Name of the channel that will output the variable timebase clock. /// </param> /// <param name="portsToUse"> /// A list of integers specifying the digital ports that this task will use. The task will automatically /// make use of the full port that the variable timebase clock belongs to. If portsToUse is null, then /// this function will automatically use both this port and its neighboring port (0 with 1, 2 with 3, etc). /// The rationale for this is that on some NI devices, these pairs of ports will share a sample clock and /// cannot truly be used independently. /// </param> /// <param name="masterFrequency"> /// The frequency, in hertz, of the master clock which will drive the variable timebase clock and the rest of the /// channels in this task. /// </param> /// <param name="sequenceData"></param> /// <param name="timebaseType"></param> /// <param name="deviceName"></param> /// <param name="deviceSettings"></param> /// <param name="sequence"></param> /// <param name="settings"></param> /// <param name="usedDigitalChannels"></param> /// <param name="usedAnalogChannels"></param> /// <param name="serverSettings"></param> /// <returns></returns> public static Task createDaqMxDigitalOutputAndVariableTimebaseSource(string channelName, List<int> portsToUse, int masterFrequency, SequenceData sequenceData, SequenceData.VariableTimebaseTypes timebaseType, string deviceName, DeviceSettings deviceSettings, SettingsData settings, Dictionary<int, HardwareChannel> usedDigitalChannels, ServerSettings serverSettings) { // First generate the variable timebase buffer. We will need stuff like its length for configuring the task, which is why we do this first. TimestepTimebaseSegmentCollection timebaseSegments = sequenceData.generateVariableTimebaseSegments(timebaseType, 1.0 / deviceSettings.SampleClockRate); bool[] variableTimebaseBuffer = sequenceData.getVariableTimebaseClock(timebaseSegments); if (deviceName.ToUpper() != HardwareChannel.parseDeviceNameStringFromPhysicalChannelString(channelName).ToUpper()) { throw new Exception("The variable timebase device " + HardwareChannel.parseDeviceNameStringFromPhysicalChannelString(channelName) + " does not match device " + deviceName + ". These must match for their their task to be created together."); } int timebasePortNum; int timebaseLineNum; try { timebasePortNum = HardwareChannel.parsePortNumberFromChannelString(channelName); timebaseLineNum = HardwareChannel.parseLineNumberFromChannelString(channelName); } catch (Exception) { throw new Exception("Channel name " + channelName + " is not a valid digital channel name. Cannot create a variable timebase output on this channel."); } if (portsToUse == null) { portsToUse = new List<int>(); portsToUse.Add(timebasePortNum); int spousePort; // this port is likely to have a shared sample clock with timebasePortNum, // at least in my experience so far. if (timebasePortNum % 2 == 0) spousePort = timebasePortNum + 1; else spousePort = timebasePortNum - 1; portsToUse.Add(spousePort); } if (!portsToUse.Contains(timebasePortNum)) { portsToUse.Add(timebasePortNum); } bool otherChannelsUsedOnUsedPort = false; foreach (HardwareChannel hc in usedDigitalChannels.Values) { if (hc.DeviceName.ToUpper() == deviceName.ToUpper()) { if (portsToUse.Contains(hc.daqMxDigitalPortNumber())) otherChannelsUsedOnUsedPort = true; } } if (otherChannelsUsedOnUsedPort) { throw new Exception("Variable timebase channel is on a port that shares a sample clock with a used output channel (on most devices, port 0 and 1 have a shared clock, and port 2 and 3 have a shared clock). This usage is not recommended, and not currently supported. Aborting buffer generation."); #region Deprecated code /* Task task = new Task("Variable timebase output task"); // Create channels in the task foreach (int portNum in portsToUse) { task.DOChannels.CreateChannel(deviceName + '/' + HardwareChannel.digitalPhysicalChannelName(portNum), "", ChannelLineGrouping.OneChannelForAllLines); } // Configure the task... task.Timing.ConfigureSampleClock("", masterFrequency, SampleClockActiveEdge.Rising, SampleQuantityMode.FiniteSamples, variableTimebaseBuffer.Length); if (serverSettings.VariableTimebaseTriggerInput != "") { task.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger(serverSettings.VariableTimebaseTriggerInput, DigitalEdgeStartTriggerEdge.Rising); } // Figure out which ports we are going to use, and which digital ID each line on each of those ports // maps to. Dictionary<int, HardwareChannel> digitalChannelsToUse = getChannelsOnDevice(usedDigitalChannels, deviceName); List<int> temp = new List<int>(digitalChannelsToUse.Keys); foreach (int id in temp) { HardwareChannel ch = digitalChannelsToUse[id]; if (!portsToUse.Contains(HardwareChannel.parsePortNumberFromChannelString(ch.ChannelName))) { digitalChannelsToUse.Remove(id); } } // Remove all of the digital channels this buffer generation is consuming from the // usedDigitalChannels dictionary. Why? Because there may still be another task to // generate on this device, and this is a way of keeping track of which channels // have already had their buffers generated. // Since digitalChannelsToUse gets passed by reference from AtticusServerRuntime.generateBuffers(...), // these changes thus get communicated back to AtticusServerRuntime. foreach (HardwareChannel hc in digitalChannelsToUse.Values) { foreach (int i in usedDigitalChannels.Keys) { if (usedDigitalChannels[i] == hc) { usedDigitalChannels.Remove(i); break; } } } List<int> ids = new List<int>(digitalChannelsToUse.Keys); ids.Sort(); List<HardwareChannel> hcs = new List<HardwareChannel>(); foreach (int id in ids) { hcs.Add(digitalChannelsToUse[id]); } Dictionary<int, int[]> port_digital_ids; List<int> usedPorts; groupDigitalChannels(ids, hcs, out port_digital_ids, out usedPorts); // now to generate the buffers. if (usedPorts.Count != 0) { byte[,] digitalBuffer; try { digitalBuffer = new byte[usedPorts.Count, variableTimebaseBuffer.Length]; } catch (Exception e) { throw new Exception("Unable to allocate digital buffer for device " + deviceName + ". Reason: " + e.Message + "\n" + e.StackTrace); } for (int i = 0; i < usedPorts.Count; i++) { int portNum = usedPorts[i]; byte digitalBitMask = 1; for (int lineNum = 0; lineNum < 8; lineNum++) { bool[] singleChannelBuffer = null; if (portNum == timebasePortNum && lineNum == timebaseLineNum) { // this current line is the variable timebase... singleChannelBuffer = variableTimebaseBuffer; } else { int digitalID = port_digital_ids[portNum][lineNum]; if (digitalID != -1) { if (settings.logicalChannelManager.Digitals[digitalID].overridden) { singleChannelBuffer = new bool[variableTimebaseBuffer.Length]; if (settings.logicalChannelManager.Digitals[digitalID].digitalOverrideValue) { for (int j = 0; j < singleChannelBuffer.Length; j++) { singleChannelBuffer[j] = true; } } } else { singleChannelBuffer = sequenceData.getDigitalBufferClockSharedWithVariableTimebaseClock(timebaseSegments, digitalID, 1.0 / deviceSettings.SampleClockRate); } } } if (singleChannelBuffer != null) { for (int j = 0; j < singleChannelBuffer.Length; j++) { if (singleChannelBuffer[j]) { digitalBuffer[i, j] |= digitalBitMask; } } } digitalBitMask = (byte)(digitalBitMask << 1); } } System.GC.Collect(); DigitalMultiChannelWriter writer = new DigitalMultiChannelWriter(task.Stream); writer.WriteMultiSamplePort(false, digitalBuffer); } return task; */ #endregion } else { return createDaqMxVariableTimebaseSource( channelName, masterFrequency, sequenceData, timebaseType, serverSettings, deviceSettings); } }
/// <summary> /// This method creates analog and digital output buffers for daqMx cards. Note that the daqmx library seems to only support /// either analog OR digital on a given card at one time. Despite the fact that this method will create both types of buffers, /// it will probably throw some daqMX level exceptions if asked to create both analog and digital buffers for the same device. /// </summary> /// <param name="deviceName"></param> /// <param name="deviceSettings"></param> /// <param name="sequence"></param> /// <param name="settings"></param> /// <param name="usedDigitalChannels">digital channels which reside on this server.</param> /// <param name="usedAnalogChannels">analog channels which reside on this server</param> /// <returns></returns> public static Task createDaqMxTask(string deviceName, DeviceSettings deviceSettings, SequenceData sequence, SettingsData settings, Dictionary<int, HardwareChannel> usedDigitalChannels, Dictionary<int, HardwareChannel> usedAnalogChannels, ServerSettings serverSettings, out long expectedSamplesGenerated) { expectedSamplesGenerated = 0; Task task = new Task(deviceName + " output task"); List<int> analogIDs; List<HardwareChannel> analogs; Dictionary<int, int[]> port_digital_IDs; List<int> usedPortNumbers; // Parse and create channels. parseAndCreateChannels(deviceName,deviceSettings, usedDigitalChannels, usedAnalogChannels, task, out analogIDs, out analogs, out port_digital_IDs, out usedPortNumbers); if (analogIDs.Count != 0) { if (deviceSettings.UseCustomAnalogTransferSettings) { task.AOChannels.All.DataTransferMechanism = deviceSettings.AnalogDataTransferMechanism; task.AOChannels.All.DataTransferRequestCondition = deviceSettings.AnalogDataTransferCondition; } } if (usedPortNumbers.Count != 0) { if (deviceSettings.UseCustomDigitalTransferSettings) { task.DOChannels.All.DataTransferMechanism = deviceSettings.DigitalDataTransferMechanism; task.DOChannels.All.DataTransferRequestCondition = deviceSettings.DigitalDataTransferCondition; } } // ok! now create the buffers #region NON variable timebase buffer if (deviceSettings.UsingVariableTimebase == false) { // non "variable timebase" buffer creation double timeStepSize = 1.0 / (double)deviceSettings.SampleClockRate; int nBaseSamples = sequence.nSamples(timeStepSize); // for reasons that are utterly stupid and frustrating, the DAQmx libraries seem to prefer sample // buffers with lengths that are a multiple of 4. (otherwise they, on occasion, depending on the parity of the // number of channels, throw exceptions complaining. // thus we add a few filler samples at the end of the sequence which parrot back the last sample. int nFillerSamples = 4 - nBaseSamples % 4; if (nFillerSamples == 4) nFillerSamples = 0; int nSamples = nBaseSamples + nFillerSamples; if (deviceSettings.MySampleClockSource == DeviceSettings.SampleClockSource.DerivedFromMaster) { task.Timing.ConfigureSampleClock("", deviceSettings.SampleClockRate, deviceSettings.ClockEdge, SampleQuantityMode.FiniteSamples, nSamples); } else { task.Timing.ConfigureSampleClock(deviceSettings.SampleClockExternalSource, deviceSettings.SampleClockRate, deviceSettings.ClockEdge, SampleQuantityMode.FiniteSamples, nSamples); } if (deviceSettings.MasterTimebaseSource != "" && deviceSettings.MasterTimebaseSource != null) { task.Timing.MasterTimebaseSource = deviceSettings.MasterTimebaseSource.ToString(); } // Analog first... if (analogIDs.Count != 0) { double[,] analogBuffer; double[] singleChannelBuffer; try { analogBuffer = new double[analogs.Count, nSamples]; singleChannelBuffer = new double[nSamples]; } catch (Exception e) { throw new Exception("Unable to allocate analog buffer for device " + deviceName + ". Reason: " + e.Message + "\n" + e.StackTrace); } for (int i = 0; i < analogIDs.Count; i++) { int analogID = analogIDs[i]; if (settings.logicalChannelManager.Analogs[analogID].TogglingChannel) { DaqMxTaskGenerator.getAnalogTogglingBuffer(singleChannelBuffer); } else if (settings.logicalChannelManager.Analogs[analogID].overridden) { for (int j = 0; j < singleChannelBuffer.Length; j++) { singleChannelBuffer[j] = settings.logicalChannelManager.Analogs[analogID].analogOverrideValue; } } else { sequence.computeAnalogBuffer(analogIDs[i], timeStepSize, singleChannelBuffer); } for (int j = 0; j < nBaseSamples; j++) { analogBuffer[i, j] = singleChannelBuffer[j]; } for (int j = nBaseSamples; j < nSamples; j++) { analogBuffer[i, j] = analogBuffer[i, j - 1]; } } singleChannelBuffer = null; System.GC.Collect(); AnalogMultiChannelWriter writer = new AnalogMultiChannelWriter(task.Stream); writer.WriteMultiSample(false, analogBuffer); // analog cards report the exact number of generated samples. for non-variable timebase this is nSamples expectedSamplesGenerated = nSamples; } if (usedPortNumbers.Count != 0) { byte[,] digitalBuffer; bool[] singleChannelBuffer; try { digitalBuffer = new byte[usedPortNumbers.Count, nSamples]; singleChannelBuffer = new bool[nSamples]; } catch (Exception e) { throw new Exception("Unable to allocate digital buffer for device " + deviceName + ". Reason: " + e.Message + "\n" + e.StackTrace); } for (int i = 0; i < usedPortNumbers.Count; i++) { int portNum = usedPortNumbers[i]; byte digitalBitMask = 1; for (int lineNum = 0; lineNum < 8; lineNum++) { int digitalID = port_digital_IDs[portNum][lineNum]; if (digitalID != -1) { if (settings.logicalChannelManager.Digitals[digitalID].TogglingChannel) { getDigitalTogglingBuffer(singleChannelBuffer); } else if (settings.logicalChannelManager.Digitals[digitalID].overridden) { for (int j = 0; j < singleChannelBuffer.Length; j++) { singleChannelBuffer[j] = settings.logicalChannelManager.Digitals[digitalID].digitalOverrideValue; } } else { sequence.computeDigitalBuffer(digitalID, timeStepSize, singleChannelBuffer); } // byte digitalBitMask = (byte)(((byte) 2)^ ((byte)lineNum)); for (int j = 0; j < nBaseSamples; j++) { // copy the bit value into the digital buffer byte. if (singleChannelBuffer[j]) digitalBuffer[i, j] |= digitalBitMask; } } digitalBitMask = (byte)(digitalBitMask << 1); } for (int j = nBaseSamples; j < nSamples; j++) { digitalBuffer[i, j] = digitalBuffer[i, j - 1]; } } singleChannelBuffer = null; System.GC.Collect(); DigitalMultiChannelWriter writer = new DigitalMultiChannelWriter(task.Stream); writer.WriteMultiSamplePort(false, digitalBuffer); /// Digital cards report the number of generated samples as a multiple of 4 expectedSamplesGenerated = nSamples; } } #endregion #region Variable timebase buffer creation else // variable timebase buffer creation... { double timeStepSize = 1.0 / (double)deviceSettings.SampleClockRate; TimestepTimebaseSegmentCollection timebaseSegments = sequence.generateVariableTimebaseSegments(serverSettings.VariableTimebaseType, timeStepSize); int nBaseSamples = timebaseSegments.nSegmentSamples(); nBaseSamples++; // add one sample for the dwell sample at the end of the buffer // for reasons that are utterly stupid and frustrating, the DAQmx libraries seem to prefer sample // buffers with lengths that are a multiple of 4. (otherwise they, on occasion, depending on the parity of the // number of channels, throw exceptions complaining. // thus we add a few filler samples at the end of the sequence which parrot back the last sample. int nFillerSamples = 4 - nBaseSamples % 4; if (nFillerSamples == 4) nFillerSamples = 0; int nSamples = nBaseSamples + nFillerSamples; if (deviceSettings.MySampleClockSource == DeviceSettings.SampleClockSource.DerivedFromMaster) { throw new Exception("Attempt to use a uniform sample clock with a variable timebase enabled device. This will not work. To use a variable timebase for this device, you must specify an external sample clock source."); } else { task.Timing.ConfigureSampleClock(deviceSettings.SampleClockExternalSource, deviceSettings.SampleClockRate, deviceSettings.ClockEdge, SampleQuantityMode.FiniteSamples, nSamples); } // Analog first... if (analogIDs.Count != 0) { double[,] analogBuffer; double[] singleChannelBuffer; try { analogBuffer = new double[analogs.Count, nSamples]; singleChannelBuffer = new double[nSamples]; } catch (Exception e) { throw new Exception("Unable to allocate analog buffer for device " + deviceName + ". Reason: " + e.Message + "\n" + e.StackTrace); } for (int i = 0; i < analogIDs.Count; i++) { int analogID = analogIDs[i]; if (settings.logicalChannelManager.Analogs[analogID].TogglingChannel) { getAnalogTogglingBuffer(singleChannelBuffer); } else if (settings.logicalChannelManager.Analogs[analogID].overridden) { for (int j = 0; j < singleChannelBuffer.Length; j++) { singleChannelBuffer[j] = settings.logicalChannelManager.Analogs[analogID].analogOverrideValue; } } else { sequence.computeAnalogBuffer(analogIDs[i], timeStepSize, singleChannelBuffer, timebaseSegments); } for (int j = 0; j < nBaseSamples; j++) { analogBuffer[i, j] = singleChannelBuffer[j]; } for (int j = nBaseSamples; j < nSamples; j++) { analogBuffer[i, j] = analogBuffer[i, j - 1]; } } singleChannelBuffer = null; System.GC.Collect(); AnalogMultiChannelWriter writer = new AnalogMultiChannelWriter(task.Stream); writer.WriteMultiSample(false, analogBuffer); // Analog cards report the exact number of samples generated. for variable timebase this is nBaseSamples expectedSamplesGenerated = nBaseSamples; } if (usedPortNumbers.Count != 0) { byte[,] digitalBuffer; bool[] singleChannelBuffer; try { digitalBuffer = new byte[usedPortNumbers.Count, nSamples]; singleChannelBuffer = new bool[nSamples]; } catch (Exception e) { throw new Exception("Unable to allocate digital buffer for device " + deviceName + ". Reason: " + e.Message + "\n" + e.StackTrace); } for (int i = 0; i < usedPortNumbers.Count; i++) { int portNum = usedPortNumbers[i]; byte digitalBitMask = 1; for (int lineNum = 0; lineNum < 8; lineNum++) { int digitalID = port_digital_IDs[portNum][lineNum]; if (digitalID != -1) { if (settings.logicalChannelManager.Digitals[digitalID].TogglingChannel) { getDigitalTogglingBuffer(singleChannelBuffer); } else if (settings.logicalChannelManager.Digitals[digitalID].overridden) { for (int j = 0; j < singleChannelBuffer.Length; j++) { singleChannelBuffer[j] = settings.logicalChannelManager.Digitals[digitalID].digitalOverrideValue; } } else { sequence.computeDigitalBuffer(digitalID, timeStepSize, singleChannelBuffer, timebaseSegments); } // byte digitalBitMask = (byte)(((byte) 2)^ ((byte)lineNum)); for (int j = 0; j < nBaseSamples; j++) { // copy the bit value into the digital buffer byte. if (singleChannelBuffer[j]) digitalBuffer[i, j] |= digitalBitMask; } } digitalBitMask = (byte)(digitalBitMask << 1); } for (int j = nBaseSamples; j < nSamples; j++) { digitalBuffer[i, j] = digitalBuffer[i, j - 1]; } } singleChannelBuffer = null; System.GC.Collect(); DigitalMultiChannelWriter writer = new DigitalMultiChannelWriter(task.Stream); writer.WriteMultiSamplePort(false, digitalBuffer); // digital cards report number of samples generated up to multiple of 4 expectedSamplesGenerated = nSamples; } } #endregion if (deviceSettings.StartTriggerType == DeviceSettings.TriggerType.TriggerIn) { task.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger( deviceSettings.TriggerInPort, DigitalEdgeStartTriggerEdge.Rising); } task.Control(TaskAction.Verify); task.Control(TaskAction.Commit); task.Control(TaskAction.Reserve); return task; }