;****************************************************************************
; Metronome based on a PIC16F84 microcontroller
;
; Cecilio Salmeron, 1999
;
;  Prefixes for data types:
;     f - flag                #define fLed   Flags,7
;     b - bit #               bLed    equ    7
;     p - port/RAM word       #define pLed   Flags
;    io - ioline              #define ioLed  portb,0
;     t - timer (software)    tKeyb   rem    1
;    dt - time interval       dtKeyb  rem    1
;    af - array of flags      #define afKeys  00100001b
;****************************************************************************
;
   list      p=16F84       ; list directive to define processor
   #include <p16F84x.inc>  ; processor specific variable definitions


	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _RC_OSC

; '__CONFIG' directive is used to embed configuration data within .asm file.
; The lables following the directive are located in the P16F84X.INC file. The
; following values are possible:
;
;      _CP_ON       Code Protection ON
;      _CP_OFF      Code Protection OFF
;
;      _PWRTE_ON    Power-Up Timer Enable Bit ON
;      _PWRTE_OFF   Power-Up Timer Enable Bit ON
;
;      _WDT_ON      Watch Dog Timer ON
;      _WDT_OFF     Watch Dog Timer OFF
;
;      _LP_OSC      Low Power crystal oscillator (32Khz-200Khz)
;      _XT_OSC      XT crystal oscillator (100Khz-4MHz)
;      _HS_OSC      HS crystal oscillator (4Mhz-10Mhz)
;      _RC_OSC      RC oscillator

;****************************************************************************
; Definitions for variables allocated in internal RAM
;****************************************************************************

; variables for "ISR" routine
;-------------------------------------------

   cblock 0x10
      save_w           ;temp. for context saving: reg W
      save_status      ;temp. for context saving: reg STATUS
      tTickH           ;static. 16bits timer: time between ticks
      tTickL
      nCurBeat         ;static. counter to track the measure
      kTickH           ;static. 16 bits value to reload tTick
      kTickL
      t_10ms           ;static. 10ms timer. Unit = 256us
      n_10ms           ;static. Value to reload the 10ms timer
      kNote            ;static. Value to reload timer0 when in note mode 
   endc

K_10ms equ 39   ;to reload 10ms timer in metronome mode: 39*256us = 9.984 ms


; LCD presentation atributes:

#define DISPLAY_ON   0x04 | 0x08     ; Display ON
#define CURSOR_ON    0x02 | 0x08     ; Cursor ON
#define BLINKING_ON  0x01 | 0x08     ; Blink character at cursor position

   cblock      ; LCD routines
      LCD_aux      ; nibble to write is kept in low nibble of this byte
      LCD_addr     ; current address
      LCD_saveW    ; to save W
      LCD_char     ; char to write to LCD (used only by WriteChar, putc)
   endc

   cblock      ; timming routines (Wait_Wx1ms, Wait_Wx100us, Wait_Wx10us)
      nWaitT1      ;temp. loop counter
      nWaitT2      ;temp. loop counter
   endc

   cblock      ; ScanKeys
      afKeysB      ;static: port B scan code (1=key ON, 0=key OFF)
      nChanges     ;temp. 1s in keys changed since last scan
      tKeyb        ;static: ScanKeys service timer. Unit = 10ms
      dtKeyb       ;static: min time to give service to ScanKeys again
   endc

KEY_SLOW     equ  50    ; 60 * 10ms = 600ms -> 3 hits / 2 sec
KEY_FAST     equ  5     ; 5 * 10ms = 50ms -> 20 hits/sec
KEY_DECR     equ  15    ; from 1.5 to 20 hits/s in 3 secs


