;************************************************************************** ; DGPS.ASM for a 16F84 Version 1.0 ; ; This program runs the DGPS receiver. ; Copyright (c) 1998, Jim Bixby. All rights reserved ; This program can be freely used for non-commercial purposes, and ; carries no warranty of any kind, including any implied warranty ; of fitness for any particular purpose. ; Acknowledgement and thanks to Rich Heineck for the code for the ; parity calculation and word sync algorithm. ;************************************************************************** LIST P=16F84, R=DEC INCLUDE __CONFIG _WDT_OFF & _XT_OSC ERRORLEVEL -302 ;kill the annoying bank select message ;-------------------------------------------------------------------------- ;External connections equates ;-------------------------------------------------------------------------- ;PORTA pin assignments ;-------------------------------------------------------------------------- #define SC104_Pin PORTA,0 ;SC104 output INVERTED_104 EQU 0 ;1 if SC-104 output is thru an inverter #define Synth_Clk PORTA,1 ;Shift clock for the PLL #define LCD_RS PORTA,2 ;RS pin on parallel LCD display #define Select4800 PORTA,2 ;Jumper input read at init time -- ;selects the SC-104 output baud rate ;0: 9600 baud 1:4800 baud #define Select4800_Bit TRISA,2 ;used to reverse direction of IO pin #define LCD_E PORTA,3 ;E pin on parallel LCD display #define Synth_Enb PORTA,4 ;ENB pin on the PLL ; These defines establish the output signal levels #define Synth_Enb_High bsf Synth_Enb #define Synth_Enb_Low bcf Synth_Enb #define Synth_Clk_High bsf Synth_Clk #define Synth_Clk_Low bcf Synth_Clk #define Synth_Data_High bsf Synth_Data #define Synth_Data_Low bcf Synth_Data PAGE ;-------------------------------------------------------------------------- ;PORTB pin assignments ;-------------------------------------------------------------------------- #define CarrierVCO PORTB,0 ;FM demod vco input - used for ;checking whether it is locked #define Detector PORTB,1 ;Detector output signal from ;the receiver. It is the limited ;input carrier from IF2. Used ;for checking carrier PLL lock #define Synth_Data PORTB,1 ;Serial data to PLL #define Synth_Data_Bit TRISB,1 ;used to reverse direction of pin #define CarrierDet PORTB,2 ;Low if receiver detects ;a carrier #define SynthLock PORTB,3 ;Low if freq synth is locked #define RcvrData PORTB,4 ;Serial data input from receiver #define SkipIfDataOne btfss Bit,4 #define SkipIfDataZero btfsc Bit,4 #define ModeSwitch PORTB,5 ;'MODE' pushbutton - not used ; pressed = high #define DownSwitch PORTB,6 ;'DOWN' pushbutton - pressed = high #define UpSwitch PORTB,7 ;'UP' pushbutton - pressed = high SwMask EQU B'11000000' ;Where there are switches on PORTB DataMask EQU B'00010000' ;Where RcvrData is on PORTB PAGE ;-------------------------------------------------------------------------- ;Serial Output and LCD Equates ;-------------------------------------------------------------------------- K9600 EQU 30 ;for 9600 baud delay K4800 EQU 65 ;for 4800 baud delay IF INVERTED_104 #define SC104_MARK bsf SC104_Pin #define SC104_SPACE bcf SC104_Pin ELSE #define SC104_MARK bcf SC104_Pin #define SC104_SPACE bsf SC104_Pin ENDIF ;the above #defines set up to transmit on a line ;where the quiescent value is low, like a PC ;would want on its COM input line DispRow1 EQU 128 ;base address of row 1 DispRow2 EQU 192 ;base address of row 2 FreqPos EQU 0 ;Freq starts in first char of first row FreqWidth EQU 3 ;Three digits used to display frequency IDLabelPos EQU 6 ;"ID" label starts in char 7, row 1 StatPos EQU 9 ;Status identifiers start in 10th char ;The seven sync/lock status positions on the LCD must stay in the order ;specified below, or LCD updating will get screwed up SynthStatPos EQU 9 ;Synthesizer Status CarrierDetPos EQU 10 ;Carrier Detect CarrierStatPos EQU 11 ;Carrier Lock status BitSyncPos EQU 12 ;Bit Sync Lock status WordSyncPos EQU 13 ;Word Sync status FrameSyncPos EQU 14 ;Frame Sync Status ParityPos EQU 15 ;Parity Good/Error BitRatePos EQU 0 ;bit rate starts in first char of second row BitRateWidth EQU 2 ;two digits used to display bit rate ;(the trailing 0 is fixed) IDPos EQU 5 ;Station ID starts in char 6 row 2 IDWidth EQU 3 ;three digits used to display Station ID GoodChar EQU "+" ;char for "good" status BadChar EQU "X" ; and for bad status PAGE ;-------------------------------------------------------------------------- ; Processor equates ;-------------------------------------------------------------------------- #define _C STATUS,0 #define _Z STATUS,2 ;-------------------------------------------------------------------------- ; Bit, Word and Frame Synchronizer Equates ;-------------------------------------------------------------------------- BitWidth EQU 78 ;one bit width SyncCenter EQU 38 ;middle of bit ConfWindow EQU 16 ;TMR0 counts either side of middle, to say ;a bit edge was "good" instead of "bad" Phase_Limit EQU 7 ;max amount to tweak TMR0 on each ;detected bit edge, if out of sync ;(if in sync, only steps of 1 are made) BitSyncUp EQU 1 ;Up and Down count values BitSyncDown EQU -7 ;for Bit Sync Lock confidence register WordSyncUp EQU 16 WordSyncDown EQU -64 FrameSyncUp EQU 64 FrameSyncDown EQU -120 CarrierSyncUp EQU 4 CarrierSyncDown EQU -32 Preamble EQU B'01100110' ;Preamble marking start of frame ;where the parity bits are in received data register Data_A D25 EQU 7 D26 EQU 6 D27 EQU 5 D28 EQU 4 D29 EQU 3 D30 EQU 2 ;where D29 and D30 from the previous word are, in Data_E D29P EQU 1 D30P EQU 0 ;-------------------------------------------------------------------------- ; Other equates ;-------------------------------------------------------------------------- IF1 EQU 122 ;First IF is at 122 KHz IF2 EQU 3 ;Second IF is at 3 KHz LO1_Base EQU 285+IF1 ;First-LO freq for lower end of beacon band Synth2 EQU 1000 ;Second divider in synth used to make ;1 khz reference from 1 MHz AuxRefCntr EQU 0x4FA0 ;This sets the AuxRefCntr to zero (the actual ;value is a don't-care, since we don't use it) ;and tells the PLL to put the output of the ;12-bit Ref div-by-n counter on fR1 RefRegCntl EQU 0x3000 ;'OR' this value into the Ref div-by-n value ;to tell the PLL Phase detectors to ;listen to fR1 (see MC145162 data sheet) RefFreq EQU 4000+RefRegCntl ;Reference Freq in KHz, plus the ;control bits PLL_Cntl EQU 0xA0 ;PLL control word -- sets Test Mode, turns off ;ADin pin, sets RefOut to div-by-4 PAGE ;-------------------------------------------------------------------------- ; Variables ;-------------------------------------------------------------------------- cblock 0x0C LO1:2 ;Two bytes for first LO div-by-n value Chan ;Current Channel Number BitSync ;Bit Sync Lock confidence counter. Count ;up by SyncUpCount to max of 255 when good bit ;sync phase is detected, down by SyncDownCount ;when bad bit sync phase detected. BitRate ;1:200bps, 2:100bps, 3:50bps OldBitRate ;Last bit rate displayed. WordSync ;Word Sync Confidence counter, like BitSync FrameSync ;Frame Sync Confidence Counter, like BitSync CarrierSync ;Carrier Sync Confidence Counter, like BitSync Bit ;Value of last bit received Flags ;1-bit Flags registers, for various things Flags2 ;This one is *not* cleared during init. OldSync ;Flag bits for the previous sync state CurrSync ;Flag bits for the current sync state LCD_char ;buffer to hold a char to send to the LCD Bit_K ;holds delay count for SC104 output SC104_cntr ;counter for sending out the SC104 char SC104_char ;buffer to hold a char to send to SC104 out SC104_in ;accum to capture the six incoming bits SC104_in_cntr ;where we count up the six incoming bits Data_E ;Registers for the parity algorithm and Data_D ;word sync. Data_X holds the incoming Data_C ;stream and it is shifted on every incoming Data_B ;bit Data_A ThisWord_D ;ThisWord_X holds the sync'd 24bit ThisWord_C ;data word, polarity corrected. When in ThisWord_B ;sync, it is changed only every 30 bit word Parity ;Where parity is computed PriorWord_D PriorWord_C PriorWord_B Data_Count ;Counts bits shifted into Data_X SeqNum ;Sequence number read from input data FrameLength ;Frame length read from input data WordNumber ;Which word we just received in the frame ; Word 0 is the one with the preamble ; Counts up from there StaID:0 ;Two bytes for Station ID, from input data StaID_MSB ; Most significant byte StaID_LSB ; Least significant byte OldStaID:0 ;Last Station ID put in LCD OldStaID_MSB OldStaID_LSB RegA ;used by the BCD converter RegB RegC temp ;temporary register used in interrupt handler tempbg ;temporary registers used in background routine delay_cntr1 ;used in delay routines delay_cntr2 Save_W ;used by the interupt handler Save_Status Save_FSR endc ;Flags definitions: #define BadSyncFlag Flags,0 ;1 if a BitEdge is detected ;out of sync zone #define SC104Flag Flags,1 ;1 to request a char send ;on the SC104 out port #define LCDFlag Flags,2 ;1 to request a char send ;to the LCD #define LCDFreqUpdRqst Flags,4 ;rqst from int handler to ;background to update the LCD ;Freq display #define GoodFSTest Flags,6 ;1 if FS test criteria met on a test #define TrialWordSync Flags2,1 ;1 if we have found good parity ;and want to keep testing this position #define TrialFrameSync Flags2,2 ;1 if we have found the preamble, ;and want to keep testing this position #define RS Flags2,3 ;0 to send a command to the LCD ;1 to send a character to display ;CurrSync and OldSync flags registers definitions ;These must stay in the order below, or LCD updating will get screwed up SynthFlag EQU 0 CarrierDetFlag EQU 1 CarrierFlag EQU 2 BitSyncFlag EQU 3 WordSyncFlag EQU 4 FrameSyncFlag EQU 5 GoodParityFlag EQU 6 ;------------------------------------------------------------------------- ;Compute how much data memory is left ;------------------------------------------------------------------------- cblock EndMem:0 ;define the end of memory endc ;------------------------------------------------------------------------- ;Data EEPROM initialization ;------------------------------------------------------------------------- ORG 0x2100 ;where data eeprom starts VERSION de "Ver 1.0 DGPS.ASM" de "copyright Jim Bixby 1998" LastChan de 17 ;last channel the receiver was ; locked to. It is the kHz offset ; from 285kHz (Chan 17 is 302 kHz, ; the Point Loma frequency) EndData de 0 ;last free byte of data EEPROM ;------------------------------------------------------------------------- ; Calculate remaining file space and data eeprom, for reference ;------------------------------------------------------------------------- MemLeft EQU 0x50-EndMem ;number of files registers unused EELeft EQU 0x2140-EndData ;amount of data eeprom unused PAGE ORG 0x000 goto Start ORG 0x004 goto int ;-------------------------------------------------------------------------- ; Parity_Tbl -- determines the parity of the nibble passed in W. ; **** The upper nibble must be zero ******* ; Returns in W: ; 0: even parity ; 1: odd parity ;-------------------------------------------------------------------------- Parity_Tbl addwf PCL,f retlw 0 ;0000 retlw 1 ;0001 retlw 1 ;0010 retlw 0 ;0011 retlw 1 ;0100 retlw 0 ;0101 retlw 0 ;0110 retlw 1 ;0111 retlw 1 ;1000 retlw 0 ;1001 retlw 0 ;1010 retlw 1 ;1011 retlw 0 ;1100 retlw 1 ;1101 retlw 1 ;1110 retlw 0 ;1111 ;----------------------------------------------------------------------------- ;The next two tables return the first and second Bit Rate characters ; Enter with the bit rate in W ;----------------------------------------------------------------------------- BR_First addwf PCL,1 retlw " " ;bit rate can't be zero retlw "2" retlw "1" retlw " " BR_Second addwf PCL,f retlw " " retlw "0" retlw "0" retlw "5" ;------------------------------------------------------------------------------ ; ChkParity -- a MACRO ; ; Verify the specified parity bit matches the parity of the masked 24 data ; bits and previous "polarity" bit. Parity calculation is done a nibble ; at a time using a lookup table. ; ; Inputs: ; Mask 3 byte mask to apply to 24-bit data word ; ParBit The parity bit being tested ; PolBit D29' or D30' from previous word ; ; Return: ; Jumps to 'badparity' if parity test fails ; Falls through if parity matches ;------------------------------------------------------------------------------ ChkParity MACRO Mask, ParBit, PolBit LOCAL even, match clrf Parity ; Parity "accumulator". Initialize btfsc Data_E,PolBit ; it with D29 or D30 from the incf Parity,f ; previous frame swapf ThisWord_D,w ; Begin with new block d1..d4 andlw (Mask >> 20) & 0x0F ; Bits go in low nibble of W call Parity_Tbl xorwf Parity,f ; Parity ^= W movf ThisWord_D,w ; d5..d8 andlw (Mask >> 16) & 0x0F call Parity_Tbl xorwf Parity,f swapf ThisWord_C,w ; d9..d12 andlw (Mask >> 12) & 0x0F call Parity_Tbl xorwf Parity,f movf ThisWord_C,w ; d13..d16 andlw (Mask >> 8) & 0x0F call Parity_Tbl xorwf Parity,f swapf ThisWord_B,w ; d17..d20 andlw (Mask >> 4) & 0x0F call Parity_Tbl xorwf Parity,f movf ThisWord_B,w ; d21..d24 andlw Mask & 0x0F call Parity_Tbl xorwf Parity,f bz even ; Test parity, skip next if 1 btfss Data_A,ParBit ; Odd parity. Verify parity bit matches. goto badparity goto match even btfsc Data_A,ParBit ; Even. Verify parity bit matches. goto badparity match ENDM PAGE ;-------------------------------------------------------------------------- ; Interrupt service routines ;-------------------------------------------------------------------------- int movwf Save_W ;interrupted -- save W movfw STATUS ;and STATUS movwf Save_Status movfw FSR ;save the FSR movwf Save_FSR btfss INTCON,INTE ;is the CarrierVCO int enabled? goto _int_chk_tmr ; no-go check other sources btfss INTCON,INTF ; yes - was that the int? goto _int_chk_tmr ; no ;here to service an RB0/INT interrupt -- do one measurement on the lock ;status of the Carrier PLL VCO movlw CarrierSync ;set up for a confidence test movwf FSR ;on CarrierSync movlw CarrierSyncUp ;assume a good test btfsc Detector ;and skip if it was good movlw CarrierSyncDown ;get the 'bad' test value call ConfCntr ;go do the test btfss _C ;skip if test finished goto _exit_carrier ;we have determined carrier lock status -- set the flag ;accordingly bsf CurrSync,CarrierFlag ;assume it passed btfsc _Z ;and skip if it did bcf CurrSync,CarrierFlag ;no - it failed _exit_carrier bcf INTCON,INTE ;disable another interrupt goto _int_restore _int_chk_tmr btfss INTCON,T0IF ;see if it was the Timer (bit interval) goto _int_chk_edge ;no, go see if a bit edge or switch ;press happened ;Interrupt handler for TMR0, the bit interval timer movlw -BitWidth ;reload the timer movwf TMR0 bcf INTCON,T0IF ;clear the flag ;sample and process the incoming bit ;send them out, six at a time movfw PORTB ;read the bit andlw DataMask ;and save it, in position, in movwf Bit ;Bit bcf _C ;clear the carry prior to shifting rrf SC104_in,f ;make room for the new bit movlw 0x20 ;this is where the new bit goes SkipIfDataZero ;skip if the received bit is zero iorwf SC104_in,f ;if 1, set the bit decfsz SC104_in_cntr,f ;see if we have 6 bits yet goto Parity_Test ;not yet -- go calculate parity ;we have received six bits -- send them out the SC104 port movfw SC104_in ;set bit six and iorlw 0x40 ;send the char movwf SC104_char bsf SC104Flag ;tell the handler to send it movlw 6 ;reset the counter movwf SC104_in_cntr ;and clear the bit accumulator clrf SC104_in PAGE ;-------------------------------------------------------------------- ; Do the parity calculation and set/maintain WordSyncFlag ;-------------------------------------------------------------------- Parity_Test movlw -1 ;put the received bit addwf Bit,w ;into Carry rlf Data_A,f ;then shift into Data rlf Data_B,f rlf Data_C,f rlf Data_D,f rlf Data_E,f ;If we are out of sync, or 30th bit was shifted in ;test the parity to see if it is good btfss TrialWordSync ;if not trying this position goto dotest ;go test on every bit decfsz Data_Count,f ;else, only test on 30th bit goto FSDone ;no test required at this time ;and not time to test FS either dotest movlw 30 ;reset the bit counter movwf Data_Count ;save data from prior word, then ;make copy of data, with correct polarity movfw ThisWord_B movwf PriorWord_B movfw ThisWord_C movwf PriorWord_C movfw ThisWord_D movwf PriorWord_D movfw Data_B movwf ThisWord_B movfw Data_C movwf ThisWord_C movfw Data_D movwf ThisWord_D btfss Data_E,D30P ;use previous word's D30 goto dopar ;to invert data comf ThisWord_B,f comf ThisWord_C,f comf ThisWord_D,f ; Calculate parity using the signal spec algorithm and compare to ; each of the parity bits. Note: A steady stream of 0's will pass ; the parity test. dopar EXPAND ChkParity 0xEC7CD2,D25,D29P NOEXPAND ChkParity 0x763E69,D26,D30P ChkParity 0xBB1F34,D27,D29P ChkParity 0x5D8F9A,D28,D30P ChkParity 0xAEC7CD,D29,D30P ChkParity 0x2DEA27,D30,D29P ; Now make sure everything's not all 0's or 1's movfw Data_A iorwf Data_B,w iorwf Data_C,w iorwf Data_D,w btfsc _Z goto badparity movfw Data_A andwf Data_B,w andwf Data_C,w andwf Data_D,w xorlw 0xFF btfsc _Z goto badparity ; We get here if the parity check passed bsf CurrSync,GoodParityFlag btfss TrialWordSync ;if we are trying this position, run ;the confidence counter goto start_trial ;else go set flag to try this position ;30 bits from now movlw WordSync ;point FSR at the WordSync conf. counter movwf FSR movlw WordSyncUp call ConfCntr ;go count the counter up btfsc _C bsf CurrSync,WordSyncFlag ;overflow - say we are in sync goto WSDone start_trial bsf TrialWordSync ;show that we want to try this spot DropSync movlw 0x80 ;re-init the confidence counters movwf WordSync movwf FrameSync bcf CurrSync,WordSyncFlag ;can't possibly be in word sync now bcf CurrSync,FrameSyncFlag ;or in frame sync bsf StaID,7 ;mark station ID as invalid goto WSDone ; We get here if the parity test failed badparity bcf CurrSync,GoodParityFlag btfss TrialWordSync ;skip if we testing a trial location goto WSDone ;no, exit ; We get here if are testing a trial position, but a bad parity test movlw WordSync ;so run the confidence counter movwf FSR movlw WordSyncDown call ConfCntr ;go count the counter down btfss _C ;if inbounds, exit goto WSDone bcf TrialWordSync ;underflow: setup to start hunting anew goto DropSync WSDone PAGE ;--------------------------------------------------------------------- ; Frame synchronization code, to read the station ID ;--------------------------------------------------------------------- FrSync btfss CurrSync,WordSyncFlag ;if not in word sync goto FSDone ;don't try frame sync incf WordNumber,f btfss TrialFrameSync ;are we trying a position? goto FSNotTrying ;no ;we are testing a location -- see if now is the time movfw WordNumber xorwf FrameLength,w btfss _Z goto FSDone ;nope - don't check incf SeqNum,f ;right time, so inc. seq num movlw Preamble ;see if the preamble is there xorwf PriorWord_D,w btfss _Z goto BadFS ;nope - test failed movfw ThisWord_C ;compare seq numbers xorwf SeqNum,w andlw 0x07 btfss _Z goto BadFS ;nope - test failed bsf GoodFSTest ;remember this test passed movlw FrameSyncUp ;passed - go up goto DoFSConf BadFS bcf GoodFSTest movlw FrameSyncDown bsf StaID,7 DoFSConf movlw FrameSync ;do the conf count movwf FSR call ConfCntr btfss _C ;over/underflow? goto Exit_FS ;no - finished for now btfsc _Z ;skip if overflow goto FSReject ;underflow-reject this spot bsf CurrSync,FrameSyncFlag ;success - show Frame Sync goto Exit_FS ;and do the wrap up FSReject bcf CurrSync,FrameSyncFlag ;out of sync bcf TrialFrameSync ;start all over movlw 0x80 ;reset conf counter movwf FrameSync bsf StaID,7 ;not in sync, so StaID is bad goto FSDone FSNotTrying movlw Preamble xorwf PriorWord_D,w ;Preamble? btfss _Z goto FSDone ;no-exit movlw 0x80 ;init conf counter movwf FrameSync bsf StaID,7 bsf TrialFrameSync ;show we want to test this spot Exit_FS clrf WordNumber ;this is word 0 movfw ThisWord_C ;save sequence number andlw 0x07 movwf SeqNum movfw ThisWord_B ;and FrameLength movwf FrameLength rrf FrameLength,f rrf FrameLength,f rrf FrameLength,w andlw 0x1F addlw 2 movwf FrameLength ;this is when the ;next start of frame is bsf StaID,7 btfss CurrSync,FrameSyncFlag ;if in sync goto FSDone btfss CurrSync,GoodParityFlag ;and good parity goto FSDone btfss GoodFSTest ;and the FS criteria was met goto FSDone movfw PriorWord_B ;save the Station ID movwf StaID_LSB movfw PriorWord_C andlw 0x03 movwf StaID_MSB goto FSDone FSDone PAGE ;--------------------------------------------------------------------- ; Serial output routine. ; To send a char to the LCD, put the char in LCD_char. ; For a control char, clear flag RS ; For a char to be displayed, set flag RS ; Then set flag LCD_char, and wait until this ; routine comes along, sends the char, and clears ; the flag. LCDFlag is set *only* in the background ; code. ; To send a char to the SC104 serial out port, put the ; char in SC104_char and set flag SC104Flag. After ; sending, this routine clears SC104Flag. SC104Flag ; is set *only* by the TMR0 interrupt service routine. ; The SC104 baud rate is determined by Bit_K, which in turn is ; set during Init, after looking at the 4800/9600 jumper ; on the receiver board. ;--------------------------------------------------------------------- SerOut btfss LCDFlag ;LCD character waiting? goto _chk_SC104 ;no, go check for SC104 output BANKSEL TRISB ;switch port to output; clrf TRISB BANKSEL PORTB bcf LCD_E ;make sure E is low, and set RS bcf LCD_RS ;appropriately btfsc RS bsf LCD_RS lcdsend movfw LCD_char ;output the char movwf PORTB bsf LCD_E ;and strobe the E pin bcf LCD_E BANKSEL TRISB ;and switch the port back to inputs comf TRISB,f BANKSEL PORTB bcf LCDFlag ;note that the char has been sent _chk_SC104 btfss SC104Flag ;if SC104 flag set, send a char goto SerDone ;nothing to send _do_serial movlw 9 ;8 data + start movwf SC104_cntr SC104_SPACE ;set the start bit goto $+1 ;equalize delay paths goto $+1 nop _baud_delay movfw Bit_K ;baud delay timer movwf temp _ser_loop decfsz temp,f ;wait one baud goto _ser_loop decfsz SC104_cntr,f ;count down one bit goto _next_bit goto _SC104_Done _next_bit rrf SC104_char,f ;put the bit to send in the carry btfsc _C goto _SC_Mark ;send a Mark for a 1 SC104_SPACE nop ;delay equalizer goto _baud_delay _SC_Mark SC104_MARK goto _baud_delay _SC104_Done goto $+1 nop bcf SC104Flag ;note char has been sent goto $+1 SC104_MARK ;send the stop bit SerDone PAGE ;-------------------------------------------------------------------------- ;_int_chk_edge: Interrupt handler for switch presses or a bit edge received ;-------------------------------------------------------------------------- _int_chk_edge btfss INTCON,RBIF ;see if switch press or bit edge goto exit_int ;nope -- exit the handler ;switch or bit edge -- check bit edge first movfw PORTB ;read RcvrData andlw DataMask ;pick out the right bit xorwf Bit,w ;xor with last bit received -- result btfsc _Z ;non-zero if bit changed goto _int_chk_switch ;no change: go check for a switch press ;Interrupt handler for BitEdge - just process the edge event, not the bit bcf BadSyncFlag ;start off assuming sync is good movlw BitSync ;point FSR at the BitSync conf cntr movwf FSR movfw TMR0 ;read the timer addlw SyncCenter ;check the phase movwf temp ;and save the error btfss _C ;skip if phase lagging goto _leading ; phase is lagging or exact -- temp has the phase error btfsc _Z ;if exact, do nothing goto _end_adjust ; Adust timer, proportional to error value (but not more than three) addlw -Phase_Limit-1 ;See if outside of PhaseLimit btfss _C ;skip if so goto _lag_adj ;otherwise, just go adjust movfw temp ;get the error back addlw -ConfWindow btfsc _C ;if >Window, declare bsf BadSyncFlag ;BitEdge out of bounds movlw Phase_Limit ;Clip to adjustment limit movwf temp _lag_adj movfw temp btfsc CurrSync,BitSyncFlag ;only move one if in sync movlw 1 subwf TMR0,f goto _end_adjust _leading ; phase is leading and outside of zone -- increment timer ; temp has the phase error, and is negative comf temp,f ;make positive incf temp,f movfw temp ;clip at Phase_Limit addlw -Phase_Limit-1 btfss _C goto _lead_adj movfw temp addlw -ConfWindow btfsc _C ;if >Window, declare bsf BadSyncFlag ;BitEdge out of bounds movlw Phase_Limit movwf temp _lead_adj movfw temp ;and adjust the phase btfsc CurrSync,BitSyncFlag ;but only one if in sync movlw 1 addwf TMR0,f ;done with phase adjustment -- update sync confidence counter _end_adjust movlw BitSyncUp ;get amount to move counter btfsc BadSyncFlag movlw BitSyncDown call ConfCntr ;and go move it btfss _C ;Carry means under/overflow goto _exit_BitEdge btfss _Z ; Z means underflow: out of sync goto _into_bitsync ;Test ended, and concluded no sync to be found bcf CurrSync,BitSyncFlag ;declare out of bit sync decfsz BitRate,w ;Move to next rate goto $+2 movlw 3 movwf BitRate ;rollover, back to 50 bps call SetBitRate ;go set the next rate to try goto _exit_BitEdge _into_bitsync bsf CurrSync,BitSyncFlag ;declare sync _exit_BitEdge ;fall into checking the pushbutton switches PAGE ;--------------------------------------------------------------------------- ; _int_chk_switch: Interrupt handler for pushbutton presses ; (The Mode switch is ignored and is available for possible future ; expansion of the code) ;--------------------------------------------------------------------------- _int_chk_switch movlw 41 ;maximum channel number plus 1 btfss UpSwitch ;see if UP pressed goto _checkDown ;nope call UpOneChan ; UP pressed -- increment Chan goto _newchan _checkDown btfss DownSwitch ;check the DOWN switch goto exit_int ;nope, exit call DownOneChan ; DOWN pressed -- decrement Chan ; Now, update the PLL and LCD with the new channel number _newchan call Set_PLL bsf LCDFreqUpdRqst ;Ask the background to display ;the new frequency ; Wait for switch to be released _swloop movfw PORTB andlw SwMask ;all switches should be off btfss _Z ;skip if they are goto _swloop ;and test again if not movlw 4 ;wait 40 ms call Delay_Long movfw PORTB andlw SwMask ;all switches should be off btfss _Z ;skip if they are goto _swloop ;and test again if not PAGE ;---------------------------------------------------------------------------- ; exit_int -- to exit the interrupt handler ;---------------------------------------------------------------------------- exit_int bsf INTCON,RBIE ;make sure switch ints enabled bcf INTCON,RBIF ;and clear the source flag bsf INTCON,INTE ;enable a Carrier interrrupt next _int_restore movfw Save_FSR movwf FSR swapf Save_W,f ;tricky move to restore W movfw Save_Status ;get the saved status movwf STATUS ;and restore it swapf Save_W,w ;restore W w/o affecting Z ;of status bcf INTCON,INTF ;clear Carrier int. flag retfie PAGE ;---------------------------------------------------------------------------- ; Some routines called from the interrupt handler ;---------------------------------------------------------------------------- DownOneChan decf Chan,f btfss Chan,7 ;see if sign went negative return ;nope, all done movlw 40 movwf Chan ;underflow -- wrap around to 40 return UpOneChan incf Chan,f subwf Chan,w ;test for over max (41 is in w) btfsc _Z ;skip if not over movwf Chan ;went over; wrap around to zero return ;-------------------------------------------------------------------------------- ; ConfCntr -- generalized confidence counter routine ; Enter with FSR pointing to the counter to be updated ; and the amount to change in W (signed byte) ; Result is returned in the Zero and Carry bits in the ; status register as follows: ; Carry clear: counter stayed above 0 and below 255 ; ; Zero bit indeterminant ; ; Carry set: counter overflowed (tried to go over 255) or ; counter underflowed (tried to go below 0) ; ; Zero set: Underflow -- counter set to 0 ; Zero clear: Overflow -- counter set to 255 ;--------------------------------------------------------------------------------- ConfCntr movwf temp ;save delta so we can remember ;the direction we moved addwf INDF,f ;update the counter btfss temp,7 ;skip if we moved down goto _conf_up btfsc _C ;going down: no carry means underflow goto _inbounds clrf INDF ;zero the counter (also sets Z:underflow) bsf _C ;set carry to show over/underflow return _conf_up btfss _C ;going up: carry means overflow goto _inbounds clrf INDF ;set counter to 255 comf INDF,f ; (also clears Z: overflow) bsf _C ;set carry to show over/underflow return _inbounds bcf _C ;clear the carry to show we stayed inbounds return Page ;-------------------------------------------------------------------------- ; Main Program -- ; Initialize the registers ; Init. the PLL and LCD ; Loop doing the background processing ;-------------------------------------------------------------------------- Start movlw 20 ;wait .2 sec for the LCD to power up call Delay_Long call Init ;then initialize everything bsf INTCON,GIE ;and then enable global interrupts call Init_LCD ;and initialize the LCD display ;(ints must be enabled to get ;chars to the LCD display) call LCD_Freq ;display the frequency call LCD_BitRate ;and bit rate background btfss LCDFreqUpdRqst ;see if the LCD freq needs updating goto DisplayStatus ;no, move on call LCD_Freq ;do it, if so bcf LCDFreqUpdRqst ;and note that we did it ; Now see if any of the sync/lock status bits changed, and if so ; update the LCD display DisplayStatus bcf CurrSync,CarrierDetFlag ;first, get the carrier and synth btfss CarrierDet ;from the hardware and put in bsf CurrSync,CarrierDetFlag ;CurrSync. Signals are low true bcf CurrSync,SynthFlag btfss SynthLock bsf CurrSync,SynthFlag ;now, see if any Sync bits changed since last LCD update movfw CurrSync xorwf OldSync,w ;non zero means something changed btfsc _Z goto DispStaID ;nothing changed - go do the Station ID ;something changed, so update the sync status display movfw CurrSync ;temporarily, use OldSync to display movwf OldSync ;current status movlw DispRow2+StatPos ;go to status position on LCD call LCD_cmd movlw 7 ;there are seven status chars movwf tempbg ;temporary counter _stat_loop rrf OldSync,f movlw GoodChar btfss _C ;display each char movlw BadChar call LCD_Out decfsz tempbg,f goto _stat_loop movfw CurrSync ;remember the display status movwf OldSync ;see if StaID changed -- if so, display DispStaID bcf INTCON,GIE ;turn off interrupts to get a clean movfw StaID_MSB ;read of the two bytes movwf RegB movfw StaID_LSB movwf RegC bsf INTCON,GIE ;and turn interrupts back on movfw RegB ;see if it changed xorwf OldStaID_MSB,w btfss _Z goto DispSta ;MSB changed - go display movfw RegC ;MSB same -- check LSB xorwf OldStaID_LSB,w btfss _Z DispSta call LCD_StaID ;changed so display the value DispBR movfw OldBitRate ;see if bit rate changed xorwf BitRate,w btfss _Z call LCD_BitRate ;yup - display the new value goto background ;and just keep going around PAGE ;-------------------------------------------------------------------------- ; Init -- Initialize I/O pins and variables, and interrupts ;-------------------------------------------------------------------------- Init BANKSEL TRISB ;set up direction of i/o pins clrf TRISB ;TRISB all inputs, normally comf TRISB,f clrf TRISA ;and TRISA all outputs bsf Select4800_Bit ;except this bit for now is an input BANKSEL PORTA movlw K9600 btfsc Select4800 ;check jumper: skip if 9600 selected movlw K4800 movwf Bit_K BANKSEL TRISA clrf TRISA ;put TRISA back to all outputs now BANKSEL PORTB ;switch back to bank zero ;Init the serial output and Synth clock SC104_MARK Synth_Clk_Low ;Init the PLL Control Register Synth_Enb_High movlw 10 ;wait .1 sec to make sure Synth is up call Delay_Long movlw PLL_Cntl call PLL_Send ;send w to the PLL Synth_Enb_Low ;Take ENB back low nop ;Wait a beat Synth_Enb_High ;Raise ENB again movlw high RefFreq ;and load the Ref counter call PLL_Send ;divide by n movlw low RefFreq call PLL_Send movlw high AuxRefCntr call PLL_Send movlw low AuxRefCntr call PLL_Send Synth_Enb_Low ;Init the PLL Tx (LO1) and Rx (LO2) counters call Get_Last_Chan ;fetch the last channel from eeprom to Chan call Set_PLL ;and init the PLL counters for LO1 and LO2 ;Init the counter and accumulator for SC104 bits movlw 6 ;six bits go into each byte movwf SC104_in_cntr clrf SC104_in ;clear the incoming-bit accumulator ;Init Flags to all zeroes and reset sync status clrf Flags call Reset_Sync ;Init the interrupt handler bcf INTCON,RBIF ;clear the interrupt flag and bsf INTCON,RBIE ;enable interrupts on PORTB change return ;------------------------------------------------------------------------ ;Reset_Sync -- clears all sync bits, and inits all conf counters to ; mid-scale ;------------------------------------------------------------------------ Reset_Sync movlw 0x80 movwf CarrierSync movwf BitSync movwf WordSync movwf FrameSync movwf CarrierSync clrf CurrSync clrf OldSync bsf StaID,7 ;mark StaID as invalid movlw 3 movwf BitRate ;set up to seek a new bit rate ;starting at 50bps bcf CurrSync,BitSyncFlag return PAGE ;------------------------------------------------------------------------ ;PLL_Send -- sends the byte in w to the MC145162 synthesizer ;------------------------------------------------------------------------ PLL_Send movwf temp ;save the byte to send movlw 8 ;set up to send 8 bits movwf delay_cntr2 ;use this as a temp reg - it is safe ;when this routine is called to do so Synth_Clk_Low ;make sure clock is low to start BANKSEL TRISB ;and make the data pin an output bcf Synth_Data_Bit BANKSEL PORTB _PLL_Next rlf temp,f ;put next bit into carry Synth_Data_Low ;assume data bit is zero btfsc _C ;skip if the data should be zero Synth_Data_High ;data=1, raise the data line Synth_Clk_High ;give the PLL a clock pulse Synth_Clk_Low decfsz delay_cntr2,f ;see if done goto _PLL_Next ;nope - go send next bit BANKSEL TRISB ;return data pin to being an input bsf Synth_Data_Bit BANKSEL PORTB return ;-------------------------------------------------------------------------- ; Set_PLL: Adds Chan to the LO1_Base, and sends LO1 and Synth2 divide-by-n ; values to the PLL. (Calls PLL_Send). Since this is only ; called on init and on a freq change, also clear the sync ; status. After Init, called only from the interrupt service ; routine, so the use of the IO pins will not collide with ; sending data to the LCD. ;-------------------------------------------------------------------------- Set_PLL movlw high LO1_Base ;get the base value movwf LO1 movlw low LO1_Base movwf LO1+1 movfw Chan ;add the channel offset addwf LO1+1,f ;and add it to the LO1 base frequency btfsc _C incf LO1,f Synth_Enb_Low ;send out the 32 bit word movfw LO1 call PLL_Send movfw LO1+1 call PLL_Send movlw high Synth2 call PLL_Send movlw low Synth2 call PLL_Send Synth_Enb_High ;pulse ENB to latch data Synth_Enb_Low call Set_Last_Chan ;save this channel in the eeprom call Reset_Sync ;can't be in sync if changing freq movlw 3 ;start bit rate hunting at 50bps call SetBitRate return PAGE ;-------------------------------------------------------------------------- ; SetBitRate: sets the BitRate, sets up TMR0, and turns on the ; TMR0 interrupt ; Enter with BitRate in W: ; 1 : 200 bps ; 2 : 100 bps ; 3 : 50 bps ;-------------------------------------------------------------------------- SetBitRate bcf INTCON,T0IE ;disable TMR0 interrupts movwf BitRate ;save this bit rate movlw 0x80 ;init BitSync conf counter movwf BitSync bcf CurrSync,BitSyncFlag ;declare 'out of sync' movfw BitRate ;set the prescaler from BitRate BANKSEL OPTION_REG ;Set OPTION register: PORTB pullups off, addlw B'11000100' ; INT:rising edge, TMR0 internal clock movwf OPTION_REG ; Prescaler to TMR0, set prescaler BANKSEL TMR0 movlw -BitWidth ;Load the timer movwf TMR0 bcf INTCON,T0IF ;clear any pending timer interrupt bsf INTCON,T0IE ;Enable timer interrupts return ;-------------------------------------------------------------------------- ; Get_Last_Chan: Gets the last channel number from EEPROM ;-------------------------------------------------------------------------- Get_Last_Chan movlw low LastChan ;get the EEPROM address of the byte movwf EEADR BANKSEL EECON1 bsf EECON1, RD ;eeprom read BANKSEL EEDATA movf EEDATA,w ;put the byte into w movwf Chan ;and save it in Chan return ;-------------------------------------------------------------------------- ; Set_Last_Chan: Writes the current channel number to EEPROM ; ; This is called by Set_PLL, which is only called from within ; the interrupt handler, so interrupts are disabled when this is ; called ;-------------------------------------------------------------------------- Set_Last_Chan movfw Chan ;get the channel number to save movwf EEDATA movlw low LastChan ;address setup movwf EEADR BANKSEL EECON1 ;switch banks _EEWait btfsc EECON1,WR ;skip if previous write complete goto _EEWait bsf EECON1,WREN ;enable EEPROM write movlw 0x55 ;prescribed writing sequence movwf EECON2 movlw 0xaa movwf EECON2 bsf EECON1,WR ;Set WR to begin write BANKSEL EEDATA ;switch back to bank 0 return PAGE ;------------------------------------------------------------------------- ; Delay_100us -- generates a 100 us delay. Uses only W ;------------------------------------------------------------------------- Delay_100us movlw -23 _t_loop addlw 1 ;increment w btfss _C ;skip if carry goto _t_loop return ;------------------------------------------------------------------------- ; Delay_10ms -- generates a 10 ms delay. ; Uses delay_cntr1, calls Delay_100us ;------------------------------------------------------------------------- Delay_10ms movlw 100 movwf delay_cntr1 _loop10ms call Delay_100us decfsz delay_cntr1,f goto _loop10ms return ;------------------------------------------------------------------------- ; Delay_Long -- generates a delay of N * 10ms, where N is in W on entry ; (Uses delay_cntr2, calls Delay_10ms) ;------------------------------------------------------------------------- Delay_Long movwf delay_cntr2 ;save the count _looplong call Delay_10ms ;call Delay_10ms that number of decfsz delay_cntr2,f ;times goto _looplong return PAGE ;-------------------------------------- ;bin2bcd - 10 bit binary to bcd conversion ; ; Enter with ten bit number in RegB:RegC ; (ie, 2 msb's in lsbs of RegB, 8 lsb's in RegC) ; Make sure six msbs of RegB are zero ; Routine converts numbers in the range of 0-999 ; to ascii and send the three digits to the LCD ;--------------------------------------- bin2bcd movlw B'00000001' ;conversion code found on the movwf RegA ;Internet -- it is magic, and I ;have no idea how it works. If the b2bloop rlf RegC,f ;input is 1000-1023, the code generates rlf RegB,f ;0xA for the hunds digit, so if Station rlf RegA,f ;id values over 999 are ever used, then ;this is the code which would have to btfsc _C ;be modified to detect an 'A' in the goto b2bascii ;hunds digit and convert that to ;two digits of '1' and '0'. movlw 0x03 addwf RegB,f btfss RegB,3 subwf RegB,f movlw 0x30 addwf RegB,f btfss RegB,7 subwf RegB,f goto b2bloop b2bascii movfw RegA ;Hunds value call LCD_ascii swapf RegB,w ;Tens value call LCD_ascii movfw RegB ;Ones value call LCD_ascii return PAGE ;----------------------------------------------------------------------- ; LCD_cmd -- Requests that a control char be sent to the LCD display ; LCD_Out -- Requests that a char be sent to the LCD display ; LCD_ascii - Converts a digit 0-9 to ascii and sends it to the LCD ; Char sends happen on the next TMR0 interrupt ; ; *****This may be called ONLY from the background******** ; ***** not from within the interrupt handler ******** ;----------------------------------------------------------------------- LCD_cmd bcf RS ;note this is a command goto LCD_Out2 LCD_ascii andlw 0x0F addlw "0" LCD_Out bsf RS ;note this a char to send LCD_Out2 movwf LCD_char ;save the byte to send bsf LCDFlag ;signal the interrupt handler we have something ;for sending - it will get sent on ;the next TMR0 interrupt _LCD_Out_Loop btfsc LCDFlag ;wait until it has been sent goto _LCD_Out_Loop return ;-------------------------------------------------------------------------- ; Init_LCD -- initialize the LCD display ;-------------------------------------------------------------------------- Init_LCD movlw 0x38 ;8 bit mode, 1/16th duty cycle, call LCD_cmd ;5x8 font call Delay_10ms movlw 0x38 call LCD_cmd movlw 0x38 call LCD_cmd call Delay_10ms movlw 0x38 call LCD_cmd movlw 0x06 ;chars add to the right call LCD_cmd movlw 0x0C ;Disp on, no cursor, no blink call LCD_cmd movlw 0x01 ;Clear display, home cursor call LCD_cmd call Delay_10ms ;LCD is now up and running, and we can init the displayed data movlw DispRow1+FreqPos+FreqWidth call LCD_cmd movlw "k" ;put up the 'k' for 'khz' call LCD_Out movlw DispRow1+IDLabelPos call LCD_cmd movlw "I" ;put up "ID" call LCD_Out movlw "D" call LCD_Out movlw DispRow1+StatPos call LCD_cmd movlw "S" ;put up "SICBWFP" for status indicators call LCD_Out movlw "I" call LCD_Out movlw "C" call LCD_Out movlw "B" call LCD_Out movlw "W" call LCD_Out movlw "F" call LCD_Out movlw "P" call LCD_Out movlw DispRow2+BitRatePos+BitRateWidth call LCD_cmd ;put up '0b' for bits/sec in second row movlw "0" call LCD_Out movlw "b" call LCD_Out ;put dashes in the station ID position, since ;we don't know what it is call IDDash return ;---------------------------------------------------- ; IDDash -- pusts dashes in the Station ID Display ;---------------------------------------------------- IDDash movlw DispRow2+IDPos call LCD_cmd movlw IDWidth movwf tempbg movlw "-" _IDDashLoop call LCD_Out decfsz tempbg,f goto _IDDashLoop movlw " " call LCD_Out bsf OldStaID,7 ;note that it has dashes bsf StaID,7 return ;---------------------------------------------- ;LCD_Freq -- Converts and displays the 3-digit frequency ;---------------------------------------------- LCD_Freq movlw high 285 movwf RegB movfw Chan ;convert chan to freq addlw low 285 ;by adding 285 khz movwf RegC btfsc _C incf RegB,f bcf LCDFreqUpdRqst ;clear the request movlw DispRow1+FreqPos ;go to Freq position call LCD_cmd call bin2bcd ;convert it to bcd-ascii ;and display return ;--------------------------------------------------------- ;LCD_StaID -- converts and displays the Station ID ;--------------------------------------------------------- LCD_StaID movfw RegB movwf OldStaID_MSB ;remember that we displayed this ID movfw RegC movwf OldStaID_LSB btfss RegB,7 ;see if valid goto Valid_ID call IDDash ;nope, display dashes return Valid_ID movlw DispRow2+IDPos call LCD_cmd call bin2bcd ;display it return ;--------------------------------------------------------- ;LCD_BitRate -- displays the current bit rate ;--------------------------------------------------------- LCD_BitRate movfw BitRate ;remember that we have displayed movwf OldBitRate ;the current bit rate movlw DispRow2+BitRatePos call LCD_cmd ;and then actually display it movfw OldBitRate call BR_First call LCD_Out movfw OldBitRate call BR_Second call LCD_Out return END