; Casio Digital Camera Remote Control
; Using PIC12C508
; Copyright (c) 2001 by Andrew Gregory
;
; 16 Jun 2001 - Initial version
;  6 Sep 2001 - Modified to support low battery detection

              list    p=12c508
              include <p12c508.inc>

              __config _IntRC_OSC & _WDT_OFF & _CP_OFF & _MCLRE_OFF

; Inputs '2134v-B-'

inpBtn1       equ 6             ; \
inpBtn2       equ 7             ;  \__for all buttons, 1=button is down,0=button is up
inpBtn3       equ 5             ;  /
inpBtn4       equ 4             ; /
inpBtnValid   equ 3             ; 1=button data is valid,0=button data is invalid
inpBtnMask    equ B'11111000'
inpBattOk     equ 1             ; 1=battery OK,0=battery low

; LED control

commonCathode equ B'00000000'
commonAnode   equ B'11111111'

ledType       equ commonAnode

buttonLedOn   macro             ; turn the button LED on
              if ledType == commonAnode
                  bcf     led,7
              endif
              if ledType == commonCathode
                  bsf     led,7
              endif
              endm

buttonLedOff  macro             ; turn the button LED off
              if ledType == commonAnode
                  bsf     led,7
              endif
              if ledType == commonCathode
                  bcf     led,7
              endif
              endm

; display patterns  1gfedcba (bits 7..0 of the 74595)
led_S         equ B'01101101' ^ ledType
led_t         equ B'01111000' ^ ledType
led_P         equ B'01110011' ^ ledType
led_n         equ B'01010100' ^ ledType
led_d         equ B'01011110' ^ ledType
led_L         equ B'00111000' ^ ledType
led_F         equ B'01110001' ^ ledType
led_AllOn     equ B'11111111' ^ ledType
led_AllOff    equ B'00000000' ^ ledType

; bit numbers for the I/O pins
camdata       equ 0             ; Pin 7 : Camera TTL serial data out
leddata       equ 1             ; Pin 6 : Serial data out to LED 74595
inpload       equ 2             ; Pin 5 : Parallel load of 74165 (decoder)
inpdata       equ 3             ; Pin 4 : Serial data in from 74165
ledset        equ 4             ; Pin 3 : Latch of 74595 (set LED display)
clock         equ 5             ; Pin 2 : Clock of both 74165 and 74595

; general purpose registers start at 0x07

led           equ 0x07          ; 7-seg LED display bits 7-0 = 1gfedcba
work          equ 0x08          ; update/serialOut routine working storage
inputs        equ 0x09          ; inputs '2134v-B-'
cnt           equ 0x0A          ; update/serialOut routine bit counter
mode          equ 0x0B          ; current mode
previnput     equ 0x0C          ; previous input data
inputcount    equ 0x0D          ; input count
camcmd        equ 0x0E          ; camera command
tenths        equ 0x0F          ; 0.1sec counter
battlow       equ 0x10          ; battery low state, latches into low state when low state detected : 0=battery OK,1=battery low
zero          equ 0x1F          ; zero constant

; reset vector

        org     0x00
reset
        movwf   OSCCAL          ; set internal RC calibration
        movlw   B'11011111'     ; set timer0 to use internal clock
        option
        movlw   B'00001000'     ; set GP0,1,2,4,5 to be outputs
        tris    GPIO

        ; initialise the outputs
        movlw   B'00000101'     ; Bit 0 : camera data to high
        movwf   GPIO            ; Bit 2 : load signal of 74165 to high
                                ; Bit 4 : load signal of 74595 to low
                                ; Bit 5 : clock to low

        ; initialise variables
        clrf    mode
        clrf    previnput
        clrf    inputcount
        clrf    battlow
        clrf    zero

        ; turn on all the LEDs for testing
        movlw   led_AllOn
        call    updatew

        ; delay for 400ms
        clrf    cnt
tstloop clrw
        call    delay
        decfsz  cnt,F
        goto    tstloop

        goto    modeOk

; *
; * This mainline continuously reads the buttons and updates the LED display
; * The buttons are debounced by ensuring the button state stays the same for
; * long enough for a counter to reach 256.
; * For debouce of 0.1s (100,000us), each of the 256 loops should take about
; * 390us. The update takes 123us, so a delay of about 267us is required.
; *

