1// ExampleAwgModule shows the usage of the AWG module.
2// It uses the AWG sequencer to generate a wave form.
3// The defined waveform is applied, measured and the
4// results are written to a file.
5public static void ExampleAwgModule(string dev = DEFAULT_DEVICE) // Timeout(10000)
6{
7 ziDotNET daq = connect(dev);
8 resetDeviceToDefault(daq, dev);
9 // check device type, option
10 if (!isDeviceFamily(daq, dev, "UHFAWG") &&
11 !isDeviceFamily(daq, dev, "UHFQA") &&
12 !hasOption(daq, dev, "AWG"))
13 {
14 Skip("Test does not support this device.");
15 }
16 // Create instrument configuration: disable all outputs, demods and scopes.
17 const ZIListNodes_enum flags = ZIListNodes_enum.ZI_LIST_NODES_LEAVESONLY;
18 var hasDemods = daq.listNodes(String.Format("/{0}/demods/*", dev), flags).Count > 0;
19 if (hasDemods)
20 {
21 daq.setInt(String.Format("/{0}/demods/*/enable", dev), 0);
22 daq.setInt(String.Format("/{0}/demods/*/trigger", dev), 0);
23 }
24
25 daq.setInt(String.Format("/{0}/sigouts/*/enables/*", dev), 0);
26 daq.setInt(String.Format("/{0}/scopes/*/enable", dev), 0);
27 if (hasOption(daq, dev, "IA"))
28 {
29 daq.setInt(String.Format("/{0}/imps/*/enable", dev), 0);
30 }
31 daq.sync();
32
33 // Now configure the instrument for this experiment. The following channels
34 // and indices work on all device configurations. The values below may be
35 // changed if the instrument has multiple input/output channels and/or either
36 // the Multifrequency or Multidemodulator options installed.
37 int in_channel = 0;
38 double frequency = 1e6;
39 double amp = 1.0;
40
41 daq.setDouble(String.Format("/{0}/sigouts/0/amplitudes/*", dev), 0.0);
42 daq.sync();
43
44 daq.setInt(String.Format("/{0}/sigins/0/imp50", dev), 1);
45 daq.setInt(String.Format("/{0}/sigins/0/ac", dev), 0);
46 daq.setInt(String.Format("/{0}/sigins/0/diff", dev), 0);
47 daq.setInt(String.Format("/{0}/sigins/0/range", dev), 1);
48 daq.setDouble(String.Format("/{0}/oscs/0/freq", dev), frequency);
49 daq.setInt(String.Format("/{0}/sigouts/0/on", dev), 1);
50 daq.setInt(String.Format("/{0}/sigouts/0/range", dev), 1);
51 daq.setDouble(String.Format("/{0}/awgs/0/outputs/0/amplitude", dev), amp);
52 daq.setInt(String.Format("/{0}/awgs/0/outputs/0/mode", dev), 0);
53 daq.setInt(String.Format("/{0}/awgs/0/time", dev), 0);
54 daq.setInt(String.Format("/{0}/awgs/0/userregs/0", dev), 0);
55
56 daq.sync();
57
58 // Number of points in AWG waveform
59 int AWG_N = 2000;
60
61 // Define an AWG program as a string stored in the variable awg_program, equivalent to what would
62 // be entered in the Sequence Editor window in the graphical UI.
63 // This example demonstrates four methods of definig waveforms via the API
64 // - (wave w0) loaded directly from programmatically generated CSV file wave0.csv.
65 // Waveform shape: Blackman window with negative amplitude.
66 // - (wave w1) using the waveform generation functionalities available in the AWG Sequencer language.
67 // Waveform shape: Gaussian function with positive amplitude.
68 // - (wave w2) using the vect() function and programmatic string replacement.
69 // Waveform shape: Single period of a sine wave.
70 string awg_program =
71 "const AWG_N = _c1_;\n" +
72 "wave w0 = \"wave0\";\n" +
73 "wave w1 = gauss(AWG_N, AWG_N/2, AWG_N/20);\n" +
74 "wave w2 = vect(_w2_);\n" +
75 "wave w3 = zeros(AWG_N);\n" +
76 "setTrigger(1);\n" +
77 "setTrigger(0);\n" +
78 "playWave(w0);\n" +
79 "playWave(w1);\n" +
80 "playWave(w2);\n" +
81 "playWave(w3);\n";
82
83 // Reference waves
84
85 // Define an array of values that are used to write values for wave w0 to a CSV file in the
86 // module's data directory (Blackman windows)
87 var waveform_0 = Enumerable.Range(0, AWG_N).Select(
88 v => -1.0 * (0.42 - 0.5 * Math.Cos(2.0 * Math.PI * v / (AWG_N - 1)) + 0.08 * Math.Cos(4 * Math.PI * v / (AWG_N - 1))));
89 double width = AWG_N / 20;
90 var linspace = Enumerable.Range(0, AWG_N).Select(
91 v => (v * AWG_N / ((double)AWG_N - 1.0d)) - AWG_N / 2);
92 var waveform_1 = linspace.Select(
93 v => Math.Exp(-v * v / (2 * width * width)));
94 linspace = Enumerable.Range(0, AWG_N).Select(
95 v => (v * 2 * Math.PI / ((double)AWG_N - 1.0d)));
96 var waveform_2 = linspace.Select(
97 v => Math.Sin(v));
98 linspace = Enumerable.Range(0, AWG_N).Select(
99 v => (v * 12 * Math.PI / ((double)AWG_N - 1.0d)) - 6 * Math.PI);
100 var waveform_3 = linspace.Select(
101 v => Sinc(v));
102
103 // concatenated reference wave
104 double f_s = 1.8e9; // sampling rate of scope and AWG
105 double full_scale = 0.75;
106 var y_expected = waveform_0.Concat(waveform_1).Concat(waveform_2).Concat(waveform_3).Select(
107 v => v * full_scale * amp).ToArray();
108 var x_expected = Enumerable.Range(0, 4 * AWG_N).Select(v => v / f_s).ToArray();
109
110 // Replace placeholders in program
111 awg_program = awg_program.Replace("_w2_", string.Join(",", waveform_2));
112 awg_program = awg_program.Replace("_c1_", AWG_N.ToString());
113
114 // Create an instance of the AWG Module
115 ziModule awgModule = daq.awgModule();
116 awgModule.setByte("device", dev);
117 awgModule.execute();
118
119
120 // Get the modules data directory
121 string data_dir = awgModule.getString("directory");
122 // All CSV files within the waves directory are automatically recognized by the AWG module
123 data_dir = data_dir + "\\awg\\waves";
124 if (!Directory.Exists(data_dir))
125 {
126 // The data directory is created by the AWG module and should always exist. If this exception is raised,
127 // something might be wrong with the file system.
128 Fail($"AWG module wave directory {data_dir} does not exist or is not a directory");
129 }
130 // Save waveform data to CSV
131 string csv_file = data_dir + "\\wave0.csv";
132 // The following line always formats a double as "3.14" and not "3,14".
133 var waveform_0_formatted = waveform_0.Select(v => v.ToString(CultureInfo.InvariantCulture));
134 File.WriteAllText(@csv_file, string.Join(",", waveform_0_formatted));
135
136 // Transfer the AWG sequence program. Compilation starts automatically.
137 // Note: when using an AWG program from a source file (and only then), the
138 // compiler needs to be started explicitly with
139 // awgModule.set("compiler/start", 1)
140 awgModule.setByte("compiler/sourcestring", awg_program);
141 while (awgModule.getInt("compiler/status") == -1)
142 {
143 System.Threading.Thread.Sleep(100);
144 }
145
146 // check compiler result
147 long status = awgModule.getInt("compiler/status");
148 if (status == 1)
149 {
150 // compilation failed
151 String message = awgModule.getString("compiler/statusstring");
152 System.Diagnostics.Trace.WriteLine("AWG Program:");
153 System.Diagnostics.Trace.WriteLine(awg_program);
154 System.Diagnostics.Trace.WriteLine("---");
155 System.Diagnostics.Trace.WriteLine(message, "Compiler message:");
156 Fail("Compilation failed");
157 }
158 if (status == 0)
159 {
160 System.Diagnostics.Trace.WriteLine("Compilation successful with no warnings" +
161 ", will upload the program to the instrument.");
162 }
163 if (status == 2)
164 {
165 System.Diagnostics.Trace.WriteLine("Compilation successful with warnings" +
166 ", will upload the program to the instrument.");
167 String message = awgModule.getString("compiler/statusstring");
168 System.Diagnostics.Trace.WriteLine("Compiler warning:");
169 System.Diagnostics.Trace.WriteLine(message);
170 }
171
172 // wait for waveform upload to finish
173 while (awgModule.getDouble("progress") < 1.0)
174 {
175 System.Diagnostics.Trace.WriteLine(
176 awgModule.getDouble("progress"), "Progress");
177 System.Threading.Thread.Sleep(100);
178 }
179
180 // Replace w3 with waveform_3 using vector write.
181 // Let N be the total number of waveforms and M>0 be the number of waveforms defined from CSV file. Then the index
182 // of the waveform to be replaced is defined as following:
183 // - 0,...,M-1 for all waveforms defined from CSV file alphabetically ordered by filename,
184 // - M,...,N-1 in the order that the waveforms are defined in the sequencer program.
185 // For the case of M=0, the index is defined as:
186 // - 0,...,N-1 in the order that the waveforms are defined in the sequencer program.
187 // Of course, for the trivial case of 1 waveform, use index=0 to replace it.
188 // Here we replace waveform w3, the 4th waveform defined in the sequencer program. Using 0-based indexing the
189 // index of the waveform we want to replace (w3, a vector of zeros) is 3:
190 // Write the waveform to the memory. For the transferred array, only 16-bit unsigned integer
191 // data (0...65536) is accepted.
192 // For dual-channel waves, interleaving is required.
193
194 // The following function corresponds to ziPython utility function 'convert_awg_waveform'.
195 Func<double, ushort> convert_awg_waveform = v => (ushort)((32767.0) * v);
196 daq.setVector(String.Format("/{0}/awgs/0/waveform/waves/3", dev), waveform_3.Select(convert_awg_waveform).ToArray());
197
198 // Configure the Scope for measurement
199 daq.setInt(
200 String.Format("/{0}/scopes/0/channels/0/inputselect", dev), in_channel);
201 daq.setInt(String.Format("/{0}/scopes/0/time", dev), 0);
202 daq.setInt(String.Format("/{0}/scopes/0/enable", dev), 0);
203 daq.setInt(String.Format("/{0}/scopes/0/length", dev), 16836);
204
205 // Now configure the scope's trigger to get aligned data.
206 daq.setInt(String.Format("/{0}/scopes/0/trigenable", dev), 1);
207 // Here we trigger on UHF signal input 1. If the instrument has the DIG Option installed we could
208 // trigger the scope using an AWG Trigger instead (see the `setTrigger(1);` line in `awg_program` above).
209 // 0: Signal Input 1
210 // 192: AWG Trigger 1
211 long trigchannel = 0;
212 daq.setInt(String.Format("/{0}/scopes/0/trigchannel", dev), trigchannel);
213 if (trigchannel == 0)
214 {
215 // Trigger on the falling edge of the negative blackman waveform `w0` from our AWG program.
216 daq.setInt(String.Format("/{0}/scopes/0/trigslope", dev), 2);
217 daq.setDouble(String.Format("/{0}/scopes/0/triglevel", dev), -0.600);
218
219 // Set hysteresis triggering threshold to avoid triggering on noise
220 // 'trighysteresis/mode' :
221 // 0 - absolute, use an absolute value ('scopes/0/trighysteresis/absolute')
222 // 1 - relative, use a relative value ('scopes/0trighysteresis/relative') of the trigchannel's input range
223 // (0.1=10%).
224 daq.setDouble(String.Format("/{0}/scopes/0/trighysteresis/mode", dev), 0);
225 daq.setDouble(String.Format("/{0}/scopes/0/trighysteresis/relative", dev), 0.025);
226
227 // Set a negative trigdelay to capture the beginning of the waveform.
228 daq.setDouble(String.Format("/{0}/scopes/0/trigdelay", dev), -1.0e-6);
229 }
230 else
231 {
232 // Assume we're using an AWG Trigger, then the scope configuration is simple: Trigger on rising edge.
233 daq.setInt(String.Format("/{0}/scopes/0/trigslope", dev), 1);
234
235 // Set trigdelay to 0.0: Start recording from when the trigger is activated.
236 daq.setDouble(String.Format("/{0}/scopes/0/trigdelay", dev), 0.0);
237 }
238
239 // the trigger reference position relative within the wave, a value of 0.5 corresponds to the center of the wave
240 daq.setDouble(String.Format("/{0}/scopes/0/trigreference", dev), 0.0);
241
242 // Set the hold off time in-between triggers.
243 daq.setDouble(String.Format("/{0}/scopes/0/trigholdoff", dev), 0.025);
244
245 // Set up the Scope Module.
246 ziModule scopeModule = daq.scopeModule();
247 scopeModule.setInt("mode", 1);
248 scopeModule.subscribe(String.Format("/{0}/scopes/0/wave", dev));
249 daq.setInt(String.Format("/{0}/scopes/0/single", dev), 1);
250 scopeModule.execute();
251 daq.setInt(String.Format("/{0}/scopes/0/enable", dev), 1);
252 daq.sync();
253 System.Threading.Thread.Sleep(100);
254
255 // Start the AWG in single-shot mode
256 daq.setInt(String.Format("/{0}/awgs/0/single", dev), 1);
257 daq.setInt(String.Format("/{0}/awgs/0/enable", dev), 1);
258
259 // Read the scope data (manual timeout of 1 second)
260 double local_timeout = 1.0;
261 while (scopeModule.progress() < 1.0 && local_timeout > 0.0)
262 {
263 System.Diagnostics.Trace.WriteLine(
264 scopeModule.progress() * 100.0, "Scope Progress");
265 System.Threading.Thread.Sleep(20);
266 local_timeout -= 0.02;
267 }
268 string path = String.Format("/{0}/scopes/0/wave", dev);
269 Lookup lookup = scopeModule.read();
270 ZIScopeWave[] scopeWaves1 = lookup[path][0].scopeWaves;
271 float[,] y_measured_in = SimpleValue.getFloatVec2D(scopeWaves1[0].wave);
272 float[] y_measured = new float[y_measured_in.Length];
273 for (int i = 0; i < y_measured_in.Length; ++i)
274 {
275 y_measured[i] = y_measured_in[0, i];
276 }
277
278 var x_measured = Enumerable.Range(0, y_measured.Length).Select(
279 v => -(long)v * scopeWaves1[0].header.dt +
280 (scopeWaves1[0].header.timeStamp -
281 scopeWaves1[0].header.triggerTimeStamp) / f_s
282 ).ToArray();
283
284 // write signals to files
285 String fileName = Environment.CurrentDirectory + "/awg_measured.txt";
286 System.IO.StreamWriter file = new System.IO.StreamWriter(fileName);
287 file.WriteLine("t [ns], measured signal [V]");
288 for (int i = 0; i < y_measured.Length; ++i)
289 {
290 file.WriteLine("{0} {1}", x_measured[i] * 1e9, y_measured[i]);
291 }
292 file.Close();
293
294 fileName = Environment.CurrentDirectory + "/awg_expected.txt";
295 file = new System.IO.StreamWriter(fileName);
296 file.WriteLine("t [ns], expected signal [V]");
297 for (int i = 0; i < y_expected.Length; ++i)
298 {
299 file.WriteLine("{0} {1}", x_expected[i] * 1e9, y_expected[i]);
300 }
301 file.Close();
302
303 // checks
304 AssertNotEqual(0, x_measured.Length);
305 AssertNotEqual(0, y_measured.Length);
306
307 // find minimal difference
308 double dMinMax = 1e10;
309 for (int i = 0; i < x_measured.Length - x_expected.Length; i++)
310 {
311 double dMax = 0;
312 for (int k = 0; k < x_expected.Length; k++)
313 {
314 double d = Math.Abs(y_expected[k] - y_measured[k + i]);
315 if (d > dMax)
316 {
317 dMax = d;
318 }
319 }
320
321 if (dMax < dMinMax)
322 {
323 dMinMax = dMax;
324 }
325 }
326 Debug.Assert(dMinMax < 0.1);
327
328 scopeModule.clear(); // Release module resources. Especially important if modules are created
329 // inside a loop to prevent excessive resource consumption.
330 awgModule.clear();
331 daq.disconnect();
332}