;**********************************************************************
; VelociRotor 2022
; 2022-05-28 RSP
; Target: PIC16F1615
;
; Pin Diagram       PIC16F1615
;                     ------ 
;      3.3-5V Vdd -> |1   14| <- Vss
;       ENC_B RA5 -> |2   13| <- RA0 Rx  PGD
;       ENC_A RA4 -> |3   12| -> RA1 Tx  PGC
;  MCLR   BTN RA3 -> |4   11| -> RA2 G 
;           F RC5 <- |5   10| -> RC0 A   
;           E RC4 <- |6    9| -> RC1 B  
;           D RC3 <- |7    8| -> RC2 C 
;                     ------ 
; Interface:
;   Up to 63 encoders in a string
;   Serial @ 38400
;   Boards are daisy chained (round robin)
;
; Example with 2 rotary encoder units:
;
;        +-----+       receiving   sending
;        |     |Rx       data      command
;        |  +------+     
;        |  |      |      1           0
;        |  +------+
;        |     |Tx      unit addressing reverses      
;        |     v       depending on data direction
;        |     |Rx
;        |  +------+
;        |  |      |      0           1
;        |  +------+
;        |     |Tx
;        |     |
;        ^     v
;     +-----------+
;     |    MPU    |
;     +-----------+
;
; Identifying how many encoders are in the string:
;   Send benign command (e.g. lamp off) to address 63
;   If it loops back through the chain then count = 63 - address
;   else broken chain.
;
; Logic:
;   Outgoing packets (acceleration & lamp control)
;     1'st in chain is address zero
;       Each module checks if address == 0
;           Process and eat packet
;       Else
;           Subtract 1 from address and forward the packet
;     If packet makes it back to host error is detected
;   Incoming packets (sensors)
;     1'st in chain is address zero
;     Each module adds 1 to the address and forwards the packet
;   Because outgoing and incoming packets are at opposite ends of the chain,
;   the physical module addresses reverse for outgoing and incoming.
;
; Packets are atomic 2 bytes; address, data
;    B7  B6  B5  B4  B3  B2  B1  B0
;    i/o a/d x   x   x   x   x   x     i/o = traffic direction, a/d = address/data
;
; Encoding:
;     1   0   0   0   0   0   0   0    reserved for chain diagnostics == set pixel #0 off
;   command: lamp control
;     1   1   1   1   1   1   1   1    broadcast address
;     1   1   a   a   a   a   a   a    specific address
;     1   0   0   0   n   n   n   n    show value n (0..9,A..F)
;     1   0   0   1   0   n   n   n    show special character (- _ H J L P o u)
;     1   0   0   1   1   i   b   b    set brightness; bb = brightness (0=off), i=invert orientation
;   command: acceleration profile (and other config)
;     1   1   1   1   1   1   1   1    broadcast address
;     1   1   a   a   a   a   a   a    specific address 
;     1   0   1   x   y   z   p   p    set config; acceleration p0=none, p1=min, p2=med, p3=max
;                                                  xyz=enabled button events; x=disable press, y=disable hold, z=enable release
;   data: rotary encoder spin
;     0   1   a   a   a   a   a   a    address 
;     0   0   n   n   n   n   n   n    dial spin; nnnnn = -30..+30 (6 bit signed int with reserved codes)
;   data: button event
;     0   1   a   a   a   a   a   a    address 
;     0   0   1   0   0   0   0   0    -32 button event: press
;     0   0   1   0   0   0   0   1    -31 button event: hold (long press)
;     0   0   0   1   1   1   1   1    +31 button event: release
;**********************************************************************
    
    list      p=16F1615           ; list directive to define processor
    #include <p16F1615.inc>       ; processor specific variable definitions
    errorlevel  -302              ; suppress bank select warnings
  
;------------------------------------------------------------------------------
; HELPER MACROS
;------------------------------------------------------------------------------

    #include "../Library/macros.h"

;------------------------------------------------------------------------------
; CHIP CONFIGURATION
;------------------------------------------------------------------------------    

     __CONFIG _CONFIG1, _FOSC_INTOSC & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
     __CONFIG _CONFIG2, _WRT_OFF & _ZCD_OFF & _PLLEN_ON & _STVREN_OFF & _BORV_LO & _LPBOR_OFF & _LVP_OFF
     __CONFIG _CONFIG3, _WDTE_OFF

