; ------------------------------------------------------------------------
;
; Title:
;
;   PG41 -- Tunable RC clock with 1 MHz, 100 Hz, 1 Hz, 10 s outputs
;
; Function:
;
;   This PIC program implements a low-accuracy, low-stability frequency
;   generator with four different square wave outputs.
;
;   To permit frequency tuning experiments, each pulse to pin5/GP2 steps
;   the oscillator frequency. The step is increment if pin4/GP3 is high
;   and decrement if low.
;
;   Coarse tuning is done with the 5-bit (32 step) OSCTUNE register.
;
;   Fine tuning is done with microsteps, which is implemented by rapid,
;   proportional dithering between two adjacent OSCTUNE steps (like PWM).
;
; Diagram:
;                                ---__---
;                5V (Vdd)  +++++|1      8|=====  Ground (Vss)
;                1 Hz out  <----|2  pG  7|---->  100 Hz out
;               1 MHz out  <----|3  40  6|---->  10 s out
;                 up/down  o--->|4      5|<+---  step
;                                --------
; Notes:
;
;   o Tie or pull pin4/GP3 to Vdd/Vss (do not leave open).
;   Output drive current is 25 mA maximum per pin.
;   Coded for Microchip 12F683.
;
; Version:
;
;   07-Apr-2014  Tom Van Baak (tvb)  www.LeapSecond.com/pic
;
; ------------------------------------------------------------------------

; Microchip MPLAB IDE assembler code (mpasm).

        list        p=pic12f683
        include     p12f683.inc
        __config    _INTRC_OSC_CLKOUT & _MCLRE_OFF & _FCMEN_OFF & _WDT_OFF

        ; _INTRC_OSC_CLKOUT -- use internal RC oscillator
        ; _MCLRE_OFF        -- make GP3 an input pin rather than master clear
        ; _FCMEN_OFF        -- no need for failsafe clock monitor
        ; _WDT_OFF          -- no need for watchdog timer

; Register definitions.

        cblock  0x20            ; define register base
            intr_w, intr_status ; saved interrupt context
            gpcopy              ; shadow of output pins
            dig6, dig5, dig4, dig3, dig2, dig1, dig0
            osc0                ; value for OSCTUNE
            osci, oscf
        endc

OSTEPS  equ     d'16'           ; 0 to +15/-16 OSCTUNE steps
USTEPS  equ     d'100'          ; microsteps per step

; Define entry points.

        org     0               ; power-on entry
        goto    init            ;
        org     4               ; interrupt entry
        goto    step            ;

; If interrupt occurs, adjust 5-bit OSCTUNE value up or down by one.

step:
        movwf   intr_w          ; save W to intr_w[bank] (no flags)
        swapf   STATUS,W        ; copy STATUS to W (no flags)
        bcf     STATUS,RP0      ; force bank 0
        movwf   intr_status     ; save STATUS to intr_status[0]

        incf    oscf,F          ; increment the fraction
        movlw   USTEPS          ;
        subwf   oscf,W          ; compare limit
        skpnz                   ;
          clrf  oscf            ; wrap: reset to zero

        movlw   0               ;
        skpnz                   ;
          movlw   1             ; wrap: increment by one
        addwf   osc0,F          ;

        movlw   OSTEPS-1        ; step limit (leave room for max fraction)
        subwf   osc0,W          ; at limit?
        movlw   -OSTEPS         ; minimum step (and zero fraction)
        skpnz                   ;
          movwf osc0            ; wrap: reset to zero

        bcf     INTCON,INTF     ; reset interrupt flag

        swapf   intr_status,W   ; get saved STATUS
        movwf   STATUS          ;   and restore
        swapf   intr_w,F        ; get saved W (no flags)
        swapf   intr_w,W        ;   and restore (no flags)
        return                  ; return (not refie) do not re-enable GIE

; PIC 12F683 power-on hardware initialization.

init:
        errorlevel -302
        bsf     STATUS,RP0      ; bank 1
        clrf    ANSEL           ; all digital (no analog) pins
        movlw   1<<INTEDG       ; WPU(NOT_GPPU=0), GP2/INT rising edge trigger
        movwf   OPTION_REG      ;
        movlw   1<<GP2
        movwf   WPU             ; activate weak pullup input pin(s)
        movlw   ~(1<<GP5 | 1<<GP4 | 1<<GP1 | 1<<GP0)
        movwf   TRISIO          ; set pin directions (0=output)
        movlw   b'01100001'     ; 4 MHz HFINTOSC, SCS
        movwf   OSCCON          ; configure internal oscillator

        errorlevel +302
        bcf     STATUS,RP0      ; bank 0
        movlw   0x07
        movwf   CMCON0          ; disable comparator
        call    clear           ; initialize counter and pins
        movlw   1<<GIE | 1<<INTE | 0<<INTF
        movwf   INTCON          ; enable GP2 edge-trigger interrupt