main
        movlw   0x2C            ; delay for 268us
        call    delay
        call    update
        movf    inputs,W        ; same as the last one?
        subwf   previnput,W
        btfsc   STATUS,Z
        goto    inpsame         ; yes
        movf    inputs,W        ; no
        movwf   previnput       ;  - save the button state
        goto    inpdiff         ;  - and reset the counter

inpsame
        incfsz  inputcount,F    ; must be the same 256 times in a row
        goto    main
        incf    tenths,F        ; 256 loops ~= 0.1sec

; latch battery state to low if it's low
        btfss   inputs,inpBattOk
        bsf     battlow,0

; LEDs are on if ((inpBtnValid==1) || (battlow==0) || (tenths:bit3 == 1))
; otherwise they're off.
; This ensures the display is on while a button is held down, but allows
; it to blink (0.8sec on, 0.8sec off) if the battery is low.
; Note that the state of tenths:bit3 is arbitrary - it just needs to change
; at ~0.8sec intervals.
        movf    mode,W
        call    lookupTable
        movwf   led
        movlw   led_AllOff      ; default to LEDs all off
        btfsc   inputs,inpBtnValid
        movf    led,W
        btfss   battlow,0
        movf    led,W
        btfsc   tenths,3
        movf    led,W
        movwf   led

        btfsc   inputs,inpBtnValid ; skip button handling if not valid
        goto    handleButton

inpdiff
        clrf    inputcount
        goto    main

; *
; * Button handling
; *

handleButton
        buttonLedOn
        call    update

; Check the button state
        movf    inputs,W        ; mask the inputs so only button data remains
        andlw   inpBtnMask
        movwf   inputs
        movlw   (1 << inpBtn1) | (1 << inpBtnValid) ; button 1?
        subwf   inputs,W
        btfsc   STATUS,Z
        goto    hdlbtn1
        movlw   (1 << inpBtn2) | (1 << inpBtnValid) ; button 2?
        subwf   inputs,W
        btfsc   STATUS,Z
        goto    hdlbtn2
        movlw   (1 << inpBtn3) | (1 << inpBtnValid) ; button 3?
        subwf   inputs,W
        btfsc   STATUS,Z
        goto    hdlbtn3
        movlw   (1 << inpBtn4) | (1 << inpBtnValid) ; button 4?
        subwf   inputs,W
        btfsc   STATUS,Z
        goto    hdlbtn4
        goto    waitbr          ; no valid buttons (could be multiple buttons) - wait for them to be released

hdlbtn1
        movlw   0x01
        goto    sendCommand
hdlbtn2
        movlw   0x02
        goto    sendCommand
hdlbtn3
        movlw   0x03
        goto    sendCommand

hdlbtn4 ; change modes
        movlw   modeEntrySize   ; increment mode by the size of a mode entry
        addwf   mode,F
        movlw   modeTableSize   ; if the mode is now equal to the size of the table
        subwf   mode,W
        btfsc   STATUS,Z
        clrf    mode            ; reset it back to zero
modeOk  ; display current mode
        movf    mode,W
        call    lookupTable
        movwf   led
        buttonLedOn
        goto    waitbr

sendCommand
        addwf   mode,W
        call    lookupTable     ; W now has the camera command
        movwf   camcmd
        movf    camcmd,F        ; if camcmd==0 then skipSend
        btfsc   STATUS,Z
        goto    skipSend
        movf    camcmd,W
        call    serialOut       ; send to camera
        btfss   camcmd,7        ; if camcmd.bit7==0 then skipWait
        goto    skipWait
skipSend
        call    update          ; wait for button release (i.e. become invalid)
        btfsc   inputs,inpBtnValid
        goto    skipSend
skipWait
        movf    camcmd,F        ; if camcmd==0 then buttonDone
        btfsc   STATUS,Z
        goto    buttonDone
        movf    camcmd,W
        iorlw   0x20            ; make command lowercase
        call    serialOut       ; send to camera
        btfsc   camcmd,7        ; if camcmd.bit7==1 then buttonDone
        goto    buttonDone
waitbr
        call    update          ; wait for button release (i.e. become invalid)
        btfsc   inputs,inpBtnValid
        goto    waitbr
buttonDone
        buttonLedOff
        goto    main

; *
; * Send LED data via the 74595 and read the button inputs via the 74165.
; * Shifts button data into the work byte at the same time it shifts LED data out.
; * The 74595 shifts data into the Q0 output first. This means that the final Q0
; * value has to be shifted out *last*. Therefore the LED data is shifted MSB first.
; * The 74165 shifts data out of Q7 first. Therefore the button data is shifted MSB
; * first also.
; *
; * (execution time, including originating CALL = 123us)
; *