; digital pins
#define BUTTON_PIN PORTA,B3 ; encoder press
#define _ENCODER_A   B4     ; on port A
#define _ENCODER_B   B5
#define G_PIN      LATA,B2

BAUDCON_VALUE EQU 0 ; 8 bit BRG
#include "../Library/baudrates.h"

;------------------------------------------------------------------------------
; VARIABLE DEFINITIONS
;
; The 12F1822 has 128 bytes of RAM
;   80 bytes in Bank 0
;   32 bytes in Bank 1
;   16 bytes of bankless access RAM
;------------------------------------------------------------------------------

ACCESS_RAM  UDATA_SHR  0x70 ; bankless, 16 bytes

pgm_flags RES 1   ; program state
#define PF_BUTTON      B0 ; button state
#define PF_PRESSED     B1 ; button press event
#define PF_HOLD        B2 ; button hold event
#define PF_RELEASE     B3 ; button release event
#define PF_COMMAND     B5 ; got command packet address in pkt_address
#define PF_DATA        B6 ; got data packet address in pkt_address

msec_timer RES 1 ; 1->10 msec timer chain

; configuration
profile    RES 1 ; config
#define PR_DI_PRESS   B4 ; suppress press events
#define PR_DI_HOLD    B3 ; suppress press events
#define PR_EN_RELEASE B2 ; enable release events

; button
debounce   RES 1 ; button debouncer, 1/100 sec ticks
hold_timer RES 1 ; long press detection, 1/100 sec ticks
#define longpress_ticks 100 ; 1 second long press time

; dial
rotor        RES 1 ; steps since last read (signed int8)
elapsed_msec RES 1 ; elapsed msec since last encoder rotation event

; transmit queue
tx_index    RES 1  ; head position in tx_buf
tx_count    RES 1  ; chars in tx_buf
tx_ch       RES 1  ; transmit temp

pkt_address RES 1 ; 1'st byte of 2 byte packet

; parameters to post_to_tx_queue
tx_address  RES 1 
tx_data     RES 1

; temporary variables
rx_ch     RES 1
temp      RES 1

;- - - - - - - - - - - - - - - - - - - - - - - -

BANK0       UDATA       0x20    ; 80 bytes

#define TXBUFSIZ  80 ; transmit buffer
tx_buf  RES TXBUFSIZ

;- - - - - - - - - - - - - - - - - - - - - - - -

BANK1       UDATA       0xA0    ; 80 bytes

segments   RES 1 ; LED segment image
brightness RES 1 ; LED brightness, 0..3, B2=invert orientation
pwm        RES 1 ; PWM phase, 0..3, 1msec

;------------------------------------------------------------------------------
; RESET VECTOR
;------------------------------------------------------------------------------

RESET_VECT  CODE    0x0000  ; processor reset vector

    bra     START

;------------------------------------------------------------------------------
; INTERRUPT SERVICE ROUTINE
;
; Automatically saves/restores W, STATUS, BSR, FSRs, and PCLATH
;------------------------------------------------------------------------------

ISR         CODE    0x0004

;-- the only interrupt is USART RCIF or TXIF

    banksel PIR1    ; figure out which interrupt it is
    btfss   PIR1, RCIF
    bra     int_tx  ; must be TXIF

;-- receive data ready interrupt

    bcf     PIR1, RCIF

RX_IN_INTERRUPT EQU 1 ; rx runs in interrupt
RX_MUTEX_QUEUE  EQU 0 ; don't protect calls to post_packet_to_tx_queue
    #include "../Library/receive.h"
   ;retfie

;-- transmit buffer empty interrupt

int_tx
    ; rev A4 silicon bug workaround
    ; (A4 is the currently the only rev of PIC16F1615)
    banksel TX1STA
    btfss   TX1STA, TRMT    ; wait for transmit buffer empty
    bra     int_tx

TX_IN_INTERRUPT EQU 1 ; running in interrupt
TX_MUTEX_QUEUE  EQU 0
    #include "../Library/send.h"
    ; handle empty queue (turn off ready signal)
    ; disable transmit interrupt if queue is empty now
    banksel PIE1
    btfsc   STATUS, Z       ; is queue empty ?
    bcf     PIE1, TXIE      ;   yes, turn of TXIE
    retfie                  ; RETurn From IntErrupt

