-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTestSequence.cpp
More file actions
778 lines (712 loc) · 41.7 KB
/
TestSequence.cpp
File metadata and controls
778 lines (712 loc) · 41.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
#include "TestSequence.h"
#include "ControlAPI.h"
#include "QTCPIP.h"
#include "main.h"
#include <QMessageBox>
#include <QApplication>
#include <QFile>
//#include <QThread>
#include <unistd.h>
#include <QTime>
#include <QThread>
//#define AQuRA_Clock
CControlAPI CA;
bool BlockButtons = false;
#ifdef AQuRA_Clock
const char* ParamFileDirectory = "C:\\AQuRA\\OpticsFoundry_Control_AQuRA\\ConfigParams\\";
const QString DebugFileDirectory = "C:\\AQuRA\\DebugControlQt";
const QString LogFileDirectory = "C:\\AQuRA\\Data\\";
#else
const char* ParamFileDirectory = "D:\\Florian\\OpticsFoundry\\OpticsFoundryControl\\OpticsFoundry_Control_AQuRA\\ConfigParams\\";
const QString DebugFileDirectory = "D:\\Florian\\OpticsFoundry\\OpticsFoundryControl\\DebugControlQt";
const QString LogFileDirectory = "D:\\Florian\\OpticsFoundry\\OpticsFoundryControl\\Data\\";
#endif
void Sleep_ms_and_call_CA_OnIdle(int delay_in_milli_seconds)
{
QTime dieTime= QTime::currentTime().addMSecs(delay_in_milli_seconds);
int remaining = 0;
int MaxNrLoops = delay_in_milli_seconds / 10 + 1;
int NrLoops = 0;
do {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
//We need to call the API's idle function regularly if we want it to cycle automatically
CA.OnIdle();
remaining = QTime::currentTime().msecsTo(dieTime);
if (remaining>0) {
QThread::msleep(10);
remaining = QTime::currentTime().msecsTo(dieTime);
}
NrLoops++;
} while ((remaining > 0) && (NrLoops<MaxNrLoops));
}
void SetStatusTextAndLog(QString aMessage, bool AddNewLine = true) {
static QString LogFileName = "";
if (LogFileName=="") {
// Format: YYYY-MM-DD_hh-mm-ss.dat
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss");
LogFileName = LogFileDirectory + "log_" + timestamp + ".dat";
}
QFile file(LogFileName);
if (file.open(QIODevice::Append)) {
QTextStream stream(&file);
stream << aMessage;
if (AddNewLine) stream << "\n";
file.close();
}
TelnetTester->setStatusText(aMessage, true);
}
void MessageBox(QString aMessage) {
QMessageBox msgBox;
msgBox.setText(aMessage);
msgBox.exec();
SetStatusTextAndLog(aMessage);
}
bool Cycling = false;
bool LittleCycle = true;
bool CycleSuccessful = true;
bool TerminateCycling(bool ok) {
CA.StopCycling();
CA.StoreSequenceInMemory(false);
BlockButtons = false;
SetStatusTextAndLog("Finished cycling");
Cycling = false;
return ok;
}
void TerminateLittleCycle() {
//For debugging, I want the system to stall. Therefore I don't remedy the problem and return.
//return; //comment this line out when not debugging.
LittleCycle = false; //something went quite wrong -> start cycling from scratch
//somehow setting LittleCycle to false isn't sufficient as we are apparently
//stuck, probably in a CA call, but can get unstuck by calling TerminateCycling()
TerminateCycling(true);
Cycling = true;
//We restart cycling explicitly from this timer event (could alternatively be done by Qt message).
//It's not great to duplicate the cycling process in this way, but better than having just one stuck process.
//Longterm it would be better to find out what the root cause of the issue is.
SetStatusTextAndLog("Restarting cycling");
CycleSequenceWithIndividualCommandUpdate();
}
//We implement a dead man's switch (a timer) that checks if cycling is proceeding normally.
//If it isn't for a long time, we set LittleCycle = false.
//That will restart cycling from scratch.
qint64 LastSuccessfulCycleEndTimeSinceMidnight = 0;
//The dead-man's switch is now implemented directly in CycleSequenceWithIndividualCommandUpdate(), therefore CheckIfSequencerCycling() not absolutely needed.
//Comment out the following line if you prefer simpler code, without this timer.
#define UseTimerBasedDeadMansSwitch
#ifdef UseTimerBasedDeadMansSwitch
qint64 LastCheckTimeSinceMidnight = 1;
void CheckIfSequencerCycling() {
if (LastCheckTimeSinceMidnight == LastSuccessfulCycleEndTimeSinceMidnight) {
if (Cycling) {
//This currently happens about 1 in 50 thousand cycles
SetStatusTextAndLog("CheckIfSequencerCycling: Sequencer not cycling for more than 30 seconds.");
TerminateLittleCycle();
}
}
LastCheckTimeSinceMidnight = LastSuccessfulCycleEndTimeSinceMidnight;
}
#endif
QTimer CheckIfSequenceCyclingTimer;
bool InitializeSequencer(QTelnet *atelnet) {
//IP address only needed if we connect to low level software over ethernet.
//If we connect over DLL, it's not needed. The IP of the FPGA is specified in the ControlHardwareConfig.json configuration file of the low-level software.
//The directory of the configuration file is defined in the config.txt file, which is in the directory specified, or in the directory of the exe file that uses the dll.
if (!CA.ConnectToLowLevelSoftware(atelnet, ParamFileDirectory, /*IP*/ "10.0.2.42"/*192.168.58.157"*/, /*Debug*/ true, DebugFileDirectory)) { //Irene's place: 192.168.0.103 Odido: 192.168.1.155
MessageBox("TestSequence.cpp : Initialize() : couldn't connect to low level software.");
return false;
}
//CA.ConnectToSequencer(/*IP*/ "192.168.0.115"); //done automatically for now
//CA.ConfigureControlAPI(/*DisplayCommandErrors*/ false);
//The dead-man's switch is now implemented directly in CycleSequenceWithIndividualCommandUpdate(), therefore CheckIfSequencerCycling() not absolutely needed.
#ifdef UseTimerBasedDeadMansSwitch
//Adding recovery functionality: check regularly if sequencer is cycling, like a dead man's switch.
//If not cycling for a long time, start cycling from scratch.
CheckIfSequenceCyclingTimer.setInterval(30000); // Try every 30 seconds
QAbstractSocket::connect(&CheckIfSequenceCyclingTimer, &QTimer::timeout, []() {
CheckIfSequencerCycling(); // call your global function
});
CheckIfSequenceCyclingTimer.start();
#endif
return true;
}
bool CheckSequencer() {
SetStatusTextAndLog(CA.UsingDLL() ? "Using Control.dll to connect to sequencer" : "Using ethernet connection to Control.exe to connect to sequencer");
CA.ConfigureControlAPI(/*DisplayCommandErrors*/ false);
CA.SwitchDebugMode(/*On*/ false, /*DebugTimingOn*/ false); //put debug mode to true if you want to debug sequence (see all the files created by the Visual Studio code, and the USB-serial port output of the ZYNQ FPGA). Usually leave this "false".
CA.StopCycling();//just in case cycling wasn't correctly terminated before
CA.StoreSequenceInMemory(false); //just in case StoreSequenceInMemory mode is active.
if (!CA.CheckIfLowLevelSoftwareReady(/*timeout_in_seconds*/ 10)) {
MessageBox("Low level software not ready");
return false;
}
if (!CA.CheckIfSequencerReady(/*timeout_in_seconds*/ 10)) {
MessageBox("FPGA sequencer not ready");
return false;
}
return true;
}
//function called by GUI button
bool ResetSystem() {
if (BlockButtons) return false;
if (!CheckSequencer()) return false;
//enter sequence programming mode
CA.ProgramSequence();
//put or ramp outputs to state needed for MOT
CA.Command("SetFrequencyBlueMOTDPAOM(205);");
CA.Command("SetFrequencyBlueMOTDPAOM(210);");
long lineNrError;
QString badCodeLine;
if (CA.DidCommandErrorOccur(lineNrError, badCodeLine)) {
//there was an error. Discard programmed sequence by going back to direct output mode.
CA.SwitchToDirectOutputMode();
//QString str = QString("Error at line number: %d in command line: %s").arg(lineNrError, badCodeLine);
MessageBox("Error at line number: "+ QString::number(lineNrError) + " in command line: " + badCodeLine);
return false;
} else {
CA.StartSequence(/*diplay_progress_dialog*/ true);
CA.WaitTillSequenceEnds(/*timeout_in_seconds*/ 10);
//now we are automatically back in direct output mode
SetStatusTextAndLog("Reset system done");
}
return true;
}
void SaveInputDataToFile(QString filename, unsigned int* buffer, unsigned long buffer_length) {
//save input data as ASCII table
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
for (unsigned long i=0;i<buffer_length;i++) {
/*
//for 32-bit data format (mostly for debugging)
unsigned long buf_high = buffer[i] >> 16;
unsigned long buf_low = buffer[i] & 0xFFFF;
char out_buf[100];
unsigned long data = buf_low & 0xFFF;
sprintf(out_buf, "%u %u %u %u %x %x\n", i, data, buf_high, buf_low, buf_high, buf_low);
stream << out_buf;
*/
//for usual, 16-bit data format
char out_buf[100];
sprintf(out_buf, "%u %u\n", i, buffer[i]);
//sprintf(out_buf, "%u %u 0x%08X\n", i, buffer[i], buffer[i]);
stream << out_buf;
}
file.close();
} else {
MessageBox("Couldn't open file for writing");
}
}
long ModifyCodeLineNr1 = 0;
long ModifyCodeLineNr2 = 0;
long ModifyCodeLineNr3 = 0;
void TestSequence(bool RestartMOTLoading, bool take_photodiode_data) {
//Starting point of sequence: end of MOT loading
//end point of sequence: just after start of MOT loading
// in this way we can do photodiod data treatment and update of next parameters during MOT loading time
// Fixed duration of MOT loading time is guaranteed by FPGA when used in cycle mode
CA.Command("WriteSystemTimeToInputMemory();"); //SystemTime is a 56-bit counter that counts the time since the FPGA was switched on in units of the FPGA clock period, i.e. usually 10ns.
if (RestartMOTLoading) {
//drop all atoms, restart MOT, wait MOT loading time
//ToDo: implement more commands than just SetFrequencyBlueMOTDPAOM(), write complete clock sequence.
CA.Command("SetFrequencyBlueMOTDPAOM(200);Wait(10);SetFrequencyBlueMOTDPAOM(210);Wait(10);");
}
for (int i=0;i<2;i++) {
CA.Command("SetFrequencyBlueMOTDPAOM(205);Wait(25);");
CA.Command("SetFrequencyBlueMOTDPAOM(200);Wait(25);");
}
CA.Command("DoNothing();");
//storing the line number of the last command enables you to replace it using ReplaceCommand(), see CycleSequenceWithIndividualCommandUpdate()
//The command that can be replaced consists of as manny ;-separated sub-commands as you like.
//In the example here, we inserted a placeholder command, doing nothing, and replace it later by a real command
//The main application of this technique is to update the frequency of the clock laser probe beam.
ModifyCodeLineNr1 = CA.GetLastCommandLineNumber();
CA.Command("Wait(10);");
//Message("Modify code line number: "+ QString::number(ModifyCodeLineNr));
if (take_photodiode_data) {
//The FPGA input BRAM (2048 32-bit words) has to periodically copied into DDR by the ZYNQ's CPU (DMA is used for sequence output; too complicated to mesh two DMA transfers).
//To avoid this CPU being stuck too long treating TCP/IP data and thereby overlooking the need to transfer the input buffer, we can ignore the TCP/IP communication (e.g. a request if sequence have finished by PC), we halt all TCP/IP communication for a while.
//The TCP/IP data is not lost, it's just staying a bit longer in an input buffer, which is copied to DDR once we enabel TCP/IP communication again.
//This is only a safety measure and maybe not needed.
//GoBackInTime is used to start ignoring TCPIP a bit earlier than needed, just in case the last TCP/IP command takes a little bit of time to finish.
//CA.Command("GoBackInTime(100);IgnoreTCPIP(1);Wait(100);");
//Placing the System time (as zero padded 8*8-bit = 64-bit value) right into the data set that contains also the photodiode readout makes it possible to determine if the AQuRA clock runs have been executed in the desired time sequence or not.
//Clock runs that don't meet the timing requirements should be ignored.
//optionally we can leave some data in the input buffer, e.g. as markers to that can be used to check if the memory got corrupted, or to leave data about a specific sequence, see the use of ReplaceCommand() below to store cycle number in input memory.
//this is useful for debugging, but not needed for normal operation
//The next command will be replaced in CycleSequenceWithIndividualCommandUpdate() with a command that writes the cycle number into the input buffer.
//This is only done to identify which dataset belongs to which run, i.e. to detect cycle slips.
CA.Command("WriteInputMemory(0);"); //cycle number
ModifyCodeLineNr2 = CA.GetLastCommandLineNumber();
//More general form of memory command:
CA.Command("WriteInputMemory(1, 1, 0);"); //WriteInputMemory(unsigned long input_buf_mem_data, bool write_next_address = 1, unsigned long input_buf_mem_address = 0)
CA.Command("SwitchDebugLED(1);");
CA.Command("AddMarker(1);"); //for debug: displays marker (here "1") on ZYNQ USB port output (use Termite or similar to see it)
CA.Command("StartAnalogInAcquisition(0,1,0,0,1000,0.1);");// StartAnalogInAcquisition(/*SequencerNr*/ 0, /*SPI_port*/ 1, /*SPI_CS*/ 0, /*channel_number*/ 2, /* number_of_datapoints */ 100, /* delay_between_datapoints_in_ms*/ 1)
for (int i=0;i<50;i++) {
CA.Command("SwitchSpareDigitalOut0(On);Wait(1);");
CA.Command("SwitchSpareDigitalOut0(Off);Wait(1);");
}
/*
CA.Wait(1);
for (int i=0;i<8;i++) {
char command[100];
sprintf(command, "StartAnalogInAcquisition(%u,100,0.1);Wait(11);", 3 + i);
CA.Command(command);
}
*/
CA.Command("Wait(10);");
CA.Command("WriteInputMemory(3);");
CA.Command("WriteInputMemory(4);");
CA.Command("SetFrequencyBlueMOTDPAOM(201);Wait(10);");// repump atoms (here just a dummy command)
CA.Command("SetFrequencyBlueMOTDPAOM(202);Wait(10);");// repump atoms (here just a dummy command)
CA.Command("SetFrequencyBlueMOTDPAOM(201);Wait(10);");// repump atoms (here just a dummy command)
CA.Command("StartAnalogInAcquisition(0,1,0,0,1000, 0.1);Wait(200);");// StartAnalogInAcquisition(/*channel_number*/ 2, /* number_of_datapoints */ 100, /* delay_between_datapoints_in_ms*/ 1)
CA.Command("AddMarker(2);");
//reenable TCP/IP communication. (Optional, it's reenabled automatically after the sequence ends)
//CA.Command("IgnoreTCPIP(0);");
//CA.Command("Wait(5000);");
CA.Command("WriteInputMemory(5);");
CA.Command("WriteInputMemory(6);");
CA.Command("SwitchDebugLED(0);");
}
CA.Command("SetFrequencyBlueMOTDPAOM(205);Wait(10);");
CA.Command("SetFrequencyBlueMOTDPAOM(201);Wait(10);");
//example of a linear ramp
CA.Command("Ramp(\"SetFrequencyBlueMOTDPAOM\", LastValue, 210, 100, 1);"); //Ramp(unsigned char* output_name, double start_value /* use LAST_VALUE for last value */, double end_value, double ramp_time_in_ms, double timestep_in_ms = 0.1)
CA.Command("WaitTillRampsEnd();");
CA.Command("SetFrequencyBlueMOTDPAOM(210);");
CA.Command("Wait(10);");
CA.Command("Wait(0);");
ModifyCodeLineNr3 = CA.GetLastCommandLineNumber();
CA.Command("SetFrequencyBlueMOTDPAOM(201);");
//We note in the input buffer how long the sequence took. We'll use this time to check if the cycle time was ok.
CA.Command("WriteSystemTimeToInputMemory();Wait(0.001);");
//ToDo: add commands needed to start MOT loading
//end point of sequence: just after start of MOT loading
}
bool DidCommandErrorOccur() {
long lineNrError;
QString badCodeLine;
if (CA.DidCommandErrorOccur(lineNrError, badCodeLine)) {
//there was an error. Discard programmed sequence by going back to direct output mode
CA.SwitchToDirectOutputMode();
//QString str = QString("Error at line number: %d in command line: %s").arg(lineNrError, badCodeLine);
MessageBox("Error at line number: "+ QString::number(lineNrError) + " in command line: " + badCodeLine);
} else return false;
return true;
}
unsigned int* buffer = NULL;
bool TreatPhotodiodeData(bool take_photodiode_data, bool message) {
bool success;
unsigned long buffer_length = 0;
if (take_photodiode_data) {
success = CA.WaitTillEndOfSequenceThenGetInputData(buffer, buffer_length, /*timeout_in_seconds*/ 20);
if (success && (buffer != NULL)) {
QString myQString = "Data received: "+ QString::number(buffer_length) + " 16-bit values";
if (message) MessageBox(myQString);
else SetStatusTextAndLog(myQString);
//process input data
SaveInputDataToFile("D:\\Florian\\OpticsFoundry\\OpticsFoundryControl\\input.dat", buffer, buffer_length);
//freeing buffer is done in CA and shouldn't be done here if DLL is used.
//delete[] Buffer;
} else {
if (message) MessageBox("No input data received");
SetStatusTextAndLog("No input data received");
}
} else CA.WaitTillSequenceEnds(/*timeout_in_seconds*/ 10);
//now we are automatically back in direct output mode
return true;
}
//
//Example of executing experimental sequence a single time.
//
//function called by GUI button
bool ExecuteTestSequence() {
if (BlockButtons) return false;
if (!CheckSequencer()) return false;
bool take_photodiode_data = true;
//enter sequence programming mode
CA.ProgramSequence();
TestSequence(/*restart_MOT_loading*/ true, take_photodiode_data);
if (DidCommandErrorOccur()) return false;
CA.StartSequence(/*diplay_progress_dialog*/ true, /*timeout_in_seconds*/ 5);
TreatPhotodiodeData(take_photodiode_data, /*display_message*/ false);
return true;
}
//
//Example of using a cyclic sequence with your own code in between (probably not appropriate for AQuRA, but good for debugging).
//
void InitializeCycleSequence() {
//We provide the FPGA with the desired cycle time.
//When we launch a sequence the FPGA will wait till at least that time has elapsed since the last time we did run
if (!CheckSequencer()) return;
//Here we define the cycle time. This resets the cycle.
CA.SetPeriodicTrigger(/*PeriodicTriggerPeriod_in_ms*/2000, /*PeriodicTriggerAllowedWaitTime_in_ms*/ 2000);
}
bool StartCycleSequence(bool take_photodiode_data) {
//enter sequence programming mode
CA.ProgramSequence();
//send sequence over
TestSequence(/*restart_MOT_loading*/ true, take_photodiode_data);
//check if sequence ok
if (DidCommandErrorOccur()) return false;
//if yes, execute sequence
if (!CA.StartSequence(/*diplay_progress_dialog*/ true)) {
MessageBox("Cycle Sequence couldn't start");
return false;
}
return true;
}
void YourOwnCode() {
//here you can run your own code, as long as you give control back soon enough to process photodiode data and tell system to start next run, before the cycle has expired.
//If your code takes too long, we will get a longer time between two runs of a sequence than desired. The FPGA will notify us with a "missed cycle" message.
}
void EndCycleSequence(bool first_cycle, bool take_photodiode_data) {
//The first cycle used an undefined MOT loading time. Make sure to ignore that data.
TreatPhotodiodeData(take_photodiode_data, /*display_message*/ false);
if (CA.GetPeriodicTriggerError()) {
MessageBox("Cycle was triggered too late.");
}
}
//function called by GUI button
bool CycleSequence() {
if (BlockButtons) return false;
bool take_photodiode_data = true;
InitializeCycleSequence();
constexpr int nr = 3;
for (int n =0; n<nr ;n++) { //here you could also use a while loop, that's stopped whenever you want
QString mess= QString::number(n+1) + "/"+ QString::number(nr) + ": ";
SetStatusTextAndLog(mess);
StartCycleSequence(take_photodiode_data);
YourOwnCode();
EndCycleSequence(/*first_cycle*/ n == 0, take_photodiode_data);
}
CA.StopCycling();
return true;
}
//
//Example of using a cyclic sequence with individual command updates. This is likely what we will use for AQuRA.
//
double PeriodicTriggerPeriod_in_ms = 0;
long PreviousLastCycleEndTime = 0;
long PreviousLastCycleStartPreTriggerTime = 0;
unsigned long long PreviousFPGASystemTime = 0;
unsigned int NumberOfTimesFailedRun = 0;
unsigned int* Buffer = NULL; //32-bit data
void GetCycleData(bool take_photodiode_data, long TimeTillNextCycleStart_in_ms, unsigned long ExpectedCycleNumber, long &CycleNumber, unsigned long &CycleNrFromBuffer) {//}, long &LastCycleEndTime) {
bool success;
unsigned long BufferLength = 0;
long LastCycleEndTime = 0; //ToDo: should be DWORD, i.e. unsigned long
long LastCycleStartPreTriggerTime; //ToDo: should be DWORD, i.e. unsigned long
bool CycleError; //if true start of this sequence happend with too much delay, i.e. we jump over at least one cycle
QString ErrorMessages;
success = CA.GetCycleData(Buffer, BufferLength, CycleNumber, LastCycleEndTime, LastCycleStartPreTriggerTime, CycleError, ErrorMessages);
if ((!success) || (!Buffer) || (BufferLength<3)) {
SetStatusTextAndLog("GetCycleData: no data");
CycleSuccessful = false;
return;
}
//FPGA SystemTime is in first 8 bytes thanks to CA.Command("WriteSystemTimeToInputMemory();"); command
//unsigned long FPGASystemTimeLowStart = Buffer[0];
//unsigned long FPGASystemTimeHighStart = Buffer[1];
unsigned long long FPGASystemTimeStart = ((unsigned long long*)Buffer)[0]; // in units of the clock period, i.e. usually 10ns
unsigned long FPGASystemTimeLow = Buffer[2];
unsigned long FPGASystemTimeHigh = Buffer[3];
unsigned long long FPGASystemTime = ((unsigned long long*)Buffer)[1]; // in units of the clock period, i.e. usually 10ns
//unsigned long long FPGASystemTimeAtSequenceEnd = ((unsigned long long*)Buffer)[BufferLength/2-2]; // in units of the clock period, i.e. usually 10ns
CycleNrFromBuffer = Buffer[4];
double WaitForTriggerTime = 0.00001 * (FPGASystemTime - FPGASystemTimeStart);
//We check if the MOT loading time was ok.
//For that, PeriodicTriggerPeriod_in_ms must contain the duration of the previous sequence plus the desired MOT loading time.
//The only variable part of the sequence is the blue MOT duration.
//We measure this blue MOT's duration by determining the time between the start of the last sequence and the start of this sequence.
//The blue MOT duration is ElapsedFPGASystemTime - the duration of the last sequence.
//We don't calculate the blue MOT duration explicitly, but check if ElapsedFPGASystemTime is within the expected range.
unsigned long long ElapsedFPGASystemTime = FPGASystemTime - PreviousFPGASystemTime;
if (ElapsedFPGASystemTime>PeriodicTriggerPeriod_in_ms*100000+10) {
ErrorMessages+= " Overtime.";
CycleSuccessful = false;
} else if (ElapsedFPGASystemTime<PeriodicTriggerPeriod_in_ms*100000-10) {
ErrorMessages+= " Undertime.";
CycleSuccessful = false;
}
if (CycleNumber != CycleNrFromBuffer) {
ErrorMessages+= " Cycle number slip (expected " + QString::number(CycleNumber)+ ", got "+ QString::number(CycleNrFromBuffer)+").";
CycleSuccessful = false;
}
if (CycleNumber != ExpectedCycleNumber) {
ErrorMessages+= " Unexpected cycle number (expected " + QString::number(ExpectedCycleNumber)+ ", got "+ QString::number(CycleNumber)+").";
CycleSuccessful = false;
}
PreviousFPGASystemTime = FPGASystemTime;
char out_buf[100];
sprintf(out_buf, "%4u %4u %4u %4u %4u %4.0f %4li %10llu %03X %08X f%03u rc%u %u",
CycleNrFromBuffer,
BufferLength,
LastCycleStartPreTriggerTime - PreviousLastCycleStartPreTriggerTime,
LastCycleEndTime - PreviousLastCycleEndTime,
LastCycleEndTime - LastCycleStartPreTriggerTime,
WaitForTriggerTime,
TimeTillNextCycleStart_in_ms,
ElapsedFPGASystemTime,
FPGASystemTimeHigh,
FPGASystemTimeLow,
NumberOfTimesFailedRun,
CA.GetNumberReconnects(),
(CycleError) ? 1 : 0);
QString myQString = QString::fromUtf8(out_buf) + ErrorMessages;
SetStatusTextAndLog(myQString);
PreviousLastCycleEndTime = LastCycleEndTime;
PreviousLastCycleStartPreTriggerTime = LastCycleStartPreTriggerTime;
if (take_photodiode_data) {
//ToDo: use 16-bit data format instead of 32-bit format in order to speed up data handling?
if (success && (Buffer != NULL)) {
//process input data
char filename[100];
sprintf(filename, "%s\\input%04u.dat",LogFileDirectory.toUtf8().data() , CycleNumber);
SaveInputDataToFile(filename, Buffer, BufferLength);
//freeing buffer is done in CA and shouldn't be done here if DLL is used.
//delete[] Buffer;
} else {
SetStatusTextAndLog("no input data received 2");
CycleSuccessful = false;
}
}
}
//function called by GUI button
bool StopCyclingButtonPressed() {
bool ok = true;
if (Cycling) Cycling = false;
else TerminateCycling(ok); //needed if QtControl newly started, but Visual Studio Control cycling
if (!ok) MessageBox("Could not stop cycling");
return ok;
}
//function called by GUI button
bool CycleSequenceWithIndividualCommandUpdate() {
//CycleSequenceWithIndividualCommandUpdate() is similar to CycleSequence, but sequence only programmed once.
//From then on only desire command changes are scheduled for certain run numbers.
//In AQuRA, the DDS frequency that controls the clock laser probe frequency is updated.
if (BlockButtons) return false;
//Below in this procedure, we use Sleep_ms_and_call_CA_OnIdle(), which calls QCoreApplication::processEvents().
//If GUI buttons are pressed, processEvents() could create calls to routines that execute other experimental sequences,
//which would create a mess with the sequence executed here.
//To avoid this, we block these procedures, which is eseentially the same as blocking the buttons on the GUI.
BlockButtons = true;
//if the synchronization to FPGA is lost we set LittleCycle = false.
//This will restart everything from scratch, unless "Cycling" is false.
//We implement a dead man switch (a timer) that checks if cycling is proceeding normally. If it isn't for a long time, we set LittleCycle = false.
bool ret = true;
Cycling = true;
unsigned int NumberOfFailedRunsSinceLastSuccessfulRun = 0;
QTime now = QTime::currentTime();
LastSuccessfulCycleEndTimeSinceMidnight = QTime(0, 0).msecsTo(now);
bool AdditionalCycleTimeStatus = false;
double StandardPeriodicTriggerPeriod_in_ms = 0;
double NextPeriodicTriggerPeriod_in_ms = 0;
while (Cycling) {
SetStatusTextAndLog("Starting to cycle from scratch.");
LittleCycle = true;
if (!CheckSequencer()) {
BlockButtons = false;
return false;
}
//We tell the low level software to store the sequence, so that it can be used over and over again.
CA.StoreSequenceInMemory(true);
bool take_photodiode_data = true;
//Next we program the sequence.
TestSequence(/*restart_MOT_loading*/ true, take_photodiode_data);
//We determine the duration of the sequence, such that we can adapt the periodic trigger accordingly.
double SequenceDuration_in_ms = 0;
if (!CA.GetSequenceDuration(SequenceDuration_in_ms)) {
MessageBox("Could not get sequence duration");
return TerminateCycling(false);
}
//WaitTimeBetweenSequences_in_ms is the blue MOT loading time.
//The MOT loading time is used
// 1) to get data of last run and send that data to DLL
// 2) to program FPGA with new sequence and start it.
//The latter has to finish before the MOT time expires, otherwise the FPGA will add another full cycle to the MOT loading time
//and an overtime error will be detected in GetCycleData() by analysing the time stamps in the data that is read in.
//SoftPreTriggerTime_in_ms is the time before the sequence starts, i.e. the next red MOT starts,
//at which the DLL starts to calculate the new sequence and sends the changes in the sequence to the FPGA.
//We need to keep SoftPreTriggerTime_in_ms long enough for all these tasks to be executed reliably, despite Windows timing jitter.
//On the other hand
//WaitTimeBetweenSequences_in_ms - SoftPreTriggerTime_in_ms
//needs to be long enough for the input data to be read in and analysed by GetCycleData()
//and changes to the next sequence to be programmed by
//CA.ReplaceCommand() commands.
//If the DLL is used WaitTimeBetweenSequences_in_ms can reliably work down to about 200ms (for 8000 photodiode data points, with 76Mbit/s bandwidth to FPGA).
//In TCP/IP mode, it can be down to 300ms.
//SoftPreTriggerTime_in_ms should be at least 100ms for reliable operation
#ifdef USE_CA_DLL
constexpr double WaitTimeBetweenSequences_in_ms = 300;
constexpr long SoftPreTriggerTime_in_ms = 150;
//Note on SoftPreTriggerTime_in_ms: It's better to use CA.Trigger() instead of waiting for the DLL to trigger the data transfer to the DLL.
//If CA.Trigger() is used, SoftPreTriggerTime_in_ms can be kept short, but must be > 0.
//(SoftPreTriggerTime_in_ms == 0 means that the next CA::OnIdle() call triggers the sequence, equivalent to what CA.Trigger does).
#else
constexpr double WaitTimeBetweenSequences_in_ms = 500; //This is the MOT loading time. If the DLL is used it can reliably work down to about 200ms (for 8000 photodiode data points, with 76Mbit/s bandwidth to FPGA). In TCP/IP mode, it can be down to 300ms.
constexpr long SoftPreTriggerTime_in_ms = 250;
#endif
PeriodicTriggerPeriod_in_ms = SequenceDuration_in_ms + WaitTimeBetweenSequences_in_ms;
StandardPeriodicTriggerPeriod_in_ms = PeriodicTriggerPeriod_in_ms;
SetStatusTextAndLog("Cycling with " + QString::number(PeriodicTriggerPeriod_in_ms) + " ms period of which " + QString::number(SequenceDuration_in_ms) + " ms sequence duration.");
//WaitTimeBetweenSequences_in_ms is essentially the MOT loading time.
//During this time the data of the last run is read out, analyzed and the frequency command for the next run(s) are sent.
double MaxSequenceDuration_in_s = 20 + PeriodicTriggerPeriod_in_ms/1000; //for the detection of timeouts
//Next we define the cycle time.
//When we launch a sequence the FPGA will wait till at least PeriodicTriggerPeriod_in_ms has elapsed since the last time we did run (unless it's the first sequence in a cycle).
//If FPGA needs to wait longer than PeriodicTriggerAllowedWaitTime_in_ms, the CycleError flag is set high. That flag is retrieved with GetCycleData().
CA.SetPeriodicTrigger(PeriodicTriggerPeriod_in_ms, /*PeriodicTriggerAllowedWaitTime_in_ms*/ SequenceDuration_in_ms + WaitTimeBetweenSequences_in_ms);
NextPeriodicTriggerPeriod_in_ms = PeriodicTriggerPeriod_in_ms;
long NextCycleNumber = 1;
unsigned long CycleNrFromBuffer = 0;
long CycleNumberFromCADataRead = 0;
long TimeTillNextCycleStart_in_ms = 0;
constexpr long ReadoutPreTriggerTime_in_ms = 100;
CA.ResetCycleNumber(); //this sets the next cycle number to 0
long CycleNumber = 0;
//Parameters of StartCycling:
//ReadoutPreTriggerTime_in_ms & SoftPreTriggerTime_in_ms: The Visual Studio code will stop listening to TCPIP commands readout_pre_trigger_in_ms before periodic cycle ends, in order to be ready to immediatley read out data when cycle ends.
//The Visual Studio code will send the next command sequence to the FPGA, soft_pre_trigger_in_ms before its estimation of the next FPGA periodic trigger event
//In order for the Visual Studio's cycle start time and the FPGA's cycle start time to remain in sync:
//at the end of each cycle, Visual Studio measures the end time and calculates the time at which the last periodic trigger must have happened.
//TransmitOnlyDifferenceBetweenCommandSequenceIfPossible: if possible transmitt only difference of sequency to FPGA. This option saves time and should usually be set to true.
//DoWindowsEnterCriticalPriorityMode: DLL brings this process and threat into critical priority mode, if Windows allows that. This can make cycling slightly more reliable.
//diplay_progress_dialog: the display of a progress bar dialog is probably only useful for debugging and can be set to "false" in normal use
//Note that if sequence is not ok, GetCycleData() will return error messages about which line of the sequence was faulty. //ToDo: check that this works.
if (!CA.StartCycling(ReadoutPreTriggerTime_in_ms, /*soft_pre_trigger_in_ms*/ SoftPreTriggerTime_in_ms, /*TransmitOnlyDifferenceBetweenCommandSequenceIfPossible*/ true, /*DoWindowsEnterCriticalPriorityMode*/true, /*diplay_progress_dialog*/ false)) { //this starts cycle 0
if (DidCommandErrorOccur()) return TerminateCycling(false);
MessageBox("CycleSequence couldn't start");
return TerminateCycling(false);
}
while (Cycling && LittleCycle) { //here you could also use a while loop that's stopped whenever you want
//here you can run your own code, as long as you give control back soon enough to process photodiode data and tell system to start next run, before the cycle has expired.
//If your code takes too long, we will get a longer time between two runs of a sequence than desired. The FPGA will notify us with a "missed cycle" message.
YourOwnCode();
CycleSuccessful = true;
//check if cycling was aborted because of incorrect command
if (!CA.IsCycling(/* timeout_in_seconds*/ MaxSequenceDuration_in_s)) {
if (DidCommandErrorOccur()) return TerminateCycling(false);
MessageBox("CA.IsCycling : Error while cycling (1)");
return TerminateCycling(false);
}
unsigned int Attempts = 0;
const unsigned int MaxAttempts = 10;
while ((!CA.DataAvailable(/* timeout_in_seconds*/ MaxSequenceDuration_in_s + 1)) && (Attempts<MaxAttempts)) {
Attempts++;
Sleep_ms_and_call_CA_OnIdle(10);
}
if (!(Attempts<MaxAttempts)) {
if (!CA.IsCycling(/* timeout_in_seconds*/ MaxSequenceDuration_in_s)) {
if (DidCommandErrorOccur()) return TerminateCycling(false);
MessageBox("CA.IsCycling : Error while cycling (2)");
return TerminateCycling(false);
}
MessageBox("CA.IsCycling : couldn't get data");
return TerminateCycling(false);
}
GetCycleData(take_photodiode_data, TimeTillNextCycleStart_in_ms, CycleNumber, CycleNumberFromCADataRead, CycleNrFromBuffer);
//If new cycle data wasn't transmitted in time, the cycle might have been executed twice and we have data from the repeated cycle. This should normally not happen.
unsigned long GetDataWhileLoopCount = 0;
constexpr unsigned long MaxGetDataWhileLoopCount = 20;
while (CA.DataAvailable(/* timeout_in_seconds*/ 0) && Cycling && LittleCycle && (GetDataWhileLoopCount<MaxGetDataWhileLoopCount)) {
//This should be a repeat of the cycle we just read out, i.e. same cycle number.
//If it's not a repeat, then even more went wrong and GetCycleData will detect that the expected cycle number doesn't match
//the cycle number stored in the input data.
SetStatusTextAndLog("warning: more data available [" + QString::number(GetDataWhileLoopCount) + "]");
GetCycleData(take_photodiode_data, TimeTillNextCycleStart_in_ms, CycleNumber, CycleNumberFromCADataRead, CycleNrFromBuffer);//, LastCycleEndTime);//, /*timeout_in_seconds*/ MaxSequenceDuration_in_s);
GetDataWhileLoopCount++;
}
if (GetDataWhileLoopCount == MaxGetDataWhileLoopCount) {
SetStatusTextAndLog("error: GetDataWhileLoopCount == MaxGetDataWhileLoopCount");
CycleSuccessful = false;
}
//get number of next cycle (just in case too much time elapsed and the DLL did run multiple identical cycles)
if (!CA.GetNextCycleStartTimeAndNumber(TimeTillNextCycleStart_in_ms, NextCycleNumber, /* timeout_in_seconds*/ MaxSequenceDuration_in_s + 1000)) {
MessageBox("Couldn't get next cycle number (2)");
return TerminateCycling(false);
}
CycleNumber = NextCycleNumber;
//Next you can specify updated commands for the next several cycles
//You can schedule the replacement of several code lines within one cycle
//A code line can consist of several ;-separated commands
//You can store up to 32 replacement commands.
//This limit can easily be increased by increasing const unsigned int ReplaceCommandListLength = 32; in ControlAPI.h in the Visual C++ Control code
//This update happens during the blue MOT stage.
//NextCycleNumber refers to the cycle that starts with the red MOT stage belonging to the current blue MOT.
CA.ReplaceCommand(NextCycleNumber, ModifyCodeLineNr1, "SetFrequencyBlueMOTDPAOM(201);");
char command[100];
sprintf(command, "WriteInputMemory(%u);", NextCycleNumber);
CA.ReplaceCommand(NextCycleNumber, ModifyCodeLineNr2, command);
//The periodic trigger time has to be set depending on the duration of the preceding sequence.
//Change it if the last sequence had a different duration than the one before that.
if (PeriodicTriggerPeriod_in_ms != NextPeriodicTriggerPeriod_in_ms) {
CA.SetPeriodicTrigger(NextPeriodicTriggerPeriod_in_ms, /*PeriodicTriggerAllowedWaitTime_in_ms*/ SequenceDuration_in_ms + WaitTimeBetweenSequences_in_ms + (NextPeriodicTriggerPeriod_in_ms-StandardPeriodicTriggerPeriod_in_ms));
PeriodicTriggerPeriod_in_ms = NextPeriodicTriggerPeriod_in_ms;
}
//example of alternating between two cycle times
AdditionalCycleTimeStatus = !AdditionalCycleTimeStatus;
double AdditionalCycleTime = (AdditionalCycleTimeStatus) ? 0 : 300;
sprintf(command, "Wait(%u);", AdditionalCycleTime);
CA.ReplaceCommand(NextCycleNumber, ModifyCodeLineNr3, command);
//The next periodic trigger time depends on the duration of the current sequence.
//It will be transferred to the FPGA after the next cycle has ended, i.e. during the blue MOT time of the next cycle.
//This guarantees that the blue MOT time is always the same, independent of the duration of the sequence.
NextPeriodicTriggerPeriod_in_ms = StandardPeriodicTriggerPeriod_in_ms + AdditionalCycleTime;
//you can also update commands of cycles that lie more than one in the future
//CA.ReplaceCommand(NextCycleNumber+2, ModifyCodeLineNr1, "SetFrequencyBlueMOTDPAOM(202);");
//the new sequence parameters have been sent. We can trigger the transfer of the next sequence to the FPGA.
//Triggering is optional, as it will also happen in an OnIdle call once the soft pre trigger time is reached.
//However, its better to trigger as early as possible, in order to have enough time for the transfer before the next cycle must start.
CA.Trigger();
if (CycleSuccessful) {
QTime now = QTime::currentTime();
LastSuccessfulCycleEndTimeSinceMidnight = QTime(0, 0).msecsTo(now);
NumberOfFailedRunsSinceLastSuccessfulRun=0;
} else {
NumberOfTimesFailedRun++;
NumberOfFailedRunsSinceLastSuccessfulRun++;
if (NumberOfFailedRunsSinceLastSuccessfulRun>20) {
LittleCycle = false; //something went quite wrong -> start cycling from scratch
TerminateCycling(true);
Cycling = true;
NumberOfFailedRunsSinceLastSuccessfulRun = 0;
SetStatusTextAndLog("error: NumberOfFailedRunsSinceLastSuccessfulRun > 20; terminating little cycle");
}
}
}
if (LittleCycle) { //if LittleCycle is false, TerminateCycling was already executed.
//the "while (Cycling && LittleCycle)" loop was broken by (Cycling == false)
//bool aCycling = Cycling;
ret = TerminateCycling(true);
//Cycling = aCycling;
} else ret = true;
}
Cycling = false;
return ret;
}
//
//debug tool
//
//If you have problems with TCPIP use this routine to intensely test the connection
bool TestTCPIP() {
// CA.SwitchDebugMode(/*On*/ true);
long CycleNumber;
double MaxSequenceDuration_in_s = 10;
while (true) {
TelnetTester->setStatusText("/", true, false);
Sleep_ms_and_call_CA_OnIdle(10); //wait for 10ms
if (!CA.IsCycling(/* timeout_in_seconds*/ MaxSequenceDuration_in_s)) {
}
Sleep_ms_and_call_CA_OnIdle(10); //wait for 10ms
TelnetTester->setStatusText("|", true, false);
if (!CA.CheckIfLowLevelSoftwareReady()) {
}
Sleep_ms_and_call_CA_OnIdle(10); //wait for 10ms
if (!CA.CheckIfSequencerReady()) {
}
TelnetTester->setStatusText("\\", true, false);
}
return true;
}