{ ======================================================================================== A I R G U N / R E C O R D I N G S Y N C H R O N I Z A T I O N Tabs : 2 Program : geir.PAS Description : Synchronize airgun and recording system. Shoot on GPS-based time, set shot interval and deep-sea delay of recording system. Based on OBS Master clock synchronization program. GPS PPS pulse assumed to be active high, of TTL level, and of min 1 ms duration. Leading edge marks second boundary. ___ | | ______| |_____ ^ |___ Second boundary Program is tested with Garmin GPS mod 35-HVS. PC ports: LPT1 : Pin 10 (ACK~) = Input, PPS from GPS. The positive edge of a signal on this pin can be used to generate an interrupt (assumed INT7 on the PC PIC-1 (Programmable Interrupt Controller). Pin 2 (D0) = Output, OBS Master Clock calibration signal. Pin 18 = GND COM : Program uses COM2. Use only RXD-pin and GND. NMEA sentence used: GPGGA = Global Positioning System Fix Data $GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh <1> UTC time of position fix, hhmmss format <2> Latitude, ddmm.mmmm format (leading zeros will be transmitted) <3> Latitude hemisphere, N or S <4> Longitude, dddmm.mmmm format (leading zeros will be transmitted) <5> Longitude hemisphere, E or W <6> GPS quality indication, 0 = fix not available, 1 = Non-differential GPS fix available, 2 = Differential GPS (DGPS) fix available <7> Number of satellites in use, 00 to 12 (leading zeros will be transmitted) <8> Horizontal dilution of precision, 0.5 to 99.9 <9> Antenna height above/below mean sea level, -9999.9 to 99999.9 meters <10> Geoidal height, -999.9 to 9999.9 meters <11> Differential GPS (RTCM SC-104) data age, number of seconds since last valid RTCM transmission (null if non-DGPS) <12> Differential Reference Station ID, 0000 to 1023 (leading zeros transmitted, null if non-DGPS) Copyright : University of Bergen, Norway Institute of Solid Earth Physics Language : Turbo Pascal 7.0 History : Rev. Date By Description -------------------------------------------------------- 0.1 23 feb 2001 Geir Main structure - Gunsync 0.2 27 apr 2001 OM Small changes in decriptive text in heading Notes: The program will log the position and shot number after each recording into the filename that you enter when the program starts. When you enter the filename, you should enter the extension as well (.txt) or the program might start to behave strangely. If the system hangs or if power to the computer is somehow lost when the program is running, the log file still remain intact. The file is saved on the hard drive after each recording. This is called flushing. If you get a flush error, it means that something went wrong under the file saving procedure. If you get an error at this point, the program will proably quit, leaving the log file on the hard drive. Do not enter spaces before the information you enter in the first stage of the program, it will cause errors. If you make any mistakes in this stage, you can select 'no' at the 'is this correct?' question and you will be allowed to enter the text again. To quit when the program is running, type quit in the main window. -Geir ======================================================================================== } PROGRAM OBS_Sync; Uses Crt, DOS; { ================================================================= C o n s t a n t s ================================================================= } CONST GUNCO_CLOSURE = $01; GUNCO_FIRE = $02; RECORDING_TRIGGER = $04; NMEA_FIELD_SEPERATOR = ','; NMEA_FAILURE = '|'; CR = #$0D; LF = #$0A; COM1_BIOS_OFFSET = $400; COM2_BIOS_OFFSET = $402; LPT1_BIOS_OFFSET = $408; LINE_STATUS_REG = 05; {COM UART Line Status Register offset from base adr.} LINE_CONTROL_REG = 03; {COM UART Line Control Register offset from base adr.} COM_1 = $0000; COM_2 = $0001; Baud9600 = $E0; Baud4800 = $C0; NoParity = $00; OddParity = $08; EvenParity = $18; OneStopBit = $00; TwoStopBits = $04; Len7Bits = $02; Len8Bits = $03; TimerBaseFreq = 1193180; {CLK frekvens PC Timer chip 8254} (* FLUSHRATE = 10000; { The interval at which the log file will be flushed in MS. Not used in last version. } *) { ================================================================= V a r i a b l e s ================================================================= } VAR OldExitProc : Pointer; OldLpt1_Vector : Pointer; OldTimer_Vector : Pointer; LPT1_data : Byte; LPT1_data_adr : Word; LPT1_ctrl_adr : Word; COM_base_adr : Word; UartLineStatus : Byte; UartDataIn : Byte; Seconds : Byte; nmea : string; Gps_Nmea : String; No_of_satellites: string; PPS_occured : Boolean; SecondsFlag : Boolean; MilliSec : Integer; TickFreq : Integer; Timer0_Value : Integer; FireInterval : Integer; { Delay between the shots } RecordingDelay : Integer; { Delay between fireing and recording } FireIntervalMS : Integer; { The fire delay converted to milliseconds } PassedMS : Integer; { Amount of passed milliseconds, used to keep track of the fireing and recording sequences. } FireFlag : Boolean; { Used to check if the cannons should be fired. (Naturally, we don't want the cannons to fire 1000 times every second...) } RecordFlag : Boolean; { Same as above. Used to check if the recording should start. } HasFiredOnce : Boolean; { Used to make sure that the system won't start recording before a shot has been fired. When the first FireInterval time delay has been passed, this flag is set to False, which will allow the program to start recording. } LogFlag : Boolean; { This flag is used to control when to log coordinates and number of shots into a file. } FiredShots : LongInt; { The amount of fired shots. The program cannot fire more than 2 147 483 647 shots in one session. With 20 seconds between each shot, if would take around 14 years to fire that many shots. } GuncoMSCount : Integer; { This value is used in the Gunco procedure to wait 30 ms. } GuncoCount : Boolean; { This flag is used to tell the Timer_Interrupt procedure when to count GuncoMSCounter. } FileName : String; { The log file's file name } LoggFile : Text; { The log file itself. } FileIsOpen : Boolean; { Is the log file open? Used to decide when to close the file. } { The following variables are used to store data that will be logged into the log file. } Latitude : String; { Used to log the position data to the file. } NorS : String; { Same as above. This is the north or south hemisphere variable. } Longitude : String; { Same as above. } EorW : String; { Same as abobe. This is the east or west hemisphere variable. } UTCTime : String; { Same as above. UTC time. } FlushErrors: Integer; { This is used to keep track of the file flush error count. } { The following flush values are not used in the latest version. } (* FlushMS: LongInt; { The file should be flushed every X milliseconds. This value is the counter. } FlushFlag: Boolean; { Should the file be flushed? } *) AllowExit: Boolean; { This is set to true if the user has tapped. } QuitState: Integer; { This is used to keep track of the user's Q-U-I-T-typing } ThisKey: Integer; { Same as above. This is the character's ASCII number. } { ================================================================= P r o c e d u r e s / f u n c t i o n s ================================================================= } PROCEDURE Recording; { This procedure uses the same delay system as the Gunco procedure. Do not use this procedure and the Gunco procedure at the same time. } BEGIN LPT1_data := LPT1_data OR RECORDING_TRIGGER; (* ON *) port[LPT1_data_adr] := LPT1_data; GuncoCount := True; { Activate the flag. } Repeat Until GuncoMSCount = 10; { Wait for 10 ms. } GuncoMSCount := 0; { Reset counter. } GuncoCount := False; LPT1_data := LPT1_data XOR RECORDING_TRIGGER; (* wait 10 ms, OFF *) port[LPT1_data_adr] := LPT1_data; END; { Recording } PROCEDURE AppendFile; { This procedure updates the log file. The file is closed & saved and then reopened. } VAR ErrorCode: Integer; BEGIN ErrorCode := 0; { Reset, so we won't get the same error message twice } Close(LoggFile); { Save & close the log file} Assign(LoggFile, FileName); { Reassign the file name to the file } Append(LoggFile); { Reopen & make ready to recieve more data } IF ErrorCode <> 0 THEN { Error! } BEGIN INC(FlushErrors); GotoXY(1,25); TextColor(14); { Errors are yellow } Write('Error during file flushing! Flush errors: ',FlushErrors); TextColor(7); { Reset text color } END; GotoXY(40,23); { Reset the cursor } END; { AppendFile } (* This procedure did not work too well, and is therefore removed. It is still here just in case anyone needs it. The AppendFile procedure is used instead. Since it does not need to write the whole file into another file, it can be called more often. PROCEDURE FlushFile; { This procedure flushes the log file.(Takes a backup) } VAR Buffer : Text; OneLine : String[35]; ErrorCode : Integer; BEGIN Close(LoggFile); { Close the file. } Assign(LoggFile, FileName); { Reassign the LoggFile variable to FileName } Reset(LoggFile); { Open it again. } IF IOResult <> 0 THEN { If an error occurred } BEGIN INC(FlushErrors); GotoXY(1,25); TextColor(14); Write('Error during file flushing! Flush errors: ',FlushErrors); END; Assign(Buffer, 'backup.txt'); { Assign the buffer file } Rewrite(Buffer); WHILE NOT Eof(LoggFile) DO { This loop writes all the data to the buffer file } BEGIN ReadLn(LoggFile, OneLine); WriteLn(Buffer, OneLine); END; Rewrite(LoggFile); { Empty the file. It should be ready to be filled again. } Reset(Buffer); { Open the buffer file } WHILE NOT Eof(Buffer) DO { This loop rewrites the buffer data into the log file. Please note that since the log file is not closed, it will not be saved. } BEGIN ReadLn(Buffer, OneLine); WriteLn(LoggFile, OneLine); END; { The file is now saved to disk. } Close(Buffer); { Close the buffer file } IF ErrorCode <> 0 THEN { If error... } BEGIN INC(FlushErrors); GotoXY(1,25); TextColor(14); Write('Error during file flushing! Flush errors: ',FlushErrors); TextColor(7); END; GotoXY(40,23); { Reset the cursor } END; { FlushFile } *) PROCEDURE InitLogFile; { This procedure initializes the log file. } VAR ErrorCode : Integer; A : Char; { Answer } BEGIN { Create/load file } ErrorCode := 0; { Reset this, just in case } Assign(LoggFile, FileName); {$i-} Rewrite(LoggFile); {$i+} FileIsOpen := True; IF ErrorCode <> 0 THEN { Error! } BEGIN TextColor(14); { This is an error. Text goes yellow. } WriteLn('Error during log file creation. Log file may not be saved'); WriteLn('correctly. Do you wish to continue, retry or quit?'); Write('(C/R/Q)'); ReadLn(A); CASE A OF 'Q', 'q' : BEGIN TextColor(7); { Reset color } Halt; { Quit } END; 'C', 'c' : BEGIN END;{ Do nothing } 'R', 'r' : BEGIN TextColor(9); { Reset text color } InitLogFile; { Retry } Exit; { Break from the current procedure } END; ELSE { If none of these, just retry. } BEGIN TextColor(9); { Reset text color } InitLogFile; Exit; { Break from the current procedure } END; END; { CASE-OF-ELSE } END; { IF-THEN-ELSE } END; { InitLogFile } FUNCTION FileExists( Var f: Text) : Boolean; { This function is copied from 'Mastering Turbo Pascal 5.5' } BEGIN Assign(F, FileName); {$I-} Reset(F); {$I+} if IOResult = 0 then { NOTE: This picks up ANY errors in the file reading. } FileExists := True END; { FileExists } procedure Gunco; begin LPT1_data := LPT1_data OR GUNCO_CLOSURE; (* closure to gunco ON *) port[LPT1_data_adr] := LPT1_data; GuncoCount := True; { Activate the flag. } Repeat Until GuncoMSCount = 30; { Wait for 30 ms. } GuncoMSCount := 0; { Reset counter. } GuncoCount := False; LPT1_data := LPT1_data XOR GUNCO_CLOSURE; (* wait 30 ms, then closure OFF *) port[LPT1_data_adr] := LPT1_data; GuncoCount := True; { Activate the flag. } Repeat Until GuncoMSCount = 30; { Wait for 30 ms. } GuncoMSCount := 0; { Reset counter. } GuncoCount := False; LPT1_data := LPT1_data OR GUNCO_FIRE; (* fire to gunco ON *) port[LPT1_data_adr] := LPT1_data; GuncoCount := True; { Activate the flag. } Repeat Until GuncoMSCount = 30; { Wait for 30 ms. } GuncoMSCount := 0; { Reset counter. } GuncoCount := False; LPT1_data := LPT1_data XOR GUNCO_FIRE; (* wait 30 ms, then fire OFF *) port[LPT1_data_adr] := LPT1_data; end; {NMEA parsing function. If target field is empty, e.g. when targeting field no. 1 in 'GPGGA,,,', function will return empty string. An error code is produced if: - If NMEA code does not match - If there are not sufficient NMEA separators - If first char in NMEA sentence is not dollar sign NMEA code is e.g. 'GPGGA'. } FUNCTION NMEA_parsing(NMEA_code, NMEA_sentence : string; NMEA_field_no : integer) : string; VAR NMEA_field_counter : integer; NMEA_field_found : Boolean; Match_found : Boolean; k : Integer; s : string; BEGIN {-- Variable initialization} NMEA_field_counter := 0; Match_found := TRUE; NMEA_field_found:= FALSE; NMEA_parsing := NMEA_FAILURE; {-- First check that NMEA sentence starts with dollar sign. Then ensure that sentence is longer than matching pattern plus 2 extra characters (dollar sign and one comma) ---} If (NMEA_sentence[1] <> '$') then Match_found := FALSE; If (ord(NMEA_sentence[0]) <= (ord(NMEA_code[0])+2)) then Match_found := FALSE; {-- Pattern matching check } For k:= 1 to length(NMEA_code) DO begin IF NMEA_code[k] <> NMEA_sentence[k+1] then begin Match_found := FALSE; break; end; end; {If match, locate field ..} IF Match_found then Begin For k:=1 to length(NMEA_sentence) DO begin If (NMEA_sentence[k] = NMEA_FIELD_SEPERATOR) then INC(NMEA_field_counter); If (NMEA_field_counter = NMEA_field_no) then Begin NMEA_field_found:= TRUE; break; end; end; {If field found, extract content of this field } IF NMEA_field_found then begin s := ''; INC(k); While ((NMEA_sentence[k] <> NMEA_FIELD_SEPERATOR) AND (k <= length(NMEA_sentence))) DO begin s := s + NMEA_sentence[k]; INC(k); end; NMEA_parsing := s; end; end; END; PROCEDURE GetFireAndRecordingDelay; VAR A : Char; BEGIN { Show heading first } TextColor(14); { This is yellow, a fit color for the heading } ClrScr; WriteLn; WriteLn(' GUNSYNC '); WriteLn('--------------------------------------------------------------------------------'); WriteLn(' G U N C O N T R O L , R E C O R D I N G C O N T R O L A N D'); WriteLn(' T I M E D E L A Y / I N T E R V A L C O N T R O L P R O G R A M'); WriteLn; WriteLn('--------------------------------------------------------------------------------'); WriteLn; WriteLn(' This program will fire the gun controller at a specific interval and start the'); WriteLn(' recorder after a certain delay after each shot. It needs a GPS that is'); WriteLn(' connected to COM1 to function. It will also log latitude and longitude for'); WriteLn(' each shot in a log file. If the computer crashes while the program is working,'); WriteLn(' the log file will remain intact in the program directory. The file on the'); WriteLn(' hard drive is updated after each recording.'); WriteLn; WriteLn(' The first shot will be fired at the first interval. (If you specify 20 seconds'); WriteLn(' as the fire interval, the program will fire the gun conrolller after 20'); WriteLn(' seconds.)'); WriteLn; WriteLn('--------------------------------------------------------------------------------'); { End of heading part } TextColor(9); { Set the text color to something inspiring, deep blue } WriteLn; Write('Enter the interval between each fired shot in seconds: '); ReadLn(FireInterval); { Error checking } IF FireInterval <= 0 THEN { If the interval is less than 1, there will (naturally) be errors. } BEGIN TextColor(4); { Warnings in red } WriteLn; WriteLn('The fire interval is less than 1 second! The value'); WriteLn('is invalid. Press any key to enter new values.'); Repeat Until KeyPressed; { Wait for keystroke } TextColor(9); { Reset color } GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; { IF-THEN-ELSE } IF FireInterval > 30 THEN { Check if the firing interval is going to be greater than MaxInt. If so, there will be errors. } BEGIN TextColor(4); { Warnings in red } WriteLn; WriteLn('The fire interval is too great! It cannot be over 30'); WriteLn('seconds. Press any key to enter new values.'); Repeat Until KeyPressed; TextColor(9); GetFireAndRecordingDelay; Exit; END; { IF-THEN-ELSE } { End error checking } FireIntervalMS := FireInterval * 1000; { Convert the FireInterval value } Write('Enter delay between the shot and the recoring start in milliseconds: '); ReadLn(RecordingDelay); { This is only milliseconds, so it does not need any processing before use. } { Error checking } IF FireIntervalMS <= RecordingDelay THEN { Check if the recording delay is greater than or equal to the fireing delay.If so, there will be errors. } BEGIN TextColor(4); { Warnings in red } WriteLn; WriteLn('The recording delay is greater than the fireing interval!'); WriteLn('This will cause errors. Press any key to enter new values.'); Repeat Until KeyPressed; TextColor(9); { Reset color to blue } GetFireAndRecordingDelay; Exit; END; { IF-THEN-ELSE } IF RecordingDelay > 29999 THEN { 29999 is the max allowed value here, because of MaxFireInterval. (30) } BEGIN TextColor(4); WriteLn; WriteLn('The recording delay cannot be greater than 29999ms! Press'); WriteLn('any key to enter new values.'); Repeat Until KeyPressed; TextColor(9); { Reset color to blue } GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; { IF-THEN-ELSE } IF RecordingDelay <= 0 THEN { If it is smaller than 1... } BEGIN TextColor(4); WriteLn; WriteLn('The recording delay is less than 1ms! This will cause errors.'); WriteLn('Press any key to enter new values.'); Repeat Until KeyPressed; GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; { IF-THEN-ELSE } { End error checking } Write('Enter the file name of the log file: '); ReadLn(FileName); { Error checking } IF FileName = '' THEN { He must enter a file name! } BEGIN TextColor(4); { Warnings in red } WriteLn('You must specify a file name! Press any key to enter new'); WriteLn('values.'); Repeat Until KeyPressed; TextColor(9); { Reset color to blue } GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; { IF-THEN-ELSE } IF FileExists(LoggFile) = True THEN { If there is a file with the same file name... } BEGIN TextColor(4); { Warnings in red } WriteLn; WriteLn('A file with that name already exists!'); Write('Are you sure you wish to overwrite it? (Y/N)'); ReadLn(A); CASE A OF 'Y', 'y' : TextColor(9); { If yes...reset color to blue and continue! } ELSE BEGIN { If NOT yes... } TextColor(9); { Reset color to blue } GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; END; { CASE-OF-ELSE } END; { IF-THEN-ELSE } { End error checking } WriteLn; WriteLn; TextColor(10); { Color to green } Write('Is this information correct? (Y/N)'); { Check if he is sure... } ReadLn(A); CASE A OF 'Y', 'y': TextColor(9); { If what he entered begins with Y or y, then it means Yes...reset color and continue! } ELSE { If NOT y... } BEGIN TextColor(9); { Reset color to blue } GetFireAndRecordingDelay; Exit; { Break from the current procedure } END; END; { CASE-OF-ELSE } { Reset all the flags } AllowExit := False; HasFiredOnce := False; LogFlag := False; FireFlag := False; RecordFlag := False; FlushErrors := 0; PassedMS := 0; { Done! } END; {GetFireAndRecordingDelay} FUNCTION BCD2Word(Value : Byte) : Word; VAR W : Word; BEGIN W := ((Value AND $F0) shr 4) * 10; {Hi part} W := W + (Value AND $0F); {Lo part} BCD2Word := W; END; FUNCTION Hex2Str(Value : Byte) : String; FUNCTION Digit( n : Byte ) : Char; { For 0 <= n <= 15 } BEGIN IF n < 10 THEN Digit := Chr( ord('0') + n ) ELSE Digit := Chr( ord('A') + ( n - 10 ) ) END; { Digit } BEGIN Hex2Str := Digit(Value DIV 16)+Digit(Value MOD 16); END; FUNCTION BCD2Str(Value : Byte) : String; VAR Kladd : Byte; s1,s2 : String; BEGIN Kladd := (Value AND $F0) shr 4; STR(Kladd,s1); Kladd := Value AND $0F; STR(Kladd,s2); BCD2Str := s1+s2; END; PROCEDURE COM_Init(ComPort: Word; Rate, Parity, Stop, LenBits: Byte); VAR {NOTE: RTS strapped to CTS in conn!!} Reg : Registers; BEGIN WITH Reg DO BEGIN ah := $00; al := Rate+Parity+Stop+LenBits; dx := ComPort; INTR($14, Reg); {Writeln('ah = ', BCD2Str(reg.ah), ' al = ', BCD2Str(reg.al));} END; END; PROCEDURE HardwareSetup; Var b : byte; BEGIN {----- Look in BIOS equipment list to locate LPT1 data port address. LPT1 Control port is assumed to be positioned two units higher in the address space. ----} LPT1_data_adr := memw[0:LPT1_BIOS_OFFSET]; LPT1_ctrl_adr := LPT1_data_adr + 2; {writeln('LPT1 data adr = ', Hex2Str(hi(LPT1_data_adr))+Hex2Str(lo(LPT1_data_adr))+'H');} If (LPT1_data_adr = 0) then begin Writeln('LPT1 port not found in BIOS equipment list.'); Writeln('Check that LPT1 is enabled in BIOS setup.'); Writeln('Press any key to quit.'); end; {----- Look in BIOS equipment list to locate COM port address. ----} COM_base_adr := memw[0:COM2_BIOS_OFFSET]; {Change to use COM1} {writeln('COM base adr = ', Hex2Str(hi(COM_base_adr))+Hex2Str(lo(COM_base_adr))+'H');} If (COM_base_adr = 0) then begin Writeln('COM port not found in BIOS equipment list.'); Writeln('Check that COM1 is enabled in BIOS setup.'); Writeln('Press any key to quit.'); end; COM_Init(COM_1, Baud9600, NoParity, OneStopBit, Len8Bits); COM_Init(COM_2, Baud9600, NoParity, OneStopBit, Len8Bits); PORT[COM_base_adr + LINE_CONTROL_REG] := PORT[COM_base_adr + LINE_CONTROL_REG] AND $7F; b := PORT[COM_base_adr+LINE_STATUS_REG]; {Read the Line Status Register (LSR) to reset any errors it indicates} b := PORT[COM_base_adr]; {Read the receive buffer in case it contains a char} END; PROCEDURE WriteLogData; { This procedure writes the log data to the file. } BEGIN IF FileIsOpen THEN { If the file has been created/opened } WriteLn(LoggFile, FiredShots,' ',UTCTime,' ',Latitude,' ',NorS,' ',Longitude,' ',EorW); END; { WriteLogData } { ================================================================== I n t e r r u p t / e x i t r o u t i n e s ================================================================== } PROCEDURE Timer_Interrupt; INTERRUPT; { This procedure is used to count milliseconds to fireing and recording... } BEGIN INC(MilliSec); (* The flush counter is not used in the latest version. The AppendFile procedure is used instead, and it is called after each recording, when it has most time to complete before next fireing. { Flush counter } INC(FlushMS); { FlushMS := FlushMS + 1 } IF FlushMS >= FLUSHRATE THEN { If the flush time limit is reached then... } BEGIN FlushFlag := True; { Set the flush flag. The flushing itself is done in the main procedure. } FlushMS := 0; { Reset counter } END; { IF-THEN-ELSE } *) { Fire and recording counters } INC(PassedMS); { PassedMS := PassedMS + 1 } IF PassedMS = RecordingDelay THEN { If it is time to record.. } IF HasFiredOnce = True THEN { If the program has already fired once, it is time to start recording now. } BEGIN RecordFlag := True; { Set the flag for recording. This flag is controlled continously in the main loop of the program. The recording phase is done there. Remember to reset the flag after recording! } LogFlag := True; { Set the flag for logging position and number of shots. The logging procedure itself is called from the main loop of the program. Reset the flag after use. } END; { IF-THEN-ELSE } IF PassedMS = FireIntervalMS THEN { If it is time to fire... } BEGIN INC(FiredShots); { FiredShots := FiredShots + 1 } HasFiredOnce := True; { It has fired once. This flag allows the recording part to record the signal after RecordingDelay milliseconds. Reset this flag after fireing as well. } PassedMS := 0; { Reset the MS counter. The recording is started when PassedMS reaches RecordingDelay. } FireFlag := True; END; { IF-THEN-ELSE } IF GuncoCount = True THEN { If Gunco wants to wait 30 ms... } INC(GuncoMSCount); { GuncoMSCount := GuncoMSCount + 1 } { Internal second counter } IF MilliSec=1000 THEN BEGIN MilliSec := 0; SecondsFlag := TRUE; END; end; PROCEDURE Lpt1_Interrupt; INTERRUPT; VAR Dummy : Word; BEGIN INC(Seconds); IF (Seconds = 60) then Seconds := 0; IF (((Seconds MOD 5) = 0) AND NOT(Seconds = 5) AND NOT(Seconds=10)) THEN BEGIN LPT1_data := LPT1_data XOR 1; {Flip least sign. bit = D0} port[LPT1_data_adr] := LPT1_data; END; PPS_occured := TRUE; PORT[$20] := $20; {Issue non-specific EOI to 8259 interrupt Controller} END; {$F+} PROCEDURE MyExitProc; BEGIN ExitProc := OldExitProc; { Restore exit procedure address } SetIntVec($0f, OldLpt1_Vector); SetIntVec($1C, OldTimer_Vector); { Close the log file if it is open } IF FileIsOpen THEN BEGIN { AddTrailerBlockToFile(LoggFile); } { I do not know why this command was to be placed here. } Close(LoggFile); FileIsOpen := FALSE; END; SOUND(1000); Delay(100); NOSOUND; TextColor(7); { Reset color } ClrScr; end; { MyExitProc } {$F-} PROCEDURE Init_InterruptAndExitVectors; var b: Byte; BEGIN OldExitProc := ExitProc; { Save previous exit proc } ExitProc := @MyExitProc; { Insert our exit proc in chain } GetIntVec($0f, OldLpt1_Vector); SetIntVec($0f, @Lpt1_Interrupt); GetIntVec($1C, OldTimer_Vector); SetIntVec($1C, @Timer_Interrupt); TickFreq := 1000; {Hertz} Timer0_Value := ROUND(TimerBaseFreq/TickFreq); PORT[ $43 ] := $36; PORT[ $40 ] := LO(Timer0_Value); PORT[ $40 ] := HI(Timer0_Value); b := port[$21]; {Setup PC's PIC-1 (Interrupt Controller): Enable INT 7} {write('Old Int mask: ', hex2str(b));} b := b AND $7f; port[$21] := b; b := port[$21]; {writeln(' New Int mask: ', hex2str(b));} port[LPT1_ctrl_adr] := $10; {0001 0000 Bit 4 = 1: Enable ack interrupt} PORT[$20] := $20; {Issue non-specific end-of-interrupt to 8259 PIC-1interrupt Controller} END; {-------------------------------------------------------} {------------ M A I N P R O G R A M -------------} {-------------------------------------------------------} Var b : Byte; Quit : Boolean; d : string; dd: string; Nmea_Error_Counter : LongInt; I, Code: Integer; BEGIN GetFireAndRecordingDelay; { Get the fireing and recording delays before anything else is done. } InitLogFile; { Prepare the log file for...Logging! } { Colors for this section are already set. } ClrScr; WriteLn('**********************************************************'); WriteLn('* The program translates NMEA sentences from the GPS and*'); WriteLn('* saves them to a log file. If an error in the NMEA *'); Writeln('* sentence occurs, the program will print an error *'); WriteLn('* and the affected sentence in the log will contain |s. *'); WriteLn('* Within the program, different information has *'); WriteLn('* different colors: Information is blue, data is gray, *'); WriteLn('* warnings are red and errors are yellow. It is done *'); WriteLn('* like this because it must be easier to notice error *'); WriteLn('* messages than the normal data flow. The program must *'); WriteLn('* be run in DOS mode. Programmed by Ole Meyer and *'); WriteLn('* Geir S. Type quit to exit. *'); WriteLn('* *'); WriteLn('* Institute of Solid Earth Physics *'); WriteLn('* University of Bergen, NORWAY *'); WriteLn('* February 2001 *'); WriteLn('**********************************************************'); Writeln; Quit := FALSE; PPS_occured := FALSE; SecondsFlag := FALSE; MilliSec := 0; seconds := 0; Gps_Nmea := ''; Nmea_Error_Counter := 0; LPT1_data := 0; HardwareSetup; Init_InterruptAndExitVectors; {--- This is the main loop. Runs until any key is pressed. ---} REPEAT {--- Did PPS from GPS generate interrupt? If so, PPS_occured flag was set.} { Gray text for normal info. } TextColor(7); IF PPS_occured then begin PPS_occured := FALSE; gotoxy(1,20); write('Shots fired: ',FiredShots); {write('PPS interrupt! Internal second counter = ', Seconds,' ');} end; {--- Reception of serial characters is based on polling the UART Data Ready bit in the Line Status Register.} UartLineStatus := port[COM_base_adr+LINE_STATUS_REG] AND 1; {Keep Data Ready bit} IF (UartLineStatus <> 0) then begin {Character present, get it, store it ..} UartDataIn := port[COM_base_adr]; Gps_Nmea := Gps_Nmea + Chr(UartDataIn); IF (Chr(UartDataIn) = LF) then begin {End of Line character, start parsing.} { This section collects information from the NMEA sentence for the log file. } Latitude := NMEA_parsing('GPGGA', Gps_Nmea, 2); NorS := NMEA_parsing('GPGGA', Gps_Nmea, 3); Longitude := NMEA_parsing('GPGGA', Gps_Nmea, 4); EorW := NMEA_parsing('GPGGA', Gps_Nmea, 5); UTCTime := NMEA_parsing('GPGGA', Gps_Nmea, 1); { Write position information on screen } gotoxy(1,22); Write('Latitude: ',Latitude,' ',NorS,' Longitude: ',Longitude,' ',EorW); d := NMEA_parsing('GPGGA', Gps_Nmea, 1); {Field 1 = time} No_of_satellites := NMEA_parsing('GPGGA', Gps_Nmea, 7); {Field 7 = No of satellites in use} Gps_Nmea := ''; IF ((d = NMEA_FAILURE) OR (No_of_satellites=NMEA_FAILURE)) then begin INC(Nmea_Error_Counter); gotoxy(1,21); { This is a warning. Output in yellow. } TextColor(14); write('Error in GPS NMEA sentence, error count: ', Nmea_Error_Counter); TextColor(7); { Reset text color } end else begin gotoxy(1,23); sound(1000); write('UTC: ',d, ' Number of satellites: ', No_of_satellites,' '); (* This is not used in the latest version. The AppendFile procedure is called after every recording is done. The AppendFile procedure works in the same way as the flush procedure. IF FlushFlag = True THEN { If it is time to flush... (make backup) } BEGIN AppendFile; { Flush the file } {FlushFile;} FlushFlag := False; END; *) { This is the section where the fireing, recording and logging is started. } IF FireFlag = True THEN { If it wants to fire... } BEGIN Gunco; { Fire the gun controller } FireFlag := False; { Reset the flag } END; IF RecordFlag = True THEN { If it wants to start recording... } BEGIN Recording; { Start the recorder } RecordFlag := False; { Reset the flag } IF LogFlag = True THEN BEGIN WriteLogData; { Log to the file } LogFlag := False; { Reset the flag } END; { Recording and logging is done. Now we have some time to the next fireing, so flush (append) the file now. } AppendFile; END; dd := 'xx'; dd[1] := d[5]; dd[2] := d[6]; Val(dd, I, Code); { Error during conversion to integer? } if code <> 0 then BEGIN { Warning. Yellow text. } TextColor(14); Write('Error at position: ', Code); TextColor(7); { Reset color } END else begin {If (I=0) then} Seconds := I; { Synchronize the counters } Write('UTC second = ', I,' '); end; delay(1); NoSound; end; end; end; IF QuitState = 0 THEN ThisKey := 113; { If he has pressed q... } IF QuitState = 1 THEN ThisKey := 117; { If he has pressed u... } IF QuitState = 2 THEN ThisKey := 105; { If he has pressed i... } IF QuitState = 3 THEN ThisKey := 116; { If he has pressed t... } IF QuitState = 4 THEN AllowExit := True; { Allow him to exit! The key checking is done bellow. } IF KeyPressed THEN IF ReadKey = CHR(ThisKey) THEN INC(QuitState) { QuitState := QuitState + 1 } ELSE QuitState := 0; { If he did not type the correct letter in 'q u i t'... } UNTIL AllowExit; { Close the log file } IF FileIsOpen THEN BEGIN {$i-} Close(LoggFile); FileIsOpen := FALSE; {$i+} END; ClrScr; end.