;   ***************************************************************
;   * Copyright (c) 2001, Embed Inc (http://www.embedinc.com)     *
;   *                                                             *
;   * Permission to copy this file is granted as long as this     *
;   * copyright notice is included in its entirety at the         *
;   * beginning of the file, whether the file is copied in whole  *
;   * or in part and regardless of whether other information is   *
;   * added to the copy.                                          *
;   *                                                             *
;   * The contents of this file may be used in any way,           *
;   * commercial or otherwise.  This file is provided "as is",    *
;   * and Embed Inc makes no claims of suitability for a          *
;   * particular purpose nor assumes any liability resulting from *
;   * its use.                                                    *
;   ***************************************************************
;
;   Module controlling the LED "eyes".  These eyes can shift left and right
;   to four different positions, and occasionally go off for a short time
;   to simulate blinking.
;
         include "hal.inc"

         extern  rand_max    ;set REG0 to random number in range 0 thru REG1
         extern  rand        ;set REG0 to a random byte value

         extern_flags        ;declare global flag bits EXTERN
;
;***********************************************************************
;
;   Configuration constants.
;
lbank    equ     0           ;register bank for the local state of this module
blnkmin  equ     60          ;min time between blinks, 100mS units
blnkmax  equ     80          ;max time between blinks, 100mS units
shftmin  equ     3           ;min time between shifting eyes, 100mS units
shftmax  equ     20          ;max time between shifting eyes, 100mS units
;
;   Derived constants.
;
lbankadr equ     bankadr(lbank) ;address within local state register bank
;
;***********************************************************************
;
;   Global state.  All this state is assumed to be in the GBANK register
;   bank by other modules.
;
.bank#v(gbank) udata

;
;***********************************************************************
;
;   Local state.
;
  if lbank != gbank
.bank#v(lbank) udata
    endif

eyes     res     1           ;"eyes" enable value, always exactly two bits 1
cntblnk  res     1           ;100mS ticks until next blink
cntshft  res     1           ;100mS ticks until next eyes shift

.eyes    code
;
;***********************************************************************
;
;   Subroutine EYES_INIT
;
;   Initialize the hardware and software state managed by this module.
;
         glbsub  eyes_init, noregs
;
;   Initialize the local state.
;
         dbankif lbankadr

         movlw   b'01000100' ;init the eyes value
         movwf   eyes

         movlw   (blnkmin + blnkmax) / 2 ;init time until next blink
         movwf   cntblnk

         movlw   (shftmin + shftmax) / 2 ;init time until next eyes shift
         movwf   cntshft
;
;   Initialize the hardware.
;
         dbankif lbankadr
         comf    eyes, w     ;get the value to write to the output port
         dbankif portb
         movwf   portb       ;light the LEDs indicated by the EYES value

         leaverest
;
;***********************************************************************
;
;   Subroutine EYES_OFF
;
;   OFF mode was just entered.  Update the eyes handling accordingly.
;
         glbsub  eyes_off, noregs

         dbankif portb
         movlw   h'FF'       ;shut off all the eye lights
         movwf   portb

         leaverest
;
;***********************************************************************
;
;   Subroutine EYES_100MS
;
;   This routine is called once every 100mS.  This routine updates the
;   eyes LEDs if appropriate.
;
         glbsub  eyes_100ms, regf0 | regf1
;
;   Check for OFF mode, in which case there is nothing to do.
;
         dbankif gbankadr
         btfsc   flag_off    ;not in OFF mode ?
         goto    leave_100ms ;in OFF mode, eye lights are disabled
;
;   Decide whether to blink this 100mS time slice.  If so, FLAG_BLINK will
;   be set, which will cause all the LEDs to be unlit later.
;
         dbankif gbankadr
         bcf     flag_blink  ;init flag to indicate eyes not blinking

         dbankif lbankadr
         decfsz  cntblnk     ;blink timer just expired, time to do a blink ?
         goto    done_blink  ;not time to do another blink yet

         dbankif gbankadr
         bsf     flag_blink  ;indicate we will now be blinking
         ;
         ;   Update the blink timer to the number of 100mS ticks until the
         ;   next blink.
         ;
         movlw   blnkmax - blnkmin ;make number of valid blink times in range
         movwf   reg1        ;pass random number max limit
         gcall   rand_max    ;get random number in REG0 that is at or below REG1
         movf    reg0, w     ;get the 0 - N random number into W
         addlw   blnkmin     ;make randomly chosen blink time
         dbankif lbankadr
         movwf   cntblnk     ;set new ticks until next blink
done_blink unbank
;
;   Decide whether to shift the eyes this time slice.  If so, update EYES
;   to the shifted value and reset CNTSHFT to the number of ticks until
;   the next time to shift the eyes.
;
         dbankif lbankadr
         decfsz  cntshft     ;count one less tick until time to shift the eyes
         goto    done_shift  ;not time yet to shift the eyes
         ;
         ;   It is time to shift the eyes.
         ;
         ;   Reload CNTSHFT to the number of 100mS ticks until the next
         ;   eyes shift.
         ;
         movlw   shftmax - shftmin ;make number of valid shift times in range
         movwf   reg1        ;pass random number max limit
         gcall   rand_max    ;get random number in REG0 that is at or below REG1
         movf    reg0, w     ;get the 0 - N random number into W
         addlw   shftmin     ;make randomly chosen shift time
         dbankif lbankadr
         movwf   cntshft     ;set new ticks until next shift
         ;
         ;   A random choice will be made to shift the eyes
         ;   left or right unless the eyes are already fully left or right.
         ;   In those cases, the eyes will be shifted in the only direction
         ;   possible.
         ;
         dbankif lbankadr
         btfsc   eyes, 7     ;could shift left ?
         goto    shift_right ;no, shifting right is the only choice
         btfsc   eyes, 0     ;could shift right ?
         goto    shift_left  ;no, shifting left is the only choice
         ;
         ;   The eyes could be shifted either direction.  Get a random number
         ;   to decide.
         ;
         gcall   rand        ;get a random byte value in REG0
         btfss   reg0, 0     ;shift left ?
         goto    shift_right ;shift right

shift_left unbank            ;shift the eyes left one position
         dbankif lbankadr
         bcf     status, c   ;set bit value to shift in
         rlf     eyes        ;shift the eyes left one position
         goto    done_shift

shift_right unbank           ;shift the eyes right one position
         dbankif lbankadr
         bcf     status, c   ;set bit value to shift in
         rrf     eyes        ;shift the eyes right one position

done_shift unbank            ;all done shifting the eyes
;
;   Write the current eyes state to the hardware.  Note that all LEDs should
;   be unlit when FLAG_BLINK is set.
;
         dbankif lbankadr
         comf    eyes, w     ;get hardware value for the indicated eyes lit
         dbankif gbankadr
         btfsc   flag_blink  ;not blinking right now ?
         movlw   h'FF'       ;blinking, get hardware value for all LEDs off
         dbankif portb
         movwf   portb       ;write the new eyes LED bits to the hardware port

leave_100ms unbank           ;common exit point
         leaverest

         end