; Global variables. Options
;--------------------------
   cblock
      nTempo       ;static. Tempo: number of beats (ticks) per minute (40-240)
      nMeasure     ;static. Number of beats in a bar (2-8)
      nNote        ;static. Number of note to generate (1-La, 12-Sol#)
      nScale       ;static. Number of the scale: 1 to 7
      tTempo       ;timer. Delay to ackowledge a tempo modific. Unit = 10ms
      afFlags      ;Flags, as follows
      afFlags2     ;Flags, as follows
      afFlags3     ;flags, as follows:
   endc

#define fLed           afFlags,0   ;Ligth is active
#define fSound         afFlags,1   ;Sound is active
#define fTick          afFlags,2   ;It is time to beep
#define fFirstBeat     afFlags,3   ;This is the first beat in a bar
#define fTempoModif    afFlags,4   ;Tempo has been modified
#define fKeyStatus     afFlags,5   ;Status, ON or OFF, of the key tested
#define fDisplay       afFlags,6   ;there is a request to update the display
#define fTempoTimer    afFlags,7   ;Tempo timer is enabled

#define fHighVol       afFlags2,0  ;sound at high volumen
#define fCompas54      afFlags2,1  ;it is a 5/4 compas
#define fCompas74      afFlags2,2  ;it is a 7/4 compas
#define fCompas84      afFlags2,3  ;it is a 8/4 compas
#define fModeNote      afFlags2,4  ;Mode note ON. Produce note
#define fOutLevel      afFlags2,5  ;sound-pin voltage level is High
#define fKbPhase2      afFlags2,6  ;ScanKeys is in phase 2
#define fAutoSpeed     afFlags2,7  ;Auto-speed mode in keyboard is enabled

#define f2ndPart       afFlags3,0  ;it is the 2nd 2-beats part of a 7/4 or
				   ;the 2nd 3-beats part of a 8/4

TEMPO_DELAY   equ  100   ; delay to acknowledge a "Tempo" modification.
			 ; 100 * 10ms = 1s

   cblock   ; Beats display
      nBeatAddr      ;static. To track LCD module address
      nBeatCount     ;static. counter to track the current beat
      nBeatNum       ;static. counter to track the current beat number
   endc

; Temporary data. In use only while the affected routine is being executed.
;---------------------------------------------------------------------------


   cblock   ; TempoToTime
      nRem       ; Remainder
      nCteH
      nCteM
      nCteL
      nDivCnt
   endc

   cblock   ; BinToBCD
      HSD
      MSD
      LSD
   endc

   cblock   ; Beep routine
      nFreq
      nCycles
   endc

   cblock
      aux1   ; test routine
      aux2   ; test routine
      temp
   endc


; Other definitions
;---------------------------------------------------------------------------
     variable  iLbl=0         ; to generate unique labels for macros

; Ports A & B: pin names and definitions
;---------------------------------------------------------------------------

#define  ioSound1   porta,2
#define  ioSound2   porta,3
#define  ioKBE      porta,4      ; keyboard enable (active LOW)

#define  Bkeys   11110110b    ; 1 = pins assigned to keys (RB7-RB4,RB2,RB1)
#define  BShared 00001111b    ; 0 = pins shared. The should be outputs.
			      ;   Programed as inputs only for reading the
			      ;   keyboard (RB7-RB4)
#define  afAutoSpeedB  00110000b  ; keys with auto-speed option enabled
				  ;    key1 & key2

#define  ioLED   portb,0      ; LED through a 470 ohm resistor to GND.
#define  fKey1   afKeysB,4    ; flag. Key 1 is pressed
#define  fKey2   afKeysB,5    ; flag. Key 2 is pressed
#define  fKey3   afKeysB,6    ; flag. Key 3 is pressed
#define  fKey4   afKeysB,7    ; flag. Key 4 is pressed
#define  fKey5   afKeysB,2    ; flag. Key 5 is pressed
#define  fKey6   afKeysB,1    ; flag. Key 6 is pressed

#define  ioLCD_E   porta,1    ;port bit assigned to LCD control line E
#define  ioLCD_RW  porta,0    ;port bit assigned to LCD control line RW
#define  ioLCD_RS  portb,3    ;port bit assigned to LCD control line RS

#define  port_LCD  portb      ;port used for LCD data lines
bLCD_D7  equ       7          ;bit number assigned to LCD line D7
#define  tris_LCD  trisb      ;tris register for LCD data lines

#define LCD_lines  1         ; display type: 1 line or 2 lines

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Macros

; decrement WORD (fh,fl) - 8us @ 4MHz
;-------------------------------------
dcr16:   macro  fh,fl
     movf   fl,f         ; if (fl==0)
     btfsc  status,z
     decf   fh,f         ;    fh--
     decf   fl,f         ; fl--
  endm

; increment WORD (fh,fl)  -  3us @ 4MHz
;-----------------------------------------
incr16:   macro    fh,fl
     incr   fl,f       ; fl++
     btfsc  status,z   ; if (fl==0)   // if overflow
     incr   fh,f       ;    fh++
  endm


; decrement WORD (fh,fl) and goto "label" if result is zero  - 16us @ 4MHz
;---------------------------------------------------------------------------
dcr16jz:    macro     fh,fl,label
iLbl++
     movf   fh,f       ; if (fh==0)
     btfss  status,z
     goto   else#v(iLbl)
		       ; {
     decfsz fl,w      ;    fl--
     goto   endif#v(iLbl)
     goto   label      ;    if (fl==0) jump to label
else#v(iLbl):          ; } else {
     dcr16  fh,fl      ;    (fh,fl)--
endif#v(iLbl):         ; }

  endm

; jump if WORD is zero - 5us @ 4 MHz
;-----------------------------------
jz16:   macro   fh,fl,label
     movf   fl,w              ;  w = fl || fh
     iorwf  fh,w
     bz    label              ;  if (!(fl || fh)) goto label
   endm

; jump if WORD is not zero - 5us @ 4 MHz
;---------------------------------------
jnz16:   macro   fh,fl,label
     movf   fl,w              ;  w = fl || fh
     iorwf  fh,w
     bnz    label             ;  if (fl || fh) goto label
   endm


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Code


;****************************************************************************
; Program code starts here
;****************************************************************************
Reset:
	org    0x000         ; processor reset vector
	goto   Start         ; go to beginning of program

;****************************************************************************
; ISR - Interrupt Service Routine
; In mode metronome (fModeNote == FALSE):
;   A timer0 interrupt is produced every 256us (f=4MHz). The elapsed
;   time counter "tTick" is decremented. If the count reaches zero then
;   the flags fTick and fFirstBeat are set properly, so that in the main loop
;   this condition is detected and a 'tick' sound and a ligth flash
;   are produced.
;
; In mode note (fModeNote == TRUE):
;   A timer 0 interrupt is produced every 8x(256-114)us = 1136us. The
;   voltage level at sound pin is toggled.
;
; In both modes:
;   Other timers are updated. But as the t_10ms timer is calculated to
;   work in metronome mode, keyboard would receive attention much slower.
;   To avoid it, the reload value (n_10ms) for the t_10ms timer is changed
;   depending on the mode and on the note frequency.
;****************************************************************************
ISR:
     org     0x004           ; interrupt vector address
     movwf   save_w            ; save off current W register contents
     movf    status,w          ; move status register into W register
     movwf   save_status       ; save off contents of STATUS register
     bcf     status,rp0        ; ensure we are in bank 0

     btfss   fModeNote         ; if (Mode == note)
     goto    ISR_noNote        ; {
     call    ToggleNote        ;    execute "Toggle note" task
     movf    kNote,w           ;    reload timer 0
     movwf   tmr0
     goto    ISR_timers        ; }
ISR_noNote:                    ; else {   // Mode == metronome
     dcr16  tTickH,tTickL      ;    decrement elapsed time counter
     jnz16  tTickH,tTickL,ISR_noTick
			       ;    if (tTick==0) {
     incf   nBeatCount,f       ;       nBeatCount++
     incf   nBeatNum,f         ;       nBeatNum++
     bsf    fTick              ;       fTick = TRUE
     decfsz nCurBeat,f         ;       if(--nCurBeat == 0)
     goto   ISR_noFirstBeat    ;       {
     movf   nMeasure,w         ;          nCurBeat = nMeasure
     movwf  nCurBeat
     bsf    fFirstBeat         ;          fFirstBeat = TRUE
     movlw  1                  ;          nBeatNum = 1
     movwf  nBeatNum
     btfss  fCompas54          ;          if (compas == 5/4)
     goto   ISR_no54           ;          {
     movlw  3                  ;             if (nMeasure == 3)
     subwf  nMeasure,w
     bnz    ISR_measure_2      ;             {
     decf   nMeasure,f         ;                nMeasure--
     movlw  1                  ;                nBeatCount = 1
     movwf  nBeatCount
     goto   ISR_measure_ok     ;             }
ISR_measure_2:                 ;             else
     incf   nMeasure,f         ;                nMeasure++
     goto   ISR_measure_ok     ;          }
ISR_no54:
     btfss  fCompas74          ;          else if (compas == 7/4)
     goto   ISR_no74           ;          {
     movlw  3                  ;             if (nMeasure == 3)
     subwf  nMeasure,w
     bnz    ISR_measure_two    ;             {
     decf   nMeasure,f         ;                nMeasure--
     bcf    f2ndPart           ;                2nd 2-beats part = FALSE
     movlw  1                  ;                nBeatCount = 1
     movwf  nBeatCount
     goto   ISR_measure_ok     ;             }
ISR_measure_two:               ;             else {
     btfsc  f2ndPart           ;                if (2nd 2-beats part)
     incf   nMeasure,f         ;                   nMeasure++
     bsf    f2ndPart           ;                2nd 2-beats part = TRUE
     goto   ISR_measure_ok     ;             }
ISR_no74:                      ;          }

     btfss  fCompas84          ;          else if (compas == 8/4)
     goto   ISR_no84           ;          {
     movlw  2                  ;             if (nMeasure == 2)
     subwf  nMeasure,w
     bnz    ISR_measure_three  ;             {
     incf   nMeasure,f         ;                nMeasure++
     bcf    f2ndPart           ;                2nd 3-beats part = FALSE
     movlw  1                  ;                nBeatCount = 1
     movwf  nBeatCount
     goto   ISR_measure_ok     ;             }
ISR_measure_three:             ;             else {
     btfsc  f2ndPart           ;                if (2nd 3-beats part)
     decf   nMeasure,f         ;                   nMeasure--
     bsf    f2ndPart           ;                2nd 3-beats part = TRUE
     goto   ISR_measure_ok     ;             }
ISR_no84:                      ;          }
			       ;          else {  // no 5/4, 7/4, 8/4
     movlw  1                  ;                nBeatCount = 1
     movwf  nBeatCount
			       ;          }
ISR_measure_ok:                ;       }  // if nCurBeat == 0
ISR_noFirstBeat:
     movf   kTickH,w           ;       tTick=kTick
     movwf  tTickH
     movf   kTickL,w
     movwf  tTickL
ISR_noTick:                    ;    }
ISR_timers:                    ; }
     decfsz  t_10ms,f          ; decrement 10ms timer
     goto    ISR_final         ; if (10ms elapsed) {
			       ;    // reload 10ms timer and update timers
			       ;    // based on 10ms units
     movf    n_10ms,w          ;    reload t_10ms timer.
     movwf   t_10ms
     incf    tKeyb,f           ;    incr keyboard timer. Unit = 10ms
     btfss   fTempoTimer       ;    if (Tempo timer is enabled)
     goto    ISR_final         ;    {
     decfsz  tTempo,f          ;       if (-- Tempo timer == 0)
     goto    ISR_final         ;       {  // Tempo delay elapsed
     bcf     fTempoTimer       ;          disable Tempo timer
     bsf     fTempoModif       ;          request service to change Tempo
			       ;       }
ISR_final:                     ;    }
			       ; }

     ; Final tasks
     bcf     intcon,t0if       ; reset the Interrupt flag
     clrwdt                    ; clear watchdog and prescaler

     ; Return from interrupt
     movf    save_status,w     ; retrieve copy of STATUS register
     movwf   status            ; restore pre-isr STATUS register contents
     swapf   save_w,f
     swapf   save_w,w          ; restore pre-isr W register contents
     retfie                    ; return from interrupt


;****************************************************************************
;  Reset code. Set up and initialize the processor
;****************************************************************************
Start:
	clrf   tmr0
	clrf   status

	; clear all RAM memory from 05h to 4fh
	movlw  porta                ; fsr = *beginning of RAM address
	movwf  fsr
clear_mem:                          ; do {
	clrf   indf                 ;    *fsr = 0
	incf   fsr,f                ;    fsr++
	movlw  0x50                 ; } while (fsr != 50h)
	subwf  fsr,w
	bnz    clear_mem
	clrf   fsr                  ; lastly, clear FSR

	; initialize port a: all pins are outputs
	clrf   porta                ; output data latches to 0
	bsf    status,rp0           ; select bank 1
	clrf   trisa                ; program port A: 1=inputs, 0=outputs
	bcf    status,rp0           ; select bank 0

	; initialize port b: all pins are outputs with weak pull-up
	clrf   portb                ; output data latches to 0
	bsf    status,rp0           ; select bank 1
	bcf    option_reg,not_rbpu  ; enable weak pull-up resistors
	clrf   trisb                ; all lines as outputs
	bcf    status,rp0           ; select bank 0

	; initialize the hardware
	bsf    ioKBE                ; disable keys on portb
	call   LCD_Initialize       ; initialize the LCD module

	; initialize static variables
	clrf   afKeysB         ;no key pressed
	clrf   tKeyb
	movlw  K_10ms
	movwf  n_10ms
	movwf  t_10ms

	movlw  120             ;nTempo = 120 ticks per minute
	movwf  nTempo
	movlw  3               ;nMeasure = 3 (3 beats in a bar, i.e.: 3/4)
	movwf  nMeasure
	movlw  1               ;nCurBeat = 1 so next tick became first of bar
	movwf  nCurBeat
	movwf  nNote           ;nNote = 1, to generate "La" note
	movlw  4
	movwf  nScale          ; scale = 4. La = 440 Hz
	call   TempoToTime     ;set up time counters for ISR
	clrf   afFlags         ;all flags OFF
	clrf   afFlags2
	bsf    fLed            ;light=ON
	bsf    fSound          ;sound=ON
	bsf    fAutoSpeed      ;auto-speed mode enabled

	; Welcome message "ELEKTOR 2000"
	call   LCD_CleanUp     ;0
	movlw  'E'
	call   LCD_SendData    ;1
	movlw  'l'
	call   LCD_SendData    ;2
	movlw  'e'
	call   LCD_SendData    ;3
	movlw  'k'
	call   LCD_SendData    ;4
	movlw  't'
	call   LCD_SendData    ;5
	movlw  'o'
	call   LCD_SendData    ;6
	movlw  'r'
	call   LCD_SendData    ;7
	movlw  ' '
	call   LCD_SendData    ;8
	movlw  '2'
	call   LCD_SendData    ;9
	movlw  '0'
	call   LCD_SendData    ;10
	movlw  '0'
	call   LCD_SendData    ;11
	movlw  '0'
	call   LCD_SendData    ;12

	movlw  250             ; wait 1 second
	call   Wait_Wx1ms
	movlw  250
	call   Wait_Wx1ms
	movlw  250
	call   Wait_Wx1ms
	movlw  250
	call   Wait_Wx1ms

	call   UpdateDisplay      ; start the metronome show

	; Now set up timer0, enable interruptions and go on
	bsf    status,rp0         ; select bank 1
	bcf    option_reg,t0cs    ; select timer mode
	bsf    option_reg,psa     ; assign prescaler to watchdog
	bcf    intcon,t0if        ; clear interruption flag
	bsf    intcon,t0ie        ; enable timer0 interruptions
	bsf    intcon,gie         ; global: enable interrupts
	bcf    status,rp0         ; select bank 0

	goto   MainLoop

;****************************************************************************
;****************************************************************************
;**  Main loop start here
;****************************************************************************
;****************************************************************************

MainLoop:

; Task #1: produce ticks

	btfsc   fTempoTimer        ; if tempo delay timer is enabled
	goto    chk_ticks_end      ;    ignore ticks
	btfss   fTick              ; if (time to produce a "tick")
	goto    chk_ticks_end      ; {
	call    DisplayBeatNumber  ;    do visual show
	call    LightAndSound      ;    do noise and flash light
	bcf     fFirstBeat         ;    if it was first beat, reset it
	bcf     fTick              ;    mark request as served
chk_ticks_end:                     ; }

; Task #2: update the information on the display

	btfss   fDisplay           ; if update display requested
	goto    chk_display_end    ; {
	call    UpdateDisplay      ;    update the display
	bcf     fDisplay           ;    mark request as served
chk_display_end:                   ; }

; Task #3: read keyboard and process keys

	movf    dtKeyb,w           ; if (tKeyb >= dtKeyb)
	subwf   tKeyb,w            ;
	bnc     chk_keyb_end       ; {
	clrf    tKeyb              ;    reset timer
	call    ScanKeys           ;    ScanKeys()
	btfsc   fKbPhase2          ;    if (phase 2 not finished)
	goto    chk_keyb_end       ;       skip. Key readings not ready yet
	btfss   fKey1              ;    if (key 1 is pressed)
	goto    key2?              ;    {
	btfss   fModeNote          ;       if (mode == metronome)
	goto    IncrTempo          ;          Increment_tempo()
	goto    IncrScale          ;       else Increment_Scale()
key2?:                             ;    }
	btfss   fKey2              ;    else if (key 2 is pressed)
	goto    key3?              ;    {
	btfss   fModeNote          ;       if (mode == metronome)
	goto    DecrTempo          ;          Decrement_Tempo()
	goto    DecrScale          ;       else  Decrement_Scale()
key3?:                             ;    }
	btfsc   fKey3              ;    else if (key 3 is pressed)
	goto    ChangeSound        ;       Toggle sound status
	btfsc   fKey4              ;    else if (key 4 is pressed)
	goto    ToggleLed          ;       Toggle light status
	btfss   fKey5              ;    else if (key 5 is pressed)
	goto    key6?              ;    {
	btfss   fModeNote          ;       if (mode == metronome)
	goto    IncrMeasure        ;          Increment_Measure()
	goto    IncrNote           ;       else  Increment_Note()
key6?:                             ;    }
	btfsc   fKey6              ;    else if (key 6 is pressed)
	goto    ChangeMode         ;       Change mode
chk_keyb_end:                      ; }

; Task #4: update counters when tempo is changed

	btfss   fTempoModif        ; if (fTempoModified)
	goto    chk_Tempo_end      ; {
	call    TempoToTime        ;    reload elapsed time counter and...
				   ;    ...reload it with new data
	bsf     fDisplay           ;    ask for updating the display
	bcf     fTempoModif        ;    fTempoModif=FALSE
chk_Tempo_end                      ; }

	goto    MainLoop


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Increment tempo
IncrTempo:
     incf    nTempo,f             ; nTempo++
     movlw   240                  ; if (nTempo >= 240)
     subwf   nTempo,W
     bnc     TempoOK              ; {
     movlw   240                  ;    nTempo = 240
     movwf   nTempo               ; }
TempoOK:
     bsf     fDisplay             ; request for an update of the display
     movlw   TEMPO_DELAY          ; load Tempo delay timer
     movwf   tTempo
     bsf     fTempoTimer          ; start it
     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Decrement tempo
DecrTempo:
     decf    nTempo,f             ; nTempo--
     movlw   40                   ; if (nTempo < 40) {
     subwf   nTempo,W
     bc      TempoOK              ; {
     movlw   40                   ;    nTempo = 40
     movwf   nTempo               ; }
     goto    TempoOK

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Increment scale
IncrScale:
     incf    nScale,f             ; nScale++
     movlw   7                    ; if (nScale >= 7)
     subwf   nScale,w
     bnc     scale_not_7          ; {
     movlw   6                    ;    nScale = 6
     movwf   nScale
     goto    scale_ok             ; }
scale_not_7:                      ; else {
     bcf     status,c             ;    n_10ms *= 2
     rlf     n_10ms,f
scale_ok:                         ; }
     call    SetPrescaler         ; change the prescaler ratio
     movf    n_10ms,w             ; reset 10ms timer
     movwf   t_10ms
     bsf     fDisplay             ; request for an update of the display
     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Decrement scale
DecrScale:
     decf    nScale,f             ; nScale--
     movlw   2                    ; if (nScale < 2) {
     subwf   nScale,W
     bc      scale_not_1          ; {
     movlw   2                    ;    nScale = 2
     movwf   nScale
     goto    scale_ok             ; }
scale_not_1:                      ; else {
     bcf     status,c             ;    n_10ms /= 2
     rrf     n_10ms,f
     goto    scale_ok             ; }

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Change sound status.
; Cycle in mode "metronome":  Vol H -> Vol L -> sound OFF -> Vol H
; Cycle in mode "note": Vol H -> Vol L -> Vol H

ChangeSound:
     btfss   fHighVol         ; if (Volume == High)
     goto    sound_low
     bcf     fHighVol         ;    Volume = Low     // Vol H -> Vol L
     goto    sound_end        ;    done
sound_low:
     btfss   fSound           ; if ((Sound == ON)
     goto    sound_on
     btfsc   fModeNote        ;    && (mode != note))
     goto    sound_on         ; {
     bcf     fSound           ;    Sound = OFF      // Vol L -> sound OFF
     goto    sound_end        ;    done
sound_on:                     ; } else {
     bsf     fSound           ;    Sound = ON       // sound OFF -> Vol H
     bsf     fHighVol         ;    Vol = High
sound_end:                    ; }
     bsf     fDisplay         ; request to update the display
     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TLD - Toggle light status: if active, deactivate it; and viceversa

ToggleLed:                 ; fLed = ~fLed
     btfsc   fLed             ; if (Led == OFF)
     goto    TLD_off          ; {
     bsf     fLed             ;    Led = ON
     goto    TLD_done         ; }
TLD_off:                      ; else
     bcf     fLed             ;    Led = OFF
TLD_done:
     bsf     fDisplay         ; ask for updating the display
     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INC - Increment note

IncrNote:
     incf    nNote,f              ; nNote++
     movlw   13                   ; if (nNote >= 13)
     subwf   nNote,w
     bnc     INC_no12             ; {
     movlw   1
     movwf   nNote                ;    nNote = 1
INC_no12:                         ; }
     movlw   HIGH NoteTable       ; load PCLATH with 5 high bits of address
     movwf   pclath
     decf    nNote,w              ; Get reload value for timer0
     call    NoteTable
     movwf   kNote                ; and store it
     bsf     fDisplay             ; request for an update of the display
     goto    chk_keyb_end

NoteTable:
     addwf   pcl,f
     retlw   116         ; La
     retlw   124         ; La#
     retlw   131         ; Si
     retlw   139         ; Do
     retlw   145         ; Do#
     retlw   152         ; Re
     retlw   158         ; Re#
     retlw   163         ; Mi
     retlw   169         ; Fa
     retlw   174         ; Fa#
     retlw   178         ; Sol
     retlw   183         ; Sol#
NoteTable_End:
;
   if ( (NoteTable & 0x0FF) >= (NoteTable_End & 0x0FF) )
       MESSG   "Warning: table NoteTable crosses page boundry"
    endif
;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INM - Increment number of beats in a bar. It loops through the sequence:
;         1, 2, 3, 4, 5, 6, 7, 5/4, 7/4, 8/4

IncrMeasure:
     btfss   fCompas54            ; if (compas == 5/4)
     goto    INM_no_54            ; {
     bcf     fCompas54            ;    5/4 compas = FALSE
     bsf     fCompas74            ;    7/4 compas = TRUE
     goto    INM_done             ; }
INM_no_54:
     btfss   fCompas74            ; if (compas == 7/4)
     goto    INM_no_74            ; {
     bcf     fCompas74            ;    7/4 compas = FALSE
     bsf     fCompas84            ;    8/4 compas = TRUE
     goto    INM_done             ; }
INM_no_74:
     btfss   fCompas84            ; else if (compas == 8/4)
     goto    INM_no_84            ; {
     movlw   1                    ;    nMeasure = 1
     movwf   nMeasure
     bcf     fCompas84            ;    8/4 compas = FALSE
     goto    INM_done             ; }
INM_no_84:                        ; else {   // here nMeasure < 8
     incf    nMeasure,f           ;    nMeasure++
				  ;    // Check if new compas is 5/4
     movlw   8                    ;    if (nMeasure == 8)
     subwf   nMeasure,w
     bnz     INM_no8              ;    {
     bsf     fCompas54            ;       5/4 compas = TRUE
     movlw   3
     movwf   nMeasure             ;       nMeasure = 3
INM_no8:                          ;    }
INM_done:                         ; }

     ; Restart bar count
     movlw   1               ; nCurBeat=1, so next tick will be start of bar
     movwf   nCurBeat
     bsf     fDisplay        ; request for an update of the display

     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; CHM - Toggle between metronome and note modes

ChangeMode:
     bcf     intcon,gie       ; disable interrupts
     btfsc   fModeNote        ; if (Mode note == FALSE)
     goto    CHM_is_on        ; {
     bsf     fModeNote        ;    Mode note = TRUE
     bcf     fAutoSpeed       ;    disable auto-speed on keyboard
     movlw   116              ;    f=440 Hz (LA) -> T=2273us -> T/2=1136us ->
     movwf   kNote            ;    -> 1136/8=142 -> 258-142=116
     movlw   8                ;    reload value for 10ms timer: 10ms/1136us=
     movwf   n_10ms           ;        = 10000/1136 = 8,8
     movwf   t_10ms           ;    and reset 10ms timer
     movlw   4                ;    nScale = 4
     movwf   nScale
     clrwdt                   ;    feed the watchdog to avoid problems
     bsf     status,rp0       ;    change to bank 1
     bcf     option_reg,psa   ;    assign prescaler to timer 0
     call    SetPrescaler     ;    set prescaler ratio
     goto    CHM_done         ; }
CHM_is_on:                    ; else {
     bcf     fModeNote        ;    Mode note = FALSE
     bsf     fAutoSpeed       ;    enable auto-speed on keyboard
     bcf     ioSound1         ;    ioSound1 = OFF  ; ensure no DC bias
     bcf     ioSound2         ;    ioSound2 = OFF
     movlw   K_10ms           ;    reload value for 10ms timer
     movwf   n_10ms
     movwf   t_10ms           ;    and reset 10ms timer
     clrf    tmr0             ;    clear tmr0 and prescaler
     bsf     status,rp0       ;    change to bank 1
     clrwdt                   ;    clear watchdog
     bsf     option_reg,psa   ;    assign prescaler to watchdog
     movlw   11111000b        ;    clear ps2:ps0 -> prescaler rate 1:1
     andwf   option_reg,f
     bcf     status,rp0       ;    return to bank 0
CHM_done:                     ; }
     bsf     intcon,gie       ; enable interrupts
     bsf     fDisplay         ; request for an update of the display
     goto    chk_keyb_end

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TGN - Toggle sound pins. Call by ISR routine

ToggleNote:
     btfss   fOutLevel         ; if (sound-pin level == H)
     goto    TGN_level_low     ; {
     bcf     ioSound1          ;    ioSound1 = OFF
     btfsc   fHighVol          ;    if (volumen == high)
     bsf     ioSound2          ;       ioSound2=ON to double volt. change
     bcf     fOutLevel         ;    sound-pin level = L
     goto    TGN_level_done    ; }
TGN_level_low:                 ; else {
     bsf     ioSound1          ;    ioSound1 = ON
     bcf     ioSound2          ;    ioSound2 = OFF
     bsf     fOutLevel         ;    sound-pin level = H
TGN_level_done:                ; }
     return

;****************************************************************************
;****************************************************************************
;**  Subroutines start here
;****************************************************************************
;****************************************************************************

;****************************************************************************
; SCK - ScanKeys
;****************************************************************************
ScanKeys:
	btfsc  fKbPhase2           ; if second phase of ScanKeys goto
	goto   SCK_phase_2         ; code after waiting 20m for debouncing
				   ; keys

; Phase 1: first read of keyboard

	; ensure LCD is disabled and change shared io lines to inputs
	bcf    ioLCD_E
	bsf    status,rp0           ; select bank 1
	movlw  Bkeys                ; 1=pins assigned to keys
	iorwf  trisb,f              ; program port b
	bcf    status,rp0           ; select bank 0

	; read keys at port b
	bcf    ioKBE                ; enable keyboard
	movf   portb,w              ; read keyscan
	xorlw  0ffh                 ; negate w so keys pressed became 1s
	xorwf  afKeysB,w            ; w = changes since last scan
	movwf  nChanges             ; save changes

	; restore shared io lines as outputs
	bsf    status,rp0           ; select bank 1
	movlw  BShared              ; 0= lines to restore as outputs
	andwf  trisb,f              ; program portb pins
	bcf    status,rp0           ; select bank 0

	bsf    fKbPhase2            ; change to phase 2
	clrf   tKeyb                ; wait 20ms for debouncing keys
	movlw  2                    ;    (2x10ms = 20ms)
	movwf  dtKeyb
	return                      ; and return

; Phase 2: after waiting for 20ms re-read keyboard again for debouncing keys

SCK_phase_2:
	; ensure LCD is disabled and change shared io lines to inputs
	bcf    ioLCD_E
	bsf    status,rp0           ; select bank 1
	movlw  Bkeys                ; 1=pins assigned to keys
	iorwf  trisb,f              ; program port b
	bcf    status,rp0           ; select bank 0

	; read keys at port b
	movf   portb,w              ; read again keyscan
	xorlw  0ffh                 ; negate w so keys pressed became 1s
	xorwf  afKeysB,w            ; w = detect again changes
	andwf  nChanges,f           ; reset bits not equal in both readings
	movf   nChanges,w           ; get keys changed
	xorwf  afKeysB,f            ; update keyscan with keys pressed
	movlw  Bkeys                ; 1=pins assigned to keys
	andwf  afKeysB,f            ; mask off bits not used
	andwf  nChanges,f           ; mask off bits not used
	bsf    ioKBE                ; disable keyboard

	; restore shared io lines as outputs
	bsf    status,rp0           ; select bank 1
	movlw  BShared              ; 0= lines to restore as outputs
	andwf  trisb,f              ; program portb pins
	bcf    status,rp0           ; select bank 0

	; key auto-speed feature management. For keys with this feature
	; enabled, if the key is hold pressed, its repetition rate is
	; pregresively incremented.

	btfss  fAutoSpeed        ; if auto-speed mode enabled
	goto   no_auto_speed     ; {
	movf   afKeysB,w         ;    if (any new key pressed)
	andwf  nChanges,w
	bnz    speed_slow        ;       Set slow repetition rate
	movf   afKeysB,w         ;    else if(any autospeed key still pressed)
	andlw  afAutoSpeedB
	bnz    speed_up          ;    increase repetition rate
no_auto_speed:                   ; }
	movf   afKeysB,w         ; if (any other key still pressed)
	bnz    speed_slow        ;    keep slow repetition rate
	goto   speed_max         ; else  no key. Fast response to keyboard

speed_up:              ;the service time is decreased to "speed up" the key.
	movf   dtKeyb,w          ; if (dtKeyb == KEY_FAST)
	sublw  KEY_FAST
	bz     speed_ok          ;    maximun speed reached. No changes.
	movlw  KEY_DECR          ; if (dtKeyb >= KEY_DECR)
	subwf  dtKeyb,f          ;    dtKeyb -= KEY_DECR
	bnc    speed_max         ; else goto speed_max
	movlw  KEY_FAST          ; if (dtKeyb >= KEY_FAST)
	subwf  dtKeyb,w
	bc     speed_ok          ;    goto speed_ok

speed_max:
	movlw  KEY_FAST          ; dtKeyb = KEY_FAST
	movwf  dtKeyb
	goto   speed_ok

speed_slow:
	movlw  KEY_SLOW
	movwf  dtKeyb

speed_ok:
	bcf    fKbPhase2         ; next time, do phase 1
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Change timer 0 prescaler ratio: ps2:ps0 = 6 - nScale

SetPrescaler:
     movlw   6                    ; temp = 6 - nScale
     movwf   temp
     movf    nScale,w
     subwf   temp,f
     bsf     status,rp0           ; change to bank 1
     movlw   11111000b            ; clear ps2:ps0
     andwf   option_reg,f
     movf    temp,w               ; ps2:ps0 = 6 - nScale
     iorwf   option_reg,f
     bcf     status,rp0           ; return to bank 0

     return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Binary To BCD Conversion Routine
;
; This routine converts the 8 bit binary number in the W register to a
; 3 digit BCD number. The least significant digit is returned in location
; LSD, the next digit is returned in MSD and the most significant digit
; is returned in HSD.
;
BinToBCD:
	clrf    HSD             ; HSD = 0
	clrf    MSD             ; MSD = 0
	movwf   LSD             ; LSD = w
B2B_loop:                       ; while(TRUE) {
	movlw   .100            ;    if (LSD >= 100)
	subwf   LSD,W
	btfss   status,c
	goto    B2B_else        ;    {
	movwf   LSD             ;       LSD -= 100
	incf    HSD,f           ;       HSD++
	goto    B2B_loop        ;    }
B2B_else:                       ;    else {
	movlw   .10             ;       if (LSD < 10)
	subwf   LSD,W
	btfss   status,C
	return                  ;          return
	movwf   LSD             ;       LSD -= 10
	incf    MSD, F          ;       MSB++
				;    }
	goto    B2B_loop        ; }


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TTT - TempoToTime
;   This routine calculates the number of timer0 interruptions necessary
;   to generate the required tempo. This number is saved in kTick<H,L>
;   Then, the elapsed time counter tTick<H,L> is reloaded with the
;   computed value, and the counter nCurBeat is reloaded to start a new bar;
;   flags fFirstBeat and fTick are set.
;
;   The next formulas are used (tempo is expresed in beats per minute):
;       Period = 60/tempo
;       No. of interruptions = Period/interrupt_period =
;              = (60/interrupt_period)/tempo = Cte/tempo
;
;   where  Cte = 60/interrupt_period = 60/256E-6 = 234375

TempoToTime:
     ; disable interrupts
     bcf    intcon,gie

     ; compute new value for kTick
     movlw  0x03        ; nCte = 0x039387  (234375)
     movwf  nCteH
     movlw  0x93
     movwf  nCteM
     movlw  0x87
     movwf  nCteL
		      ; 24bit/8bit division algorithm:
		      ;    nRem = nCte<H,M,L> % nTempo
		      ;    nCte<M,L> = nCte<H,M,L> / nTempo
     clrf    nRem            ; Rem = 0
     movlw   24              ; i=24   // No. bits of Num
     movwf   nDivCnt
TTT_loop:                    ; for(; i > 0; i--) {
     bcf     status,c        ;    C:Num = Num*2;  Quo <<= 1;
     rlf     nCteL,f
     rlf     nCteM,f
     rlf     nCteH,f
     rlf     nRem,f          ;    Rem = 2*Rem + C
     btfsc   status,c        ;    if (Rem >= Div)  // if C==1 then Rem>Div
     goto    TTT_then
     movf    nTempo,w
     subwf   nRem,w
     btfss   status,c
     goto    TTT_lower
TTT_then:                    ;    {
     movf    nTempo,w
     subwf   nRem,f          ;       Rem -= Div
     bsf     nCteL,0         ;       Quo |= 1
TTT_lower:                   ;    }
     decfsz  nDivCnt,f       ; }
     goto    TTT_loop

     movf   nCteM,w          ; kTick<H,L> = nCte<M,L>
     movwf  kTickH
     movf   nCteL,w
     movwf  kTickL

     ; Reload elapsed time counter tTick
     movf    kTickH,w        ; tTick=kTick
     movwf   tTickH
     movf    kTickL,w
     movwf   tTickL

     ; Restart bar measure
     movlw  1               ;nCurBeat=1, so next tick will be the first
     movwf  nCurBeat

     ; enable interrupts
     bsf    intcon,gie

     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LAS - LightAndSound
;   Produce a LED pulse for 100ms and a sound pulse during the first 5ms
;   At tempo=240 the period between ticks is 60/240 = 0,25 s = 250 ms.
;   Then, in order not to overlap ticks, sound and light pulses should not
;   last more than 200ms.
;   Note that to generate a sound for 5ms, the number of cycles is
;   the sound frequency / 200, i.e. a beep of 440Hz for 5ms is 2 cycles.
;   The first tick is f=932Hz, La#6
;   Any other tick is f=880Hz, La6
;

LightAndSound:
     btfss   fFirstBeat           ; if (fFirstBeat)
     goto    LAS_NoFirstBeat      ; {
     btfsc   fLed                 ;    if (fLed)
     bsf     ioLED                ;       ioLED=ON
     btfss   fSound               ;    if (fSound)
     goto    LAS_NoSound1         ;    {
     movlw   3                    ;        Beep(frec=932Hz, t=5ms)
     movwf   nCycles              ;        // 932/200 = 4,66
     movlw   107                  ;        // f=932Hz -> T=1073us / 10 = 107
     call    Beep
     goto    LAS_EndSound         ;    }
LAS_NoSound1:                     ;    else {
     movlw   50                   ;       Wait(50ms)
     call    Wait_Wx1ms
LAS_EndSound:                     ;    }
     movlw   50                   ;    Wait(50ms)
     call    Wait_Wx1ms
     bcf     ioLED                ;    ioLED=OFF
     goto    LAS_End              ; }
LAS_NoFirstBeat:                  ; else {
     btfss   fSound               ;    if (fSound)
     goto    LAS_NoSound2         ;    {
     movlw   4                    ;        Beep(frec=880Hz, t=5ms)
     movwf   nCycles              ;        // 880/200 = 4,4
     movlw   114                  ;        // f=880Hz -> T=1136us / 10 = 114
     call    Beep
LAS_NoSound2:                     ;    }
LAS_End:                          ; }
     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; DBN - DisplayBeatNumber
;   Shows the current beat number in the second line of the display.
;   The number is moving across the display to facilitate visual tracking
;
;   The position in the display is computed as follows:
;     nMeasure    0123456789abcdef
;        2        1   .   .   .  2    +f
;        3        1   .  2.   .  3    +7 +8
;        4        1   .2  . 3 .  4    +5 +5 +5
;        5        1   2   3   4  5    +4 +4 +4 +3
;        - (5/4)  1   2   3   1  2    +4 +4 +4 +3
;        6        1  2. 3 .4  5  6    +3 +3 +3 +3 +3
;        7        1  2.3  4 5 .6 7    +3 +2 +3 +2 +3 +2
;        - (7/4)  1  2.3  1 2 .1 2    +3 +2 +3 +2 +3 +2
;        - (8/4)  1 2 3 1 2 3 1  2    +2 +2 +2 +2 +2 +3
;
;     static int nBeatAddr;
;     static int EvenTable[6] = {15, 7, 5, 4, 3, 3}
;     static int OddTable[6]  = { 0, 8, 5, 4, 3, 2}
;     int index;
;
;         if FirstBeat  addr=0
;         else if (fCompas84)  addr += 2
;         else if (fCompas54)  addr += 4
;         else {
;            index = (fCompas74 ? 5 : nMeasure-2)
;            if is_odd(nBeatCount)
;               addr += OddTable[index]
;            else
;               addr += EvenTab[index]
;         }
;         if (addr > 0x0f) addr=0x0f
;--------------------------------------------------------------------------
DisplayBeatNumber:

      ; move blank to previous position
      movf   nBeatAddr,w             ; LCD_SetAddr(nBeatAddr)
      iorlw  0x0c0                   ; add command bits: set addr, line 2
      call   LCD_SetAddr
      movlw  ' '                     ; Display(' ')
      call   LCD_SendData

      ; compute new address
      decfsz nBeatCount,w            ; if (nBeatCount == 1)
      goto   DBN_noFirst             ; {
      clrf   nBeatAddr               ;    nBeatAddr=0
      goto   DBN_end                 ; }
DBN_noFirst:                         ; else if (fCompas84)
      btfss  fCompas84
      goto   DBN_no84                ; {
      movlw  2                       ;    nBeatAddr += 2
      addwf  nBeatAddr,f
      goto   DBN_end                 ; }
DBN_no84:                            ; else if (fCompas54)
      btfss  fCompas54
      goto   DBN_no54                ; {
      movlw  4                       ;    nBeatAddr += 4
      addwf  nBeatAddr,f
      goto   DBN_end                 ; }
DBN_no54:                            ; else {
      btfss  fCompas74               ;    if (fCompas74)
      goto   DBN_no74                ;    {
      movlw  5                       ;       temp = 5
      movwf  temp
      goto   DBN_index_ok            ;    }
DBN_no74:                            ;    else {
      movlw  2                       ;       temp = nMeasure-2
      subwf  nMeasure,w
      movwf  temp
DBN_index_ok:                        ;    }
      btfss  nBeatCount,0            ;    if (is_odd(nBeatCount))
      goto   DBN_noOdd               ;    {
      movlw  HIGH OddTable           ;       w = OddTable[temp]
      movwf  pclath
      movf   temp,w
      call   OddTable
      addwf  nBeatAddr,f             ;       nBeatAddr += w
      goto   DBN_end                 ;    }
DBN_noOdd:                           ;    else {
      movlw  HIGH EvenTable          ;       w = EvenTable[temp]
      movwf  pclath
      movf   temp,w
      call   EvenTable
      addwf  nBeatAddr,f             ;       nBeatAddr += w
				     ;    }
DBN_end:                             ; }
      movlw  0x0f                    ; if (nBeatAddr > 0x0f)
      subwf  nBeatAddr,w
      bnc    DBN_addr_ok             ; {
      movlw  0x0f                    ;    nBeatAddr = 0x0f
      movwf  nBeatAddr
DBN_addr_ok:                         ; }

      ; display current beat number at address computed
      movf   nBeatAddr,w             ; LCD_SetAddr(nBeatAddr)
      iorlw  0x0c0                   ; add command bits: set addr, line 2
      call   LCD_SetAddr
      movf   nBeatNum,w              ; Display(nBeatNum)
      iorlw  0x30
      call   LCD_SendData

     return

OddTable:
	addwf  pcl,f
	retlw  0
	retlw  8
	retlw  5
	retlw  4
	retlw  3
	retlw  2
OddTable_End:
;
   if ( (OddTable & 0x0FF) >= (OddTable_End & 0x0FF) )
       MESSG   "Warning: table OddTable crosses page boundry"
    endif
;

EvenTable:
	addwf  pcl,f
	retlw  15
	retlw  7
	retlw  5
	retlw  4
	retlw  3
	retlw  3
EvenTable_End:
;
   if ( (EvenTable & 0x0FF) >= (EvenTable_End & 0x0FF) )
       MESSG   "Warning: table EvenTable crosses page boundry"
    endif
;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BEE - Beep
;    Generates a square wave at pins ioSound.
;    The period of the square wave to generate is received in W (Wx10 us)
;    The duration of the sound, expressed as the number of cycles to
;    generate, is received in nCycles.

Beep:
     movwf   nFreq             ; nFreq = Period
     bcf     status,c          ; carry=0 as it will be fed into MSb
     rrf     nFreq,f           ; nFreq /= 2
BEE_loop:                      ; do {
     bsf     ioSound1          ;    ioSound1 = ON
     bcf     ioSound2
     movf    nFreq,w           ;    Wait(half period)
     call    Wait_Wx10us
     bcf     ioSound1          ;    ioSound1 = OFF
     btfsc   fHighVol          ;    if (volumen == high)
     bsf     ioSound2          ;       ioSound2=ON to double volt. change
     movf    nFreq,w           ;    Wait(half period)
     call    Wait_Wx10us
     decfsz  nCycles,f         ;    nCycles--
     goto    BEE_loop          ; } while (nCycles > 0)
     bcf     ioSound2          ; ensure no DC bias applied to piezo-speaker
     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; UDY - UpdateDisplay
;    Updates the display: tempo and measure

UpdateDisplay:
	call   LCD_CleanUp      ; clear the display
	btfss  fModeNote        ; if (mode==note)
	goto   UDY_metronome    ; {
; Display information in mode Note
	movlw   HIGH Table_NameNote      ; get 5 high bits of address
	movwf   pclath                   ; and load pclath
	movf   nNote,w          ; get name
	addwf  nNote,w          ; w = 2 * nNote
	call   Table_NameNote
	call   LCD_SendData
	incf   nNote,w          ; w = 2 * nNote + 1
	addwf  nNote,w
	call   Table_NameNote
	andlw  0x0ff
	bz     UDY_scale
	call   LCD_SendData
UDY_scale:
	movf   nScale,w
	iorlw  30h
	call   LCD_SendData
	goto   UDY_done         ; }

Table_NameNote:
	addwf  pcl,f
	retlw   0
	retlw   0
	retlw  'A'
	retlw   0
	retlw  'A'
	retlw  '#'
	retlw  'B'
	retlw   0
	retlw  'C'
	retlw   0
	retlw  'C'
	retlw  '#'
	retlw  'D'
	retlw   0
	retlw  'D'
	retlw  '#'
	retlw  'E'
	retlw   0
	retlw  'F'
	retlw   0
	retlw  'F'
	retlw  '#'
	retlw  'G'
	retlw   0
	retlw  'G'
	retlw  '#'
NameNote_End:
;
   if ( (Table_NameNote & 0x0FF) >= (NameNote_End & 0x0FF) )
       MESSG   "Warning: table Table_NameNote crosses page boundry"
    endif
;


; Display information in mode metronome

UDY_metronome:                  ; else {     // mode == metronome
	movf   nTempo,w         ;    Display Tempo
	call   BinToBCD
	movf   HSD,w
	iorlw  30h
	call   LCD_SendData
	movf   MSD,w
	iorlw  30h
	call   LCD_SendData
	movf   LSD,w
	iorlw  30h
	call   LCD_SendData

	; Display Measure
	movlw  84h                 ; set address line 1 pos.4
	call   LCD_SetAddr
	btfss  fCompas54           ; if (compas==5/4)
	goto   UDY_no54            ; {
	movlw  "5"                 ;     Display("5/4")
	goto   UDY_slash4          ; }
UDY_no54:
	btfss  fCompas74           ; else if (compas==7/4)
	goto   UDY_no74            ; {
	movlw  "7"                 ;     Display("7/4")
	goto   UDY_slash4          ; }
UDY_no74:
	btfss  fCompas84           ; else if (compas==8/4)
	goto   UDY_no84            ; {
	movlw  "8"                 ;     Display("8/4")
UDY_slash4:
	call   LCD_SendData
	movlw  "/"
	call   LCD_SendData
	movlw  "4"
	call   LCD_SendData
	goto   UDY_end_measure     ; }
UDY_no84:                          ; else {
	movlw  ' '
	call   LCD_SendData
	movf   nMeasure,w          ;     Display(nMeasure)
	call   BinToBCD
	movf   LSD,w
	iorlw  30h
	call   LCD_SendData
UDY_end_measure:                  ; }

;       Display sound status
	movlw  88h
	call   LCD_SetAddr
	btfss  fSound
	goto   UDY_sound_off
	btfss  fHighVol
	goto   UDY_sound_low
	movlw  'H'
	call   LCD_SendData
	movlw  'i'
	call   LCD_SendData
	movlw  'g'
	call   LCD_SendData
	movlw  'h'
	call   LCD_SendData
	goto   UDY_sound_end
UDY_sound_low:
	movlw  'L'
	call   LCD_SendData
	movlw  'o'
	call   LCD_SendData
	movlw  'w'
	call   LCD_SendData
	movlw  ' '
	call   LCD_SendData
	goto   UDY_sound_end
UDY_sound_off:
	movlw  'O'
	call   LCD_SendData
	movlw  'f'
	call   LCD_SendData
	movlw  'f'
	call   LCD_SendData
	movlw  ' '
	call   LCD_SendData
UDY_sound_end:

;       Display led status
	movlw  0x8d
	call   LCD_SetAddr
	btfss  fLed
	goto   UDY_led_off
	movlw  'O'
	call   LCD_SendData
	movlw  'n'
	call   LCD_SendData
	movlw  ' '
	call   LCD_SendData
	goto   UDY_led_end
UDY_led_off:
	movlw  'O'
	call   LCD_SendData
	movlw  'f'
	call   LCD_SendData
	movlw  'f'
	call   LCD_SendData
UDY_led_end:

UDY_done:
     return

;****************************************************************************
;  4-bit LCD control routines for the PIC16F84
;
;  This routines are intended to control an LCD display built around the
;  HD44780 Hitachi controller, or compatible, using only one
;  controller chip (typically 1x16, 1x20, 2x16 or 2x20 displays). The
;  HD44780 is very common and widely used by most display makers.
;
;  C.Salmeron   27/Mar/99
;
;----------------------------------------------------------------------------
; USAGE:
;----------------------------------------------------------------------------
;   STEP 1. Un-comment the following definitions and code block and move it
;           to the begining of the program, at an appropiate place.
;
;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
;; LCD presentation atributes:
;
;#define DISPLAY_ON   0x04 | 0x08     ; Display ON
;#define CURSOR_ON    0x02 | 0x08     ; Cursor ON
;#define BLINKING_ON  0x01 | 0x08     ; Blink character at cursor position
;
;; LCD variables:
;
;   cblock      ; LCD routines
;      LCD_aux      ; nibble to write is kept in low nibble of this byte
;      LCD_addr     ; current address
;      LCD_saveW    ; to save W
;      LCD_char     ; char to write to LCD (used only by WriteChar, putc)
;   endc
;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
;----------------------------------------------------------------------------
; STEP 2:   LCD Configuration
;
;  Un-comment the following definitions and move them to an appropiate
;  place. Change these definitions to match your LCD module configuration:
;
;#define  ioLCD_E   portb,1    ;port bit assigned to LCD control line E
;#define  ioLCD_RW  portb,2    ;port bit assigned to LCD control line RW
;#define  ioLCD_RS  portb,3    ;port bit assigned to LCD control line RS
;
;#define  port_LCD  portb      ;port used for LCD data lines
;bLCD_D7  equ       7          ;bit number assigned to LCD line D7
;#define  tris_LCD  trisb      ;tris register for LCD data lines
;
;#define LCD_lines  1         ; display type: 1 line or 2 lines
;
;
;  In changing the above described configuration, take into
;  account that data lines D7-D4 must be assigned to a nibble of the PIC16F84
;  port, either the high nibble of PORTB (RB7-RB4) or the low nibble of
;  either porta (RA3-RA0) or portb (RB3-RB0). D7 must be assigned to the
;  most significative bit of the chosen port nibble.

;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
;-- ONLY FOR MY EYES --------------------------------------------------------
;
;  My LCD moule is wired as follows:
;
;    14  [Cinta plana-gris]  (D7)
;    ...             ...
;     8  [Cinta plana-gris]  (D1)   - pines no usados: dejar al aire
;     7  [Cinta plana-azul]  (D0)

;     6  [Naranja]   (E)
;     5  [Verde]     (R/W)
;     4  [Amarillo]  (RS)
;     3  [Azul]      (Vo)  = Cursor de potenciometro 10K entre +5 y 0v/-5v
;     2  [Rojo]      (Vdd) = +5v
;     1  [Negro]     (Vss) = 0v
;
;----------------------------------------------------------------------------
; STEP 3 (and last): Optional
;   To save program space, remove the LCD routines not used by you app.
;
; The following routines are available:
;
; a) Low level functions. (All necessary. Do not remove any of them):
;  iLCD_WaitReady     // wait until LCD module not busy
;  iLCD_SendByte      // Send to LCD the byte (data or command) received in W
;
; b) Basic functions (All necessary. Do not remove any of them):
;  LCD_SendData       // Write to LCD the data byte received in W
;                        uses: iLCD_SendByte, iLCD_WaitReady
;  LCD_SendCmd        // Send a command to LCD
;                        uses: iLCD_WaitReady, iLCD_SendByte
;  LCD_Initialize     // Inicialize LCD. MUST BE THE FIRST FUNCTION TO CALL
;                        Uses: LCD_SendCmd
;
; c) Additional functions (remove any not needed in your app.):
;  LCD_CleanUp        // Clear display and move cursor to home position
;  LCD_CursorLeft     // Shift cursor one position to the left
;  LCD_CursorRight    // Shift cursor one position to the right
;  LCD_CleanUp        // Move cursor and display window to home position
;  LCD_SetAddr        // Set address for data write (DD RAM)
;  LCD_GetAddr        // Return current address
;  LCD_putc           // Write char to current address.
;  LCD_WriteChar      // Write char to address LCD_addr
;
;****************************************************************************

;===========================================================================
;===========================================================================
;; a) Internal functions. All needed
;===========================================================================
;===========================================================================

;----------------------------------------------------------------------------
; iLCD_WaitReady  (LWR)
;   Wait until LCD is not busy.
;----------------------------------------------------------------------------
iLCD_WaitReady:
   movwf LCD_saveW            ; save w

   ; set all LCD data lines as inputs
   bsf   status,rp0           ; select bank 1
 if (bLCD_D7 == 7)       ; if D7-D4 conected to high nibble of PIC port
   movlw 0x0f0                ; 1=to be changed to inputs, 0=don't change
 else                    ; else if D7-D4 conected to low nibble of PIC port
   movlw 0x0f                 ; 1=to be changed to inputs, 0=don't change
 endif
   iorwf tris_LCD,f           ; use mask in W to program port
   bcf   status,rp0           ; select bank 0

   bcf   ioLCD_RS            ; RS=0   signal it is a command
   bsf   ioLCD_RW            ; R/W=1  signal it is a read operation
   nop                       ; wait 1us to comply with timming requirements

LWR_IsBusy?:
   bsf   ioLCD_E             ; E=1  enable LCD
   nop                       ; wait 1us to comply with timming requirements
   movf  port_LCD,w          ; read data lines
   movwf LCD_aux             ; and save
   bcf   ioLCD_E             ; E=0  disable LCD
   btfss LCD_aux,bLCD_D7      ; test busy line.
   goto  LWR_NotBusy         ; jump if LCD not busy

   ; LCD is busy. Read the low order nibble and ignore it
   bsf   ioLCD_E             ; E=1  enable LCD
   nop                       ; wait 1us to comply with timming requirements
   bcf   ioLCD_E             ; E=0  disable LCD
   goto  LWR_IsBusy?         ; loop

; LCD is not busy. Read and ignore low nibble, and return
LWR_NotBusy:
   bsf   ioLCD_E             ; E=1  enable LCD
   nop                       ; wait 1us to comply with timming requirements
   bcf   ioLCD_E             ; E=0  disable LCD

   ; reprogram all LCD data lines as outputs
   bsf   status,rp0          ; select bank 1
 if (bLCD_D7 == 7)       ; if D7-D4 conected to high nibble of PIC port
   movlw 0x0f                 ; 0=to be changed to outputs, 1=don't change
 else                    ; else if D7-D4 conected to low nibble of PIC port
   movlw 0x0f0                ; 0=to be changed to outputs, 1=don't change
 endif
   andwf tris_LCD,f           ; use mask in W to program port
   bcf   status,rp0          ; select bank 0

   movf  LCD_saveW,w         ; restore w
   return

;----------------------------------------------------------------------------
;  iLCD_SendByte (LSB)
;    Send to LCD the byte (data or command) received in W. It is assumed
;    that lines RS and RW have been properly set before calling this function.
;
;    As it is a 4-bits interface, data is sent as two nibbles, one nibble
;    at a time.
;----------------------------------------------------------------------------
iLCD_SendByte:
   ; high nibble must be sent first
   movwf LCD_saveW          ; save byte to send
   movwf LCD_aux            ; high nibble of byte to send is moved to ..
   swapf LCD_aux,f          ; .. low nibble of LCD_aux
   call  LSB_WriteNibble    ; send low nibble of LCD_aux

   ; now prepare and send low nibble
   movf  LCD_saveW,w        ; retrieve byte to send
   movwf LCD_aux            ; move it to LCD_aux
;  goto  LSB_WriteNibble    ; send low nibble of LCD_aux

; Send the low nibble of LCD_aux to LCD
LSB_WriteNibble:
   bsf    ioLCD_E             ; E=1    enable LCD
   movlw  0x0f
   andwf  LCD_aux,f           ; high nibble of data to zeros
 if (bLCD_D7 == 7)       ; if D7-D4 conected to high nibble of PIC port
   andwf  port_LCD,f          ; clear high nibble of port (data lines D7-D4)
   swapf  LCD_aux,f           ; move data to send to high nibble
 else                    ; else if D7-D4 conected to low nibble
   movlw  0x0f0
   andwf  port_LCD,f          ; clear low nibble of port (data lines D3-D0)
 endif
   movf   LCD_aux,w           ; move data to W
   iorwf  port_LCD,f          ; write data to port
   nop
   bcf    ioLCD_E             ; E=0. LCD reads data when falling edge of E
   movlw  5                   ; wait 50 uS for LCD internal process
   goto   Wait_Wx10us


;===========================================================================
;===========================================================================
;; b) Basic functions. All needed
;===========================================================================
;===========================================================================

