; ----------------------------------------------------------------------
;
; Title:
;
;   10 MHz frequency divider
;
; Function:
;
;   This PIC 16c84 program is designed to divide a 10 MHz frequency
;   source down to 1 Hz (1 PPS).
;
;   Since several extra output pins are available the program creates
;   a total of 9 square wave outputs -- one for each frequency decade
;   from 100 kHz to 0.001 Hz (1000 s).
;
;   A STOP input and a 1 PPS synchronization input are also provided.
;   Raising the STOP input high stops and resets the divider. The
;   divider resumes on the leading edge of the 1 PPS SYNC input. The
;   1 PPS output will be synchronized to the 1 PPS SYNC input to less
;   than 1.2 us (three PIC instructions at 10 MHz).
;
;   The following chip schematic shows the assignment of each pin.
;
;                        ------   ------
;   100 kHz <-       RA2 |1    ---   18| RA1         -> Red LED
; Green LED <-       RA3 |2          17| RA0         <= Stop input
; 1PPS SYNC => T0CKI/RA4 |3          16| OSC1/CLKIN  <= 10 MHz input
;    +5 VDC ->     /MCLR |4          15| OSC2/CLKOUT -- N/C
;       GND ->       Vss |5   16C84  14| Vdd         <- +5 VDC
;    10 kHz <-   INT/RB0 |6          13| RB7         -> 1000 s
;     1 kHz <-       RB1 |7          12| RB6         -> 100 s
;   100  Hz <-       RB2 |8          11| RB5         -> 10 s
;    10  Hz <-       RB3 |9          10| RB4         -> 1 Hz / 1 PPS
;                        ---------------
;
; Implementation:
;
;   To generate a 10 kHz square wave at 50% duty cycle an output pin
;   must be flipped every 50 us (125 instructions at 10 MHz clock).
;   This program does not use TMR0, the pre-scaler, or interrupts.
;   Instead it relies on the fact that given an accurate 10 MHz clock
;   each PIC instruction takes precisely 400 ns and the main loop has
;   been designed to use exactly 125 instructions.
;
;   The 100 kHz frequency (10 us period) is generated by setting an
;   output pin on and off every 25 cycles. Since 25 is an odd number
;   it is not possible for the PIC to generate this square wave with
;   a 50% duty cycle. Instead a 20% duty cycle (5 cylcles on and 20
;   cycles off) was chosen for this frequency output. A total of 5
;   pairs of 100 kHz bit set/clear code are carefully interspersed
;   within the 50 us main loop.
;
;   Pins RA0 and RA4 are not used to drive a LED. RA4 is a Schmidt
;   trigger input and O.C. output. It is used as the SYNC input.
;   The data sheet says not to toggle RA0 under some conditions so
;   it is used as the STOP input.
;
; Version:
;
;   1998-Aug-05, Version 4, tvb
;
; ----------------------------------------------------------------------

; Using Microhip assembler.

                list    p=16c84
STATUS          set     3
PORTA           set     5
PORTB           set     6

BASE2           equ     2
HALF2           equ     1
BASE10          equ     0x0a
HALF10          equ     0x05

; STATUS bit definitions

Zero            Equ     2

; PORTA bit definitions

Stop            Equ     0
Red             Equ     1
F100k           Equ     2
Green           Equ     3
Sync            Equ     4

; Temporary register definitions

OutByte         equ     0x0c
DelayTemp       equ     0x0d

; Decade counter register definitions

Digit0          equ     0x10
Digit1          equ     0x11
Digit2          equ     0x12
Digit3          equ     0x13
Digit4          equ     0x14
Digit5          equ     0x15
Digit6          equ     0x16
Digit7          equ     0x17

; ----------------------------------------------------------------------

                org     0
Start           GOTO    Init
                org     4
Interrupt       GOTO    $

Init            CLRF    PORTA           ; set pins low by default
                BCF     PORTA, Green
                BCF     PORTA, Red
                BSF     PORTA, F100k    ; start with 100 kHz high
                MOVLW   0x11            ; set RA0/RA4 input
                TRIS    PORTA

                MOVLW   0xff            ; start with outputs high
                MOVWF   PORTB
                MOVLW   0x00            ; set all pins output
                TRIS    PORTB

                CLRF    Digit0
                CLRF    Digit1
                CLRF    Digit2
                CLRF    Digit3
                CLRF    Digit4
                CLRF    Digit5
                CLRF    Digit6
                CLRF    Digit7

                MOVLW   0xff            ; start with outputs high
;
; The following implements a software decade divider chain much like
; a string of 7490 decade counter chips or an odometer.
;
; The low-order digit is incremented and when it wraps the carry is
; propagated to the next higher-oder digit. Since square waves are
; being generated in this particular case the low-order digit is
; incremented from 0 to 1 to 0. All remaining digits count from
; 0 to 9 and wrap back to 0. The STATUS Z bit is used to propagate
; carry into the next digit: Z mean carry and not-Z means no carry.
;
; The low order digit toggles every 125 instructions (50 us) for
; a period of 100 us and a frequency of 10 kHz. Each next higher
; order digit counts at 1/10 the rate of the preceding digit.
; The 100 kHz output changes too rapidly for the main loop so it
; is implemented independently.
;
; Note all 8 bits of PORTB are set at the same time so that the
; PIC acts like a synchronous, rather than an asynchronous, decade
; counter. The 100 kHz output pin is in PORTA so the phase of the
; 100 kHz frequency output lags the other outputs by one cycle.
;
Loop            MOVWF   PORTB
                BSF     PORTA, F100k    ; set 100 kHz high for 5 cycles (1 of 5)

                INCF    Digit0
                MOVLW   BASE2
                SUBWF   Digit0, W
                NOP
                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                BTFSC   STATUS, Zero
                  CLRF    Digit0

