; ------------------------------------------------------------------------
;
; Title:
;
;   PD88 -- PIC "4-pin" frequency divider (10 MHz to 440 Hz)
;
; Function:
;
;   This PIC program implements a digital frequency divider: the external
;   10 MHz input clock is digitally divided by a factor of 22727.272727~
;   in order to generate a precise 440 Hz output.
;
; Diagram:
;                                ---__---
;                5V (Vdd)  +++++|1      8|=====  Ground (Vss)
;            10 MHz input  ---->|2  pD  7|---->  440 Hz output
;           440 Hz output  <----|3  88  6|-
;                              o|4      5|-
;                                --------
; Notes:
;
;   Only 4 pins are required: power (2.0-5.5V), ground, input and output.
;   o Tie input pin4/GP3 to Vdd or Vss.
;   For TADD-2 compatibility: pin3/GP4 has same output as pin7/GP0.
;   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.
;
; Design:
;
;   Using a 10 MHz external clock the output pins are toggled 880 times
;   a second resulting in a 440 Hz output frequency.
;
;   Note that 2,500,000 is not integer divisible by 880 so the program
;   generates the output as a near perfect square wave with slightly
;   varying duty cycle and period in order to maintain average perfect
;   frequency accuracy and to minimize output jitter.
;
;   The result is that every 440 Hz rising/falling edge is within one
;   PIC instruction (400 ns) of ideal at all times. Thus the frequency
;   division is perfect when averaged over several cycles.
;
;   See also pd50 (50 Hz) and especially pd60 (60 Hz) for examples of
;   low frequency output.
;
; Version:
;
;   17-Aug-2023  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

; One-time PIC 12F675 initialization.

        org     0               ; power-on entry here
        bcf     STATUS,RP0      ; bank 0
        clrf    GPIO            ; set all pins low
        movlw   07h             ; set mode to turn
        movwf   CMCON           ;   comparator off
        bsf     STATUS,RP0      ; bank 1
        clrf    ANSEL-0x80      ; set digital IO (no analog A/D)
        movlw   b'101000'       ; set GP4, GP2, GP1, GP0 as output(0) and
        movwf   TRISIO-0x80     ;   other pins are input(1)
        bcf     STATUS,RP0      ; bank 0

; ----------------------------------------------------------------------

;
; Design notes:
;
; - With a 10 MHz external clock the PIC runs at 2.5 MIPS (2.5 million
;   instructions per second) which is 400 ns per instruction.
;
; - To generate 440 Hz the output pin(s) are toggled 880 times a second.
; - Each toggle must take 2500000 / 880 = 2840.909090~ instructions.
; - But fractional cycles are not possible in a digital microcontroller
;   so how can this work?
;
; - Note that 2500000 = 2^5 * 5^7 and 880 = 2^4 * 5 * 11 and that
;   2840.909090~ (a repeating fraction) is 2840 10/11 (exactly) and
;   it is also 2841 - 1/11 (exactly).
; - So the solution to the fractional cycle problem is to use either
;   2840 or 2841 in such a way that the net average is exact.
;
; - The goal is 440 Hz which is 880 toggles per second. We make 800 of
;   them 2841 instructions long and 80 of them 2840 instructions long.
;   The total number of instructions is then (800 * 2841) + (80 * 2840),
;   which is 2272800 + 227200 = 2500000. Thus over one second, perfect
;   frequency division is achieved.
;
; - A further simplification is to note that 800 and 80 can be reduced
;   to 20 and 2 which means we can use 2841 20 times and 2840 2 times
;   and repeat that sequence 40 times a second.
;
; - The loop below is isochronous and runs exactly 40 times a second,
;   taking exactly 62500 instructions (25 ms) per loop.
; - Within the loop the output is high 11 times and low 11 times which
;   then creates the 440 Hz frequency.
; - Within the loop 20 of those times are 2841 instructions long and 2
;   of those times are 2840 instructions long. The total number of
;   instructions is (20 * 2841) + (2 * 2840) = 56820 + 5680 = 62500,
;   so the math checks out.
;
; - The code ensures that every rising/falling edge of the output is
;   within one cycle of perfect. And the imperfections are balanced
;   so there is no accumulated time or frequency error. This reduces
;   jitter to a minimum and maintains frequency accuracy.
;

; Define macro to set output pin(s) high or low.

xhigh   macro
        movlw   0xFF            ; pins high
        movwf   GPIO            ;
        endm

xlow    macro
        movlw   0x00            ; pins low
        movwf   GPIO            ;
        endm

; Define adjustment for code within macro overhead.

SLOP    equ 4

; Define macros to use 2840 or 2841 cycles.

n2840   macro
        movlw   d'28'           ;
        call    DelayW100       ; delay W*100
        movlw   d'40'-SLOP      ;
        call    DelayW1         ; delay (15 <= W <= 255)
        endm

n2841   macro
        movlw   d'28'           ;
        call    DelayW100       ; delay W*100
        movlw   d'41'-SLOP      ;
        call    DelayW1         ; delay (15 <= W <= 255)
        endm

; Define macro to use 2841 - 2 = 2839 cycles, for the end-of-loop goto.

n2839   macro
        movlw   d'28'           ;
        call    DelayW100       ; delay W*100
        movlw   d'39'-SLOP      ;
        call    DelayW1         ; delay (15 <= W <= 255)
        endm

; Define macro that does the work in one line.

OUTPUT  macro   _state, _duration
        _state
        _duration
        endm

;
; Given the detailed comments above here is a simple loop that generates
; 11 cycles of digital output 40 times a second = 440 Hz, exactly.
;

loop:
        OUTPUT  xhigh, n2840    ; NOTE one cycle less
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2840    ; NOTE one cycle less

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2841

        OUTPUT  xhigh, n2841
        OUTPUT  xlow,  n2839    ; -2 cycles
        goto    loop            ; +2 cycles

        include delayw.asm      ; precise delay functions
        end