;----------------------------------------------------------------------------
;  LCD_SendData
;    Write to LCD the data byte received in W. After write, the address is
;    incremented or decremented by one, according to the entry mode
;----------------------------------------------------------------------------
LCD_SendData:
   call  iLCD_WaitReady      ; wait until LCD not bussy
   bcf   ioLCD_RW            ; R/W=0  signal it is a write operation
   bsf   ioLCD_RS            ; RS=1   signal it is a data byte
   goto  iLCD_SendByte

;----------------------------------------------------------------------------
; LCD_SendCmd
;    Send to LCD the command byte received in W
;
; LCD_SetAddr
;   Set address for data write to the address specified in W
;   (line 1: 80h-A7h) or (line 2: C0h-E7h). Note that changing the address
;   is just sending the address as a command: bit 7 to one is the command
;   "set address"
;---------------------------------------------------------------------------
LCD_SetAddr:
LCD_SendCmd:
   call  iLCD_WaitReady      ; wait until LCD not bussy
   bcf   ioLCD_RW            ; R/W=0  signal it is a write operation
   bcf   ioLCD_RS            ; RS=0   signal it is a command byte
   goto  iLCD_SendByte

;----------------------------------------------------------------------------
;  LCD_Initialize
;   At power on, if power supply raises from 0v to 4.5v in less than 10mS 
;   but not faster than 1mS, the LCD module will default to the following
;   settings:
;      1. Clear display
;      2. 8-bits interface, 1 line display, 5x7 dot font
;      3. Display OFF, Cursor ON, Blink OFF
;      4. Entry mode: increment, no display shift
;      5. DD RAM is selected
;
;   Whether the LCD is initialized or not, this function (LCD_Initialize)
;   will change the LCD module to the following settings:
;      1. Clear display
;      2. 4-bits interface, 2 lines display, 5x7 dot font
;      3. Display ON, Cursor ON, Blink OFF
;      4. Entry mode: increment, no display shift
;      5. DD RAM is selected
;
;   For other settings at initialization time, change this routine.
;----------------------------------------------------------------------------
LCD_Initialize:
   movlw  15
   call   Wait_Wx1ms       ;  wait 15mS after power-up
 if LCD_lines==2
   movlw  28h              ;  4-bits interface, 2 lines, 5x7 dot font
 else
   movlw  20h              ;  4-bits interface, 1 line, 5x7 dot font
 endif
   call   LCD_SendCmd
   movlw  5
   call   Wait_Wx1ms       ;  wait 5mS
 if LCD_lines==2
   movlw  28h              ;  4-bits interface, 2 lines, 5x7 dot font
 else
   movlw  20h              ;  4-bits interface, 1 line, 5x7 dot font
 endif
   call   LCD_SendCmd
   movlw  10               ;  wait 100uS
   call   Wait_Wx10us
   movlw  06h              ;  entry mode: increment mode, no shift display
   call   LCD_SendCmd
   movlw  0Eh              ;  Cursor ON, Display ON, Blinking OFF
   call   LCD_SendCmd
   movlw  01h              ;  Clear display and cursor home
   goto   LCD_SendCmd