;- - - - - - - - - - - - - - - - - - - - - - - -
; store packet (tx_address, tx_data) to transmit queue (if there's room)
; destroys FSR0
; call from interrupt only

;post_packet_to_tx_queue
    #include "../Library/queue.h"
    ; trigger immediate Tx interrupt to process the queue
    banksel PIE1
    bsf     PIE1, TXIE      ; enable TXIF
    return

;- - - - - - - - - - - - - - - - - - - - - - - -
; command from MCU

executeCommand ; rx_ch = command
    movf    rx_ch, w        ; get command
    btfsc   rx_ch, B5       ; differentiate config/LED command
    bra     executeCfgCommand
    ; LED command
    btfss   rx_ch, B4       ; show value ?
    bra     executeValue
    btfss   rx_ch, B3       ; show special ?
    bra     executeSpecial
    ; set brightness
    movf    rx_ch, w
    banksel brightness
    movwf   brightness
    return

executeCfgCommand ; 1   0   1   x   y   z   p   p    set config; acceleration p0=none, p1=min, p2=med, p3=max
    andlw   b'00011111'
    movwf   profile
    return

executeValue ; 0..F
    ; get segments
    movf    rx_ch, w        ; get value
    andlw   0x0F            ; mask to 0..15
    banksel brightness
    btfss   brightness, B2  ; handle LED orientation
    call    LEDsegmentNormal   
    btfsc   brightness, B2
    call    LEDsegmentInvert
    ; save segments
    movwf   segments
    return

executeSpecial ; 8 selected characters:  - _ H J L P o u
    ; get segments
    movf    rx_ch, w        ; get value
    andlw   0x07            ; mask to 0..7
    banksel brightness
    btfss   brightness, B2  ; handle LED orientation
    call    LEDspecialNormal   
    btfsc   brightness, B2
    call    LEDspecialInvert
    ; save segments
    movwf   segments
    return

LEDspecialNormal
    brw   ;   -GFEDCBA
    retlw   b'01000000' ; -
    retlw   b'00001000' ; _
    retlw   b'01110110' ; H
    retlw   b'00001111' ; J
    retlw   b'00111000' ; L
    retlw   b'01110011' ; P
    retlw   b'01011100' ; o
    retlw   b'00011100' ; u

LEDspecialInvert
    brw   ;   -GFEDCBA
    retlw   b'01000000' ; -
    retlw   b'00000001' ; _
    retlw   b'00110110' ; H
    retlw   b'00111100' ; J
    retlw   b'00000111' ; L
    retlw   b'00011110' ; P
    retlw   b'01100011' ; o
    retlw   b'00100011' ; u

LEDsegmentNormal
    brw   ;   -GFEDCBA
    retlw   b'00111111' ; 0
    retlw   b'00000110' ; 1
    retlw   b'01011011' ; 2
    retlw   b'01001111' ; 3
    retlw   b'01100110' ; 4
    retlw   b'01101101' ; 5
    retlw   b'01111101' ; 6
    retlw   b'00000111' ; 7
    retlw   b'01111111' ; 8
    retlw   b'01101111' ; 9
    retlw   b'01110111' ; A
    retlw   b'01111100' ; b
    retlw   b'00111001' ; C
    retlw   b'01011110' ; d
    retlw   b'01111001' ; E
    retlw   b'01110001' ; F

LEDsegmentInvert
    brw   ;   -GFEDCBA
    retlw   b'00111111' ; 0
    retlw   b'00110000' ; 1
    retlw   b'01011011' ; 2
    retlw   b'01111001' ; 3
    retlw   b'01110100' ; 4
    retlw   b'01101101' ; 5
    retlw   b'01101111' ; 6
    retlw   b'00111000' ; 7
    retlw   b'01111111' ; 8
    retlw   b'01111101' ; 9
    retlw   b'01111110' ; A
    retlw   b'01100111' ; b
    retlw   b'00001111' ; C
    retlw   b'01110011' ; d
    retlw   b'01001111' ; E
    retlw   b'01001110' ; F

;------------------------------------------------------------------------------
; MAIN PROGRAM
;------------------------------------------------------------------------------

START

;-- set Fosc = 32 MHz fOSC => 8 MHz fCYC => 125 nsec

    banksel  OSCCON
    movlw    b'01110000'    ; HFINTOSC (32 MHz with PLL enabled)
    movwf    OSCCON
    
    ; wait for accurate fOSC
    banksel  OSCSTAT
    btfss    OSCSTAT,HFIOFR ; HFINTOSC ready?
    goto     $-1
    btfss    OSCSTAT,PLLR   ; PLL ready?
    goto     $-1

;-- configure I/O pins

    banksel ANSELA              ; all digital I/O
    clrf    ANSELA 
    banksel ANSELC
    clrf    ANSELC

    banksel LATA                ; all outputs low
    clrf    LATA
    banksel LATC
    clrf    LATC

    banksel WPUA
    MOVLF   b'00111000', WPUA   ; enable pull-up on MCLR, A, B, Button
    clrf    WPUC                ; no pullups on PORTC
    banksel OPTION_REG
    bcf     OPTION_REG, NOT_WPUEN

    banksel TRISA               ; outputs: RA2=G, RA1=Tx
    MOVLF   b'00111001', TRISA  ; inputs:  RA0=Rx, RA3=MCLR, RA4=ENC_A, RA5=ENC_B
    banksel TRISC               ; outputs: RC0..RC5=A..F
    MOVLF   b'00000000', TRISC  ; inputs:  none

    banksel RXPPS               ; configure RxD on RA0 pin
    MOVLF   b'00000000', RXPPS
    banksel RA1PPS              ; configure TxD on RA1 pin
    MOVLF   b'00010010', RA1PPS

;-- set up USART

    banksel RCSTA
    MOVLF   b'10010000', RCSTA      ; enable USART module, receive enabled
    MOVLF   b'00100000', TXSTA      ; async mode, transmit enabled, low speed baud rate
    MOVLF   BAUDCON_VALUE,BAUDCON   ; 8 bit baud rate generator
    MOVLF   BRG_38400,   SPBRG

    banksel PIE1                ; enable USART interrupts
    bcf     PIE1, TXIE          ;   tx interrupt enabled when there's something to send
    bsf     PIE1, RCIF          ;   rx interrupt always enabled

;-- initialize variables

#include "../Library/clearRAM.h"

    banksel brightness
    MOVLF   2, brightness       ; default to medium brightness

;-- Timer1 + CCP1 is 1000 Hz timebase

    ; fOsc/4 = 8,000,000
    ;   want 1 msec
    ;   need 8,000 divide-by
    banksel T1CON
    MOVLF   b'00010001', T1CON      ; Fcyc clock, 1:2 prescale, timer on
    banksel CCP1CON
    MOVLF   high(4000-1),CCPR1H
    MOVLF    low(4000-1),CCPR1L
    MOVLF   b'10001011', CCP1CON    ; compare mode

;-- enable interrupts

    bsf     INTCON, PEIE
    bsf     INTCON,  GIE

;- - - - - - - - - - - - - - - - - - - - - - - -
; Bigloop

BIGLOOP

;-- encoder logic - Bourns PEC16 encoder

state_0 ; between detents (A & B high)

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; illegal transition
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; neither switch closed
    btfss   WREG, _ENCODER_A
    bra     state_cw_1

state_ccw_1 ; A closed, B open

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_ccw_2 ; B closed
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; neither switch closed
    bra     state_ccw_1

state_ccw_2 ; both A & B closed

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_ccw_2
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; neither switch closed (wtf?)
    btfsc   WREG, _ENCODER_A
    bra     state_ccw_1 ; retreat

state_ccw_3 ; A open, B closed

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_ccw_2 ; retreat
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bnz     state_ccw_3

    ; CCW turn detected
    ;   zero spin if spin direction is changed
    btfss   rotor, B7
    clrf    rotor
    ; VelociRotor
    call    calcRotorIncr
    negf    rotor, f    ; make value positive
    addwf   rotor, w
    btfsc   STATUS, C  ; overflowed 8 bits ?
    movlw   127        ; rail at 127 (-128)
    btfsc   WREG,B7    ; overflowed 7 bits ?
    movlw   127
    movwf   rotor
    negf    rotor, f   ; restore sign
    ; reset encoder event timer
    clrf    elapsed_msec
    bra     state_0

state_cw_1 ; B closed, A open

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_cw_2 ; A closed
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; neither switch closed
    bra     state_cw_1

state_cw_2 ; both A & B closed

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_cw_2
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_0   ; neither switch closed (wtf?)
    btfsc   WREG, _ENCODER_B
    bra     state_cw_1 ; retreat

state_cw_3 ; B open, A closed

    call    poll
    banksel PORTA
    movf    PORTA, w
    andlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bz      state_cw_2 ; retreat
    xorlw   MASK(_ENCODER_A) | MASK(_ENCODER_B)
    bnz     state_cw_3

    ; CW turn detected
    ;   zero spin if spin direction is changed
    btfsc   rotor,B7
    clrf    rotor
    ; VelociRotor
    call    calcRotorIncr
    addwf   rotor, w
    btfsc   STATUS, C  ; overflowed 8 bits ?
    movlw   127        ; rail at 127
    btfsc   WREG,B7    ; overflowed 7 bits ?
    movlw   127
    movwf   rotor
    ; reset encoder event timer
    clrf    elapsed_msec
    bra     state_0

;- - - - - - - - - - - - - - - - - - - - - - - -

poll

;-- 1 msec tasks

    ; wait for timer1
    banksel PIR1
    btfss   PIR1, CCP1IF
    return
    bcf     PIR1, CCP1IF

    ; bump elapsed time
    incf    elapsed_msec, w
    btfss   STATUS, Z       ; rail at 255
    incf    elapsed_msec, f

    ; LED PWM
    banksel brightness
    ; brightness
    ;  0  always off
    ;  1  1/4 time
    ;  2  1/2 time
    ;  3  always on
    incf    pwm, f
    bcf     pwm, B2
    movf    brightness, w
    andlw   0x03
    brw
    bra     ledOff
    bra     led1
    bra     led2
ledOn
    movf    segments, w
    banksel LATA
    btfss   WREG, B6
    bcf     G_PIN
    btfsc   WREG, B6
    bsf     G_PIN
    banksel LATC
    movwf   LATC
    bra     ledDone

led2 ; on 1/2 time
    btfss   pwm, B1
    bra     ledOff
    bra     ledOn

led1 ; on 1/4 time
    movf    pwm, f
    bz      ledOn
ledOff
    banksel LATC
    clrf    LATC
    banksel LATA
    bcf     G_PIN
ledDone

;-- 10 msec tasks

    ; wait for timer1 X10
    decfsz  msec_timer, f
    return
    MOVLF   10, msec_timer

;-- post events

;     0   0   1   0   0   0   0   0    -32 button event: press
;     0   0   1   0   0   0   0   1    -31 button event: hold (long press)
;     0   0   0   1   1   1   1   1    +31 button event: release

    ; button press event
    btfss   pgm_flags, PF_PRESSED
    bra     serial_no_button
    btfsc   profile, PR_DI_PRESS ; event enabled ?
    bra     serial_no_button
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; address 0
    MOVLF   0x20, tx_data    ; button press event
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    bcf     pgm_flags, PF_PRESSED
serial_no_button

    ; button long press event
    btfss   pgm_flags, PF_HOLD
    bra     serial_no_hold
    btfsc   profile, PR_DI_HOLD ; event enabled ?
    bra     serial_no_hold
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; address 0
    MOVLF   0x21, tx_data    ; button long press event
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    bcf     pgm_flags, PF_HOLD
serial_no_hold

    ; button release event
    btfss   pgm_flags, PF_RELEASE
    bra     serial_no_release
    btfss   profile, PR_EN_RELEASE ; event enabled ?
    bra     serial_no_release
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; address 0
    MOVLF   0x1F, tx_data    ; button release event
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    bcf     pgm_flags, PF_RELEASE
serial_no_release

    ; rotor spin event
    movf    rotor, w         ; any rotor spin ?
    bz      serial_end       ;   no
    btfsc   rotor, B7        ; handle +/- spin separately
    bra     serial_dn

serial_up
    incf    rotor, w         ; test for > 30
    andlw   b'11000000'      ; overflowed 6 bits ?
    bz      serial_up_low    ;   no
    ; spin > 30, only send 30 in this message
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; prepare to send to address 0
    MOVLF     30, tx_data    ; data code for +30 spin
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    subwf   rotor, f         ; rotor -= 30
    bra     serial_end

serial_up_low ; spin <= 30
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; prepare to send to address 0
    MOV     rotor, tx_data   ; full spin count
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    clrf    rotor
    bra     serial_end

serial_dn
    decf    rotor, w         ; test for < -30
    decf    WREG, w
    andlw   b'11100000'
    xorlw   b'11100000'
    bz      serial_dn_low    ;  >= -30
    ; spin < -30, only send -30 in this message
  DISABLE_INTERRUPTS
    MOVLF   0x40, tx_address ; prepare to send to address 0
    MOVLF   0x22, tx_data    ; data code for -30 spin (6 bit signed int)
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    movlw   30
    addwf   rotor, f         ; rotor += 30
    bra     serial_end

serial_dn_low ; spin >= -30
    MOVLF   0x40, tx_address ; prepare to send to address 0
    movf    rotor, w
    andlw   b'00111111'      ; mask to 6 bit signed int
  DISABLE_INTERRUPTS
    movwf   tx_data          ; sending full spin count
    MOVLF   0x40, tx_address ; prepare to send to address 0
    call    post_packet_to_tx_queue
  ENABLE_INTERRUPTS
    clrf    rotor

serial_end

;-- button logic

    banksel PORTA
    btfsc   BUTTON_PIN
    bra     button_open
    ; switch is closed
    btfsc   pgm_flags, PF_BUTTON  ; already in pressed state ?
    call    button_holddet        ;   yes, check for long press
    btfsc   pgm_flags, PF_BUTTON  ; already in pressed state ?
    bra     button_restart        ;   yes, restart debounce
    ; button press event
    bsf     pgm_flags, PF_BUTTON  ; track button state
    bsf     pgm_flags, PF_PRESSED ; set event signal
    MOVLF   longpress_ticks, hold_timer ; start long press timer
button_restart ; start debounce
    MOVLF   4, debounce           ; 40 msec debounce time
    return

button_open
    ; switch is open
    movf    debounce, f         ; debounce active ?
    bz      button_debounced
    decfsz  debounce, f         ; count down debounce time
    return
    bsf     pgm_flags, PF_RELEASE ; set event signal
button_debounced
    bcf     pgm_flags, PF_BUTTON  ; debounced, reset button state to unpressed
    return

button_holddet ; detect button hold condition
    movf    hold_timer, f       ; timer already expired ?
    bz      button_holddet_done
    decfsz  hold_timer, f       ; count down time
    bra     button_holddet_done
    ; hold detected
    bsf     pgm_flags, PF_HOLD
button_holddet_done
    return

;- - - - - - - - - - - - - - - - - - - - - - - -

VELOCIROTOR macro gear1_ms, gear2_ms, gear1_rot, gear2_rot, gear3_rot ; 3 speed velocirotor
  local go_gear1, go_gear2, go_gear3
    movlw   gear1_ms
    subwf   elapsed_msec, w ; C=0 gear1_ms > elapsed, C=1 gear1_ms <= elapsed
    bc      go_gear1
    movlw   gear2_ms
    subwf   elapsed_msec, w
    bc      go_gear2
go_gear3
    movlw   gear3_rot
    return
go_gear2
    movlw   gear2_rot
    return
go_gear1
    movlw   gear1_rot
    return
    endm

calcRotorIncr ; calc rotor increment based on profile & time since last rotation
    movf    profile, w
    andlw   0x03
    bz      use_profile_0
    decf    WREG, w
    bz      use_profile_1
    decf    WREG, w
    bz      use_profile_2
use_profile_3
    VELOCIROTOR 100, 10, 1,2,15  ; maximum acceleration

use_profile_0
    VELOCIROTOR  1,1,    1,1,1   ; no velocirotor acceleration

use_profile_1
    VELOCIROTOR 100, 20, 1,2,4   ; low acceleration

use_profile_2
    VELOCIROTOR 100, 20, 1,2,8   ; medium acceleration

;------------------------------------------------------------------------------
; END OF PROGRAM
;------------------------------------------------------------------------------

    END