; To create multiple frequency outputs the PIC increments a virtual
; '7490-style decade counter chain in a continuous isochronous loop.
; Clocking the counter at twice the output rate allows each LSB to
; generate a square wave at the desired decade frequency.
;
; A 500 us (2 kHz) toggle loop can generate a 1 kHz square wave.
; With a 4 MHz clock (1 us Tcy) 500 instructions is 500 us.

loop:   movf    gpcopy,W        ; gpcopy -> W
        movwf   GPIO            ; W -> GPIO

        ; Update counter and map each output pin to decade LSB.

        call    tune            ; set oscillator rate
        call    count           ; increment counter
        clrf    gpcopy          ;
        btfss   dig1,0          ; 100 Hz decade LSB
          bsf   gpcopy,GP0      ;
        btfss   dig4,0          ; 10 s decade LSB
          bsf   gpcopy,GP1      ;
        btfss   dig3,0          ; 1 Hz decade LSB
          bsf   gpcopy,GP5      ;

        ; Pad loop for exactly 500 instructions (use MPLAB SIM).

        movlw   d'3'            ;
        call    DelayW100       ; delay W*100
        movlw   d'121'          ;
        call    DelayW1         ; delay (15 <= W <= 255)
        goto    loop            ;

; Initialize counter (zero) and output pins (high).

clear:  clrf    dig0            ; 1000 Hz
        clrf    dig1            ;  100 Hz
        clrf    dig2            ;   10 Hz
        clrf    dig3            ;    1 Hz
        clrf    dig4            ;   10 s
        clrf    dig5            ;  100 s
        clrf    dig6            ; 1000 s
        movlw   1<<GP5 | 1<<GP1 | 1<<GP0
        movwf   gpcopy
        clrf    osc0            ; default rate
        clrf    oscf            ;
        clrf    osci            ;
        return

; Set RC oscillator rate for this pass through the loop.

tune:
        incf    osci,F          ; increment PWM index
        movlw   USTEPS          ;
        subwf   osci,W          ;
        skpnz                   ;
          clrf  osci            ; wrap to zero

        movf    oscf,W          ; desired PWM fraction
        subwf   osci,W          ; current PWM index
        skpc                    ;
          movlw 1               ; use osc0 + 1
        skpnc                   ;
          movlw 0               ; use osc0
        addwf   osc0,W          ;

        bsf     STATUS,RP0      ; bank 1
        errorlevel -302
        movwf   OSCTUNE         ; W -> OSCTUNE
        errorlevel +302
        bcf     STATUS,RP0      ; bank 0
        return

; Increment 7-digit decimal counter (isochronous code).

count:  incf    dig0,F          ; always increment LSDigit
        movlw   d'10'           ;
        subwf   dig0,W          ; check overflow
        skpnz                   ;
          clrf  dig0            ; reset to zero

        skpnz                   ;
          incf  dig1,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig1,W          ; check overflow
        skpnz                   ;
          clrf  dig1            ; reset to zero

        skpnz                   ;
          bsf   INTCON,GIE      ; [re]enable interrupts (every 0.05 s)

        skpnz                   ;
          incf  dig2,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig2,W          ; check overflow
        skpnz                   ;
          clrf  dig2            ; reset to zero

        skpnz                   ;
          incf  dig3,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig3,W          ; check overflow
        skpnz                   ;
          clrf  dig3            ; reset to zero

        skpnz                   ;
          incf  dig4,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig4,W          ; check overflow
        skpnz                   ;
          clrf  dig4            ; reset to zero

        skpnz                   ;
          incf  dig5,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig5,W          ; check overflow
        skpnz                   ;
          clrf  dig5            ; reset to zero

        skpnz                   ;
          incf  dig6,F          ; apply previous carry
        movlw   d'10'           ;
        subwf   dig6,W          ; check overflow
        skpnz                   ;
          clrf  dig6            ; reset to zero

        return

        include delayw.asm      ; precise delay functions
        end
