; GPS finder ; Version 0.1 2009/05/15 ; Mike Ingle (email: mike at opengpstracker dot org) ; http://www.opengpstracker.org ; This is open source software. .include "m88def.inc" ; ATMEGA88 at 8 MHz for 9600 baud or 4800 baud ; Set CKSEL3..0 fuses to 0010 ; Three digit common cathode multiplexed LED display ; PD7 - A ; PD6 - B ; PD5 - C ; PD4 - D ; PC3 - E ; PC2 - F ; PC1 - G ; PC0 - Decimal Point ; PB0 - Digit 1 cathode (to NPN transistor base via resistor) ; PB1 - Digit 2 cathode (all are active high) ; PB2 - Digit 3 cathode ; PB3 - GPS enable transistor (active low for PNP transistor) ; PB4 - FIND switch to ground ; PB5 - SAVE switch to ground ; RXD - GPS data in ; ; A ; ___ ; F | | B ; |___| ; | G | ; E |___| C . DP ; D ; ; Display codes: ; E1 = no datastream from GPS module ; E2 = datastream corrupt ; flashing = GPS module not tracking satellites ; Enable SERIAL_DEBUG to test with the TestNav program. ;#define SERIAL_DEBUG 1 .equ PORT_DIGIT_HIGH = PORTD .equ DDR_DIGIT_HIGH = DDRD .equ PORT_DIGIT_LOW = PORTC .equ DDR_DIGIT_LOW = DDRC .equ PORT_DIGIT_SELECT = PORTB .equ DDR_DIGIT_SELECT = DDRB .equ PORT_GPS_POWER = PORTB3 .equ PIN_KEYS = PINB .equ KEY_FIND = PINB4 .equ KEY_SAVE = PINB5 .equ INTERRUPT_DIVISOR = 40 ; for 8 MHz .equ BAUD_DIVISOR = 51 ; for 9600 baud ;.equ BAUD_DIVISOR = 103 ; for 4800 baud .equ DEFAULT_WDR_COUNT = 1024 ; 10 seconds .equ RECEIVE_TIMEOUT = 255 .equ SAVE_NUM_FIXES = 8 ; number of fixes to receive before accepting .equ GPS_DATASTREAM_WAIT_TIME = 12 ; 3 seconds .def MUL_LO = r0 .def MUL_HI = r1 .def REG_ZERO = r2 ; register always zero; saves a lot of clr instructions .def INTERRUPT_SREG = r3 ; Storage for Status flags during interrupt .def SERIAL_POINTER = r4 ; Pointer into 256-byte serial receive buffer. .def PAT = r5 ; current pattern character .def BASE = r6 ; base of string being matched .def CHECKSUM = r7 .def DELIM = r8 .def VERIFY_CHECKSUM = r9 .def SCRATCH0 = r16 .def READ_FIELD = r16 .def SCRATCH1 = r17 .def PARSE_FIELD = r17 .def SCRATCH2 = r18 .def FIELD_LENGTH = r18 .def SCRATCH3 = r19 .def SCRATCH4 = r20 .def SCRATCH5 = r21 .def FLAG1_REGISTER = r22 .def DELAY = r23 .def CHAR = r24 ; current character .equ FLAG1_REFRESH_DIGIT_LOW = 0 .equ FLAG1_REFRESH_DIGIT_HIGH = 1 .equ FLAG1_KEY_PRESSED = 2 .equ FLAG1_PARSE_NMEA = 3 .equ FLAG1_SKIP_REPEATED_DELIMITER = 4 .equ FLAG1_BLANK_DISPLAY = 5 .dseg .org 0x0100 DIGIT_1: .byte 1 ; keep all three digits DIGIT_2: .byte 1 ; | DIGIT_3: .byte 1 ; on same page DIGIT_DIRECTION: .byte 1 DIRECTION_BLINK: .byte 1 WDR_COUNT_LOW: .byte 1 ; low byte of wdr countdown WDR_COUNT_HIGH: .byte 1 ; high byte of wdr countdown ;DEBUGALIGN: .byte 9 NB1: .byte 8 NB2: .byte 8 NB3: .byte 8 NB4: .byte 8 NB5: .byte 8 NB6: .byte 8 NB7: .byte 8 NB8: .byte 8 NB9: .byte 8 NB10: .byte 8 NB11: .byte 8 NB12: .byte 8 NB13: .byte 8 NB14: .byte 8 NB15: .byte 8 NB16: .byte 8 NB17: .byte 8 NB18: .byte 8 NB19: .byte 8 NB20: .byte 8 NB21: .byte 8 NB22: .byte 8 NB23: .byte 8 NB24: .byte 8 ;NB25: .byte 8 .org 0x0200 RECEIVE_BUFFER: .byte 256 ;.org 0x0300 .equ PARSE_FIELDS_LENGTH = 64 PARSE_FIELDS: .byte PARSE_FIELDS_LENGTH LAT1: .byte 10 LON1: .byte 11 LAT2: .byte 10 LON2: .byte 11 ; Fields for parsing GPS output .equ FLDADR_GPRMC_FIXVALID = PARSE_FIELDS + 0 .equ FLDLEN_GPRMC_FIXVALID = 1 .equ FLDADR_GPRMC_TIME = FLDADR_GPRMC_FIXVALID + FLDLEN_GPRMC_FIXVALID .equ FLDLEN_GPRMC_TIME = 6 .equ FLDADR_GPRMC_LATITUDE = FLDADR_GPRMC_TIME + FLDLEN_GPRMC_TIME .equ FLDLEN_GPRMC_LATITUDE = 9 .equ FLDADR_GPRMC_LATDIR = FLDADR_GPRMC_LATITUDE + FLDLEN_GPRMC_LATITUDE .equ FLDLEN_GPRMC_LATDIR = 1 .equ FLDADR_GPRMC_LONGITUDE = FLDADR_GPRMC_LATDIR + FLDLEN_GPRMC_LATDIR .equ FLDLEN_GPRMC_LONGITUDE = 10 .equ FLDADR_GPRMC_LONGDIR = FLDADR_GPRMC_LONGITUDE + FLDLEN_GPRMC_LONGITUDE .equ FLDLEN_GPRMC_LONGDIR = 1 .equ FLDADR_GPRMC_SPEED = FLDADR_GPRMC_LONGDIR + FLDLEN_GPRMC_LONGDIR .equ FLDLEN_GPRMC_SPEED = 6 .equ FLDADR_GPRMC_COURSE = FLDADR_GPRMC_SPEED + FLDLEN_GPRMC_SPEED .equ FLDLEN_GPRMC_COURSE = 6 .equ FLDADR_GPRMC_DATE = FLDADR_GPRMC_COURSE + FLDLEN_GPRMC_COURSE .equ FLDLEN_GPRMC_DATE = 6 ; Beginning of code: interrupt vectors ; for the ATMEGA88 .cseg rjmp V_RESET rjmp V_INT0 rjmp V_INT1 rjmp V_PCINT0 rjmp V_PCINT1 rjmp V_PCINT2 rjmp V_WDT rjmp V_TIMER2_COMPA rjmp V_TIMER2_COMPB rjmp V_TIMER2_OVF rjmp V_TIM1_CAPT rjmp V_TIM1_COMPA rjmp V_TIM1_COMPB rjmp V_TIM1_OVF rjmp V_TIM0_COMPA rjmp V_TIM0_COMPB rjmp V_TIM0_OVF rjmp V_SPI_STC rjmp V_USART0_RXC rjmp V_USART0_UDRE rjmp V_USART0_TXC rjmp V_ADC rjmp V_EE_READY rjmp V_ANALOG_COMP rjmp V_TWI rjmp V_SPM_READY #define DIGIT_PATTERN_DASH 0b00000010 #define DIGIT_PATTERN_F 0b10001110 #define DIGIT_PATTERN_S 0b10110110 #define DIGIT_PATTERN_E 0b10011110 #define DIGIT_PATTERN_1 0b01100000 #define DIGIT_PATTERN_2 0b11011010 V_INT0: V_INT1: V_PCINT1: V_PCINT2: V_WDT: V_TIMER2_COMPB: V_TIMER2_OVF: V_TIM1_CAPT: V_TIM1_COMPA: V_TIM1_COMPB: V_TIM1_OVF: V_TIM0_COMPA: V_TIM0_COMPB: V_TIM0_OVF: V_SPI_STC: V_USART0_UDRE: V_USART0_TXC: V_ADC: V_EE_READY: V_ANALOG_COMP: V_TWI: V_SPM_READY: V_RESET: ; Stack pointer ldi SCRATCH0,high(RAMEND) ldi SCRATCH1,low(RAMEND) out SPH,SCRATCH0 out SPL,SCRATCH1 ; Registers clr REG_ZERO clr FLAG1_REGISTER clr SERIAL_POINTER ; I/O ports ldi SCRATCH0,0b11110000 out DDR_DIGIT_HIGH,SCRATCH0 ldi SCRATCH0,0b00101111 out DDR_DIGIT_LOW,SCRATCH0 ldi SCRATCH0,0b00001111 out DDR_DIGIT_SELECT,SCRATCH0 out PORT_DIGIT_HIGH,REG_ZERO out PORT_DIGIT_LOW,REG_ZERO ldi SCRATCH0,(1< ; Field number starts with 0 meaning first field. Put FF after last field. ; NMEA Checksum goes from right after $ to before * ; Non-NMEA checksum is whole line excluding newline ; NMEA delimiter is a comma and end of line is * then two checksum digits ; Command line delimiter is normally a space and end of line is a CR ; Set FLAG1_PARSE_NMEA for NMEA mode, clear it for command line mode ; Timeout sets T flag and aborts. PARSE_NMEA_OR_COMMAND_LINE: clr READ_FIELD ; current field clr PAT ; use as a zero for writing into memory clr CHECKSUM ; calculated in both modes, VERIFY_CHECKSUM only in NMEA mode cbr FLAG1_REGISTER,1< 1, output is 0 ret ARCCOSINE_INPUT_LOW: ldi ZH,high(COS_PI<<1) ; is pi * 2^56 ldi ZL,low(COS_PI<<1) ldi YH,high(NB9) ldi YL,low(NB9) rcall LDCONST64 ; less than -1, return pi ret ; ARCCOSINE_INPUT_GOOD: ; backup input in NB8 ldi XH,high(NB1) ldi XL,low(NB1) ldi YH,high(NB8) ldi YL,low(NB8) rcall CPY64 ; find starting point by comparing against brackets ldi ZH,high(ACOS_STP1<<1) ldi ZL,low(ACOS_STP1<<1) ARCCOSINE_FIND_STARTING_POINT: ;HB; ldi YH,high(NB9) ldi YL,low(NB9) rcall LDCONST128 ; load a starting point into NB9 and a bracket value into NB10 ldi XH,high(ACOS_EOF<<1) ldi XL,low(ACOS_EOF<<1) cp ZL,XL cpc ZH,XH ; past last bin? brsh ARCCOSINE_FOUND_STARTING_POINT ldi XH,high(NB10+8) ldi XL,low(NB10+8) ;HB; ldi YH,high(NB1+8) ldi YL,low(NB1+8) rcall CMP64 ; compare input against bracket brlt ARCCOSINE_FIND_STARTING_POINT ARCCOSINE_FOUND_STARTING_POINT: ; in NB9 ; Here we have the starting point in NB9 ldi SCRATCH0,20 ; max iterations ARCCOSINE_LOOP: push SCRATCH0 ; save max iterations ldi XH,high(NB9) ldi XL,low(NB9) ;HB; ldi YH,high(NB1) ldi YL,low(NB1) rcall CPY64 rcall COSINE ; cos(a) ;HB; ldi XH,high(NB2) ldi XL,low(NB2) ;HB; ldi YH,high(NB10) ldi YL,low(NB10) rcall CPY64 ; cosine value into NB10 ;HB; ldi XH,high(NB10+8) ldi XL,low(NB10+8) ;HB; ldi YH,high(NB8+8) ldi YL,low(NB8+8) rcall SUB64 ; cos(a) - x into NB10 ;HB; ldi YH,high(NB10) ldi YL,low(NB10) rcall REVERSE_SIGN_EXTEND_128 ; fix sign of low order (NB11) rcall SINE ;HB; ldi XH,high(NB2) ldi XL,low(NB2) ;HB; ldi YH,high(NB13) ldi YL,low(NB13) rcall CPY64 ; sine value into NB13 ;HB; ldi XH,high(NB14) ldi XL,low(NB14) rcall CLR128 ; clear NB14 and NB15 ;HB; ldi XH,high(NB10) ldi XL,low(NB10) ;HB; ldi YH,high(NB12+16) ldi YL,low(NB12+16) rcall SIGN_EXTEND_128 ldi ZH,high(NB14) ldi ZL,low(NB14) rcall DIV128S ; (cos(a) - x)/sin(a) (128 bit!) ;HB; ldi YH,high(NB15-1) ldi YL,low(NB15-1) rcall SHIFT_RIGHT_8BITS_Y ; scale result ;HB; ldi XH,high(NB9+8) ldi XL,low(NB9+8) rcall ADD64 ; a = a - ( b / c ) ; ADD instead of SUB because derivative of cos is -sin ; If NB9 went negative, set it to zero lds SCRATCH0,NB9 sbrc SCRATCH0,7 rcall CLR64 ; NB10 from division above is the absolute value of cos(a) - x ;HB; ldi YH,high(NB12) ldi YL,low(NB12) ldi ZH,high(ACOS_ERROR<<1) ldi ZL,low(ACOS_ERROR<<1) rcall LDCONST64 ;HB; ldi XH,high(NB11+8) ldi XL,low(NB11+8) rcall CMP64 ; check error pop SCRATCH0 ; max iter brcs ARCCOSINE_DONE dec SCRATCH0 brne ARCCOSINE_LOOP ; rjmp ARCCOSINE_LOOP ; DEBUG maxiter disabled ARCCOSINE_DONE: ret ; Sine ; Input in NB1 has range -pi to pi and is * 2^61 (sign, two integer bits, then fractional) ; Output in NB2 is * 2^61 ; Trashes NB3-NB7 ; This function assumes all its buffers are on one 256-page ; If that assumption is false, enable all the high-byte loads ; that are commented out with ;HB; SINE: ldi YH,high(NB3) ldi YL,low(NB3) ldi ZH,high(SINE_PI2<<1) ldi ZL,low(SINE_PI2<<1) rcall LDCONST128 ; PI/2 into NB3 and NEGPI into NB4 (they are adjacent) ldi XH,high(NB1+8) ldi XL,low(NB1+8) ;HB; ldi YH,high(NB3+8) ldi YL,low(NB3+8) rcall SUB64 ; NB1 = NB1 - NB3 subi XL,-8 ;HB; ldi YH,high(NB4+8) ldi YL,low(NB4+8) rcall CMP64 ; is new value < -pi? brge SINE_NO_ADD_2PI subi XL,-8 subi YL,-8 rcall SUB64 subi XL,-8 subi YL,-8 rcall SUB64 SINE_NO_ADD_2PI: ; rjmp COSINE ; fall through ; Cosine ; Input in NB1 has range -pi to pi and is * 2^56 ; Output in NB2 is * 2^56 ; Trashes NB3-NB7 ; This function assumes all its buffers are on one 256-page ; If that assumption is false, enable all the high-byte loads ; that are commented out with ;HB; COSINE: ldi XH,high(NB1) ldi XL,low(NB1) ld SCRATCH0,X push SCRATCH0 ; high byte, for sign test ldi YH,high(NB4) ldi YL,low(NB4) rcall CPY64 ; copy input into NB4 pop SCRATCH0 ; ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) sbrc SCRATCH0,7 ; is input negative? rcall NEG64 ; if so, invert it to positive ; ldi ZH,high(COS_INV_PI2<<1) ldi ZL,low(COS_INV_PI2<<1) ;HB; ldi YH,high(NB5) ldi YL,low(NB5) rcall LDCONST64 ; load constant inverse PI/2 into NB5 ; ;HB; ldi XH,high(NB2) ldi XL,low(NB2) rcall CLR128 ;HB; ldi XH,high(NB5+8) ldi XL,low(NB5+8) ;HB; ldi YH,high(NB4+8) ldi YL,low(NB4+8) ldi ZH,high(NB2+16) ldi ZL,low(NB2+16) rcall MULACC64U ; NB2:NB3 now has absx / pi2 ; ldi ZH,high(COS_PI<<1) ldi ZL,low(COS_PI<<1) ;HB; ldi YH,high(NB5) ldi YL,low(NB5) rcall LDCONST64 ; load constant PI into NB5 (needed later) ; ldi ZH,high(NB2) ldi ZL,low(NB2) ld SCRATCH0,Z andi SCRATCH0,0b0011 ; scratch0 is now quadrant 0-3 push SCRATCH0 ; save quadrant for later ; tst SCRATCH0 breq COSINE_QUAD0 cpi SCRATCH0,2 brlo COSINE_QUAD1 breq COSINE_QUAD2 COSINE_QUAD3: ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) ;HB; ldi YH,high(NB5+8) ldi YL,low(NB5+8) rcall SUB64 ; subtract pi ; fall through to subtract again COSINE_QUAD2: ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) ;HB; ldi YH,high(NB5+8) ldi YL,low(NB5+8) rcall SUB64 ; subtract pi rjmp COSINE_QUAD0 COSINE_QUAD1: ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) rcall NEG64 ; invert the value subi XL,-8 ;HB; ldi YH,high(NB5+8) ldi YL,low(NB5+8) rcall ADD64 ; add pi to negated value COSINE_QUAD0: ; Quad 3 could have gone negative - if so, invert it ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) lds SCRATCH0,NB4 ; high byte sbrc SCRATCH0,7 ; is input negative? rcall NEG64 ; if so, invert it to positive ; Done inverting ;HB; ldi XH,high(NB6) ldi XL,low(NB6) rcall CLR128 ;HB; ldi XH,high(NB4+8) ldi XL,low(NB4+8) ;HB; ldi YH,high(NB4+8) ldi YL,low(NB4+8) ldi ZH,high(NB6+16) ldi ZL,low(NB6+16) rcall MULACC64U ; NB6 = NB4^2 ;HB; ldi YH,high(NB6+9) ldi YL,low(NB6+9) rcall SHIFT_LEFT_8BITS_Y ; double NB6 ; ;HB; ldi XH,high(NB6) ldi XL,low(NB6) ;HB; ldi YH,high(NB7) ldi YL,low(NB7) rcall CPY64 ; NB6 and NB7 are initial t^2 ; ;HB; ldi XH,high(NB3) ldi XL,low(NB3) rcall CLR64 ;HB; ldi YH,high(NB2) ldi YL,low(NB2) ldi ZH,high(COS_P0<<1) ldi ZL,low(COS_P0<<1) rcall LDCONST64 ; NB2:5 now has P0 * 2^63 ; COSINE_POLYNOMIAL_LOOP: ; 1 = input, 2:3 = t*tt and 3 = coef ; 4:5 = accum; 6 = t; 7 = tt ; ;HB; ldi YH,high(NB5) ldi YL,low(NB5) rcall LDCONST64 ; load the coefficient ; push ZH push ZL ; ;HB; ldi XH,high(NB5+8) ldi XL,low(NB5+8) ;HB; ldi YH,high(NB7+8) ldi YL,low(NB7+8) ldi ZH,high(NB2+16) ldi ZL,low(NB2+16) rcall MULACC64S ; y += pX * tt ; pop ZL cpi ZL,low(COS_EOF<<1) brsh COSINE_POLYNOMIAL_DONE push ZL ; ;HB; ldi XH,high(NB4) ldi XL,low(NB4) rcall CLR128 ; ;HB; ldi XH,high(NB6+8) ldi XL,low(NB6+8) ;HB; ldi YH,high(NB7+8) ldi YL,low(NB7+8) ldi ZH,high(NB4+16) ldi ZL,low(NB4+16) rcall MULACC64U ; NB4:NB5 = NB6 * NB7 ; ;HB; ldi YH,high(NB4+9) ldi YL,low(NB4+9) rcall SHIFT_LEFT_8BITS_Y rcall SHIFT_RIGHT_128_Y ; only wanted to move 7 bits left ; ;HB; ldi XH,high(NB4) ldi XL,low(NB4) ;HB; ldi YH,high(NB7) ldi YL,low(NB7) rcall CPY64 ; NB7 = shifted NB4 ; pop ZL pop ZH rjmp COSINE_POLYNOMIAL_LOOP ; COSINE_POLYNOMIAL_DONE: pop ZH pop SCRATCH0 ; quadrant cpi SCRATCH0,0 breq COSINE_NO_INVERT cpi SCRATCH0,3 breq COSINE_NO_INVERT ;HB; ldi XH,high(NB2+8) ldi XL,low(NB2+8) rcall NEG64 ; invert quadrant 1 or 2 COSINE_NO_INVERT: ;HB; ldi YH,high(NB2+8) ldi YL,low(NB2+8) rcall SHIFT_LEFT_64_Y ; scale to 2^60 ret ; Load a constant from ROM into a buffer ; Constant pointer in Z, destination in Y ; Call with Y, Z at beginning of buffers ; Returns with Y, Z at end of buffer LDCONST128: rcall LDCONST64 LDCONST64: rcall LDCONST32 LDCONST32: lpm SCRATCH1,Z+ st Y+,SCRATCH1 lpm SCRATCH1,Z+ st Y+,SCRATCH1 lpm SCRATCH1,Z+ st Y+,SCRATCH1 lpm SCRATCH1,Z+ st Y+,SCRATCH1 ret ; Clear X ; Call with X at beginning of buffer, returns X at end of buffer CLR128: rcall CLR64 CLR64: rcall CLR32 CLR32: st X+,REG_ZERO st X+,REG_ZERO st X+,REG_ZERO st X+,REG_ZERO ret ; Negate X (two's compliment) ; Call with X at end of buffer, returns X at beginning of buffer NEG128: clc NEG128_CIN: rcall NEG64_CIN rjmp NEG64_CIN NEG64: clc NEG64_CIN: rcall NEG32_CIN NEG32_CIN: ld SCRATCH1,-X clr SCRATCH2 sbc SCRATCH2,SCRATCH1 st X,SCRATCH2 ld SCRATCH1,-X clr SCRATCH2 sbc SCRATCH2,SCRATCH1 st X,SCRATCH2 ld SCRATCH1,-X clr SCRATCH2 sbc SCRATCH2,SCRATCH1 st X,SCRATCH2 ld SCRATCH1,-X clr SCRATCH2 sbc SCRATCH2,SCRATCH1 st X,SCRATCH2 ret ; Copy Y = X ; Call with X,Y at beginning of buffer, returns X,Y at end of buffer CPY128: rcall CPY64 CPY64: rcall CPY32 CPY32: ld SCRATCH1,X+ st Y+,SCRATCH1 ld SCRATCH1,X+ st Y+,SCRATCH1 ld SCRATCH1,X+ st Y+,SCRATCH1 ld SCRATCH1,X+ st Y+,SCRATCH1 ret ; X = X + Y ; Call with X, Y at end of buffer ; Returns X, Y at beginning of buffer ; Carry flag in and out ADD128: clc ADD128_CIN: rcall ADD64_CIN rjmp ADD64_CIN ADD64: clc ADD64_CIN: rcall ADD32_CIN ADD32_CIN: ld SCRATCH0,-X ld SCRATCH1,-Y adc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y adc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y adc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y adc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ret ; X = X - Y ; Call with X, Y at end of buffer ; Returns X, Y at beginning of buffer ; Carry flag in and out SUB128: clc SUB128_CIN: rcall SUB64_CIN rjmp SUB64_CIN SUB64: clc SUB64_CIN: rcall SUB32_CIN SUB32_CIN: ld SCRATCH0,-X ld SCRATCH1,-Y sbc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y sbc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y sbc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ld SCRATCH0,-X ld SCRATCH1,-Y sbc SCRATCH0,SCRATCH1 st X,SCRATCH0 ; ret ; Compare X - Y ; Call with X, Y at end of buffer ; Returns X, Y at beginning of buffer ; Carry flag in and out CMP128: clc CMP128_CIN: rcall CMP64_CIN rjmp CMP64_CIN CMP64: clc CMP64_CIN: rcall CMP32_CIN CMP32_CIN: ld SCRATCH0,-X ld SCRATCH1,-Y cpc SCRATCH0,SCRATCH1 ; ld SCRATCH0,-X ld SCRATCH1,-Y cpc SCRATCH0,SCRATCH1 ; ld SCRATCH0,-X ld SCRATCH1,-Y cpc SCRATCH0,SCRATCH1 ; ld SCRATCH0,-X ld SCRATCH1,-Y cpc SCRATCH0,SCRATCH1 ; ret ; Handles signed two's compliment ; Z = Z + ( X * Y ) ; Z is a 128-bit register ; X and Y are 64-bit buffers ; Call with X, Y, Z at end of buffer ; Returns X, Y, Z at indeterminate locations ; Assumes X, Y, Z are on a 256-page MULACC64S: ; this entry point accumulates ;HB; push ZH push ZL ;HB; push YH push YL ;HB; push XH push XL ; subi XL,8 ld SCRATCH3,X ; high byte push SCRATCH3 subi XL,-8 ;HB; push XH push XL sbrc SCRATCH3,7 rcall NEG64 ; make X positive if it was negative, saving negative/positive status ; subi YL,8 ld SCRATCH0,Y ; high byte subi YL,-8 ;HB; mov XH,YH mov XL,YL sbrc SCRATCH0,7 rcall NEG64 ; make Y positive if it was negative, saving negative/positive status ; eor SCRATCH3,SCRATCH0 ; only one negative? ;HB; mov XH,ZH mov XL,ZL sbrc SCRATCH3,7 rcall NEG128 ; negate Z if one of X and Y was negative ; pop XL ;HB; pop XH push SCRATCH0 ; rcall MULACC64U ; pop SCRATCH0 ; Y neg/pos pop SCRATCH3 ; X neg/pos pop XL ;HB; pop XH sbrc SCRATCH3,7 rcall NEG64 ; negate X ; pop XL ; was Y ;HB; pop XH sbrc SCRATCH0,7 rcall NEG64 ; negate Y ; pop XL ; was Z ;HB; pop XH eor SCRATCH0,SCRATCH3 sbrc SCRATCH0,7 rcall NEG128 ; negate Z ; ret ; Z = Z + ( X * Y ) ; Z is a 128-bit register ; X and Y are 64-bit buffers ; Call with X, Y, Z at end of buffer ; Returns X, Y, Z at indeterminate locations ; Assumes X, Y, Z are on a 256-page ; Sneaky use of SCRATCH4 to propagate carry from one multiply cycle to the next MULACC64U: ldi SCRATCH2,8 ; outer loop mov SCRATCH5,ZL subi SCRATCH5,16 ; beginning marker of output buffer MULACC64U_OL: ld SCRATCH1,-Y tst SCRATCH1 ; skip the zeroes brne MULACC64U_NZ dec ZL rjmp MULACC64U_NEXT MULACC64U_NZ: ldi SCRATCH3,8 ; inner loop clr SCRATCH4 ; carry propagation register MULACC64U_IL: ld SCRATCH0,-X mul SCRATCH0,SCRATCH1 sub MUL_HI,SCRATCH4 ; if previous carry, subtract -1 (add 1) to MUL_HI ld SCRATCH0,-Z add SCRATCH0,MUL_LO st Z,SCRATCH0 ld SCRATCH0,-Z adc SCRATCH0,MUL_HI sbc SCRATCH4,SCRATCH4 ; if carry, SCRATCH4 = 0xff (-1) else it equals 0 st Z+,SCRATCH0 dec SCRATCH3 brne MULACC64U_IL ; propagate carry to high end of buffer push ZL dec ZL MULACC64U_CARRY: cp SCRATCH5,ZL ; check beginning of buffer brsh MULACC64U_CARRY_DONE add SCRATCH4,SCRATCH4 ; retrieve saved carry flag brcc MULACC64U_CARRY_DONE ; done if not set ld SCRATCH0,-Z adc SCRATCH0,REG_ZERO st Z,SCRATCH0 sbc SCRATCH4,SCRATCH4 ; save carry flag rjmp MULACC64U_CARRY MULACC64U_CARRY_DONE: pop ZL subi ZL,-7 subi XL,-8 MULACC64U_NEXT: dec SCRATCH2 brne MULACC64U_OL ret ; Signed version, call as below DIV128S: ld SCRATCH0,X ; high byte sbrc SCRATCH0,7 ; is it negative? subi XL,-16 sbrc SCRATCH0,7 ; is it negative? rcall NEG128 ; subi XL,-15 st X,REG_ZERO ; this is a hack subi XL,15 ; DIV128U does not like low order byte of X nonzero ; ld SCRATCH1,Y ; high byte eor SCRATCH0,SCRATCH1 push SCRATCH0 ; for end of function sign check ; sbrs SCRATCH1,7 ; is it negative? rjmp DIV128S_NO_INVERT_Y push XH push XL mov XH,YH mov XL,YL subi XL,-16 rcall NEG128 pop XL pop XH DIV128S_NO_INVERT_Y: ; push ZH push ZL rcall DIV128U pop XL pop XH ; subi XL,-16 ; end of buffer pop SCRATCH0 sbrc SCRATCH0,7 ; output needs inverting? rcall NEG128 ; ret ; Z = X / Y ; X, Y, Z are 128-bit registers ; clear Z first ; Assumes X, Y, Z are on a 256-page ; Call with X, Y, Z at beginning of buffer ; X must be >= Y ; Y=0 hangs DIV128U: ; First align Y with X so first 1 bit of each is in the same position mov SCRATCH3,XL subi SCRATCH3,-16 mov SCRATCH2,YL subi SCRATCH2,-16 clr MUL_LO ; used as shift counter inc MUL_LO ; start it at 1 DIV128U_FIND_NONZERO: ld SCRATCH0,X tst SCRATCH0 brne DIV128U_ALIGN_LOOP ; if nonzero inc XL inc YL cp XL,SCRATCH3 ; if dividend X == 0 brsh DIV128U_DONE ; out on X == 0 rjmp DIV128U_FIND_NONZERO ; here we found the first nonzero byte in X DIV128U_ALIGN_LOOP: dec YL ld SCRATCH1,Y+ tst SCRATCH1 ld SCRATCH1,Y brne DIV128U_ALIGNED ; previous byte is nonzero cp SCRATCH1,SCRATCH0 brsh DIV128U_ALIGNED ; if Y >= X, 1 bits are aligned inc MUL_LO ; count left shifts push YL mov YL,SCRATCH2 ; end of buffer rcall SHIFT_LEFT_128_Y pop YL rjmp DIV128U_ALIGN_LOOP DIV128U_ALIGNED: ; here registers are aligned mov XL,SCRATCH3 mov YL,SCRATCH2 rcall SUB128 ; trial subtraction sbc SCRATCH4,SCRATCH4 ; save carry flag brcc DIV128U_NO_UNDO ; Here we got underflow, so undo the subtraction mov XL,SCRATCH3 mov YL,SCRATCH2 rcall ADD128 DIV128U_NO_UNDO: push ZL subi ZL,-16 ; move to end of buffer cpi SCRATCH4,1 ; recover INVERTED carry flag (carry = 1 if no underflow) rcall SHIFT_LEFT_128_Z_CIN ; shift it into the quotient pop ZL mov YL,SCRATCH2 subi YL,16 ; align to beginning of buffer rcall SHIFT_RIGHT_128_Y ; move Y buffer right dec MUL_LO ; decrement shift count (does not set carry, go figure) breq DIV128U_DONE ; if back where we started from, we are done rjmp DIV128U_ALIGNED ; next cycle DIV128U_DONE: ret ; Call with Y set to 9 bytes into buffer SHIFT_LEFT_8BITS_Y: ld SCRATCH1,-Y ld SCRATCH2,-Y st Y,SCRATCH1 ld SCRATCH1,-Y st Y,SCRATCH2 ld SCRATCH2,-Y st Y,SCRATCH1 ld SCRATCH1,-Y st Y,SCRATCH2 ld SCRATCH2,-Y st Y,SCRATCH1 ld SCRATCH1,-Y st Y,SCRATCH2 ld SCRATCH2,-Y st Y,SCRATCH1 ld SCRATCH1,-Y st Y,SCRATCH2 ret ; Call with Y at beginning of buffer ; Moves right one byte (8 bytes moved) SHIFT_RIGHT_8BITS_Y: ld SCRATCH1,Y+ ld SCRATCH2,Y st Y+,SCRATCH1 ld SCRATCH1,Y st Y+,SCRATCH2 ld SCRATCH2,Y st Y+,SCRATCH1 ld SCRATCH1,Y st Y+,SCRATCH2 ld SCRATCH2,Y st Y+,SCRATCH1 ld SCRATCH1,Y st Y+,SCRATCH2 ld SCRATCH2,Y st Y+,SCRATCH1 st Y+,SCRATCH2 ret SHIFT_LEFT_128_Y: clc SHIFT_LEFT_128_Y_CIN: rcall SHIFT_LEFT_64_Y_CIN rjmp SHIFT_LEFT_64_Y_CIN SHIFT_LEFT_64_Y: clc SHIFT_LEFT_64_Y_CIN: rcall SHIFT_LEFT_32_Y_CIN SHIFT_LEFT_32_Y_CIN: ld SCRATCH1,-Y rol SCRATCH1 st Y,SCRATCH1 ; ld SCRATCH1,-Y rol SCRATCH1 st Y,SCRATCH1 ; ld SCRATCH1,-Y rol SCRATCH1 st Y,SCRATCH1 ; ld SCRATCH1,-Y rol SCRATCH1 st Y,SCRATCH1 ; ret ; Call with Y pointer at beginning of buffer SHIFT_RIGHT_128_Y_SIGN_EXTEND: ld SCRATCH1,Y lsl SCRATCH1 ; high bit into carry rjmp SHIFT_RIGHT_128_Y_CIN ; SHIFT_RIGHT_64_Y_SIGN_EXTEND: ld SCRATCH1,Y lsl SCRATCH1 ; high bit into carry rjmp SHIFT_RIGHT_64_Y_CIN ; SHIFT_RIGHT_128_Y: clc SHIFT_RIGHT_128_Y_CIN: rcall SHIFT_RIGHT_64_Y_CIN rjmp SHIFT_RIGHT_64_Y_CIN SHIFT_RIGHT_64_Y: clc SHIFT_RIGHT_64_Y_CIN: rcall SHIFT_RIGHT_32_Y_CIN SHIFT_RIGHT_32_Y_CIN: ld SCRATCH1,Y ror SCRATCH1 st Y+,SCRATCH1 ; ld SCRATCH1,Y ror SCRATCH1 st Y+,SCRATCH1 ; ld SCRATCH1,Y ror SCRATCH1 st Y+,SCRATCH1 ; ld SCRATCH1,Y ror SCRATCH1 st Y+,SCRATCH1 ; ret ; Call with Z pointer at end of buffer SHIFT_LEFT_128_Z_CIN: rcall SHIFT_LEFT_64_Z_CIN SHIFT_LEFT_64_Z_CIN: rcall SHIFT_LEFT_32_Z_CIN SHIFT_LEFT_32_Z_CIN: ld SCRATCH1,-Z rol SCRATCH1 st Z,SCRATCH1 ; ld SCRATCH1,-Z rol SCRATCH1 st Z,SCRATCH1 ; ld SCRATCH1,-Z rol SCRATCH1 st Z,SCRATCH1 ; ld SCRATCH1,-Z rol SCRATCH1 st Z,SCRATCH1 ; ret ; Input pointer Y set to end of a 128-bit buffer ; If the low-order 64 bits are negative, the high-order 64 bits ; are filled with 0xff (sign extended) ; If the low-order 64 bits are positive, the high-order 64 bits ; are set to zero ; Pointer left at beginning of buffer SIGN_EXTEND_128: subi YL,8 ; high byte of low 64 clr SCRATCH1 ; fill byte ld SCRATCH0,Y sbrc SCRATCH0,7 ; high byte set? dec SCRATCH1 ; fill byte = 0xff SE128_GO: rcall SE128_LOOP SE128_LOOP: st -Y,SCRATCH1 st -Y,SCRATCH1 st -Y,SCRATCH1 st -Y,SCRATCH1 ret ; This version fills low order 64 with 0xff if the high-order 64 are negative ; Call with pointer at beginning of buffer ; Returns pointer at end of buffer REVERSE_SIGN_EXTEND_128: clr SCRATCH1 ; fill byte ld SCRATCH0,Y sbrc SCRATCH0,7 ; high byte set? dec SCRATCH1 ; fill byte = 0xff subi YL,-16 rjmp SE128_GO ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Parse decimal string in XH:XL into 24-bit number in SCRATCH4:SCRATCH1:SCRATCH0 (SCRATCH0 low) ; PAT gives max length of string ; DELIM should be '.' if you want to parse decimal point, ; or 0 if you want to skip decimal point and treat decimal string as integer (45.123 -> 45123) ; or 0xff if you want to terminate on decimal point ; If decimal . found and DELIM set to '.' parse one fractional digit, so "45.67" becomes 456 ; SCRATCH0123 and CHAR used PARSE_DECIMAL: clt ; T flag marks last digit after '.' clr SCRATCH0 clr SCRATCH1 clr SCRATCH4 PSD_LOOP: ; Get digit ld CHAR,X+ tst DELIM brne PSD_NOT_SKIP_DECIMAL cpi CHAR,'.' ; skip decimal mode breq PSD_NEXTCHAR PSD_NOT_SKIP_DECIMAL: cp CHAR,DELIM brne PSD_NOT_DECIMAL set rjmp PSD_NEXTCHAR PSD_NOT_DECIMAL: subi CHAR,'0' brlo PSD_DONE cpi CHAR,10 brsh PSD_DONE ; multiply by 10 rcall MULX10_SCRATCH410 ; add digit clr SCRATCH2 add SCRATCH0,CHAR adc SCRATCH1,SCRATCH2 ; brts PSD_DONE ; out if decimal and one more digit PSD_NEXTCHAR: dec PAT brne PSD_LOOP PSD_DONE: ret ; multiply by 10 ( X*10 = X*2 + X*8 ) MULX10_SCRATCH410: lsl SCRATCH0 rol SCRATCH1 rol SCRATCH4 mov SCRATCH3,SCRATCH0 mov SCRATCH2,SCRATCH1 mov SCRATCH5,SCRATCH4 lsl SCRATCH3 rol SCRATCH2 rol SCRATCH5 lsl SCRATCH3 rol SCRATCH2 rol SCRATCH5 add SCRATCH0,SCRATCH3 adc SCRATCH1,SCRATCH2 adc SCRATCH4,SCRATCH5 ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Beginning of data ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .org 0x700 ; keep on a 256-page boundary ; 256-page is 00-7f or 80-ff because of 16-bit addressing ; Bitmap patterns for the seven-segment display ; Put them here to keep them all on one page DIGIT_PATTERNS: .db 0b11111100, \ 0b01100000, \ 0b11011010, \ 0b11110010, \ 0b01100110, \ 0b10110110, \ 0b10111110, \ 0b11100000, \ 0b11111110, \ 0b11110110 ; Keep these all on one 256-page DIGIT_DIRECTIONS: .db 0b10000000,0b00000010 ; 0 = A second digit .db 0b10000000,0b00000100 ; 1 = A third digit .db 0b01000000,0b00000100 ; 2 = B third digit .db 0b00100000,0b00000100 ; 3 = C third digit .db 0b00010000,0b00000100 ; 4 = D third digit .db 0b00010000,0b00000010 ; 5 = D second digit .db 0b00010000,0b00000001 ; 6 = D first digit .db 0b00001000,0b00000001 ; 7 = E first digit .db 0b00000100,0b00000001 ; 8 = F first digit .db 0b10000000,0b00000001 ; 9 = A first digit .db 0b00000000,0b00000000 ; null - no indicator ; Keep ACOS constants all on one 256-page ACOS_STP1: .db 0x03,0x00,0x00,0x00,0x00,0x03,0x4c,0x80 ; 3 * 2^56 ACOS_BIN1: .db 0xff,0x0c,0xcc,0xcc,0xcc,0xcd,0x48,0x00 ; -0.95 * 2^56 ACOS_STP2: .db 0x02,0x6b,0x3a,0x4d,0xf2,0x7b,0xf9,0xc0 ; acos(-0.75) ACOS_BIN2: .db 0xff,0x7f,0xff,0xff,0xff,0xff,0x73,0x40 ; -0.5 ACOS_STP3: .db 0x01,0xd2,0xcf,0x5c,0x7c,0x72,0xb9,0xc0 ; acos(-0.25) ACOS_BIN3: .db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ; 0 ACOS_STP4: .db 0x01,0x51,0x70,0x0e,0x0c,0x11,0x3b,0x40 ; acos(0.25) ACOS_BIN4: .db 0x00,0x80,0x00,0x00,0x00,0x00,0x8c,0xc0 ; 0.5 ACOS_STP5: .db 0x00,0xb9,0x05,0x1c,0x96,0x0e,0x15,0xc0 ; acos(0.75) ACOS_BIN5: .db 0x00,0xf3,0x33,0x33,0x33,0x32,0xb8,0x00 ; 0.95 ACOS_STP6: .db 0x00,0x33,0x33,0x33,0x33,0x33,0x6b,0x80 ; 0.2 ACOS_EOF: ; End keep all on one 256-page ; PI2 and NEGPI should be adjacent SINE_PI2: .db 0x01,0x92,0x1f,0xb5,0x44,0x42,0xd1,0x84 ; pi/2 * 2^56 SINE_NEGPI: .db 0xfc,0xdb,0xc0,0x95,0x77,0x7a,0x5c,0xf7 ; -pi * 2^56 ACOS_ERROR: .db 0x00,0x00,0x00,0x00,0x2a,0xf3,0x1d,0xc4 ; 0.00000001 * 2^56 ; Keep the cosine constants on one 256-page COS_P0: .db 0x00,0x7f,0xff,0xff,0xff,0xff,0x96,0xb4 ; 0.99999999999925182 * 2^55 COS_P1: .db 0xc0,0x00,0x00,0x00,0x10,0x5c,0x54,0xad ; -0.49999999997024012 * 2^63 COS_P2: .db 0x0a,0xaa,0xaa,0xa9,0xd6,0x26,0x95,0xa6 ; 0.04166666647338454 * 2^64 COS_P3: .db 0xff,0x49,0xf4,0xa3,0x55,0x73,0x43,0x8c ; -0.00138888841800042 * 2^65 COS_P4: .db 0x00,0x06,0x80,0x5e,0xa2,0x4b,0xb2,0x5e ; 0.000024801040648456 * 2^66 COS_P5: .db 0xff,0xff,0xdb,0x0e,0x96,0x1a,0x19,0xd5 ; -0.000000275246963843 * 2^67 COS_P6: .db 0x00,0x00,0x00,0x88,0xce,0x45,0xab,0x4d ; 0.000000001990785685 * 2^68 COS_EOF: ; end marker COS_PI: .db 0x03,0x24,0x3f,0x6a,0x88,0x85,0xa3,0x09 ; pi * 2^56 COS_INV_PI2: .db 0xa2,0xf9,0x83,0x6e,0x4e,0x44,0x15,0x2a ; 0.636619772366 * 2^64 ; Units conversion constants (keep in this order) DEGREES_CONV: .db 0x00,0x04,0x77,0xd1,0xa8,0x98,0xf7,0xb0 ; (1/57.2957795) * 2^56 MINUTES_CONV: .db 0x00,0x00,0x00,0x00,0x7c,0xef,0x7f,0x2e ; (1/(57.2957795*600000)) * 2^56 TWO_PI: .db 0x06,0x48,0x7e,0xd5,0x11,0x0e,0x04,0x80 ; two*pi * 2^56 RADIANS_CONV: .db 0x39,0x4b,0xb8,0x34,0x8f,0x77,0xe1,0x00 ; 57.2957795 * 2^56 YARDS_CONV: .db 0x00,0x6a,0x3e,0x0b,0x1d,0xe5,0xee,0xa0 ; radians to yards * 2^32 GPS_MATCH_PATTERN: .db low(GPS_PARSE_GPRMC),high(GPS_PARSE_GPRMC),"$GPRMC",0, \ low(GPS_PARSE_OTHER),high(GPS_PARSE_OTHER),0 GPRMC_PARSE_STRING: .db 1,(FLDLEN_GPRMC_TIME<<2)+high(FLDADR_GPRMC_TIME),low(FLDADR_GPRMC_TIME), \ 2,(FLDLEN_GPRMC_FIXVALID<<2)+high(FLDADR_GPRMC_FIXVALID),low(FLDADR_GPRMC_FIXVALID), \ 3,(FLDLEN_GPRMC_LATITUDE<<2)+high(FLDADR_GPRMC_LATITUDE),low(FLDADR_GPRMC_LATITUDE), \ 4,(FLDLEN_GPRMC_LATDIR<<2)+high(FLDADR_GPRMC_LATDIR),low(FLDADR_GPRMC_LATDIR), \ 5,(FLDLEN_GPRMC_LONGITUDE<<2)+high(FLDADR_GPRMC_LONGITUDE),low(FLDADR_GPRMC_LONGITUDE), \ 6,(FLDLEN_GPRMC_LONGDIR<<2)+high(FLDADR_GPRMC_LONGDIR),low(FLDADR_GPRMC_LONGDIR), \ 7,(FLDLEN_GPRMC_SPEED<<2)+high(FLDADR_GPRMC_SPEED),low(FLDADR_GPRMC_SPEED), \ 8,(FLDLEN_GPRMC_COURSE<<2)+high(FLDADR_GPRMC_COURSE),low(FLDADR_GPRMC_COURSE), \ 9,(FLDLEN_GPRMC_DATE<<2)+high(FLDADR_GPRMC_DATE),low(FLDADR_GPRMC_DATE), \ 0xff #ifdef SERIAL_DEBUG STR_READY: .db "GPS FINDER DEBUG MODE READY",13,10,0 STR_LATLON: .db "LAT ",0xff,high(FLDADR_GPRMC_LATITUDE), \ low(FLDADR_GPRMC_LATITUDE),FLDLEN_GPRMC_LATITUDE, \ " ",0xff,high(FLDADR_GPRMC_LATDIR), \ low(FLDADR_GPRMC_LATDIR),FLDLEN_GPRMC_LATDIR, \ "LON ",0xff,high(FLDADR_GPRMC_LONGITUDE), \ low(FLDADR_GPRMC_LONGITUDE),FLDLEN_GPRMC_LONGITUDE, \ " ",0xff,high(FLDADR_GPRMC_LONGDIR), \ low(FLDADR_GPRMC_LONGDIR),FLDLEN_GPRMC_LONGDIR,13,10,0 STR_FROM: .db 13,10,"FROM LAT ",0xff,high(LAT1),low(LAT1),FLDLEN_GPRMC_LATITUDE + 1, \ " LON ",0xff,high(LON1),low(LON1),FLDLEN_GPRMC_LONGITUDE + 1, \ 13,10,0 STR_TO: .db " TO LAT ",0xff,high(LAT2),low(LAT2),FLDLEN_GPRMC_LATITUDE + 1, \ " LON ",0xff,high(LON2),low(LON2),FLDLEN_GPRMC_LONGITUDE + 1, \ 13,10,0 STR_PARSE: .db "LAT1 ",0xfe,high(NB16),low(NB16),8," ", \ "LON1 ",0xfe,high(NB17),low(NB17),8," ",13,10, \ "LAT2 ",0xfe,high(NB18),low(NB18),8," ", \ "LON2 ",0xfe,high(NB19),low(NB19),8," ",13,10,0 STR_RESULT: .db "DISTANCE ",0xfe,high(NB22),low(NB22),8, \ " BEARING ",0xfe,high(NB9),low(NB9),8,13,10, \ "YARDS ",0xfd,high(NB14+3),low(NB14+3), \ " DEGREES ",0xfd,high(NB12),low(NB12),13,10, \ "GPS COURSE ",0xfd,high(NB13),low(NB13), \ " RELATIVE COURSE ",0xfd,high(NB13+2),low(NB13+2),13,10,0 #endif ; EOF