; Change processor to 16F505a, from 16C505. Also change I/O port
; bits for new PCB layout. Add testmode to check out LMX2306 counting
; Add power-up push switch detect: On power-up, a depressed push-switch
; will cause the LD output to give the R-divider pulse output (~82 Khz)
; August 29, 2005 G.E.Leinweber
;-------------
;Phase-Locked Loop Controller for National Semiconductor's
; LMX2306, mostly involved with frequency control over a microwire
; uni-directional serial bus.
;     A different serial interface, to a 74HC164 eight-bit shift register
; displays channel numbers on 8 discrete light-emitting-diodes.
;     An input pin from a single momentary switch allows incremental
; stepping from channel to channel. A scanning mode is included
; which auto-scans cyclicly through eight channels, looking at a
; digital squelch input - open squelch indicates channel activity,
; and scanning stops until squelch closes - after a short delay,
; if squelch is still closed, scanning continues.
; CPU clock is a 14.320339 MHz. crystal that also drives LMX2306
; reference frequency input.
 
; March 20, 2005. G.E.Leinweber
;-------------------
      LIST p=16f505
#include p16f505.inc
;set configuration: HS osc, no watchdog, no code protect, no master clear pin
 __config _HS_OSC & _WDT_OFF & _CP_OFF & _MCLRE_OFF
 
      RADIX dec               ;switch from default "hex" radix
      variable testmode = 0   ;testmode=1 sets LDoutput to give reference freq
                              ;testmode=0 sets normal PLL Lock-Detect output
