Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
main.cpp
00001 #include "mbed.h" 00002 00003 //set up the message buffer to be filled by the GPS read process 00004 #define MODSERIAL_DEFAULT_RX_BUFFER_SIZE 256 00005 00006 #include "MODSERIAL.h" 00007 #include "SDFileSystem.h" //imported using the import utility 00008 //#include "rtos.h" 00009 #include "OEM615.h" 00010 00011 #include "ADIS16488.h" 00012 #include <string> 00013 00014 #define STATUS_MSG 0 00015 #define POSVEL_MSG 1 00016 #define STARTDATA_MSG 2 00017 #define STOPDATA_MSG 3 00018 #define STARTSTREAM_MSG 4 00019 #define STOPSTREAM_MSG 5 00020 #define STARTLOGINFO_MSG 6 00021 #define STOPLOGINFO_MSG 7 00022 #define DEGREES_TO_RADIANS (3.14519/180.0) 00023 00024 //general items for this application 00025 //SDFileSystem(PinName mosi, PinName miso, PinName sclk, PinName cs, const char* name); 00026 SDFileSystem sd(p11,p12,p13,p14,"sd"); 00027 //Serial debug(USBTX, USBRX); // tx, rx USB communication to the PC for debug purposes 00028 DigitalOut ppsled(LED1); 00029 DigitalOut trig1led(LED2); 00030 DigitalOut recordDataled(LED4); 00031 InterruptIn camera1Int(p30); 00032 DigitalOut camera2Pin(p29); 00033 //USB serial data stream back to the PC 00034 Serial toPC(USBTX, USBRX); //connect the GPS TX, RX to p9 and p10 00035 00036 bool detectedGPS1PPS = false; 00037 bool recordData = false; 00038 int PPSCounter = 0; 00039 int byteCounter = 0; 00040 unsigned short perSecMessageCounter=0; 00041 bool lookingForMessages = true; 00042 bool messageDetected = false; 00043 int savedIMUClockCounter=0; 00044 int IMUClockCounter = 0; 00045 bool camera1EventDetected = false; 00046 double camera1Time; 00047 char serBuf[128]; 00048 int serBufChars=0; 00049 bool sendPosVel=false; 00050 bool sendStatus=false; 00051 bool sendRecData=false; 00052 bool streamPos=false; 00053 bool sendStreamPos=false; 00054 bool logMsgInfo=false; 00055 bool sendLogMsgInfo=false; 00056 00057 00058 //ISR for detection of the GPS 1PPS 00059 void detect1PPSISR(void) 00060 { 00061 timeFromPPS.reset(); 00062 savedIMUClockCounter = IMUClockCounter; 00063 IMUClockCounter = 0; 00064 GPS_COM1.rxBufferFlush(); 00065 00066 detectedGPS1PPS = true; 00067 lookingForMessages = true; 00068 PPSCounter++; 00069 PPSTimeOffset++; 00070 ppsled = !ppsled; 00071 }; 00072 00073 //ISR for detection of the hotshoe trigger 1 00074 void camera1ISR(void) 00075 { 00076 camera1Time = GPSTime + (double)PPSTimeOffset + timeFromPPS.read(); 00077 trig1led = !trig1led; 00078 camera1EventDetected = true; 00079 }; 00080 00081 void readFromPC() 00082 { 00083 if (toPC.readable()) //read a PC serial byte and test it for a command 00084 { 00085 // Read in next character 00086 char inChar = toPC.getc(); 00087 serBuf[serBufChars++] = inChar; 00088 // Append end of string character 00089 serBuf[serBufChars] = '\0'; 00090 // Need to parse message to determine behavior 00091 // Need to clean this up 00092 char msgList[8][32]; 00093 sprintf(msgList[STATUS_MSG], "WMsg STATUS"); 00094 sprintf(msgList[POSVEL_MSG], "WMsg POSVEL"); 00095 sprintf(msgList[STARTDATA_MSG], "WMsg RECORDDATA Y"); 00096 sprintf(msgList[STOPDATA_MSG], "WMsg RECORDDATA N"); 00097 sprintf(msgList[STARTSTREAM_MSG], "WMsg POSSTREAM Y"); 00098 sprintf(msgList[STOPSTREAM_MSG], "WMsg POSSTREAM N"); 00099 sprintf(msgList[STARTLOGINFO_MSG], "WMsg LOGINFO Y"); 00100 sprintf(msgList[STOPLOGINFO_MSG], "WMsg LOGINFO N"); 00101 // assume an invalid message which needs to be reset 00102 bool validMessage = false; 00103 bool resetMessage = true; 00104 // Check for valid message 00105 for (int m = 0; m < 8 && !validMessage; m++) 00106 { 00107 if (strncmp(serBuf, msgList[m], serBufChars) == 0) 00108 { 00109 validMessage = true; 00110 // buffer length is same as message length 00111 if (serBufChars == strlen(msgList[m])) 00112 { 00113 switch(m) 00114 { 00115 case STATUS_MSG: 00116 sendStatus = true; 00117 break; 00118 case POSVEL_MSG: 00119 sendPosVel = true; 00120 break; 00121 case STARTDATA_MSG: 00122 case STOPDATA_MSG: 00123 recordData = (m == STARTDATA_MSG); 00124 sendRecData = true; 00125 break; 00126 case STARTSTREAM_MSG: 00127 case STOPSTREAM_MSG: 00128 streamPos = (m == STARTSTREAM_MSG); 00129 sendStreamPos = true; 00130 break; 00131 case STARTLOGINFO_MSG: 00132 case STOPLOGINFO_MSG: 00133 logMsgInfo = (m == STARTLOGINFO_MSG); 00134 sendLogMsgInfo = true; 00135 break; 00136 00137 } 00138 } 00139 // message is still in progress, do not reset 00140 else 00141 { 00142 resetMessage = false; 00143 } 00144 } 00145 } 00146 // if message should be reset 00147 if (resetMessage) 00148 { 00149 // reset serial buffer character count 00150 serBufChars = 0; 00151 // if invalid message and most recent character is 'W' (first message character), 00152 // possible message collision 00153 if ((!validMessage) && (inChar == 'W')) 00154 { 00155 // start a new message 00156 serBuf[serBufChars++] = inChar; 00157 serBuf[serBufChars] == '\0'; 00158 } 00159 // Append end of string character 00160 serBuf[serBufChars] = '\0'; 00161 } 00162 } 00163 }; 00164 00165 void sendASCII(char* ASCI_message, int numChars) 00166 { 00167 //char ASCI_message[] = "unlogall COM1"; 00168 int as = numChars - 1; 00169 unsigned char CR = 0x0d; //ASCII Carriage Return 00170 unsigned char LF = 0x0a; //ASCII Line Feed 00171 00172 //printf("%s", ch); 00173 //printf("\n"); 00174 00175 for (int i=0; i<as; i++) GPS_COM1.putc(ASCI_message[i]); 00176 GPS_COM1.putc(CR); //carriage return at end 00177 GPS_COM1.putc(LF); //line feed at end 00178 }; 00179 00180 //FILE* fp = NULL; 00181 void setupCOM(void) 00182 { 00183 //system starts with GPS in reset active 00184 //dis-engage the reset to get the GPS started 00185 GPS_Reset=1; wait_ms(1000); 00186 00187 //establish 1PPS ISR 00188 PPSInt.rise(&detect1PPSISR); 00189 00190 //set the USB serial data rate -- rate must be matched at the PC end 00191 //This the serial communication back to the the PC host 00192 //Launch the C++ serial port read program there to catch the ASCII characters 00193 //toPC.baud(9600); wait_ms(100); 00194 toPC.baud(8*115200); wait_ms(100); 00195 //toPC.baud(1*115200); wait_ms(100); 00196 toPC.printf("\n\n released GPS from RESET and set to high baud rate \n\n"); 00197 00198 //just wait to lauinch the GPS receiver 00199 for (int i=0; i<5; i++) { toPC.printf(" to start: %3d \n", 4-i); wait(1); } 00200 00201 00202 mkdir("/sd/Data", 0777); 00203 00204 /* 00205 fp = fopen("/sd/Data/IMUGPS.bin", "wb"); 00206 if (fp == NULL) 00207 { 00208 toPC.printf(" cannot open the IMUGPS data file \n"); 00209 } 00210 else 00211 toPC.printf(" opened the IMUGPS data file \n"); 00212 */ 00213 00214 //this is the COM1 port from th GPS receiuver to the mbed 00215 //it should be always started at 9600 baud because thats the default for the GPS receiver 00216 GPS_COM1.baud(9600); wait_ms(100); 00217 00218 // this ASCII command sets up the serial data from the GPS receiver on its COM1 00219 char ch7[] = "serialconfig COM1 9600 n 8 1 n off"; 00220 // this is a software reset and has the same effect as a hardware reset (why do it??) 00221 char ch0[] = "RESET"; 00222 //this command stops all communication from the GPS receiver on COM1 00223 //logs should still be presented on USB port so the CDU can be used in parallel 00224 char ch1[] = "unlogall COM1"; 00225 //set the final baud rate that we will use from here 00226 //allowable baud rate values: 9600 115200 230400 460800 921600 00227 char ch2[] = "serialconfig COM1 921600 n 8 1 n off"; 00228 00229 //the below commands request the POS, VEL, RANGE, and TIME messages 00230 char ch3[] = "log COM1 BESTPOSB ONTIME 1"; //messageID = 42 00231 char ch4[] = "log COM1 BESTVelB ONTIME 1"; //messageID = 99 00232 char ch5[] = "log COM1 RANGEB ONTIME 1"; //messageID = 43 00233 char ch6[] = "log COM1 TIMEB ONTIME 1"; //messageID = 101 00234 00235 //set up VARF to be 100Hz with 1X10^4 * 10^-8 = 10^-4 sec (10usec) pulse width 00236 char ch8[] = "FREQUENCYOUT enable 10000 1000000"; 00237 00238 toPC.printf("set serial config \n"); 00239 sendASCII(ch7, sizeof(ch7)); wait_ms(500); 00240 //sendASCII(ch0, sizeof(ch0)); 00241 toPC.printf("unlog all messages \n"); 00242 sendASCII(ch1, sizeof(ch1)); wait_ms(500); 00243 toPC.printf("log BESTPOSB on COM1 \n"); 00244 sendASCII(ch3, sizeof(ch3)); wait_ms(500); 00245 toPC.printf("log BESTVELB on COM1\n"); 00246 sendASCII(ch4, sizeof(ch4)); wait_ms(500); 00247 toPC.printf("log RANGEB on COM1\n"); 00248 sendASCII(ch5, sizeof(ch5)); wait_ms(500); 00249 00250 //toPC.printf("log TIMEB om COM1 \n"); 00251 //sendASCII(ch6, sizeof(ch6)); wait_ms(100); 00252 00253 toPC.printf(" set up th VARF signal \n"); 00254 sendASCII(ch8, sizeof(ch8)); wait_ms(500); 00255 00256 //set GPS output COM1 to the final high rate 00257 toPC.printf("set the COM ports to high rate\n"); 00258 sendASCII(ch2, sizeof(ch2)); wait_ms(500); 00259 00260 //set the mbed COM port to match the GPS transmit rate 00261 //the below baud rate must match the COM1 rate coming from the GPS receiver 00262 GPS_COM1.baud(921600); wait_ms(500); //without this wait -- the baud rate is not detected when using MODSERIAL 00263 }; 00264 00265 void setupTriggers() 00266 { 00267 camera1Int.mode(PullUp); 00268 camera2Pin = 1; 00269 //establish Trigger ISR 00270 camera1Int.rise(&camera1ISR); 00271 00272 }; 00273 00274 int test = 0; 00275 unsigned short messageCounter = 0; 00276 unsigned short savedMessageCounter = 0; 00277 unsigned char msgBuffer[1536]; 00278 unsigned short messageLocation[6] = {0}; 00279 00280 //see the mbed COOKBOOK for MODSERIAL 00281 //MODSERIAL is an easy to use library that extends Serial to add fully buffered input and output. 00282 void readSerialByte(MODSERIAL_IRQ_INFO *q) 00283 { 00284 MODSERIAL *serial = q->serial; 00285 unsigned char synch0 = serial->getc(); 00286 msgBuffer[byteCounter] = synch0; 00287 00288 //we need to trap the GPS message header byte-string 0xAA44121C 00289 //generate a 4-byte sliding-window sequence from the input bytes 00290 //shift last 4-byte value left 8 bits & push recently read byte (synch0) into low-order byte 00291 test = (test<<8) | synch0; // 00292 00293 if (test == 0xAA44121C) //test for the Receiver message header signature 00294 { 00295 messageLocation[perSecMessageCounter] = byteCounter-3; //store the location of this message (with 4 synch words) 00296 perSecMessageCounter++; 00297 messageDetected = true; 00298 } 00299 byteCounter++; //total per-sec byte counter (reset to zero in main when 1PPS detected) 00300 00301 }; 00302 00303 int main() { 00304 00305 //FILE *fpIMU = NULL; 00306 //FILE *fpGPS = NULL; 00307 FILE *fpNav = NULL; 00308 OEM615BESTPOS posMsg; 00309 OEM615BESTPOS curPos; 00310 OEM615BESTVEL velMsg; 00311 OEM615BESTVEL curVel; 00312 int msgLen; 00313 int msgEnd; 00314 00315 //set up the GPS and mbed COM ports 00316 setupCOM(); 00317 00318 //set up the ADIS16488 00319 setupADIS(); 00320 00321 //setup Hotshoe 00322 setupTriggers(); 00323 //attempt to use the mbed RTOS library 00324 //Thread thread(writeThread); 00325 00326 toPC.printf("completed setting up COM ports \n"); 00327 00328 //set up the interrupt to catch the serial byte availability 00329 GPS_COM1.attach(&readSerialByte, MODSERIAL::RxIrq); 00330 00331 timeFromPPS.start(); 00332 00333 //while(PPSCounter < 1000) 00334 while(1) 00335 { 00336 //read the USB serial data from the PC to check for commands 00337 readFromPC(); 00338 00339 if (sendPosVel) 00340 { 00341 sendPosVel=false; 00342 char solReady = 'N'; 00343 if (posMsg.solStatus == 0) 00344 { 00345 solReady = 'Y'; 00346 } 00347 double nVel = velMsg.horizontalSpeed*cos(velMsg.heading*DEGREES_TO_RADIANS); 00348 double eVel = velMsg.horizontalSpeed*sin(velMsg.heading*DEGREES_TO_RADIANS); 00349 // For the 1 second deltas with which we are dealing 00350 // this calculation should be close enough for now 00351 // approximately 1 nautical mile / minute latitude, 60 minutes/degree, 1852 meters/nautical mile 00352 double latMetersPerDeg = 60.0*1852.0; 00353 // longitude separation is approximately equal to latitude separation * cosine of latitude 00354 double lonMetersPerDeg = latMetersPerDeg*cos(posMsg.latitude*DEGREES_TO_RADIANS); 00355 00356 // Elapsed time since last known GPS position 00357 double elTime = (double)PPSTimeOffset + timeFromPPS.read(); 00358 // Position time 00359 double posTime = GPSTime + elTime; 00360 00361 // Estimated position based on previous position and velocity 00362 double latPos = posMsg.latitude + (nVel/latMetersPerDeg)*elTime; 00363 double lonPos = posMsg.longitude + (eVel/lonMetersPerDeg)*elTime; 00364 double htPos = posMsg.height + velMsg.verticalSpeed/(60*1852)*elTime; 00365 toPC.printf("WMsg POSVEL %5.3lf %d %c %8.5lf %9.5lf %4.3lf %4.3lf %4.3lf %4.3lf\n", 00366 posTime, 00367 posMsg.numSolSV, 00368 solReady, 00369 latPos, 00370 lonPos, 00371 htPos, 00372 nVel, 00373 eVel, 00374 velMsg.verticalSpeed 00375 ); 00376 } 00377 if (sendStatus) 00378 { 00379 sendStatus=false; 00380 char solReady = 'N'; 00381 if (posMsg.solStatus == 0) 00382 { 00383 solReady = 'Y'; 00384 } 00385 toPC.printf("WMsg STATUS %5.3lf %c\n", 00386 GPSTime, 00387 solReady 00388 ); 00389 } 00390 if (sendRecData) 00391 { 00392 sendRecData=false; 00393 char recChar = 'N'; 00394 if (recordData) 00395 { 00396 if ((fpNav == NULL)) 00397 { 00398 fpNav = fopen("/sd/Data/NAV.bin", "wb"); 00399 } 00400 if (fpNav != NULL) 00401 { 00402 recChar = 'Y'; 00403 } 00404 //recChar = 'Y'; 00405 } 00406 else 00407 { 00408 if (fpNav != NULL) 00409 { 00410 fclose(fpNav); 00411 fpNav = NULL; 00412 } 00413 /* 00414 if (fpIMU != NULL) 00415 { 00416 fclose(fpIMU); 00417 fpIMU = NULL; 00418 } 00419 if (fpGPS != NULL) 00420 { 00421 fclose(fpGPS); 00422 fpGPS = NULL; 00423 } 00424 */ 00425 } 00426 toPC.printf("WMsg RECORDDATA %c\n", 00427 recChar 00428 ); 00429 } 00430 recordDataled = recordData; 00431 if (sendStreamPos) 00432 { 00433 sendStreamPos=false; 00434 char streamChar = 'N'; 00435 if (streamPos) 00436 { 00437 streamChar = 'Y'; 00438 } 00439 toPC.printf("WMsg POSSTREAM %c\n", 00440 streamChar 00441 ); 00442 } 00443 if (sendLogMsgInfo) 00444 { 00445 sendLogMsgInfo=false; 00446 char logChar = 'N'; 00447 if (logMsgInfo) 00448 { 00449 logChar = 'Y'; 00450 } 00451 toPC.printf("WMsg LOGINFO %c\n", 00452 logChar 00453 ); 00454 } 00455 if (IMUDataReady) 00456 { 00457 //write the IMU data 00458 //if (recordData && (fpIMU != NULL)) 00459 if (recordData && (fpNav != NULL)) 00460 { 00461 fwrite(imuRec.dataWord, sizeof(IMUREC), 1, fpNav); 00462 } 00463 IMUDataReady = false; 00464 } 00465 //if (lookingForMessages && (timeFromPPS.read() > 0.100)) //it takes less than 20msec to receive all messages 00466 { 00467 00468 //toPC.printf(" num messages = %3d time = %5d \n", perSecMessageCounter, timeFromPPS.read_us()); 00469 for (int i=0; i<perSecMessageCounter; i++) 00470 { 00471 // ensure at message header has arrived before processing 00472 if (byteCounter < (messageLocation[i] + sizeof(MESSAGEHEADER))) 00473 { 00474 // Complete message header has not been received. 00475 // Clear processed messages and break out of message processing loop 00476 for (int j = 0; j < i; j++) 00477 { 00478 messageLocation[j] = messageLocation[i+j]; 00479 perSecMessageCounter--; 00480 } 00481 break; 00482 } 00483 msgHeader[i] = (MESSAGEHEADER*)&msgBuffer[messageLocation[i]]; 00484 // Ensure complete message has been received 00485 // Message length is header length + message length + CRC 00486 msgLen = 28; 00487 switch (msgHeader[i]->messageID) 00488 { 00489 case 42: 00490 msgLen = 104; 00491 break; 00492 case 99: 00493 msgLen = 76; 00494 break; 00495 default: 00496 msgLen = msgHeader[i]->headerLength + msgHeader[i]->messageLength + sizeof(unsigned long); 00497 break; 00498 00499 } 00500 msgLen = msgHeader[i]->headerLength + msgHeader[i]->messageLength + sizeof(unsigned long); 00501 // Find last byte position of message 00502 msgEnd = messageLocation[i] + msgLen; 00503 if (byteCounter < msgEnd) 00504 { 00505 // Complete message has not been received. 00506 // Clear processed messages and break out of message processing loop 00507 for (int j = 0; j < i; j++) 00508 { 00509 messageLocation[j] = messageLocation[i+j]; 00510 perSecMessageCounter--; 00511 } 00512 break; 00513 } 00514 else if ((i < (perSecMessageCounter-1)) && 00515 (messageLocation[i+i] < msgEnd)) // ignore CRC for now 00516 { 00517 // Next message was started before this mesage was completely received. 00518 // Ignore and continue on to the next message 00519 continue; 00520 } 00521 if (logMsgInfo) 00522 { 00523 toPC.printf("WMsg MESSAGEINFO %5d %5d %5d %5d %5d = %5d (%5d)\n", 00524 msgHeader[i]->messageID, 00525 messageLocation[i], 00526 msgHeader[i]->headerLength, 00527 msgHeader[i]->messageLength, 00528 sizeof(unsigned long), 00529 msgEnd, 00530 byteCounter); 00531 } 00532 //toPC.printf(" %5d ", msgHeader[i]->messageID); 00533 if ((msgHeader[i]->messageID == 42) || 00534 (msgHeader[i]->messageID == 99)) 00535 { 00536 if (msgHeader[i]->messageID == 42) 00537 { 00538 // Wait until velocity message has also been received before using as 00539 // base position 00540 //memcpy(&curPos, &msgBuffer[messageLocation[i]], sizeof(OEM615BESTPOS)); 00541 curPos = *((OEM615BESTPOS*)&msgBuffer[messageLocation[i]]); 00542 if (streamPos) 00543 { 00544 toPC.printf("WMsg BESTPOS %d %d %d %8.5lf %9.5lf %5.3lf %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f %d %d %d %d %d\n", 00545 curPos.msgHeader.GPSTime_msecs, 00546 curPos.solStatus, 00547 curPos.posType, 00548 curPos.latitude, 00549 curPos.longitude, 00550 curPos.height, 00551 curPos.undulation, 00552 curPos.latitudeSTD, 00553 curPos.longitudeSTD, 00554 curPos.heightSTD, 00555 curPos.diffAge, 00556 curPos.solutionAge, 00557 curPos.numSV, 00558 curPos.numSolSV, 00559 curPos.numGGL1, 00560 curPos.extSolStatus, 00561 curPos.sigMask); 00562 } 00563 } 00564 else if (msgHeader[i]->messageID == 99) 00565 { 00566 // Wait until position message has also been received before using as 00567 // base position 00568 //memcpy(&curVel, &msgBuffer[messageLocation[i]], sizeof(OEM615BESTVEL)); 00569 curVel = *((OEM615BESTVEL*)&msgBuffer[messageLocation[i]]); 00570 } 00571 if ((curVel.msgHeader.GPSTime_msecs+250)/1000 == 00572 (curPos.msgHeader.GPSTime_msecs+250)/1000) 00573 { 00574 // update position and velocity used for calculation 00575 GPSTimemsecs = curPos.msgHeader.GPSTime_msecs; 00576 GPSTime = (double)GPSTimemsecs/1000.0; 00577 velMsg = curVel; 00578 posMsg = curPos; 00579 PPSTimeOffset = 0; 00580 } 00581 } 00582 if (i == (perSecMessageCounter-1)) 00583 { 00584 if (recordData && (fpNav != NULL)) 00585 { 00586 fwrite(msgBuffer, byteCounter, 1, fpNav); 00587 } 00588 byteCounter = 0; 00589 perSecMessageCounter = 0; 00590 } 00591 } 00592 //toPC.printf(" %3d %8d \n", msgHeader[0]->timeStatus, GPSTimemsecs); 00593 //if (recordData && (fpGPS != NULL)) 00594 00595 /* 00596 if (recordData && (fpNav != NULL)) 00597 { 00598 fwrite(msgBuffer, byteCounter, 1, fpNav); 00599 } 00600 */ 00601 //lookingForMessages = false; 00602 } 00603 if (messageDetected) 00604 { 00605 //toPC.printf(" msgTime = %4d \n", timeFromPPS.read_us()); 00606 messageDetected = false; 00607 } 00608 if (camera1EventDetected) 00609 { 00610 toPC.printf("WMsg TRIGGERTIME %5.3lf\n", camera1Time); 00611 camera1EventDetected = false; 00612 } 00613 if (detectedGPS1PPS) 00614 { 00615 //toPC.printf(" PPSCounter = %4d byteCounter = %10d num Messages Received = %3d IMUClock = %4d\n", 00616 // PPSCounter, byteCounter, perSecMessageCounter, savedIMUClockCounter); 00617 if (recordData && (fpNav != NULL) && (byteCounter > 0)) 00618 { 00619 fwrite(msgBuffer, byteCounter, 1, fpNav); 00620 } 00621 byteCounter = 0; 00622 perSecMessageCounter=0; 00623 detectedGPS1PPS = false; 00624 } 00625 } 00626 toPC.printf(" normal termination \n"); 00627 }
Generated on Tue Jul 12 2022 12:34:50 by
1.7.2