update
        movf    led,W
updatew
        movwf   work

        bcf     GPIO,inpload    ; pulse parallel load of 74165 low to latch input states
        bsf     GPIO,inpload

        movlw   0x08
        movwf   cnt

updtloop
        clrw                    ; read current input state bit into the carry
        btfss   GPIO,inpdata
        movlw   0x01
        subwf   zero,W
        rlf     inputs,F        ; and rotate into inputs data file register

        rlf     work,F          ; rotate the next LED data bit into the carry
        bcf     GPIO,leddata    ; and set data line with it
        btfsc   STATUS,C
        bsf     GPIO,leddata

        bsf     GPIO,clock      ; pulse the clock
        bcf     GPIO,clock

        decfsz  cnt,F           ; loop again if more to do
        goto    updtloop

        bsf     GPIO,ledset     ; pulse output latch of 74595 high to update LED display
        bcf     GPIO,ledset

        retlw   0x00

; *
; * Send the character in W as RS-232 serial at 9600 baud, 8 data bits, 1 stop bit, no parity.
; * TTL is normally one, the start bit is a zero, the stop bit a one.
; * Bit 0 (LSB) is sent first.
; *
; * Timing is critical. The numbers in the comments show the number of
; * microseconds (instruction cycles) to the next bit transmission. There
; * should be 104us between bits.
; *
serialOut
        movwf   work            ; work = W & 127
        bcf     work,7

        movlw   0x08
        movwf   cnt

        bcf     GPIO,camdata    ;   0/  0  <--- start bit
        movlw   0x0F            ; 103/103  <--- 94us
        call    delay           ; 102/102

soutloop
        call    shrtdly         ;   8/  8  <--- 4us
        rrf     work,F          ;   4/  4  <--- LSB first
        btfsc   STATUS,C        ;   3/  3
        goto    out1            ;  **/  2

out0    nop                     ;   1/ **
        bcf     GPIO,camdata    ;   0/ **
        goto    outwait         ; 103/ **
out1    bsf     GPIO,camdata    ;  **/  0
        goto    outwait         ;  **/103

outwait
        movlw   0x0E            ; 101/101  <--- 88us
        call    delay           ; 100/100
        nop                     ;  12/ 12

        decfsz  cnt,F           ;  11/ 11  <--- loop again if more to do
        goto    soutloop        ;  **/ 10

        call    shrtdly         ;   9      <--- 4us
        call    shrtdly         ;   5      <--- 4us
        nop                     ;   1
        bsf     GPIO,camdata    ;   0      <--- stop bit

        ; delay for 50ms to ensure a good stop bit and to ensure any subsequent
        ; characters aren't sent too soon for the camera
        movlw   0x20
        movwf   cnt
sbloop  clrw
        call    delay
        decfsz  cnt,F
        goto    sbloop

        retlw   0x00            ;   2

; *
; * Delay for (6W+4)us (if W=0, then pretend W=256) (10..1540us), including the CALL to it.
; *
; * Comments show times through the loops
; *
delay
        movwf   zero            ;   2        <-- starts at 2 because of originating CALL
dlyloop
        nop                     ;   3 /   9 / 15 / etc
        nop                     ;   4 /  10
        nop                     ;   5 /  11
        decfsz  zero,F          ;   6 /  12
        goto    dlyloop

; * Short delay (4us) entry point
shrtdly
        retlw   0x00            ;   8 /  14
                                ;  10 /  16  <--- instruction cycle count upon return

; *
; * Command mode table
; *

numberOfModes equ 0x07
modeEntrySize equ 0x04
modeTableSize equ numberOfModes * modeEntrySize
lookupTable
        addwf   PCL,F
modeTable
        dt      led_S, 'B'       , 'A'       , 'B' | 0x80 ; S - shutter, focus lock, held shutter
        dt      led_t, 'P'       , 'D' | 0x80, 'C' | 0x80 ; t - timer, tele, wide
        dt      led_P, 'N'       , 'H'       , 'I'        ; P - mode, right, set
        dt      led_n, 'E'       , 'J'       , 'I'        ; n - menu, down, set
        dt      led_d, 'Q' | 0x80, 'O'       ,  0         ; d - preview, display
        dt      led_L, 'K'       , 'F'       , 'J'        ; L - flash, up, down
        dt      led_F, 'L'       , 'G'       , 'H'        ; F - focus, left, right

        end

