; ------------------------------------------------------------------------
;
; Title:
;
;   PD57 -- PIC 5 MHz to 1, 10, 100, 1000 Hz frequency divider, with sync
;
; Function:
;
;   This PIC program implements a digital frequency divider: cpu hardware
;   and isochronous software divide the input clock by a factor of
;   5 thousand to 5 million.
;
;   - This version allows two Config input pins to set the divide ratio.
;     The input is assumed to be 5 MHz.
;     The output is 1 Hz or 10 Hz or 100 Hz or 1000 Hz.
;
;   - Pulse width is fixed at 100 us.
;
;   - Two inputs support optional manual 1PPS synchronization. Pull Arm
;     pin low for a second to stop divider. All outputs will synchronize
;     to next rising edge of Sync pin (within one instruction cycle).
;
; Diagram:
;                                ---__---
;                5V (Vdd)  +++++|1      8|=====  Ground (Vss)
;          5 MHz clock in  ---->|2  pD  7|<+---  ConfigA
;               pulse out  <----|3  57  6|<+---  ConfigB
;                     Arm  o--->|4      5|<+---  Sync
;                                --------
; Notes:
;
;   o External pull-up required on Arm input (pin4/GP3).
;   + Sync input (pin5/GP2) has internal WPU.
;   + Config inputs (pin7/GP0, pin6/GP1) have internal WPU.
;   Output frequency accuracy is the same as clock input accuracy.
;   Output drive current is 25 mA maximum per pin.
;   Coded for Microchip 12F675 but any '609 '615 '629 '635 '675 '683 works.
;
; Version:
;
;   22-Jan-2019  Tom Van Baak (tvb)  www.LeapSecond.com/pic
;
; ------------------------------------------------------------------------

; Microchip MPLAB IDE assembler code (mpasm).

        list        p=pic12f675
        include     p12f675.inc
        __config    _EC_OSC & _MCLRE_OFF & _WDT_OFF

; Register definitions.

        cblock  0x20            ; define register base
        endc

; Define entry points.

        org     0               ; power-on entry
        goto    init            ;
        org     4               ; interrupt entry
        goto    sync            ;

; One-time PIC 12F675 initialization.

init:   bcf     STATUS,RP0      ; bank 0
#ifdef CMCON    ; if this chip has comparator...
        movlw   0x07            ; turn comparator off
        movwf   CMCON           ;
#endif
        clrf    GPIO            ; set output latches low

        bsf     STATUS,RP0      ; bank 1
        errorlevel -302
#ifdef ANSEL    ; if this chip has ADC...
        clrf    ANSEL           ; all digital (no analog) pins
#endif
        movlw   ~(1<<GP4)
        movwf   TRISIO          ; set pin directions (0=output)
        movlw   1<<GP2 | 1<<GP1 | 1<<GP0
        movwf   WPU             ; enable weak pullup (1=enable)
        movlw   1<<INTEDG       ; WPU, GP2/INT rising edge trigger
        movwf   OPTION_REG      ;
        errorlevel +302
        bcf     STATUS,RP0      ; bank 0

; Time is kept perfectly, not with timers or counters or interrupts,
; but with carefully counted instructions in an isochronous loop.
;
; The main loop runs continuously at exactly 1 / 10 / 100 / 1000 Hz,
; which is a period of exactly 1000 / 100 / 10 / 1 ms,
; which is 1250000 / 125000 / 12500 / 1250 instruction cycles.
;
loop:   movlw   1<<GP4          ; set output high
        movwf   GPIO            ; W -> GPIO
        call    Delay5          ; (sync alignment)
sync:   call    armed           ; check for Arm request

        ; Delay so that pulse width is exactly 100 us (125 cycles @ 5 MHz).

        movlw   d'109'          ;
        call    DelayW1         ; delay (15 <= W <= 255)

        movlw   0<<GP4          ; set output low
        movwf   GPIO            ; W -> GPIO

        ; Delay some more so that loop overhead is 1000 cycles.

        movlw   d'8'            ;
        call    DelayW100       ; delay W*100
        movlw   d'61'           ;
        call    DelayW1         ; delay (15 <= W <= 255)

        ; Query config bits to determine loop period.

        movf    GPIO,W          ; get config bits
        andlw   3               ; 2-bits
        addwf   PCL,F           ; jump PCL+W (computed goto)
          goto  div_5e3         ; 00 = 5 MHz -> 1000 Hz, 1 ms
          goto  div_5e4         ; 01 = 5 MHz ->  100 Hz, 10 ms
          goto  div_5e5         ; 10 = 5 MHz ->   10 Hz, 100 ms
          goto  div_5e6         ; 11 = 5 MHz ->    1 Hz, 1000 ms

; Divide by 5e6, 5 MHz -> 1 Hz, 1000 ms period, 1,250,000 cycles.
; - delay 1250000 - 1000 = 1249000 additional cycles
;
div_5e6:
        movlw   d'124'          ;
        call    DelayW10k       ; delay W*10000
        movlw   d'90'           ;
        call    DelayW100       ; delay W*100
        goto    loop

; Divide by 5e5, 5 MHz -> 10 Hz, 100 ms period, 125,000 cycles.
; - delay 125000 - 1000 = 124000 additional cycles
;
div_5e5:
        movlw   d'12'           ;
        call    DelayW10k       ; delay W*10000
        movlw   d'40'           ;
        call    DelayW100       ; delay W*100
        goto    loop

; Divide by 5e4, 5 MHz -> 100 Hz, 10 ms period, 12,500 cycles.
; - delay 12500 - 1000 = 11500 additional cycles
;
div_5e4:
        movlw   d'114'          ;
        call    DelayW100       ; delay W*100
        movlw   d'100'          ;
        call    DelayW1         ; delay (15 <= W <= 255)
        goto    loop

; Divide by 5e3, 5 MHz -> 1000 Hz, 1 ms period, 1,250 cycles.
; - delay 1250 - 1000 = 250 additional cycles
;
div_5e3:
        movlw   d'2'            ;
        call    DelayW100       ; delay W*100
        movlw   d'50'           ;
        call    DelayW1         ; delay (15 <= W <= 255)
        goto    loop

; Implement two-pin 1PPS Arm-Sync synchronization protocol.
; - Accept Arm (low) request when output(s) are high.
; - Use GP2/INT interrupt to keep accuracy within 1 Tcy.
; - Divider resets and resumes on rising edge of Sync pin.
; - Re-enter main loop late to compensate for interrupt/code latency.
;
armed:  movf    GPIO,W          ; GPIO -> W
        andlw   (1<<GP4 | 1<<GP3)
        xorlw   (1<<GP4)        ;
        skpz                    ; Arm low, output(s) high?
          return                ;   no, continue running

        movlw   1<<GP4          ; set output high
        movwf   GPIO            ; W -> GPIO
        movlw   1<<GIE|1<<INTE  ; enable GP2 edge-trigger interrupt
        movwf   INTCON          ;   (and clear interrupt flags)
        goto    $               ; no deposit, no return, no retfie

        include delayw.asm      ; precise delay functions
        end
