; GPS speed controlled switch ; Version 1.0 2009/02/16 ; Mike Ingle (email: mike at opengpstracker dot org) ; http://www.opengpstracker.org ; This is open source software. ; .include "m8def.inc" ; ATMEGA8L at 2 MHz for 9600 baud or 4800 baud ; Set CKSEL3..0 fuses to 0010 ; Four 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 - Digit 4 cathode ; PB4 - DOWN switch to ground ; PB5 - UP switch to ground ; PC5 - RELAY OUT active high ; RXD - GPS IN ; ; A ; ___ ; F | | B ; |___| ; | G | ; E |___| C . DP ; D ; ; Display codes: ; E1 = no datastream from GPS module ; E2 = datastream corrupt ; HI = over 99 mph ; -- = GPS module not tracking satellites ; right decimal point = update received ; middle decimal point = timed out ; all four decimal points = setting saved ; Unit will reboot every 10 seconds if no GPS module is attached .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 PIN_KEYS = PINB .equ KEY_DOWN = PINB4 .equ KEY_UP = PINB5 .equ PORT_RELAY = PORTC .equ RELAY_OUT = PORTC5 .equ KEY_DEBOUNCE_INTERVAL = 10 ; around a tenth of a second .equ KEY_REPEAT_DELAY = 80 ; about 0.8 second .equ KEY_REPEAT_SPEED = 10 ; about ten per second .equ SETTING_SAVE_DELAY = 5 ; seconds approx .equ DECIMAL_FLASH_INTERVAL = 80 ; 1/5 second .equ INTERRUPT_DIVISOR = 20 ; for 2 MHz ;.equ BAUD_DIVISOR = 12 ; for 9600 baud .equ BAUD_DIVISOR = 24 ; for 4800 baud .equ DEFAULT_WDR_COUNT = 1024 ; 10 seconds .equ CHARACTER_TIMEOUT = 255 .equ SPEED_CONVERSION_FACTOR = 3771 ; KNOT * 2 before conversion ; use SPEED_CONVERSION_FACTOR 6069 for KPH .equ GPS_DATASTREAM_WAIT_TIME = 12 ; 3 seconds #define EEPROM_SPEED_SETPOINT 0 ; address of speed setpoint in eeprom .def REG_ZERO = r0 ; register always zero; saves a lot of clr instructions .def INTERRUPT_SREG = r1 ; Storage for Status flags during interrupt .def SERIAL_POINTER = r3 ; Pointer into 256-byte serial receive buffer. .def PAT = r4 ; current pattern character .def BASE = r5 ; base of string being matched .def CHECKSUM = r6 .def DELIM = r7 .def VERIFY_CHECKSUM = r8 .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 FLAG1_REGISTER = r20 .def DELAY = r21 .def CHAR = r22 ; 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 .dseg .org 0x0060 DIGIT_1: .byte 1 ; keep all four digits DIGIT_2: .byte 1 ; | DIGIT_3: .byte 1 ; | DIGIT_4: .byte 1 ; on same page KEY_DEBOUNCE_COUNTER: .byte 1 KEY_REPEAT_COUNTER: .byte 1 SPEED_SETPOINT: .byte 1 SETPOINT_CHANGED_COUNTER: .byte 1 DECIMAL_CLEAR_COUNTER: .byte 1 RELAY_WAIT_COUNTER: .byte 1 ; counts 0,1,2 to prevent relay switching repeatedly RECEIVE_TIMEOUT: .byte 1 ; delay for receiving data WDR_COUNT_LOW: .byte 1 ; low byte of wdr countdown WDR_COUNT_HIGH: .byte 1 ; high byte of wdr countdown .equ PARSE_FIELDS_LENGTH = 64 PARSE_FIELDS: .byte PARSE_FIELDS_LENGTH DEBUG_LAST_BYTE: .byte 1 .org 0x0100 RECEIVE_BUFFER: .byte 256 ; 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 .equ FLDADR_GPGGA_FIXVALID = FLDADR_GPRMC_DATE + FLDLEN_GPRMC_DATE .equ FLDLEN_GPGGA_FIXVALID = 1 .equ FLDADR_GPGGA_SATS = FLDADR_GPGGA_FIXVALID + FLDLEN_GPGGA_FIXVALID .equ FLDLEN_GPGGA_SATS = 2 .equ FLDADR_GPGGA_ALT = FLDADR_GPGGA_SATS + FLDLEN_GPGGA_SATS .equ FLDLEN_GPGGA_ALT = 5 ; Too long? .equ FLDADR_GPRMC_SPEED_NUMERIC = FLDADR_GPGGA_ALT + FLDLEN_GPGGA_ALT .equ FLDLEN_GPRMC_SPEED_NUMERIC = 2 ; Beginning of code: interrupt vectors ; for the ATMEGA8L .cseg rjmp V_RESET rjmp V_INT0 rjmp V_INT1 rjmp V_TIMER2_COMP rjmp V_TIMER2_OVF rjmp V_TIM1_CAPT rjmp V_TIM1_COMPA rjmp V_TIM1_COMPB rjmp V_TIM1_OVF rjmp V_TIM0_OVF rjmp V_SPI_STC rjmp V_USART_RXC rjmp V_USART_UDRE rjmp V_USART_TXC rjmp V_ADC rjmp V_EE_RDY rjmp V_ANA_COMP rjmp V_TWI rjmp V_SPM_RDY ; 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 #define DIGIT_PATTERN_DASH 0b00000010 #define DIGIT_PATTERN_H 0b01101110 #define DIGIT_PATTERN_I 0b01100000 #define DIGIT_PATTERN_E 0b10011110 #define DIGIT_PATTERN_1 0b01100000 #define DIGIT_PATTERN_2 0b11011010 V_INT0: V_INT1: V_TIMER2_OVF: V_TIM1_CAPT: V_TIM1_COMPA: V_TIM1_COMPB: V_TIM1_OVF: V_TIM0_OVF: V_SPI_STC: V_USART_UDRE: V_USART_TXC: V_ADC: V_EE_RDY: V_ANA_COMP: V_TWI: V_SPM_RDY: 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 YH clr SERIAL_POINTER ldi YL,1 sts SETPOINT_CHANGED_COUNTER,REG_ZERO sts RELAY_WAIT_COUNTER,REG_ZERO ; 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< 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 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_SCRATCH10 ; 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_SCRATCH10: lsl SCRATCH0 rol SCRATCH1 mov SCRATCH3,SCRATCH0 mov SCRATCH2,SCRATCH1 lsl SCRATCH3 rol SCRATCH2 lsl SCRATCH3 rol SCRATCH2 add SCRATCH0,SCRATCH3 adc SCRATCH1,SCRATCH2 ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Units conversion ; Call with number in SCRATCH1:SCRATCH0 (from PARSE_DECIMAL) ; Conversion factor in XH:XL ; Result returned in ZH:ZL ; Does a 16x16 bit multiply, retaining only the high order word of the product ; This took quite a while to figure out! The trick is to move carry into MSB. UNITS_CONVERT_X4: lsl SCRATCH0 rol SCRATCH1 UNITS_CONVERT_X2: lsl SCRATCH0 rol SCRATCH1 UNITS_CONVERT: ldi SCRATCH2,16 clr ZH clr ZL UC_LOOP: lsr XH ror XL brcc UC_ZERO add ZL,SCRATCH0 adc ZH,SCRATCH1 UC_ZERO: ror ZH ; carry from previous addition becomes MSB ror ZL dec SCRATCH2 brne UC_LOOP ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Reads into CHAR the next character from the serial buffer (pointer is Y) ; Spins if no character available ; Returns T flag set if RECEIVE_TIMEOUT exceeded while spinning ; DISABLED WDR!!! (was: watchdog reset while waiting, only when DELAY counts down) ; If interrupts fail, system will reset. GET_NEXT_CHAR: brts GNC_TIMEDOUT ; so we don't have to check before every call ldi DELAY,CHARACTER_TIMEOUT GNC_WAITCHAR: cp YL,SERIAL_POINTER ; character pending? brne GNC_HAVECHAR ; yes cp CHAR,DELAY ; has it counted down? breq GNC_NOTICK ; XXwdr mov CHAR,DELAY GNC_NOTICK: tst DELAY brne GNC_WAITCHAR ; time up? GNC_TIMEDOUT: set ; Report error ret GNC_HAVECHAR: ld CHAR,Y+ ldi YH,1 ; wrap around SERIAL_SEND_DONE: ; XXwdr ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; UART receive interrupt V_USART_RXC: in INTERRUPT_SREG,SREG push SCRATCH0 push ZH push ZL ; ldi ZH,1 ; buffer is 0x100-0x1ff mov ZL,SERIAL_POINTER in SCRATCH0,UDR st Z,SCRATCH0 inc SERIAL_POINTER ; bump pointer after saving; pointer indicates next byte ; pop ZL pop ZH pop SCRATCH0 out SREG,INTERRUPT_SREG reti ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Main periodic interrupt V_TIMER2_COMP: TIMER_INTERRUPT: in INTERRUPT_SREG,SREG push SCRATCH0 push SCRATCH1 push SCRATCH2 push SCRATCH3 push ZL push ZH ; ; Digit refresh ; in SCRATCH0,PORT_DIGIT_SELECT andi SCRATCH0,0b11110000 out PORT_DIGIT_SELECT,SCRATCH0 ; turn off all LEDs ; sbrc FLAG1_REGISTER,FLAG1_REFRESH_DIGIT_HIGH rjmp TIREF_DIGIT_34 sbrc FLAG1_REGISTER,FLAG1_REFRESH_DIGIT_LOW rjmp TIREF_DIGIT_2 ; TIREF_DIGIT_1: sbr FLAG1_REGISTER,1<