/// <summary> /// Same as other version of groupDigitalChannels, but only allows for port numbers in the list allowedPortsToUse /// </summary> /// <param name="digitalIDs"></param> /// <param name="digitals"></param> /// <param name="port_digital_IDs"></param> /// <param name="usedPortNumbers"></param> /// <param name="allowedPortsToUse"></param> private static void groupDigitalChannels(List <int> digitalIDs, List <HardwareChannel> digitals, out Dictionary <int, int[]> port_digital_IDs, out List <int> usedPortNumbers, bool is6533, List <int> allowedPortsToUse) { // Irritating but true fact of life: To make the DAQmx drivers happy // we have to output the digital outputs in 8 bit groups corresponding to // the "port" of the device. This means we first have to amalgamate the digital channels that belong // to the same port, and fill in any "holes" (ie missing channels) with empty data { usedPortNumbers = new List <int>(); // mapping from port number to an array of integers. The nth element of the array gives the digital ID // of the channel which is connected to the nth line of that port. If there is no channel on that port, // the ID is -1. port_digital_IDs = new Dictionary <int, int[]>(); for (int i = 0; i < digitalIDs.Count; i++) { int digitalID = digitalIDs[i]; HardwareChannel hc = digitals[i]; int portNum = hc.daqMxDigitalPortNumber(); int portNumBis; if (!usedPortNumbers.Contains(portNum)) { if (allowedPortsToUse.Contains(portNum)) { if (!is6533) { usedPortNumbers.Add(portNum); // Ports have to be used by pairs for the Daqmx driver to work. If port 0 is used, port 1 has to be used too, and the opposite too, etc... portNumBis = portNum - 2 * (portNum % 2) + 1; usedPortNumbers.Add(portNumBis); // create the array mapping line number to digital IDs, and fill it with -1s. port_digital_IDs.Add(portNum, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); port_digital_IDs.Add(portNumBis, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); } else { usedPortNumbers.Add(0); usedPortNumbers.Add(1); usedPortNumbers.Add(2); usedPortNumbers.Add(3); port_digital_IDs.Add(0, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); port_digital_IDs.Add(1, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); port_digital_IDs.Add(2, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); port_digital_IDs.Add(3, new int[] { -1, -1, -1, -1, -1, -1, -1, -1 }); } } } if (usedPortNumbers.Contains(portNum)) { port_digital_IDs[portNum][hc.daqMxDigitalLineNumber()] = digitalID; } } } }
/// <summary> /// This function creates channels in the given task, for the named device. /// </summary> /// <param name="deviceName"> /// The name of the device which the channels will will be created for. Eg Dev1 /// </param> /// <param name="usedDigitalChannels"></param> /// A dictionary, indexed by logical ID#, of all of the digital hardware channels parsed from parsed from settings data which /// reside on this server. /// <param name="usedAnalogChannels"> /// A dictionary, indexed by logical ID#, of all of the analog hardware channels parsed from the settings data, which reside on this server. /// </param> /// <param name="task">The task for which channels will be created.</param> /// <param name="analogIDs"> /// An out parameter, which will store a list of the analog IDs that were created, in the order created. /// </param> /// <param name="analogs"> /// An out parameter, which will store a list of the analog hardware channels corresponding to the channels created, in the order created. /// </param> /// <param name="port_digital_IDs"></param> /// An out parameter, which will store a dictionary mapping a digital port number to an 8-length array of integers specifying the logical ID# of each bit of the /// given port #. Entries of -1 in this array indicate bits that have no logical ID# assigned. See **** in this method's source code for further information. /// <param name="usedPortNumbers"> /// An out parameter, which will store a list of integers corresponding to the digital port numbers that were used on this device, in the order created. /// </param> private static void parseAndCreateChannels(string deviceName, DeviceSettings deviceSettings, Dictionary <int, HardwareChannel> usedDigitalChannels, Dictionary <int, HardwareChannel> usedAnalogChannels, Task task, out List <int> analogIDs, out List <HardwareChannel> analogs, out Dictionary <int, int[]> port_digital_IDs, out List <int> usedPortNumbers) { // figure out which of the analog and digital channels belong on this device. Add them here and index by // logical ID# Dictionary <int, HardwareChannel> analogsUnsorted = getChannelsOnDevice(usedAnalogChannels, deviceName); Dictionary <int, HardwareChannel> digitalsUnsorted = getChannelsOnDevice(usedDigitalChannels, deviceName); // sort the lists by ID analogIDs = new List <int>(); analogs = new List <HardwareChannel>(); sortDicionaryByID(analogIDs, analogs, analogsUnsorted); // list of the digital IDs of channels on this device List <int> digitalIDs = new List <int>(); // list of the corresponding hardware channels List <HardwareChannel> digitals = new List <HardwareChannel>(); sortDicionaryByID(digitalIDs, digitals, digitalsUnsorted); // **** // description of port_digital_IDs: // mapping from port number to an array of integers. The nth element of the array gives the digital ID // of the channel which is connected to the nth line of that port. If there is no channel on that port, // the ID is -1. if (deviceSettings.DeviceDescription.Contains("6533")) { groupDigitalChannels(digitalIDs, digitals, out port_digital_IDs, out usedPortNumbers, true); } else { groupDigitalChannels(digitalIDs, digitals, out port_digital_IDs, out usedPortNumbers, false); } //ok! create the channels. // analog first for (int i = 0; i < analogs.Count; i++) { task.AOChannels.CreateVoltageChannel(analogs[i].physicalChannelName(), "", -10, 10, AOVoltageUnits.Volts); } // now digital for (int i = 0; i < usedPortNumbers.Count; i++) { int portNum = usedPortNumbers[i]; task.DOChannels.CreateChannel(deviceName + '/' + HardwareChannel.digitalPhysicalChannelName(portNum), "", ChannelLineGrouping.OneChannelForAllLines); } }
/// <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, Common.getPeriodFromFrequency(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, Common.getPeriodFromFrequency(masterFrequency)); 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)); } }
long postRetriggerTime = 100; // corresponds to 10us. // A workaround to issue when using software timed groups in // fpga-retriggered words // the workaround: delay the software timed group by an immesurable amount // if it is started in a retriggered word // This functionality is sort of somewhat duplicated in sequencedata.generatebuffers. It would be good // to come up with a more coherent framework to do these sorts of operations. public bool generateBuffer(SequenceData sequence, DeviceSettings deviceSettings, HardwareChannel hc, int logicalChannelID, List <GpibRampCommandConverter> commandConverters) { this.logicalChannelID = logicalChannelID; this.deviceType = hc.GpibDeviceType; commandBuffer = new List <GpibCommand>(); if (deviceSettings.StartTriggerType != DeviceSettings.TriggerType.SoftwareTrigger) { throw new Exception("GPIB devices must have a software start trigger."); } // start by adding the initialization string to the command buffer // this does nothing for unknown device types // Modified by REO 11/25/08: Null strings produce errors if written to device, so check if (HardwareChannel.HardwareConstants.gpibInitializationCommands[(int)deviceType] != null) { commandBuffer.Add(new GpibCommand(HardwareChannel.HardwareConstants.gpibInitializationCommands[(int)deviceType], 0)); } if (deviceType == HardwareChannel.HardwareConstants.GPIBDeviceType.Unknown) { foreach (GpibRampCommandConverter conv in commandConverters) { //Modified by REO 11/25/08: Null strings produce errors if written to device, so check if (deviceSettings.DeviceDescription.Contains(conv.DeviceIdentifierSubstring) && conv.InitializationCommand != null) { commandBuffer.Add(new GpibCommand(AddNewlineCharacters(conv.InitializationCommand), 0)); } } } int currentStepIndex = -1; //measured in ticks. 1 tick = 100 ns. long currentTime = 0; // This functionality is sort of somewhat duplicated in sequencedata.generatebuffers. It would be good // to come up with a more coherent framework to do these sorts of operations. while (true) { currentStepIndex++; if (currentStepIndex >= sequence.TimeSteps.Count) { break; } TimeStep currentStep = sequence.TimeSteps[currentStepIndex]; if (!currentStep.StepEnabled) { continue; } if (currentStep.GpibGroup == null || !currentStep.GpibGroup.channelEnabled(logicalChannelID)) { currentTime += Shared.SecondsToTicks(currentStep.StepDuration.getBaseValue()); continue; } long postTime = 0; if (currentStep.RetriggerOptions.WaitForRetrigger) { postTime = postRetriggerTime; } // determine the index of the next step in which this channel has an action int nextEnabledStepIndex = sequence.findNextGpibChannelEnabledTimestep(currentStepIndex, logicalChannelID); long groupDuration = Shared.SecondsToTicks(sequence.timeBetweenSteps(currentStepIndex, nextEnabledStepIndex)); // now take action: GPIBGroupChannelData channelData = currentStep.GpibGroup.getChannelData(logicalChannelID); if (channelData.DataType == GPIBGroupChannelData.GpibChannelDataType.raw_string) { // Raw string commands just get added string stringWithCorrectNewlines = AddNewlineCharacters(channelData.RawString); commandBuffer.Add(new GpibCommand(stringWithCorrectNewlines, currentTime + postTime)); } else if (channelData.DataType == GPIBGroupChannelData.GpibChannelDataType.voltage_frequency_waveform) { GpibRampCommandConverter rampConverter = null; switch (hc.GpibDeviceType) { case HardwareChannel.HardwareConstants.GPIBDeviceType.Unknown: { foreach (GpibRampCommandConverter conv in commandConverters) { if (deviceSettings.DeviceDescription.Contains(conv.DeviceIdentifierSubstring)) { rampConverter = conv; } } if (rampConverter == null) { throw new Exception("Voltage/frequency ramp not supported for unknown gpib device " + hc.ToString() + "."); } } break; case HardwareChannel.HardwareConstants.GPIBDeviceType.Agilent_ESG_SIG_Generator: { rampConverter = ESG_SeriesRampConverter; } break; } double[] amplitudeArray; double[] frequencyArray; // get amplitude and frequency value arrays int nSamples = (int)(Shared.TicksToSeconds(groupDuration) * (double)deviceSettings.SampleClockRate); double secondsPerSample = Shared.TicksToSeconds(groupDuration) / (double)nSamples; amplitudeArray = channelData.volts.getInterpolation(nSamples, 0, Shared.TicksToSeconds(groupDuration), sequence.Variables, sequence.CommonWaveforms); frequencyArray = channelData.frequency.getInterpolation(nSamples, 0, Shared.TicksToSeconds(groupDuration), sequence.Variables, sequence.CommonWaveforms); List <DoubleIntPair> amplitudeIndexPairs = ConvertArrayToIndexValuePairs(amplitudeArray); List <DoubleIntPair> frequencyIndexPairs = ConvertArrayToIndexValuePairs(frequencyArray); int amplitudeIndex = 0; int frequencyIndex = 0; for (int i = 0; i < nSamples; i++) { bool amplitudeMatch = false; bool frequencyMatch = false; // determine if there is either a amplitude or frequency data point for this sample number if (amplitudeIndex < amplitudeIndexPairs.Count) { amplitudeMatch = amplitudeIndexPairs[amplitudeIndex].myInt == i; } if (frequencyIndex < frequencyIndexPairs.Count) { frequencyMatch = frequencyIndexPairs[frequencyIndex].myInt == i; } if (amplitudeIndex >= amplitudeIndexPairs.Count && frequencyIndex >= frequencyIndexPairs.Count) { break; } if (amplitudeMatch || frequencyMatch) { string command = ""; if (amplitudeMatch) { command += rampConverter.amplitudeCommand(amplitudeIndexPairs[amplitudeIndex].myDouble); amplitudeIndex++; } if (frequencyMatch) { command += rampConverter.frequencyCommand(frequencyIndexPairs[frequencyIndex].myDouble); frequencyIndex++; } long commandTime = currentTime + Shared.SecondsToTicks((double)i * secondsPerSample) + postTime; commandBuffer.Add(new GpibCommand(command, commandTime)); } } } else if (channelData.DataType == GPIBGroupChannelData.GpibChannelDataType.string_param_string) { if (channelData.StringParameterStrings != null) { foreach (StringParameterString sps in channelData.StringParameterStrings) { string commandWithCorrectNewlines = AddNewlineCharacters(sps.ToString()); commandBuffer.Add(new GpibCommand(commandWithCorrectNewlines, currentTime + postTime)); } } } currentTime += Shared.SecondsToTicks(currentStep.StepDuration.getBaseValue()); } return(true); }
public bool generateBuffer(SequenceData sequence, DeviceSettings deviceSettings, HardwareChannel hc, int logicalChannelID) { lock (commandBuffer) { commandBuffer.Clear(); this.logicalChannelID = logicalChannelID; if (deviceSettings.StartTriggerType != DeviceSettings.TriggerType.SoftwareTrigger) { throw new Exception("RS232 devices must have a software start trigger."); } // List<TimeStep> enabledSteps = sequence.enabledTimeSteps(); int currentStepIndex = -1; //measured in ticks. 1 tick = 100 ns. long currentTime = 0; long postRetriggerTime = 100; // corresponds to 10us. // A workaround to issue when using software timed groups in // fpga-retriggered words // the workaround: delay the software timed group by an immesurable amount // if it is started in a retriggered word // This functionality is sort of somewhat duplicated in sequencedata.generatebuffers. It would be good // to come up with a more coherent framework to do these sorts of operations. while (true) { currentStepIndex++; if (currentStepIndex >= sequence.TimeSteps.Count) { break; } TimeStep currentStep = sequence.TimeSteps[currentStepIndex]; if (!currentStep.StepEnabled) { continue; } long postTime = 0; if (currentStep.RetriggerOptions.WaitForRetrigger) { postTime = postRetriggerTime; } if (currentStep.rs232Group == null || !currentStep.rs232Group.channelEnabled(logicalChannelID)) { currentTime += seconds_to_ticks(currentStep.StepDuration.getBaseValue()); continue; } // determine the index of the next step in which this channel has an action int nextEnabledStepIndex = sequence.findNextRS232ChannelEnabledTimestep(currentStepIndex, logicalChannelID); long groupDuration = seconds_to_ticks(sequence.timeBetweenSteps(currentStepIndex, nextEnabledStepIndex)); // now take action: RS232GroupChannelData channelData = currentStep.rs232Group.getChannelData(logicalChannelID); if (channelData.DataType == RS232GroupChannelData.RS232DataType.Raw) { // Raw string commands just get added string stringWithCorrectNewlines = AddNewlineCharacters(channelData.RawString); commandBuffer.Add(new RS232Command(stringWithCorrectNewlines, currentTime + postTime)); } else if (channelData.DataType == RS232GroupChannelData.RS232DataType.Parameter) { if (channelData.StringParameterStrings != null) { foreach (StringParameterString srs in channelData.StringParameterStrings) { commandBuffer.Add(new RS232Command( AddNewlineCharacters(srs.ToString()), currentTime + postTime)); } } } currentTime += seconds_to_ticks(currentStep.StepDuration.getBaseValue()); } } return(true); }