; PIC 16F688 code for running a PWM board
;
; ### license
;
; PWM_VAR_BASE: address for this code's working state. must be Bank 0.
;

        list p = 16f688
        include p16f688.inc
        include ../include/handy.inc


        ; Define this module's variables
.gpr0   udata

; the PWM ratios
CHAN1   res     1               ; aka: 1a on PWMBoard
CHAN2   res     1               ; aka: 1b
CHAN3   res     1               ; aka: 1c
CHAN4   res     1               ; aka: 2a
CHAN5   res     1               ; aka: 2b
CHAN6   res     1               ; aka: 2c
CHAN7   res     1               ; aka: 3a
CHAN8   res     1               ; aka: 3b
CHAN9   res     1               ; aka: 3c

; External code is allowed to adjust the CHAN* values. Publish the addresses.
 global CHAN1, CHAN2, CHAN3, CHAN4, CHAN5, CHAN6, CHAN7, CHAN8, CHAN9

; The PORT bits for each channel (RC4/TX, RC5/RX, RA3 are unused)
C1BIT_C equ     3
C2BIT_C equ     2
C3BIT_A equ     4
C4BIT_A equ     5
C5BIT_A equ     0
C6BIT_A equ     1
C7BIT_A equ     2
C8BIT_C equ     0
C9BIT_C equ     1


; When (TMR0 - CHAN_VAL) is positive (no carry), then TMR0 has incremented
; past the channel value, and we must turn off the output.
; NOTE: always 4 instruction cycles.
check_A: macro   CHAN_VAL, CHAN_BIT
        movfw   TMR0
        subwf   CHAN_VAL, W
        skpc
        bcf     PORTA, CHAN_BIT
        endm

; Same as 'check_A', but for PORTC outputs.
check_C: macro   CHAN_VAL, CHAN_BIT
        movfw   TMR0
        subwf   CHAN_VAL, W
        skpc
        bcf     PORTC, CHAN_BIT
        endm

; Turn all channels ON.
all_ON: macro
        ; RA0/1/2/./4/5 and RC0/1/2/3.
        movlw   0x37
        iorwf   PORTA, F
        movlw   0x0f
        iorwf   PORTC, F
        endm


pwm:    code                    ; relocatable

; main entry point: start cycling the outputs
;
; INPUTS: none
; ASSUME: Bank 0
;
        global  run_PWM         ; Publish this entrypoint
run_PWM:

        ; Disable comparators. We're going to use all RA* as OUTPUTS.
        movlw   0x07
        movwf   CMCON0

        ; Latch all pins to ON, before we define them as OUTPUTS.
        all_ON

        bank1s

        ; With an 8 MHz clock, an instruction cycle is 0.5 us. Target
        ; operation is clocking the PWM at about 200 Hz frequency, or
        ; about 5000 us per cycle, or 10k instructions.
        ;
        ; Timer 0 wraps at 256 instructions. Setting the prescaler to 1:32
        ; will wrap at 8192 instructions, or 4.1 ms, or 244 Hz.
        movlw   0x84            ; pull-ups disabled, T0CS to internal
                                ;   instruction clock (timer mode), prescaler
                                ;   assigned to Timer 0, rate set at 1:32.
        movwf   OPTION_REG

        ; All pins are digital OUTPUTS, so clear all AN* selectors
        clrf    ANSEL

        ; All RA* pins (but RA3) are OUTPUTS.
        clrf    TRISA

        ; Mask off bits in TRISC to set RC0/1/2/3 as OUTPUTS.
        movlw   0x30
        andwf   TRISC, F

        bank0s

begin_cycle:
        ; Latch all our outputs to ON, to start the cycle.
        all_ON

        ; Ensure Timer 0 is reset
        clrf    TMR0

next_cycle:
        ; NOTE: each cycle is 4.1 ms, or 8192 instructions. This cycle is
        ;   broken down into 256 sub-cycles to manage the required duty per
        ;   cycle. Thus, each sub-cycle is 16 us, or 32 instructions.

        ; PROBLEM: the code below consumes MORE than the maximum 32
        ;   instructions per sub-cycle.

        ; Has Timer 0 overflowed?
        btfsc   INTCON, T0IF
        goto    end_cycle

        ; 9 channels, with 4 instruction cycles each, per pass. These
        ; checks will consume 18 us with the default 8 MHz clock.
        check_C CHAN1, C1BIT_C
        check_C CHAN2, C2BIT_C
        check_A CHAN3, C3BIT_A
        check_A CHAN4, C4BIT_A
        check_A CHAN5, C5BIT_A
        check_A CHAN6, C6BIT_A
        check_A CHAN7, C7BIT_A
        check_C CHAN8, C8BIT_C
        check_C CHAN9, C9BIT_C

        goto    next_cycle

end_cycle:
        ; Clear the Timer 0 overflow notification
        bcf     INTCON, T0IF

        ; An I2C interrupt could modify the CHAN* registers. If something
        ; is currently ON, then keep running the PWM.
        clrw
        iorwf   CHAN1, W
        iorwf   CHAN2, W
        iorwf   CHAN3, W
        iorwf   CHAN4, W
        iorwf   CHAN5, W
        iorwf   CHAN6, W
        iorwf   CHAN7, W
        iorwf   CHAN8, W
        iorwf   CHAN9, W
        bnz     begin_cycle

        ; Nothing is ON, so stop running any PWM.
        return


; stupid assemblers don't realize EOF means *end*
        end