;===========================================================================
;===========================================================================
;; c) Additional functions. Remove those not used by your application
;===========================================================================
;===========================================================================

;----------------------------------------------------------------------------
;  LCD_CleanUp
;   Clears display and returns cursor to home position (address 00)
;----------------------------------------------------------------------------
LCD_CleanUp:
   movlw 0x01                 ; command 01
   call  LCD_SendCmd
   movlw 2                    ; wait 2 mS
   goto  Wait_Wx1ms

;----------------------------------------------------------------------------
;  LCD_CursorLeft:
;   Shift cursor one position to the left
;----------------------------------------------------------------------------
LCD_CursorLeft:
   movlw 0x10
   goto  LCD_SendCmd

;----------------------------------------------------------------------------
;  LCD_CursorRight:
;   Shift cursor one position to the right
;----------------------------------------------------------------------------
LCD_CursorRight:
   movlw 0x14
   goto  LCD_SendCmd

;----------------------------------------------------------------------------
;  LCD_Home:
;   Command 02H: Returns cursor to home position. Returns shifted display
;   to original position. Does not clear display.
;   LCD execution time: 40uS to 1.6mS
;----------------------------------------------------------------------------
LCD_Home:
   movlw 0x02              ; Command 02H
   call  LCD_SendCmd
   movlw 2                 ; wait 2 mS
   goto  Wait_Wx1ms

