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, 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> /// Computer digital output buffer for a given channel, using a variable timebase described by timebaseSegments. /// Stores result in ans /// </summary> /// <param name="digitalID"></param> /// <param name="masterTimebaseSampleDuration"></param> /// <param name="ans"></param> /// <param name="timebaseSegments"></param> public void computeDigitalBuffer(int digitalID, double masterTimebaseSampleDuration, bool[] ans, TimestepTimebaseSegmentCollection timebaseSegments) { int currentSample = 0; for (int stepID = 0; stepID < TimeSteps.Count; stepID++) { if (TimeSteps[stepID].StepEnabled) { TimeStep currentStep = TimeSteps[stepID]; bool channelValue = TimeSteps[stepID].getDigitalValue(digitalID, TimeSteps, stepID); int nSegmentSamples = 0; if (!timebaseSegments.ContainsKey(currentStep)) throw new Exception("No timebase segment for timestep " + currentStep.ToString()); nSegmentSamples = timebaseSegments.nSegmentSamples(currentStep); for (int j = 0; j < nSegmentSamples; j++) { ans[j + currentSample] = channelValue; } currentSample += nSegmentSamples; } } ans[currentSample] = dwellWord().getDigitalValue(digitalID, TimeSteps, 0); if (this.digitalChannelUsesPulses(digitalID)) { int currentMasterSample = 0; // now fill in any pulses which act on this channel... foreach (TimeStep step in enabledTimeSteps()) { int nMasterSamplesInTimestep = timebaseSegments.nMasterSamples(step); if (step.DigitalData.ContainsKey(digitalID)) { if (step.DigitalData[digitalID].usesPulse()) { Pulse pulse = step.DigitalData[digitalID].DigitalPulse; Pulse.PulseSampleTimes sampleTimes = pulse.getPulseSampleTimes(nMasterSamplesInTimestep, masterTimebaseSampleDuration); int start = currentMasterSample + sampleTimes.startSample; int end = currentMasterSample + sampleTimes.endSample; // ok. Paint the pulse... // to do this, we need to find which derived sample (ie sample in this buffer) corresponds to // which master timestep. int derivedStart = getDerivedSampleFromMasterSample(start, timebaseSegments); int derivedEnd = getDerivedSampleFromMasterSample(end, timebaseSegments); derivedStart = Math.Max(0, derivedStart); derivedEnd = Math.Min(derivedEnd, ans.Length); for (int i = derivedStart; i < derivedEnd; i++) { ans[i] = pulse.PulseValue; } } } currentMasterSample += nMasterSamplesInTimestep; } } }
public void computeAnalogBuffer(int analogChannelID, double masterTimebaseSampleDuration, double[] ans, TimestepTimebaseSegmentCollection timebaseSegments) { int currentSample = 0; double dwellingValue = dwellWord().getEndAnalogValue(analogChannelID, Variables, CommonWaveforms); AnalogGroup currentlyRunningGroup = null; double groupRunningTime = 0; for (int stepID = 0; stepID < TimeSteps.Count; stepID++) { TimeStep currentStep = TimeSteps[stepID]; if (currentStep.StepEnabled) { if (currentStep.AnalogGroup != null) { if (currentStep.AnalogGroup.channelEnabled(analogChannelID)) { currentlyRunningGroup = currentStep.AnalogGroup; groupRunningTime = 0; } } if (currentlyRunningGroup == null) { int nSegmentSamples = timebaseSegments.nSegmentSamples(currentStep); for (int j = 0; j < nSegmentSamples; j++) { ans[j + currentSample] = dwellingValue; } currentSample += nSegmentSamples; } else { Waveform runningWaveform = currentlyRunningGroup.ChannelDatas[analogChannelID].waveform; double waveformRunningTime = groupRunningTime; foreach (VariableTimebaseSegment segment in timebaseSegments[currentStep]) { runningWaveform.getInterpolation(segment.NSegmentSamples, waveformRunningTime, waveformRunningTime + segment.NSegmentSamples * segment.MasterSamplesPerSegmentSample * masterTimebaseSampleDuration, ans, currentSample, Variables, CommonWaveforms); currentSample += segment.NSegmentSamples; waveformRunningTime += segment.NSegmentSamples * segment.MasterSamplesPerSegmentSample * masterTimebaseSampleDuration; } groupRunningTime += currentStep.StepDuration.getBaseValue(); if (runningWaveform.getEffectiveWaveformDuration() <= groupRunningTime) { currentlyRunningGroup = null; } dwellingValue = runningWaveform.getValueAtTime(runningWaveform.WaveformDuration.getBaseValue(), Variables, CommonWaveforms); } } } ans[currentSample] = dwellWord().getEndAnalogValue(analogChannelID, Variables, CommonWaveforms); }
public bool[] getVariableTimebaseClock(TimestepTimebaseSegmentCollection timebaseSegments) { int nSamples = timebaseSegments.nMasterSamples(); nSamples += 1 + 2; // add 1 false sample at the beginning so that we start with a false, and 2 at the end so we can // trigger the dwell values; if (nSamples % 4 != 0) nSamples += (4 - nSamples % 4); // for daqMx drivers, the number of samples in a digital stream has to be a multiple of 4 bool[] ans = new bool[nSamples]; int currentSample = 1; // start at sample 1, thus leaving sample 0 as false. for (int stepID = 0; stepID < TimeSteps.Count; stepID++) { TimeStep currentStep = TimeSteps[stepID]; if (currentStep.StepEnabled) { List<VariableTimebaseSegment> segments = timebaseSegments[currentStep]; foreach (VariableTimebaseSegment seg in segments) { for (int i = 0; i < seg.NSegmentSamples; i++) { ans[currentSample] = true; // make the clock pulse a little bit longer if the clocks are sufficiently far apart // for this segment. This is mainly an aesthetic detail, it makes it easier to // inspect the variable timebase on a scope, and should // make no functional different if (seg.MasterSamplesPerSegmentSample > 2) { int repeats = (seg.MasterSamplesPerSegmentSample / 2); for (int j = 0; j < repeats; j++) { ans[currentSample + j] = true; } } currentSample += seg.MasterSamplesPerSegmentSample; } } } } // Add one last pulse at the end to push into dwell values? I don't remember, this comment // it being written long after the code was. ans[currentSample] = true; return ans; }
/// <summary> /// Gets the master timebase sample from a given derived timebase sample. /// </summary> /// <param name="derivedSample"></param> /// <param name="timebaseSegments"></param> /// <returns></returns> public int getMasterSampleFromDerivedSample(int derivedSample, TimestepTimebaseSegmentCollection timebaseSegments) { int currentDerivedSample = 0; int currentMasterSample = 0; if (derivedSample == 0) return 0; foreach (TimeStep step in enabledTimeSteps()) { foreach (VariableTimebaseSegment segment in timebaseSegments[step]) { for (int i = 0; i < segment.NSegmentSamples; i++) { currentMasterSample += segment.MasterSamplesPerSegmentSample; currentDerivedSample++; if (currentDerivedSample >= derivedSample) return currentMasterSample; } } } return currentMasterSample; }
/// <summary> /// Generates the digital buffer for a digital output that will be clocked with the same clock /// as the one driving the variable timebase clock. This is for use on digital outputs that /// share a clock with the variable timebase clock output, but that we want to use /// for sequence data rather than as clocks. /// </summary> /// <param name="?"></param> /// <returns></returns> public bool[] getDigitalBufferClockSharedWithVariableTimebaseClock(TimestepTimebaseSegmentCollection timebaseSegments, int digitalID, double masterTimestepSize) { int nSamples = timebaseSegments.nMasterSamples(); nSamples += 1 + 2; // 1 extra sample at the beginning for the clock's leading LOW, and 2 at the end for the extgra dwell pulse. (this matches // the number of samples in the variable timebase clock, see the following function int currentSample = 0; if (nSamples % 4 != 0) nSamples += (4 - nSamples % 4); bool [] ans = new bool[nSamples]; ans[0] = dwellWord().getDigitalValue(digitalID, TimeSteps, 0); currentSample++; int stepID = 0; foreach (TimeStep step in TimeSteps) { if (step.StepEnabled) { int nStepSamples = timebaseSegments.nMasterSamples(step); // if the digital is true, fill this part of the buffer with trues. If not, // no need to do anything as the inital value of the array is false. if (step.getDigitalValue(digitalID, TimeSteps, stepID)) { for (int j = 0; j < nStepSamples; j++) { ans[j + currentSample] = true; } } currentSample += nStepSamples; } stepID++; } // fill the rest of the buffer with dwell values if (dwellWord().getDigitalValue(digitalID, TimeSteps, 0)) { for (int j = currentSample; j < nSamples; j++) { ans[j] = true; } } // now, search for digital pulses, and if there are any on this channel, paint them on if (digitalChannelUsesPulses(digitalID)) { currentSample = 1; foreach (TimeStep step in enabledTimeSteps()) { int nStepSamples = timebaseSegments.nMasterSamples(step); if (step.DigitalData[digitalID].usesPulse()) { Pulse pulse = step.DigitalData[digitalID].DigitalPulse; Pulse.PulseSampleTimes sampleTimes = pulse.getPulseSampleTimes(nStepSamples, masterTimestepSize); int start = currentSample + sampleTimes.startSample; int end = currentSample + sampleTimes.endSample; start = Math.Max(0, start); end = Math.Min(end, ans.Length); for (int i = start; i < end; i++) { ans[i] = pulse.PulseValue; } } currentSample += nStepSamples; } } return ans; }
/// <summary> /// /// </summary> /// <param name="timebaseType"></param> /// <param name="masterTimebaseSampleDuration"></param> /// <returns></returns> public TimestepTimebaseSegmentCollection generateVariableTimebaseSegments(VariableTimebaseTypes timebaseType, double masterTimebaseSampleDuration) { switch (timebaseType) { case VariableTimebaseTypes.AnalogGroupControlledVariableFrequencyClock: { TimestepTimebaseSegmentCollection ans = new TimestepTimebaseSegmentCollection(); Dictionary<TimeStep, List<DigitalImpingement>> digitalImpingements = getDigitalImpingements(masterTimebaseSampleDuration); for (int stepID = 0; stepID < TimeSteps.Count; stepID++) { if (TimeSteps[stepID].StepEnabled) { TimeStep currentStep = TimeSteps[stepID]; VariableTimebaseSegmentCollection timestepSegments = new VariableTimebaseSegmentCollection(); Dictionary<AnalogGroup, double> runningGroups = getRunningGroupRemainingTime(stepID); // first cull groups that have less remaining time than 2 master timbase cycles. { List<AnalogGroup> groups = new List<AnalogGroup>(runningGroups.Keys); foreach (AnalogGroup ag in groups) { if (runningGroups[ag] < 2 * masterTimebaseSampleDuration) { runningGroups.Remove(ag); } } } if (runningGroups.Count == 0) { timestepSegments.Add(new VariableTimebaseSegment(1, (int)(currentStep.StepDuration.getBaseValue() / masterTimebaseSampleDuration))); } else { double timeIntoCurrentStep = 0; while (runningGroups.Count != 0 && timeIntoCurrentStep < currentStep.StepDuration.getBaseValue()) { // determine the most sensitive running group AnalogGroup smallestResolutionGroup = null; double smallestResolution = Double.MaxValue; double durationTiebreaker = Double.MinValue; foreach (AnalogGroup ag in runningGroups.Keys) { if (ag.TimeResolution.getBaseValue() < smallestResolution) { durationTiebreaker = runningGroups[ag]; smallestResolution = ag.TimeResolution.getBaseValue(); smallestResolutionGroup = ag; } else if (ag.TimeResolution.getBaseValue() == smallestResolution) { if (runningGroups[ag] >= durationTiebreaker) { durationTiebreaker = runningGroups[ag]; smallestResolution = ag.TimeResolution.getBaseValue(); smallestResolutionGroup = ag; } } } double timeToRunGroup = Math.Min(currentStep.StepDuration.getBaseValue() - timeIntoCurrentStep, runningGroups[smallestResolutionGroup]); int masterTimebaseSamplesPerSegmentSample = 0; if (smallestResolutionGroup.TimeResolution.getBaseValue() != 0) { masterTimebaseSamplesPerSegmentSample = (int)(0.5 + smallestResolutionGroup.TimeResolution.getBaseValue() / masterTimebaseSampleDuration); } if (masterTimebaseSamplesPerSegmentSample < 2) { masterTimebaseSamplesPerSegmentSample = 2; } int nSegmentSamples = (int)(timeToRunGroup / ((double)masterTimebaseSamplesPerSegmentSample * masterTimebaseSampleDuration)); // if (nSegmentSamples > 0) // { double actualGroupRunTime = nSegmentSamples * masterTimebaseSampleDuration * masterTimebaseSamplesPerSegmentSample; if (nSegmentSamples != 0) { timestepSegments.Add(new VariableTimebaseSegment(nSegmentSamples, masterTimebaseSamplesPerSegmentSample)); } else { } runningGroups.Remove(smallestResolutionGroup); List<AnalogGroup> groups = new List<AnalogGroup>(runningGroups.Keys); foreach (AnalogGroup ag in groups) { runningGroups[ag] -= actualGroupRunTime; if (runningGroups[ag] <= 2 * masterTimebaseSampleDuration) runningGroups.Remove(ag); } timeIntoCurrentStep += actualGroupRunTime; // } // else // { // timeIntoCurrentStep = currentStep.StepDuration.getBaseValue(); // } } // if the present collection of segments does not get us to the end of the timestep, add // one filler segment. if (currentStep.StepDuration.getBaseValue() - timeIntoCurrentStep >= 2 * masterTimebaseSampleDuration) { timestepSegments.Add(new VariableTimebaseSegment(1, (int)(0.5 + (currentStep.StepDuration.getBaseValue() - timeIntoCurrentStep) / masterTimebaseSampleDuration))); } } // ok, now, for the love of god, we have to take into account digital pulse impingements, which may // require the creation of yet more segments (by splitting those segments which already exist...) #region Splitting segments due to digital pulse impingements. This is messy ugly business. if (digitalImpingements.ContainsKey(currentStep)) { List<DigitalImpingement> impigs = digitalImpingements[currentStep]; foreach (DigitalImpingement impig in impigs) { int samplesTillImpig = impig.nSamplesFromTimestepStart; VariableTimebaseSegment segmentToSplit = null; foreach (VariableTimebaseSegment seg in timestepSegments) { if (samplesTillImpig <= 0) break; if (samplesTillImpig < seg.NSegmentSamples * seg.MasterSamplesPerSegmentSample) { segmentToSplit = seg; break; } samplesTillImpig -= seg.NSegmentSamples * seg.MasterSamplesPerSegmentSample; } // if necessary, now we split the current segment (segmentToSplit)... argh. if (samplesTillImpig > 0) { // we now need to split the segment // this may take as many as 4 new segments to accomplish List<VariableTimebaseSegment> newSegments = new List<VariableTimebaseSegment>(); int newSegmentSamples = 0; // Lead in segment. This runs at the original segment's usual clock rate, and runs until right before temp int nLeadSegmentSamples = samplesTillImpig / segmentToSplit.MasterSamplesPerSegmentSample; if (nLeadSegmentSamples != 0) { VariableTimebaseSegment newSeg = new VariableTimebaseSegment(nLeadSegmentSamples, segmentToSplit.MasterSamplesPerSegmentSample); newSegments.Add(newSeg); newSegmentSamples += newSeg.NSegmentSamples * newSeg.MasterSamplesPerSegmentSample; samplesTillImpig -= newSeg.MasterSamplesPerSegmentSample * newSeg.NSegmentSamples; } // Now, if necessary, add small jog in and jog out segment int jogIn = samplesTillImpig; int jogOut = segmentToSplit.MasterSamplesPerSegmentSample - jogIn; // have to be carefull. We can't jog in or out by less than 2. // If both are either greater than 1 or equal to zero, no problem. // If jogIn is 1, we will make it 2 if we can (ie if jogOut is not 0 or 2). // If jogIn is 1 and jogOut is 0, we make jogOut and jogIn 0. // If jogIn is 1 and jogOut is 2, we make jogin 3 and jogOut 0. // If jogOut is 1, then we will make it 0 if we can. if (jogIn == 1) { if (jogOut != 0 && jogOut != 2) { jogIn++; jogOut--; } else { if (jogOut == 0) { jogIn = 0; } if (jogOut == 2) { jogOut = 0; jogIn = 3; } } } if (jogOut == 1) { if (jogIn > 2) { jogOut--; jogIn++; } else { jogOut = 0; } } if (jogIn != 0) { VariableTimebaseSegment newSeg = new VariableTimebaseSegment(1, jogIn); newSegments.Add(newSeg); newSegmentSamples += jogIn; } if (jogOut != 0) { VariableTimebaseSegment newSeg = new VariableTimebaseSegment(1, jogOut); newSegments.Add(newSeg); newSegmentSamples += jogOut; } // now add lead out. int nSamplesToReplace = segmentToSplit.MasterSamplesPerSegmentSample * segmentToSplit.NSegmentSamples - newSegmentSamples; int nEndSegments = nSamplesToReplace / segmentToSplit.MasterSamplesPerSegmentSample; if (nEndSegments * segmentToSplit.MasterSamplesPerSegmentSample != nSamplesToReplace) { throw new InvalidDataException("Confusion during splitting of variable timebase segments due to digital pulses. This should not happen."); } if (nEndSegments != 0) { VariableTimebaseSegment newSeg = new VariableTimebaseSegment(nEndSegments, segmentToSplit.MasterSamplesPerSegmentSample); newSegments.Add(newSeg); } // ok. We have constructed the replacement segments. Now lets do the swap. int originalIndex = timestepSegments.IndexOf(segmentToSplit); timestepSegments.RemoveAt(originalIndex); timestepSegments.InsertRange(originalIndex, newSegments); } } } #endregion // clean up -- if there any segments of length <=0, remove them List<VariableTimebaseSegment> segmentsToRemove = new List<VariableTimebaseSegment>(); foreach (VariableTimebaseSegment seg in timestepSegments) { if (seg.MasterSamplesPerSegmentSample <= 0 || seg.NSegmentSamples <= 0) segmentsToRemove.Add(seg); } foreach (VariableTimebaseSegment seg in segmentsToRemove) { timestepSegments.Remove(seg); } ans.Add(currentStep, timestepSegments); } } return ans; } default: throw new Exception("Unsupported variable timebase type."); } }
public static byte[] createByteArray(TimestepTimebaseSegmentCollection segments, SequenceData sequence, out int nSegments, double masterClockPeriod, bool assymetric) { List<ListItem> listItems = new List<ListItem>(); for (int stepID = 0; stepID < sequence.TimeSteps.Count; stepID++) { if (sequence.TimeSteps[stepID].StepEnabled) { if (sequence.TimeSteps[stepID].WaitForRetrigger) { listItems.Add(new ListItem(0, 0, 0)); // 0,0,0 list item is code for "wait for retrigger // FPGA knows how to handle this } List<SequenceData.VariableTimebaseSegment> stepSegments = segments[sequence.TimeSteps[stepID]]; for (int i = 0; i < stepSegments.Count; i++) { ListItem item = new ListItem(); SequenceData.VariableTimebaseSegment currentSeg = stepSegments[i]; item.repeats = currentSeg.NSegmentSamples; item.offCounts = currentSeg.MasterSamplesPerSegmentSample / 2; item.onCounts = currentSeg.MasterSamplesPerSegmentSample - item.offCounts; // in assymmetric mode (spelling?), the clock duty cycle is not held at 50%, but rather the pulses are made to be // 5 master cycles long at most. This is a workaround for the weird behavior of one of our fiber links // for sharing the variable timebase signal. if (assymetric) { if (item.onCounts > 5) { int difference = item.onCounts - 5; item.onCounts = 5; item.offCounts = item.offCounts + difference; } } if (!item.isAllZeros()) { // filter out any erroneously produced all-zero codes, since these have // special meaning to the FPGA (they are "wait for retrigger" codes listItems.Add(item); } } } } // Add one final "pulse" at the end to trigger the dwell values. I'm basing this off the // old variable timebase code that I found in the SequenceData program. // This final pulse is made to be 100 us long at least, just to be on the safe side. (unless assymetric mode is on) int minCounts = (int)(0.0001 / masterClockPeriod); if (minCounts <= 0) minCounts = 1; ListItem finishItem = new ListItem(minCounts, minCounts, 1); if (assymetric) { finishItem.onCounts = 5; } listItems.Add(finishItem); nSegments = listItems.Count; byte[] byteArray = new byte[listItems.Count * 16]; // This loop goes through the list items and creates // the data as it is to be sent to the FPGA // the data is a little shuffled because // of the details of the byte order in // piping data to the fpga. for (int i = 0; i < listItems.Count; i++) { ListItem item = listItems[i]; byte[] onb = splitIntToBytes(item.onCounts); byte[] offb = splitIntToBytes(item.offCounts); byte[] repb = splitIntToBytes(item.repeats); int offs = 16 * i; byteArray[offs + 2] = onb[1]; byteArray[offs + 3] = onb[0]; byteArray[offs + 4] = onb[3]; byteArray[offs + 5] = onb[2]; byteArray[offs + 8] = offb[1]; byteArray[offs + 9] = offb[0]; byteArray[offs + 10] = offb[3]; byteArray[offs + 11] = offb[2]; byteArray[offs + 12] = repb[1]; byteArray[offs + 13] = repb[0]; byteArray[offs + 14] = repb[3]; byteArray[offs + 15] = repb[2]; } return byteArray; }
/// <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 = Common.getPeriodFromFrequency(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 = Common.getPeriodFromFrequency(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); }
/// <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)); } }
/// <summary> /// Create byte array for use in programming FPGA /// </summary> /// <param name="segments"></param> /// <param name="sequence"></param> /// <param name="nSegments"></param> /// <param name="masterClockPeriod"></param> /// <param name="assymetric"></param> /// <returns></returns> private static byte[] createByteArray(TimestepTimebaseSegmentCollection segments, SequenceData sequence, out int nSegments, double masterClockPeriod, bool assymetric) { List <ListItem> listItems = new List <ListItem>(); for (int stepID = 0; stepID < sequence.TimeSteps.Count; stepID++) { if (sequence.TimeSteps[stepID].StepEnabled) { if (sequence.TimeSteps[stepID].RetriggerOptions.WaitForRetrigger) { uint waitTime = (uint)(sequence.TimeSteps[stepID].RetriggerOptions.RetriggerTimeout.getBaseValue() / masterClockPeriod); uint retriggerFlags = 0; if (sequence.TimeSteps[stepID].RetriggerOptions.RetriggerOnEdge) { retriggerFlags += 1; } if (!sequence.TimeSteps[stepID].RetriggerOptions.RetriggerOnNegativeValueOrEdge) { retriggerFlags += 2; } listItems.Add(new ListItem(waitTime, retriggerFlags, 0)); // counts = 0 is a special signal for WAIT_FOR_RETRIGGER mode // in this mode, FPGA waits a maximum of on_counts master samples // before moving on anyway. // (unless on_counts = 0, in which case it never artificially retriggers) // retrigger flags set if the FPGA will trigger on edge or on value // and whether to trigger on positive or negative (edge or value) } List <SequenceData.VariableTimebaseSegment> stepSegments = segments[sequence.TimeSteps[stepID]]; for (int i = 0; i < stepSegments.Count; i++) { ListItem item = new ListItem(); SequenceData.VariableTimebaseSegment currentSeg = stepSegments[i]; item.repeats = (uint)currentSeg.NSegmentSamples; item.offCounts = (uint)(currentSeg.MasterSamplesPerSegmentSample / 2); item.onCounts = (uint)(currentSeg.MasterSamplesPerSegmentSample - item.offCounts); // in assymmetric mode (spelling?), the clock duty cycle is not held at 50%, but rather the pulses are made to be // 5 master cycles long at most. This is a workaround for the weird behavior of one of our fiber links // for sharing the variable timebase signal. if (assymetric) { if (item.onCounts > 5) { uint difference = item.onCounts - 5; item.onCounts = 5; item.offCounts = item.offCounts + difference; } } if (!item.isAllZeros()) { // filter out any erroneously produced all-zero codes, since these have // special meaning to the FPGA (they are "wait for retrigger" codes listItems.Add(item); } } } } // Add one final "pulse" at the end to trigger the dwell values. I'm basing this off the // old variable timebase code that I found in the SequenceData program. // This final pulse is made to be 100 us long at least, just to be on the safe side. (unless assymetric mode is on) int minCounts = (int)(0.0001 / masterClockPeriod); if (minCounts <= 0) { minCounts = 1; } ListItem finishItem = new ListItem((uint)minCounts, (uint)minCounts, 1); if (assymetric) { finishItem.onCounts = 5; } listItems.Add(finishItem); nSegments = listItems.Count; byte[] byteArray = new byte[listItems.Count * 16]; // This loop goes through the list items and creates // the data as it is to be sent to the FPGA // the data is a little shuffled because // of the details of the byte order in // piping data to the fpga. // Each list item takes up 16 bytes in the output FIFO. for (int i = 0; i < listItems.Count; i++) { ListItem item = listItems[i]; byte[] onb = splitIntToBytes(item.onCounts); byte[] offb = splitIntToBytes(item.offCounts); byte[] repb = splitIntToBytes(item.repeats); int offs = 16 * i; byteArray[offs + 2] = onb[1]; byteArray[offs + 3] = onb[0]; byteArray[offs + 4] = onb[3]; byteArray[offs + 5] = onb[2]; byteArray[offs + 8] = offb[1]; byteArray[offs + 9] = offb[0]; byteArray[offs + 10] = offb[3]; byteArray[offs + 11] = offb[2]; byteArray[offs + 12] = repb[1]; byteArray[offs + 13] = repb[0]; byteArray[offs + 14] = repb[3]; byteArray[offs + 15] = repb[2]; } return(byteArray); }
/// <summary> /// Create byte array for use in programming FPGA /// </summary> /// <param name="segments"></param> /// <param name="sequence"></param> /// <param name="nSegments"></param> /// <param name="masterClockPeriod"></param> /// <param name="assymetric"></param> /// <returns></returns> private static byte[] createByteArray(TimestepTimebaseSegmentCollection segments, SequenceData sequence, out int nSegments, double masterClockPeriod, bool assymetric) { List<ListItem> listItems = new List<ListItem>(); for (int stepID = 0; stepID < sequence.TimeSteps.Count; stepID++) { if (sequence.TimeSteps[stepID].StepEnabled) { if (sequence.TimeSteps[stepID].RetriggerOptions.WaitForRetrigger) { uint waitTime = (uint)(sequence.TimeSteps[stepID].RetriggerOptions.RetriggerTimeout.getBaseValue() / masterClockPeriod); uint retriggerFlags = 0; if (sequence.TimeSteps[stepID].RetriggerOptions.RetriggerOnEdge) retriggerFlags += 1; if (!sequence.TimeSteps[stepID].RetriggerOptions.RetriggerOnNegativeValueOrEdge) retriggerFlags += 2; listItems.Add(new ListItem(waitTime, retriggerFlags, 0)); // counts = 0 is a special signal for WAIT_FOR_RETRIGGER mode // in this mode, FPGA waits a maximum of on_counts master samples // before moving on anyway. // (unless on_counts = 0, in which case it never artificially retriggers) // retrigger flags set if the FPGA will trigger on edge or on value // and whether to trigger on positive or negative (edge or value) } List<SequenceData.VariableTimebaseSegment> stepSegments = segments[sequence.TimeSteps[stepID]]; for (int i = 0; i < stepSegments.Count; i++) { ListItem item = new ListItem(); SequenceData.VariableTimebaseSegment currentSeg = stepSegments[i]; item.repeats = (uint)currentSeg.NSegmentSamples; item.offCounts = (uint)(currentSeg.MasterSamplesPerSegmentSample / 2); item.onCounts = (uint)(currentSeg.MasterSamplesPerSegmentSample - item.offCounts); // in assymmetric mode (spelling?), the clock duty cycle is not held at 50%, but rather the pulses are made to be // 5 master cycles long at most. This is a workaround for the weird behavior of one of our fiber links // for sharing the variable timebase signal. if (assymetric) { if (item.onCounts > 5) { uint difference = item.onCounts - 5; item.onCounts = 5; item.offCounts = item.offCounts + difference; } } if (!item.isAllZeros()) { // filter out any erroneously produced all-zero codes, since these have // special meaning to the FPGA (they are "wait for retrigger" codes listItems.Add(item); } } } } // Add one final "pulse" at the end to trigger the dwell values. I'm basing this off the // old variable timebase code that I found in the SequenceData program. // This final pulse is made to be 100 us long at least, just to be on the safe side. (unless assymetric mode is on) int minCounts = (int)(0.0001 / masterClockPeriod); if (minCounts <= 0) minCounts = 1; ListItem finishItem = new ListItem((uint)minCounts, (uint)minCounts, 1); if (assymetric) { finishItem.onCounts = 5; } listItems.Add(finishItem); nSegments = listItems.Count; byte[] byteArray = new byte[listItems.Count * 16]; // This loop goes through the list items and creates // the data as it is to be sent to the FPGA // the data is a little shuffled because // of the details of the byte order in // piping data to the fpga. // Each list item takes up 16 bytes in the output FIFO. for (int i = 0; i < listItems.Count; i++) { ListItem item = listItems[i]; byte[] onb = splitIntToBytes(item.onCounts); byte[] offb = splitIntToBytes(item.offCounts); byte[] repb = splitIntToBytes(item.repeats); int offs = 16 * i; byteArray[offs + 2] = onb[1]; byteArray[offs + 3] = onb[0]; byteArray[offs + 4] = onb[3]; byteArray[offs + 5] = onb[2]; byteArray[offs + 8] = offb[1]; byteArray[offs + 9] = offb[0]; byteArray[offs + 10] = offb[3]; byteArray[offs + 11] = offb[2]; byteArray[offs + 12] = repb[1]; byteArray[offs + 13] = repb[0]; byteArray[offs + 14] = repb[3]; byteArray[offs + 15] = repb[2]; } return byteArray; }