;-------------------------------------------------
#define LEDdata   PORTB,1     ;74HC164 shifting input DATA
#define LEDclk    PORTB,0     ;74HC164 shifting input CLOCK
#define button    PORTC,2     ;button input N.O.switch pulled high by R.
#define squelch   PORTC,0     ;digital squelch input (channel activity)
#define PLLlock   PORTB,3     ;LMX2306 digital lock-detect output
#define PLLdata   PORTC,4     ;LMX2306 microwire input DATA
#define PLLclk    PORTC,5     ;LMX2306 microwire input CLOCK
#define PLLen     PORTC,3     ;LMX2306 microwire input LOAD ENABLE
;
#define     ld_write bsf PLLen
#define ld_enable bcf   PLLen
;relate frequency to channel number:
#define NO.CHAN         8
#define f137.9125       0x07
#define f137.850        0x06
#define f137.770        0x05
#define f137.620        0x04
#define f137.500        0x03
#define f137.400        0x02
#define f137.300        0x01
#define f137.100        0x00
;define count offsets to save RAM space (let's us use 8-bit values):
#define Roffset 150
#define Noffset 170
 
; File Register (Random Access Memory)
base  equ   0x08        ;start of RAM
tcnt  equ   base+0      ;a general purpose counter
tcnt1 equ   base+1      ;another general purpose counter
sqcnt equ   base+2      ;squelch timeout counter
temp  equ   base+3      ;a general purpose temporary storage
curchan equ base+4      ;current channel # (0 - 7), 8=scanner
 
;-------------------------------------------------
      ORG   0           ;Start from power up
 
      b     cfpu        ;save's subroutine space @ low memory.
;
;-------------------------------------------------
; subroutines prefer low memory locations:
;-------------------------------------------------
; De-bounce the push-switch. "PUSH" is 1-to-0 edge, "RELEASE" is
; 0-to-1 edge. These routines block progress till high (for debrel)
; or low (for debpush).
 
debrel:                 ;debounce button release (0 to 1 edge)
      btfss button      ;has button gone high yet?
      b     debrel      ;no - loop till high (blocking)
      b     wf55m       ;yes...wait for end of bounce (a solid high)
;
debpush:                ;debounce button push (1 to 0 edge)
      btfsc button      ;has button gone low yet?
      b     debpush     ;no - loop till low (blocking), then
wf55m:                  ;wait for 55 milliseconds
      clrf  temp
      decfsz tcnt,f
      b     $-1
      decfsz temp,f
      b     wf55m+1
      retlw NO.CHAN     ;by default, eight channels
;
;-------------------------------------------------
;Look-up PLL N-count, to set the VCO output frequency. To save space,
;and make lookup easy, some data compression allows constants to fit
;into 8-bit space. N-counter concatenates a three-bit swallow count
;(National Semi calls it "A" count), and a thirteen-bit "B" count. The
;highest three bits are swallow, lowest five bits are "B" count less
;the Noffset constant. Noffset is added back before writing to LMX2306.
; Final VCO frequency is 8 * B + A multiplied by reference freq.
getN:
      incf  curchan,w   ;get current channel+1
      b     getR+1
;-------------------------------------------------
;Look-up PLL R-count, to set PLL reference frequency. To save space,
;and make lookup easy, data compression allows R to fit into 8-bit space.
;R-count sent to LMX2306 is Roffset larger than returned value from
;getR. PLL reference frequency = 14320339 / R-count
getR:
      movf  curchan,w         ;get the current channel (0 - 7)
      addwf curchan,w         ;make a table offset. (2 bytes per channel)
      addwf PCL,f             ;offset lookup
      retlw 173 - Roffset           ;R count for f137.100 m 82.8K
      retlw 190-Noffset | 7 << 5    ;N count, swallow
      retlw 163 - Roffset           ;R count for f137.300 m 87.9K
      retlw 180-Noffset | 1 << 5    ;N count, swallow
      retlw 164 - Roffset           ;R count for f137.400 m 87.4K
      retlw 181-Noffset | 3 << 5    ;N count, swallow
      retlw 165 - Roffset           ;R count for f137.500 m 86.8K
      retlw 182-Noffset | 5 << 5    ;N count, swallow
      retlw 175 - Roffset           ;R count for f137.620 m 81.8K
      retlw 193-Noffset | 7 << 5    ;N count, swallow
      retlw 158 - Roffset           ;R count for f137.770 m 90.635K
      retlw 175-Noffset | 2 << 5    ;N count, swallow
      retlw 157 - Roffset           ;R count for f137.850 m 91.2K
      retlw 174-Noffset | 2 << 5    ;N count, swallow
      retlw 180 - Roffset           ;R count for f137.9125 m 79.6K
      retlw 199-Noffset | 7 << 5    ;N count, swallow
;
;-------------------------------------------------
; Write-a-byte to PLL LMX2306. Eight bits, msb first.
wab:                    ;write-a-byte from w to PLL, msb first
      movwf temp        ;save w to shift
      movlw 8           ;prepare to write eight bits
      movwf tcnt
wablp1:
      bcf   PLLdata     ;set-up to write a zero
      rlf   temp,f      ;unless top shift bit is a one,
      skpnc             ;write a one
      bsf   PLLdata
      bcf   PLLclk
      bsf   PLLclk      ;rising edge clocks in the data
      decfsz tcnt,f
      b     wablp1
      retlw 0
;
;-------------------------------------------------
; Write a single bit - logic "1", serially to PLL LMX2306
wa1:
      bsf   PLLdata
      bcf   PLLclk
      bsf   PLLclk
      retlw 0
;
; Write a single bit - logic "0", serially to PLL LMX2306
wa0:
      bcf   PLLdata
      b     wa1+1
;
;-------------------------------------------------
; Write to LMX2306 the reference count (R-count), and then the
;VCO divider N-count. That's two separate 21-bit serial writes.
;N-count is separated into a 5-bit swallow count pre-scaler,
;combined with a main 13-bit frequency divider. The pre-scaler
;is a two-modulus type divde-by-8 or divide-by-9.
;After writing R & N counts, display on 8 LEDs the channel by
;shifting data out to 74HC164 shift register.
setfreq:
      ld_enable
      call  wa1         ;digital lock_detect after 5 cycles
      call  wa0         ;four test mode bits require zero write..
      call  wa0
      movlw b'00000000' ;more high-order R bits that don't change
      call  wab         ;write eight bits to PLL
      call  getR        ;lookup R counter for this channel
      movwf temp
      movlw Roffset     ;get R counter offset,
      addwf temp,f      ;add offset + R
      call  wab+1
      call  wa0         ;PLL register address of R counter
      call  wa0         ;PLL register address of R counter (lsb)
      ld_write          ;WRITE to PLL's latch
 
      call  getN        ;waste short time while PLL latches...
      ld_enable
      movlw b'00000000' ;write six high-order N bits, 0.25mA CP
      movwf temp
      movlw 6           ;six high-order bits don't ever change
      call  wab+2       ;write-a-byte (six most-significant bits)
      call  getN        ;look-up N-count for real
      andlw 0x1F        ;get JUST the N count, dump swallow
      movwf temp
      movlw Noffset     ;for de-compression,
      addwf temp,f      ;add N counter offset to look'd up N
      call  wab+1       ;write-a-byte to PLL
      call  getN        ;now we're after the swallow
      andlw 0xE0        ;mask off the N count part
      movwf temp        ;save for a bit, while we
      clrc              ;pack in two zero bits, since
      rrf   temp,f      ;LMX2306 uses only 3 bits of 5-bit field.
      rrf   temp,f
      movlw 5           ;swallow has only five bits to write
      call  wab+2       ;write five swallow bits to PLL,
      call  wa0         ;then the two N-counter address bits
      call  wa1
      ld_write          ;WRITE to PLL's latch
;
dispchan:   ;display a channel via 74164 shift register, serially.
      movlw 8           ;eight LEDs to shift
      movwf tcnt
      movf  curchan,w   ;get the current channel #
      movwf temp
      incf  temp,f
disploop:
      bcf   LEDdata
      decfsz temp,f     ;this LED to be lit?
      bsf   LEDdata     ;no
      bsf   LEDclk      ;shift register: toggle its clock
      bcf   LEDclk
      decfsz tcnt,f     ;done eight yet?
      b     disploop
      bsf   LEDdata
      retlw 0
;End of subroutines
;-------------------------------------------------
;continue from power-up...do a lotta register setup....
cfpu:                   ;continue from reset or power-up
      clrf  tcnt
      call  wf55m       ;time delay till power gets up further
      call  wf55m
      clrf  FSR         ;point to first bank
      movlw 0xdf        ;enable RC5, disable TOCK1 input
      OPTION
      movlw b'00111111' ;initialize PORTC data
      movwf PORTC
      movlw b'00001100' ;setup for PORTB direction
      tris  PORTB
      movlw b'00000111' ;setup for PORTC direction
      tris  PORTC
      call  wf55m       ;let everyone settle in for awhile
      clrf  curchan     ;start with channel 0
      btfsc button      ;is button depressed on power-up?
      b     normalstart ;no - do normal PLL receiver startup
 
      ld_enable         ;yes - do abnormal PLL setup
      call  wa0         ;msb of 21-bit F-word
      call  wa0
      call  wa0
      movlw b'00000001' ;setup no fastlock, high on Fastlock pin.
      call  wab
      movlw b'00001000' ;N-count on LD/Fo pin
      call  wab
      call  wa1         ;address of F-word
      call  wa1
      b     sucont      ;set-up continue
;
;initialize PLL with Function register write
normalstart:
      ld_enable
 if testmode == 0
      call  wa0         ;msb of F-word
      call  wa0
      call  wa0
      movlw b'00100001' ;no fastlock
      call  wab
      movlw b'00000100' ;Digital Lock detect on LD output pin
      call  wab
 else
      call  wa0
      call  wa0
      call  wa0
      movlw b'00100001' ;no fastlock
      call  wab
      movlw b'00010000' ;R-count divider on LD output pin
      call  wab
 endif
      call  wa1         ;then the two F-word address bits
      call  wa1         ;initialize mode
sucont:
      ld_write          ;latch in the F-word
      call  getN        ;waste a bit of time to latch
 
      clrf  curchan     ;start with channel 0
      call  setfreq     ;send freq to PLL and display
      b     main
;
;-------------------------------------------------
;main loop here. Check for button activity. Scan from channel
;zero through last channel. Once we overflow the last channel,
;then switch into scanner mode, starting from channel 0.
;Any button-press in scanner mode resets to channel 0. From
;there, increment channels on each button press, till we hit
;channel overflow again.
 
enter:
      movlw 0xff        ;start with channel zero
      movwf curchan
      call  debrel      ;wait for an inactive button
main:
      call  debpush     ;wait for a button-press
      incf  curchan,f   ;go to next
      subwf curchan,w   ;last channel ?
      skpnz
      b     scanner     ;yes - go into scanner mode
      call  setfreq     ;write channel to PLL, display LEDs
      call  debrel      ;wait for button-release
      b     main        ;end of main loop
;------------------------------------------------
scanner:
      movlw NO.CHAN+1   ;this'll end up resetting to channel 0
deadchan:
      incf  curchan,f   ;go to next channel
      subwf curchan,w   ;time to wrap to channel 0?
      skpnz
      clrf  curchan     ;yes
      call  setfreq     ;wriet channel to PLL, display LEDs
      call  debrel      ;ensure user has released button
      btfss PLLlock     ;wait for PLL to lockup to new freq.
      b     $-2
      call  wf55m       ;wait for squelch to settle down
sqtst:                  ;loop on active channel
      call  wf55m
      btfss button      ;check for button press (to end scanner)
      b     enter       ;end scanner by going back to main loop
      btfss squelch     ;does squelch indicate channel active?
      b     deadchan    ;nothing there - end loop, scan next.
      movlw .40         ;wait about 2 second after detecting
      movwf sqcnt       ;active channel, before checking
 
      call  wf55m       ;the squelch line again. Meanwhile,
      btfss button      ;check for button press (to end scanner)
      b     enter       ;yes, end scanner, go back to main loop.
      Decfsz sqcnt,f
      b     $-4
      b     sqtst       ;no, continue to dwell on this channel.
;------------------------------------------------
      END
 