;----------------------------------------------------------------------------
;  LCD_GetAddr  (LGA)
;   Current address is returned in "LCD_addr"
;----------------------------------------------------------------------------
LCD_GetAddr:
   call   iLCD_WaitReady

   ; set all LCD data lines as inputs
   bsf    status,rp0           ; select bank 1
 if (bLCD_D7 == 7)       ; if D7-D4 conected to high nibble of PIC port
   movlw 0x0f0                ; 1=to be changed to inputs, 0=don't change
 else                    ; else if D7-D4 conected to low nibble of PIC port
   movlw 0x0f                 ; 1=to be changed to inputs, 0=don't change
 endif
   iorwf tris_LCD,f           ; use mask in W to program port
   bcf    status,rp0           ; select bank 0

   ; read address, high nibble
   bcf    ioLCD_RS            ; RS=0   signal it is a command
   bsf    ioLCD_RW            ; R/W=1  signal it is a read operation
   nop                        ; wait 1us to comply with timming requirements
   bsf    ioLCD_E             ; E=1  enable LCD
   nop                        ; wait 1us to comply with timming requirements
   movf   port_LCD,w          ; read data lines
   movwf  LCD_addr            ; save
   bcf    ioLCD_E             ; E=0  disable LCD
 if (bLCD_D7 != 7)       ; if D7-D4 not conected to high nibble of PIC port
   swapf  LCD_addr,f          ; move data to high nibble
 endif
   movlw  0x0f0
   andwf  LCD_addr,f          ; low nibble to ceros


   ; read the low order nibble
   bsf    ioLCD_E             ; E=1  enable LCD
   nop                        ; wait 1us to comply with timming requirements
   movf   port_LCD,w          ; read data lines
   movwf  LCD_saveW           ; save data in saveW
   bcf    ioLCD_E             ; E=0  disable LCD
 if (bLCD_D7 == 7)      ; if D7-D4 conected to high nibble of PIC port
   swapf  LCD_saveW,f         ; move data to low nibble
 endif
   movlw  0x0f
   andwf  LCD_saveW,f         ; high nibble to ceros
   movf   LCD_saveW,w
   iorwf  LCD_addr,f          ; merge both nibbles

   ; reprogram all LCD data lines as outputs
   bsf    status,rp0          ; select bank 1
 if (bLCD_D7 == 7)       ; if D7-D4 conected to high nibble of PIC port
   movlw 0x0f                 ; 0=to be changed to outputs, 1=don't change
 else                    ; else if D7-D4 conected to low nibble of PIC port
   movlw 0x0f0                ; 0=to be changed to outputs, 1=don't change
 endif
   andwf tris_LCD,f           ; use mask in W to program port
   bcf    status,rp0          ; select bank 0

   return