Loop1           BTFSC   STATUS, Zero
                  INCF    Digit1
                MOVLW   BASE10
                SUBWF   Digit1, W
                BTFSC   STATUS, Zero
                  CLRF    Digit1

                BTFSC   STATUS, Zero
                  INCF    Digit2
                MOVLW   BASE10
                SUBWF   Digit2, W
                BTFSC   STATUS, Zero
                  CLRF    Digit2

                BTFSC   STATUS, Zero
                  INCF    Digit3
                MOVLW   BASE10
                SUBWF   Digit3, W
                NOP
                BSF     PORTA, F100k    ; set 100 kHz high for 5 cycles (2 of 5)
                BTFSC   STATUS, Zero
                  CLRF    Digit3

                BTFSC   STATUS, Zero
                  INCF    Digit4
                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                MOVLW   BASE10
                SUBWF   Digit4, W
                BTFSC   STATUS, Zero
                  CLRF    Digit4

                BTFSC   STATUS, Zero
                  INCF    Digit5
                MOVLW   BASE10
                SUBWF   Digit5, W
                BTFSC   STATUS, Zero
                  CLRF    Digit5

                BTFSC   STATUS, Zero
                  INCF    Digit6
                MOVLW   BASE10
                SUBWF   Digit6, W
                BTFSC   STATUS, Zero
                  CLRF    Digit6

                BTFSC   STATUS, Zero
                  INCF    Digit7
                MOVLW   BASE10
                BSF     PORTA, F100k    ; set 100 kHz high for 5 cycles (3 of 5)
                SUBWF   Digit7, W
                BTFSC   STATUS, Zero
                  CLRF    Digit7
;
; Now compress 8 digits into one byte of 50% duty cycle bits. If the
; odometer digit is less than 5 create a one bit and if the odometer
; digit is 5 or more create a zero bit.
;
                MOVLW   HALF2
                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                SUBWF   Digit0, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit1, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit2, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit3, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit4, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit5, W
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit6, W
                BSF     PORTA, F100k    ; set 100 kHz high for 5 cycles (4 of 5)
                RRF     OutByte, 1

                MOVLW   HALF10
                SUBWF   Digit7, W
                RRF     OutByte, 1

                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                MOVLW   0xff
                XORWF   OutByte
;
; Add delays so that the loop takes exactly 125 cycles (50 us).
;
                CALL    Delay10
                CALL    Delay6
                NOP
                BSF     PORTA, F100k    ; set 100 kHz high for 5 cycles (5 of 5)
                CALL    Delay4
                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                CALL    Delay10
                CALL    Delay4
;
; Check STOP signal once per loop then exit the loop with the next
; version of OutByte in W. The top of the loop writes PORTB.
;
                MOVF    OutByte, W
                BTFSS   PORTA, Stop     ; is STOP pin high?
                  GOTO    Loop          ; no, continue
;
; STOP-SYNC protocol:
;
;  1) Divider free running (No LEDs)
;     - User sets STOP pin high.
;     - PIC stops frequency generator.
;
;  2) Divider stopped (Red LED)
;     - PIC waits for STOP pin to go low again.
;     - User sets STOP pin low.
;     - PIC resets counter and output lines.
;
;  3) Waiting for SYNC (Red and Green LEDs)
;     - PIC waits for SYNC pin to go from low to high.
;     - User sends 1 PPS pulse to SYNC pin.
;     - PIC resumes frequency generator.
;
;  4) Divider running in sync (Green LED)
;
                BCF     PORTA, Green
                BSF     PORTA, Red

                BTFSC   PORTA, Stop     ; is STOP pin low?
                  GOTO    $-1           ; no, keep checking
                BSF     PORTA, Green

                CLRF    Digit0
                CLRF    Digit1
                CLRF    Digit2
                CLRF    Digit3
                CLRF    Digit4
                CLRF    Digit5
                CLRF    Digit6
                CLRF    Digit7

                MOVLW   0xff            ; start with outputs high
                MOVWF   PORTB
                BSF     PORTA, F100k    ; start with 100 kHz high
;
; Resume on leading edge of SYNC pin. The PIC will be one to three
; instructions behind the actual 1 PPS sync pulse.
;
                BTFSC   PORTA, Sync     ; is 1 PPS sync low?
                  GOTO    $-1           ; no, keep checking

                BTFSS   PORTA, Sync     ; is 1 PPS sync high?
                  GOTO    $-1           ; no, keep checking
;
; Now synchronously rejoin main loop.
;
                BCF     PORTA, Red
                INCF    Digit0
                NOP
                BCF     STATUS, Zero
                BCF     PORTA, F100k    ; set 100 kHz low for 20 cycles
                GOTO    Loop1

; ----------------------------------------------------------------------
;
; Delay routines.
;
; - To delay 1 cycle use NOP.
; - To delay 2 cycles use GOTO $+1.
; - To delay 3 cycles use NOP and GOTO $+1.
; - To delay 4 cycles use CALL Delay4.
; - To delay 10 cycles use CALL Delay10, etc.
;
; For other delays use a combination of the above.
;

Delay100        GOTO    $+1
Delay98         MOVLW   24
                MOVWF   DelayTemp
                DECFSZ  DelayTemp
                  GOTO    $-1

Delay25         GOTO    $+1
Delay23         MOVLW   4
                MOVWF   DelayTemp
                DECFSZ  DelayTemp
                  GOTO    $-1

Delay10         GOTO    $+1
Delay8          GOTO    $+1
Delay6          GOTO    $+1
Delay4          RETURN

                END
