; PIC 16F688 code for managing a TLC5940 PWM LED driver
;
; ### license
;
; OPERATION
;
; The PIC remains asleep until an I2C message arrives, indicating that
; operation should begin. Until the LED operation finishes, the PIC will
; remain awake, using TIMER interrupts to perform its functions (and a
; busy-loop to wait for timers and/or I2C interrupts).
;
; TIMING CONSIDERATIONS
;
; If the PIC is clocked at maximum (20 MHz), then an instruction cycle is
; 200 ns (4 x 50 ns clock cycles).
;
; SCLK hi/lo (t.wh0/t.wl0) is a minimum of 16 ns.
;
; Design around a 38 KHz frequency. This matches IR needs, and can also
; function just fine for dimmable LED driving. For 38 KHz, the 5940 needs
; to be reset every 26.3 µs.
;
;

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

tlc5940: code                   ; relocatable

; Assume defaults for the DATA and CLOCK outputs, if not defined externally.
; The pins must reside on the same port (A or C).
;
; NOTE: the application MUST initialize these port pins to digital outputs.
 ifndef DATA
  constant DATA = RA0
 endif
 ifndef CLOCK
  constant CLOCK = RA1
 endif
 ifndef PORT
  constant PORT = PORTA
 endif

; If the external compilation does not define our variable base, then we
; will place it in the high register area, just below the bank-shared
; registers.
 ifndef TLC5940_BASE
  constant TLC5940_BASE = 0x5C
 endif

; Current bits being delivered
BITS    equ     TLC5940_BASE

; Bit counter (we send 8 or 12 bits)
BIT_CUR equ     TLC5940_BASE + 0x01
BIT_MAX equ     TLC5940_BASE + 0x02

; Byte counter (we always send 16)
BYTE_C  equ     TLC5940_BASE + 0x03

; When sending data (dot correction or grayscale values) to the 5940, it
; will be pulled from this buffer. The GS value delivery is the largest
; buffer, at 192 bits (24 bytes). However, we only support 8 bits per
; channel, for a total of 16 bytes.
;
; NOTE: this buffer is not altered during the delivery operation (we could
;       choose a different, destructive algorithm to save some mem/code)
BUFFER  equ     TLC5940_BASE + 0x04
B_END   equ     TLC5940_BASE + 0x04 + 0x0f ; ### symbol needed?


; Called when new greyscale values need to be delivered to the chip
gs_update:
        ; ### adjust pin outputs for GS programming

        movlw   0x0C
        goto    deliver

; Called when the dot correction values need to be delivered to the chip
dc_update:
        ; ### adjust pin outputs for DC programming

        movlw   0x08
        ; FALLTHROUGH

; Should arrived with the bit count in W.
deliver:
        movwf   BIT_MAX

        ; Send 16 pieces of data, each BIT_MAX long.
        movlw   0x10
        movwf   BYTE_C

        movlw   BUFFER
        movwf   FSR

byte_loop:
        decfsz  BYTE_C, f
        goto    done

        movfw   INDF            ; Load the next byte (via FSR) of data
        movwf   BITS

        incf    FSR, f

        movfw   BIT_MAX
        movwf   BIT_CUR         ; Reset the bit count

bit_loop:
        ; ### if the required delay between bits is long enough, then we
        ; ### could spend an extra instruction here by using W for the bit
        ; ### count (saving the BIT_CUR register). we could SUBLW, and then
        ; ### SKPNZ.
        ; ### no required delay. stay fast. move this ### into docco.
        decfsz  BIT_CUR, f
        goto    byte_loop

        ; Sending MSB first, so rotate left into the carry bit (ensuring
        ; that the carry is 0, rotating into the LSB). For the 12-bit
        ; grayscale values, we send the 8 most-significant bits and then
        ; 0 for the remaining 4 least-significant bits.
        clrc
        rlf     BITS, f
        bcf     PORT, DATA
        skpc
        bsf     PORT, DATA

        ; Clock the DATA bit into the 5940. Our instruction cycle is a
        ; minimum of 200 ns, and the 5940 requires a min 16 ns on SCLK
        ; HI/LO. A simple set/clear is plenty of time for clocking.
        bsf     PORT, CLOCK
        bcf     PORT, CLOCK
        goto    bit_loop

done:
        ; ### cycle the clock one more time?

        ; ### cycle XLAT, iirc. queue it for after a PWM cycle? (ie. we don't
        ; ### want to change values mid-cycle.

        return


; ### comments below either go into code, or to top for module docco.

; ### need code for cycling the PWM outputs. where are all the clocks?

; ### when chip sleeps, use port interrupt to wake for activity.
; ### allows I2C slave code to trigger GS/DC tweaks and delivery, or
; ### to begin PWM operation.

; ### a GS/DC delivery is full-busy. thus: PWM operation and I2C must
; ### use interrupts during that. the delivery code can be interrupted
; ### at any point, as no timing concerns are present.

; ### be wary of triggering update-during-update. set flag? adjusting
; ### BUFFER during delivery is certainly allowed, but depending upon
; ### BYTE_C, the change may/not be delivered in that operation.

; ### app should generally sleep, then fully wake during any PWM activity.
; ### we cannot sleep during activity. (interrupt overhead is generally
; ### higher cost than busy-wait)

; ### hmm. no sleep? if PWMs are driving accent lighting, then the chip
; ### will be always-on. however, the MCU uses about 1 mA typical. compared
; ### to the lighting, this is not a problem. for IR emitter operation,
; ### the signal is delivered within (hundreds) of milliseconds. again,
; ### not an issue to remain on for operation.


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