;----------------------------------------------------------------------------
; LCD_WriteChar
;   Writes char LCD_char at logical address LCD_addr. LCD_char is not changed
;
;   The display internal RAM is 80 chars. If the display size is less than
;   80 chars what is on the screen is a "window" on the RAM. What is
;   displayed depends on the entry mode settings. This RAM memory is
;   divided into two lines: addresses 80h-A7h contains the information
;   to display in the first LCD line, and addresses C0h-E7h contains
;   the second line.
;
;   When a 1x16 LCD type built with only one Hitachi controller is used,
;   the first 8 displayed positions are mapped to addresses 80h-87h (first
;   line memory) and the last half of the line is mapped to addresses
;   C0h-C7h (second line memory). This complicates the application
;   program as addressing is not linear. For example, you must split the
;   messages to display.
;
;   In order to avoid the complexities in your program, the routines
;   LCD_putc and LCD_puts take care of all this address housekeeping.
;----------------------------------------------------------------------------
LCD_WriteChar:             ; void LCD_WriteChar(char, LCD_addr) {
   bsf    LCD_addr,7          ;    set msb of LCD_adr 
 if (LCD_lines == 1)
   movlw  0C0h                ;    if (LCD_addr >= C0h)
   subwf  LCD_addr,w
   btfsc  status,c
   goto   LEC_addrOk          ;       goto address OK
   movlw  88h                 ;    if (LCD_addr > 87h)
   subwf  LCD_addr,w
   btfss  status,c
   goto   LEC_addrOk          ;    {
   movlw  38h                 ;       NewAddr = 40h+LCD_addr-8
   addwf  LCD_addr,f          ;    }
 endif
 
LEC_addrOk:                   
   movf   LCD_addr,w          ;    MoveCursor(LCD_addr)
   call   LCD_SetAddr
   movf   LCD_char,w          ;    putc(char)
   goto   LCD_SendData        
			      ; }

