;   ***************************************************************
;   * 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 of routines that handle the A/D.
;
         include "hal.inc"

         extern  loop_main   ;main event loop, jump here after servicing an event

         extern_flags        ;declare global flag bits EXTERN
;
;***********************************************************************
;
;   Configuration constants.
;
filtb    equ     4           ;bits to shift right for filtering
save_read equ    10          ;save A/D reading every this many individual readings
n_save   equ     5           ;number of previous SAVE_READ readings to save
lbank    equ     0           ;register bank for the local state of this module
maxval   equ     25          ;light above this level results in OFF mode, % of full
maxjump  equ     maxval      ;max light jump to allow ON mode, % of full (disabled)
;
;   Derived constants.
;
lbankadr equ     bankadr(lbank) ;address within local state register bank
maxv     equ     maxval * 255 / 100 ;max light for ON mode, 0-255 scale
maxj     equ     maxjump * 255 / 100 ;max light jump for ON mode, 0-255 scale
;
;***********************************************************************
;
;   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

filt     res     2           ;filtered current A/D value
cntsave  res     1           ;number of readings until reading gets saved

ii       set     0           ;make SAVE0 - SAVEn previous saved values
  while ii < n_save
save#v(ii) res   1
ii       set     ii + 1
    endw

.ad    code
;
;***********************************************************************
;
;   Subroutine AD_INIT
;
;   Initialize the hardware and software state managed by this module.
;
         glbsub  ad_init, noregs
;
;   Set up the A/D hardware.
;
         dbankif adcon1
         movlw   b'00001110'
                 ; 0-------  result will be justified in high bits of 16 bit value
                 ; -XXX----  unimplemented
                 ; ----1110  single input channel, use PWR and GND as reference
         movwf   adcon1

         dbankif adcon0
         movlw   b'10000001'
                 ; 10------  conversion clock is oscillator/32
                 ; --000---  select analog channel 0
                 ; -----0--  don't start new conversion now
                 ; ------X-  unimplemented
                 ; -------1  enable the A/D converter
         movwf   adcon0

         dbankif pir1
         bcf     pir1, adif  ;reset to A/D conversion not just completed
;
;   Init local state.
;
         dbankif lbankadr

         movlw   maxv        ;init current value to max light before OFF mode
         movwf   filt+1
         clrf    filt+0

         movlw   n_save      ;init to max readings before save and process one
         movwf   cntsave

         movlw   255         ;init all saved readings to maximum brightness
ii       set     0
  while ii < n_save
         movwf   save#v(ii)
ii       set     ii + 1
    endw

         leaverest
;
;***********************************************************************
;
;   Routine AD_START
;
;   Start a new A/D conversion.  This routine is jumped to from the main
;   event loop, and must jump to LOOP_MAIN when done.  All registers may
;   be trashed.
;
         glbent  ad_start

         dbankif gbankadr
         bcf     flag_adconv ;reset the event condition

         dbankif adcon0
         bsf     adcon0, go  ;start a new A/D conversion

         gjump   loop_main   ;back to main event loop
;
;***********************************************************************
;
;   Local subroutine FILTSHIFT
;
;   Shift the 16 bit value in REG1,REG0 right by FILTB bits.
;
  if filtb <= 0              ;nothing to shift, this version does nothing
         locsub  filtshift, noregs
         leaverest
    endif


  if filtb > 0               ;must shift at least one bit
         locsub  filtshift, regf2

         movlw   filtb       ;init loop counter
         movwf   reg2

filt_loop                    ;back here each new bit to shift
         bcf     status, c   ;set bit value to shift in
         rrf     reg1        ;shift the 16 bit value right one bit
         rrf     reg0
         decfsz  reg2        ;count one less bit left to shift
         goto    filt_loop   ;back to shift another bit right

         leaverest
    endif
;
;***********************************************************************
;
;   Routine AD_READ
;
;   An A/D conversion has completed.  Process the result and jump back to
;   LOOP_MAIN when done.  All registers may be trashed.
;
         glbent  ad_read

         dbankif pir1
         bcf     pir1, adif  ;reset the event condition
;
;   Remove the filter fraction from FILT.
;
         dbankif lbankadr
         movf    filt+0, w   ;get filtered value into REG1,REG0
         movwf   reg0
         movf    filt+1, w
         movwf   reg1
         mcall   filtshift   ;shift right to make filter fraction

         dbankif lbankadr
         sub16   filt, reg0  ;remove the filter fraction from FILT
;
;   Get the A/D result as a 16 bit value in REG1,REG0.
;
         dbankif adresl
         movf    adresl, w   ;get the low A/D result byte
         movwf   reg0
         dbankif adresh
         movf    adresh, w   ;get the high A/D result byte
         movwf   reg1

         movwf   reg2        ;get high 8 bits also into REG2
         rrf     reg2
         rrf     reg2, w     ;get high 6 A/D bits in low 6 bits of W
         andlw   h'3F'       ;mask in only the 6 bits
         iorwf   reg0        ;replicate high 6 bits as low 6 bits
;
;   The A/D result is in REG1,REG0 expanded into a 16 bit unsigned value.
;
;   Now make the appropriate filter fraction of it and add it into FILT.
;
         mcall   filtshift   ;shift right to make filter fraction
         dbankif lbankadr
         add16   filt, reg0  ;add new value fraction into FILT
;
;   FILT has been completely updated with the new reading.
;
;   Now set FLAG_NEWOFF if the light level is above the max allowed before
;   everything needs to be shut down.
;
         dbankif lbankadr
         movf    filt+1, w   ;get the current light level
         sublw   maxv        ;compare to max allowed light level
         skip_wgt            ;too bright ?
         goto    done_toobr  ;not too bright

         dbankif gbankadr
         bsf     flag_newoff ;indicate to switch into OFF mode, if not already

done_toobr unbank            ;done checking absolute light level
;
;   Now check CNTSAVE and process and save this reading if it's time to do so.
;
         dbankif lbankadr
         decfsz  cntsave     ;count one less reading until check and save
         goto    done_save   ;this reading doesn't get processed and saved
;
;   Compare the new light level to the oldest saved, and set FLAG_NEWOFF
;   if the light increased by more than MAXJ.
;
         dbankif lbankadr
         movf    save0, w    ;get oldest saved light level
         subwf   filt+1, w   ;make light level increase from oldest saved to now
         skip_wle            ;light level went up or stayed the same ?
         goto    done_ljump  ;done dealing with light level jump

         sublw   maxj        ;compare light increase to max allowed before shutdown
         skip_wgt            ;light jump too much ?
         goto    done_ljump  ;light increase is within limits

         dbankif gbankadr
         bsf     flag_newoff ;indicate to switch into OFF mode, if not already

done_ljump unbank            ;done checking for and handling light level jump
;
;   Update the saved readings.  The oldest reading will be lost, and the
;   new value will be shifted in as the newest saved reading.
;
         dbankif lbankadr

ii       set     0
  while ii < (n_save - 1)    ;once for all but newest saved reading
         movf    save#v(ii+1), w ;get the next newer reading
         movwf   save#v(ii)  ;move it into this saved reading slot
ii       set     ii + 1
    endw

         movf    filt+1, w   ;get the current value
         movwf   save#v(n_save-1) ;copy it into the newest saved reading

done_save unbank             ;done checking and saving the latest value

         gjump   loop_main   ;done handling this event, back to main loop

         end