;----------------------------------------------------------------------------
;  LCD_putc
;   Writes char W at current logical address. Address is incremented.
;----------------------------------------------------------------------------
LCD_putc:                     ;  void LCD_putc(char c)
			      ;  {
   movwf LCD_char             ;     LCD_char = c
   call  LCD_GetAddr          ;     LCD_addr = GetAddr()
   goto  LCD_WriteChar        ;     WriteChar(LCD_char, LCD_addr)
			      ;  }

;----------------------------------------------------------------------------
;  LCD_EEputs  (LEE)    -- not tested yet --
;   Writes ASCIIZ string from EEDATA memory at current LCD logical address.
;   Address is incremented by size of string
;----------------------------------------------------------------------------
LCD_EEputs:                   ; void LCD_EEputs(char *s)
			      ; {
   movwf eeadr                ;    ptr = s
LEE_loop:                     ;    while (*ptr != 0)
   bsf   status,rp0           ;       select bank 1
   bsf   eecon1,rd            ;       perform read:  eedata= *eeadr
   bcf   status,rp0           ;       return to bank 0
   movf  eedata,w             ;       w = *ptr
   btfsc status,z             ;       if (w==0) return. End of string
   return
   call  LCD_putc             ;       putc(w)
   incf  eeadr,f              ;       ptr++
   goto  LEE_loop             ;    }
			      ; }


;****************************************************************************
;****************************************************************************
;**  Timming routines start here
;****************************************************************************
;****************************************************************************

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WNM-  Wait_Wx1ms
;    Execution remains in this routine just the milisecons specified in
;    register W (1-255). If W==0 it is assumed 256ms.
;
;    From a timming viewpoint, the sentence:
;               call  Wait_Wx1ms
;    is executed in exactly  W x 1ms + 4us (clock at 4MHz)
;

Wait_Wx1ms:
     movwf  nWaitT1         ; save W
WNM_loop1:
     movlw  249             ; n2 = 249
     movwf  nWaitT2
WNM_loop2:
     nop
     decfsz nWaitT2,f
     goto   WNM_loop2
;End_loop2:                 ; t2 = 4*n2-1 = 4*249-1 = 995us
     decfsz nWaitT1,f
     goto   WNM_loop1
;End_loop1:                 ; t1 = (2+t2+3)*W-1 = (5+995)*W-1 =1000*W-1
     return                 ; total = 3+t1+2 = 1000*W+4 us


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WNC- Wait_Wx100us
;    Execution remains in this routine just the tenths of milisecons
;    specified in register W (1-255). If W==0 it is assumed 25.6ms.
;
;    From a timming viewpoint, the sentence:
;               call  Wait_Wx100us
;    is executed in  W x 100us + 4 us (clock at 4MHz)
;

Wait_Wx100us:
     movwf  nWaitT1         ; save W
WNC_loop1:
     movlw  32              ; n2 = 32
     movwf  nWaitT2
WNC_loop2:
     decfsz nWaitT2,f
     goto   WNC_loop2
;End_loop2:                 ; t2 = 3*n2-1 = 3*32-1 = 95us
     decfsz nWaitT1,f
     goto   WNC_loop1
;End_loop1:                 ; t1 = (2+t2+3)*W-1 = (5+95)*W-1 = 100*W - 1
     return                 ; total = 3+t1+2 = 100*W+4 us



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WNX-  Wait_Wx10us
;    Execution remains in this routine just the tenths of milisecons
;    specified in register W (1-255). If W==0 the delay is 256x10us = 2.56ms
;
;    From a timming viewpoint, the sentence:
;               call  Wait_Wx10us
;    is executed in, exactly,  W x 10us (clock at 4MHz)
;

Wait_Wx10us:
     movwf  nWaitT1         ; save W
     decfsz nWaitT1,f       ; N = W-1
     goto   WNX_loop1
     goto   WNX1
WNX_loop1:
     movlw  0x02            ; n2 = 2
     movwf  nWaitT2
WNX_loop2:
     decfsz nWaitT2,f
     goto   WNX_loop2
;End_loop2:                 ; t2 = 3*n2-1 = 3*2-1 = 5us
     decfsz nWaitT1,f
     goto   WNX_loop1
;End_loop1:                 ; t1 = (2+t2+3)*N-1 = (5+5)*(W-1)-1 = 10*W-11
     goto   WNX1
WNX1 nop
     return                 ; total =2+4+t1+5 = 11+t1 = 10*W us

 ifdef DBG
dbg:
	movwf  aux1
led_loop:
	bsf    ioLED            ; LED ON for 500ms
	movlw  250
	call   Wait_Wx1ms
	movlw  250
	call   Wait_Wx1ms
	bcf    ioLED            ; LED OFF for 500ms
	movlw  250
	call   Wait_Wx1ms
	movlw  250
	call   Wait_Wx1ms
	decfsz aux1,f
	goto   led_loop
	return
  endif

   END


