;****************************************************************************
; 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 - io line             #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 & _XT_OSC

; '__CONFIG' directive is used to embed configuration data within .asm file.
; The labels after the directive are defined 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 OFF
;
;      _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


   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_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-Do, 12-Si)
      nOctave      ;static. Number of the octave: MIN_OCTAVE to MAX_OCTAVE
      tTempo       ;timer. Delay to ackowledge a tempo modific. Unit = 10ms
      afFlags      ;Flags, as follows
      afFlags2     ;Flags, as follows
      afFlags3     ;flags, as follows:
   endc

#define MAX_OCTAVE   7
#define MIN_OCTAVE   2

#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 fCompas1       afFlags2,0  ;No compas. Only beats.
#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
#define fFifths        afFlags3,1  ;ON=Tune by fifths, OFF=equal temperade

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

    cblock  ; Sine wave generation
       nPhaseIncrL   ; static. phase increment (16 bits fixed point)
       nPhaseIncrH
       nPhaseL       ; temp. current phase (16 bits fixed point)
       nPhaseH
     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  portDAC    portb      ; port used for DAC output
#define  ioDAC_E    porta,3    ; enable DAC when 1

#define  ioLED      porta,2      ; LED through a 470 ohm resistor to GND.
#define  ioKBE      porta,4      ; keyboard enable (active LOW)

#define  Bkeys    11110110b   ; 1 = pins assigned to keys (RB7-RB4,RB2,RB1)
#define  afAutoSpeedB  00000110b  ; keys with auto-speed option enabled
				  ;    key1 & key2 (pins 1 & 2)

#define  fKey1   afKeysB,1    ; flag. Key 1 is pressed
#define  fKey2   afKeysB,2    ; flag. Key 2 is pressed
#define  fKey3   afKeysB,5    ; flag. Key 3 is pressed
#define  fKey4   afKeysB,7    ; flag. Key 4 is pressed
#define  fKey5   afKeysB,6    ; flag. Key 5 is pressed
#define  fKey6   afKeysB,4    ; 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
;   Interrupts are only enabled in mode metronome (fModeNote == FALSE). In
;   this mode 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. Other timers are updated.
;****************************************************************************
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

     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
     goto   ISR_measure_ok     ;          }
ISR_measure_three:             ;          else {
     btfss  f2ndPart           ;             if (2nd 3-beats part)
     goto   ISR_84_no_2nd      ;             {
     decf   nMeasure,f         ;                nMeasure--
     goto   ISR_measure_ok     ;             }
ISR_84_no_2nd:                 ;             else {
     bsf    f2ndPart           ;               2nd 3-beats part = TRUE
     movlw  1                  ;               nBeatCount = 1
     movwf  nBeatCount         ;             }
     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:                    ; }

     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
	bcf    ioDAC_E              ; disable DAC
	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
	movlw  10              ;nNote = 10, to generate "La" note
	movwf  nNote
	movlw  4
	movwf  nOctave         ;octave = 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      "
	call   LCD_CleanUp
	movlw  ' '
	call   LCD_SendData    ;1
	movlw  ' '
	call   LCD_SendData    ;2
	movlw  ' '
	call   LCD_SendData    ;3
	movlw  ' '
	call   LCD_SendData    ;4
	movlw  'E'
	call   LCD_SendData    ;5
	movlw  'L'
	call   LCD_SendData    ;6
	movlw  'E'
	call   LCD_SendData    ;7
	movlw  'K'
	call   LCD_SendData    ;8
	movlw  'T'
	call   LCD_SendData    ;1
	movlw  'O'
	call   LCD_SendData    ;2
	movlw  'R'
	call   LCD_SendData    ;3
	movlw  ' '
	call   LCD_SendData    ;4

	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

;****************************************************************************
;****************************************************************************
;**  Main loop when in metronome mode
;****************************************************************************
;****************************************************************************

MetronomeLoop:

; 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
	btfsc   fKey1              ;    if (key 1 is pressed)
	call    IncrTempo          ;       Increment_tempo()
	btfsc   fKey2              ;    else if (key 2 is pressed)
	call    DecrTempo          ;       Decrement_Tempo()
	btfsc   fKey3              ;    else if (key 3 is pressed)
	call    ChangeSound        ;       Toggle sound status
	btfsc   fKey4              ;    else if (key 4 is pressed)
	call    ToggleLed          ;       Toggle light status
	btfsc   fKey5              ;    else if (key 5 is pressed)
	call    IncrMeasure        ;       Increment_Measure()
	btfsc   fKey6              ;    else if (key 6 is pressed)
	goto    DiapasonMode       ;       Change to mode diapason
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    MetronomeLoop


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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
     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Toggle sound status.
; toggle between ON and OFF

ChangeSound:
     btfss   fSound           ; if ((Sound == ON)
     goto    sound_on         ; {
     bcf     fSound           ;    Sound = OFF
     goto    sound_end        ; }
sound_on:                     ; else {
     bsf     fSound           ;    Sound = ON
sound_end:                    ; }
     bsf     fDisplay         ; request to update the display
     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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
     return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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   2                    ;    nMeasure = 2
     movwf   nMeasure
     bsf     fCompas1             ;    only beats = TRUE
     bcf     fCompas84            ;    8/4 compas = FALSE
     goto    INM_done             ; }
INM_no_84:
     btfss   fCompas1             ; else if (only beats)
     goto    INM_increment        ; {
     bcf     fCompas1             ;    only beats = FALSE  (nMeasure is 2)
     goto    INM_done             ; }
INM_increment:                    ; else {   // here 2 <= 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

     return

;****************************************************************************
;****************************************************************************
;**  Subroutines start here
;****************************************************************************
;****************************************************************************

;****************************************************************************
; RDK - Read the keys.
;   The scan code is returned in W: 1=key pressed, 0=key released
;****************************************************************************
ReadKeys:
	; ensure other devices are disabled
	bcf    ioLCD_E              ; disable LCD

	; change data bus lines assigned to keys into inputs
	bsf    status,rp0           ; select bank 1
	movlw  Bkeys                ; 1=pins assigned to keys
	movwf  trisb                ; program those pins as inputs
	bcf    status,rp0           ; select bank 0

	; read keys at port b
	bcf    ioKBE                ; enable keyboard
	movf   portb,w              ; read keyscan
	bsf    ioKBE                ; disable keyboard
	xorlw  0ffh                 ; negate w so keys pressed became 1s

	; restore data bus lines as outputs
	bsf    status,rp0           ; select bank 1
	clrf   trisb                ; program portb pins
	bcf    status,rp0           ; select bank 0

	return                      ; scan code in w

;****************************************************************************
; 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
	call   ReadKeys             ; get scan code
	xorwf  afKeysB,w            ; w = changes since last scan
	movwf  nChanges             ; save changes
	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:
	call   ReadKeys             ; get scan code
	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

	; 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

;****************************************************************************
; 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#5
;   Any other tick is f=880Hz, La5
;

LightAndSound:
     btfsc   fFirstBeat           ; if (fFirstBeat || fCompas1)
     goto    LAS_First
     btfss   fCompas1
     goto    LAS_NoFirstBeat
LAS_First:                        ; {
     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 a space 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 display 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
      btfss  fCompas1                ; if (only beats)
      goto   DBN_number              ; {
      movlw  0ffh                    ;    Display(black square)
      goto   LCD_SendData            ; }     // goto = call + return
DBN_number:
      movf   nBeatNum,w              ; Display(nBeatNum)
      iorlw  0x30
      goto   LCD_SendData            ; // goto = call + 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.
;    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
     decf    nFreq,f           ; substract 10us to compensate DAC write time
     bcf     status,c          ; carry=0 as it will be fed into MSb
     rrf     nFreq,f           ; nFreq /= 2     // half period time
BEE_loop:                      ; do {
     movlw   0ffh              ;    WriteToDAC(0ffh)
     movwf   portDAC
     bsf     ioDAC_E
     bcf     ioDAC_E
     movf    nFreq,w           ;    Wait(half period)
     call    Wait_Wx10us
     clrf    portDAC           ;    WriteToDAC(00h)
     bsf     ioDAC_E
     bcf     ioDAC_E
     movf    nFreq,w           ;    Wait(half period)
     call    Wait_Wx10us
     decfsz  nCycles,f         ;    nCycles--
     goto    BEE_loop          ; } while (nCycles > 0)
     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  "T"
	btfsc  fFifths
	movlw  "F"
	call   LCD_SendData

	movlw  0x0c0            ; set addr: line 2, pos 0
	call   LCD_SetAddr
	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_octave
	call   LCD_SendData
UDY_octave:
	movf   nOctave,w
	iorlw  30h
	call   LCD_SendData
	goto   UDY_sound        ; }


; 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:
	movlw  ' '                 ; else {
	call   LCD_SendData        ;    Display(' ')
	btfss  fCompas1            ;    if (only beats)
	goto   UDY_normal          ;    {
	movlw  '1'                 ;       Display("1")
	goto   UDY_dsp_measure     ;    }
UDY_normal:                        ;    else {
	movf   nMeasure,w          ;       Display(nMeasure)
	call   BinToBCD
	movf   LSD,w
	iorlw  30h                 ;    }
UDY_dsp_measure:
	call   LCD_SendData        ; }
UDY_end_measure:

;       Display led status
	movlw  0x8d                ; set display address: line 1, pos.13
	call   LCD_SetAddr
	btfss  fLed                ; if (fLed==ON)
	goto   UDY_led_off         ; {
	call   UDY_on              ;    Display("On ");
	goto   UDY_led_end         ; }
UDY_led_off:                       ; else {
	call   UDY_off             ;    Display("Off");
UDY_led_end:                       ; }

; Display sound status
UDY_sound:
	movlw  88h                 ; set display address: line1, pos.8
	call   LCD_SetAddr
	btfss  fSound              ; if (fSound==ON)
	goto   UDY_sound_off       ; {
	call   UDY_on              ;    Display("On ")
	goto   UDY_sound_end       ; }
UDY_sound_off:                     ; else {
	call   UDY_off             ;    Display("Off")
UDY_sound_end:                     ; }

     return

; Display "On " at current display address
UDY_on:
     movlw  'O'
     call   LCD_SendData
     movlw  'n'
     call   LCD_SendData
     movlw  ' '
     goto   LCD_SendData         ; goto = call + return

; Display "Off" at current display address
UDY_off:
     movlw  'O'
     call   LCD_SendData
     movlw  'f'
     call   LCD_SendData
     movlw  'f'
     goto   LCD_SendData         ; goto = call + return

Table_NameNote:
	addwf  pcl,f
	retlw   0
	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  '#'
	retlw  'A'
	retlw   0
	retlw  'A'
	retlw  '#'
	retlw  'B'
	retlw   0
NameNote_End:
;
   if ( (Table_NameNote & 0x0FF) >= (NameNote_End & 0x0FF) )
       MESSG   "Warning: table Table_NameNote crosses page boundry"
    endif
;


;****************************************************************************
;  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_SetAddr        // Set address for data write (DD RAM)
;
;****************************************************************************

;===========================================================================
;===========================================================================
;; 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 OFF, 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  0Ch              ;  Cursor OFF, 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


;****************************************************************************
;****************************************************************************
;**  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


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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


;****************************************************************************
; DPL - Change to diapason mode
;
; The technique used to produce a continuous sine wave generation is based
; on Direct Digital Synthesis (DDS).  In DDS, a counter (phase accumulator)
; is used to step through a Sine look-up table, which is then fed into a DAC.
; In this program, the phase accumulator is a 16 bit variable called
; nPhase (nPhaseH, nPhaseL).  On each loop cycle the value in nPhaseInc
; is added to it.
;
; The variables nPhase and nPhaseInc can be regarded as having an integer
; portion and a fractional portion, thusly iiiiiiii.ffffffff, where the
; integer portion is in "degrees" of 1/256 of a cycle.  This integer portion
; is used to index into the sine table and the value obtained from the
; table is then output to the DAC.
;
; On each loop cycle the keyboard is read to verify if any new key is
; pressed and, in that case, control is transferred to the read keyboard
; routine, interrupting the production of the sinewave. The port B
; interruption facility is not used because of the need to control 6
; keys. Testing the keyboard on each cycle increments the loop time,
; but the resulting sampling frequency is still high enough (>20KHz).
;
;****************************************************************************
DiapasonMode:
     bcf     intcon,gie       ; disable interrupts
     bsf     fModeNote        ; Mode note = TRUE
     movlw   10               ; nNote = 10   // La
     movwf   nNote
     movlw   4                ; nOctave = 4   // A4, 440Hz
     movwf   nOctave
     call    UpdateDisplay

     ; ensure data bus is free
     bsf    ioKBE                ; disable keyboard

     ; initialize variables
     clrf    afKeysB        ; no key pressed but key6
     bsf     fKey6
     clrf    nPhaseL        ; nPhase = 0
     clrf    nPhaseH
     call    SetPhaseIncr   ; SetPhaseIncr(nNote, nOctave, fFifths)

DiapasonLoop:
     ; update the sine wave phase (6us)
     movf    nPhaseIncrL,w    ; nPhase += nPhaseIncr
     addwf   nPhaseL,f
     btfsc   status,c
     incf    nPhaseH,f
     movf    nPhaseIncrH,w
     addwf   nPhaseH,f

     ; calculate the sample value (14us)
     movlw   HIGH SineTbl  ; load PCLATH with 5 high bits of address
     movwf   pclath
     movf    nPhaseH,w     ; W = nPhase
     btfsc   nPhaseH,6     ; if (2nd half of a half cycle)
     xorlw   0ffh          ;    complement W to do a subtract from 63
     andlw   03fh          ; reduce W to 6 bits for 90 degrees table
     call    SineTbl       ; get sinewave amplitude from table
     btfsc   nPhaseH,7     ; if (2nd half of a cycle)
     xorlw   0ffh          ;    complement sine value to invert it

     ; send the sample value to DAC (5us)
     movwf   portDAC       ; move the sample value to DAC port
     btfsc   fSound        ; if (sound == ON)
     bsf     ioDAC_E       ;    enable DAC:  1us pulse
     bcf     ioDAC_E       ; disable DAC

     ; verify if any key is pressed (15us)
     bsf    status,rp0     ; select bank 1
     movlw  Bkeys          ; 1=pins assigned to keys
     movwf  trisb          ; program those pins as inputs
     bcf    status,rp0     ; return to bank 0
     bcf    ioKBE          ; enable keyboard
     movf   portb,w        ; read keyscan
     bsf    ioKBE          ; disable keyboard
     bsf    status,rp0     ; select bank 1
     clrf   trisb          ; restore data bus lines as outputs
     bcf    status,rp0     ; return to bank 0
     xorlw  0ffh           ; negate w so keys pressed became 1s
     xorwf  afKeysB,w      ; w = changes since last scan
     andlw  Bkeys          ; mask off bits not assigned to keys
     bz     DiapasonLoop   ; if no changes, repeat the loop

	    ; Total loop time = 40us -> sampling freq = 25,00 KHz

     ; A change in keyboard has been detected. The sinewave loop is
     ; suspended and the key is processed
     call    ReadKeys           ; get scan code
     movwf   afKeysB            ; save the new pattern of keys pressed
     movlw   20                 ; wait 20ms for debouncing
     call    Wait_Wx1ms
     call    ReadKeys           ; get scan code again
     andwf   afKeysB,f          ; 1= keys that remain pressed
     bz      DiapasonLoop       ; if no key pressed return to sinewave loop

     btfsc   fKey1              ; if (key 1 is pressed)
     goto    IncrNote           ;    Increment_Note()
     btfsc   fKey2              ; else if (key 2 is pressed)
     goto    DecrNote           ;    Decrement_Note()
     btfsc   fKey3              ; else if (key 3 is pressed)
     goto    ToggleSound        ;    Toggle sound status
     btfsc   fKey4              ; else if (key 4 is pressed)
     goto    IncrOctave         ;    Increment_Octave()
     btfsc   fKey5              ; else if (key 5 is pressed)
     goto    DecrOctave         ;    Decrement_Octave()
     btfsc   fKey6              ; else if (key 6 is pressed)
     goto    MetronomeMode      ;    enter into metronome mode
     goto    DiapasonLoop       ; return to sinewave generation loop

; Advance to "tuned by fifths" mode or enter into metronome mode
MetronomeMode:
     btfss   fFifths          ; if (fFifths)
     goto    DPL_fifths       ; {
     bcf     fFifths          ;    "Tuned by fifths" mode = FALSE
     bcf     fModeNote        ;    Mode note = FALSE
     bsf     intcon,gie       ;    enable interrupts
     bsf     fDisplay         ;    request for an update of the display
     goto    MetronomeLoop    ;    goto metronome mode
			      ; }
DPL_fifths:                   ; else
     bsf     fFifths          ;    "Tuned by fifths" mode = TRUE
     goto    octave_and_note_ok     ; update display and phase increment


; Increment octave ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IncrOctave:
     incf    nOctave,f            ; nOctave++
     movlw   MAX_OCTAVE+1         ; if (nOctave >= MAX_OCTAVE+1)
     subwf   nOctave,w
     bnc     octave_and_note_ok   ; {
     movlw   MAX_OCTAVE           ;    nOctave = MAX_OCTAVE
     movwf   nOctave
octave_and_note_ok:               ; }
     call    SetPhaseIncr         ; update phase increment
     call    UpdateDisplay
     goto    DiapasonLoop         ; return to sinewave generation loop


; Decrement octave ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DecrOctave:
     decf    nOctave,f            ; nOctave--
     movlw   MIN_OCTAVE           ; if (nOctave < MIN_OCTAVE)
     subwf   nOctave,W
     bc      octave_and_note_ok   ; {
     movlw   MIN_OCTAVE           ;    nOctave = MIN_OCTAVE
     movwf   nOctave
     goto    octave_and_note_ok   ; }

; TGS - Toggle sound ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ToggleSound:
     btfss   fSound               ; if ((Sound == ON)
     goto    TGS_on               ; {
     bcf     fSound               ;    Sound = OFF
     goto    octave_and_note_ok   ; }
TGS_on:                           ; else {
     bsf     fSound               ;    Sound = ON
     goto    octave_and_note_ok   ; }

; Increment note ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IncrNote:
     incf    nNote,f              ; nNote++
     movlw   13                   ; if (nNote >= 13)
     subwf   nNote,w
     bnc     octave_and_note_ok   ; {
     movlw   1                    ;    nNote = 1
     movwf   nNote
     incf    nOctave,f            ;    nOctave++
     movlw   MAX_OCTAVE+1         ;    if (nOctave >= MAX_OCTAVE+1)
     subwf   nOctave,w
     bnc     octave_and_note_ok   ;    {
     movlw   MAX_OCTAVE           ;       nOctave = MAX_OCTAVE
     movwf   nOctave
     movlw   12                   ;       nNote = 12
     movwf   nNote
				  ;    }
				  ; }
     goto    octave_and_note_ok

; Decrement note ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DecrNote:
     decfsz  nNote,f              ; nNote--
     goto    octave_and_note_ok   ; if (nNote == 0) {
     movlw   12                   ;    nNote = 12
     movwf   nNote
     decf    nOctave,f            ;    nOctave--
     movlw   MIN_OCTAVE           ;    if (nOctave < MIN_OCTAVE)
     subwf   nOctave,W
     bc      octave_and_note_ok   ;    {
     movlw   MIN_OCTAVE           ;       nOctave = MIN_OCTAVE
     movwf   nOctave
     movlw   1                    ;       nNote = 1
     movwf   nNote
				  ;    }
				  ; }
     goto    octave_and_note_ok


;****************************************************************************
; SPI - Get the phase increments for the note to generate
;
; These equates define the phase increments necessary to generate sine
; waves of the appropriate frequencies.  The values are
; computed with the formula 256 * Frequired * Tsampling.  The 256 accounts
; for the effective domain of the sine function (256 steps = 2 * PI
; radians). As the phase accumulator has a decimal part of 8 bits, the
; value 65536 is used in the formula, instead of 256.
; Tsampling = 40us (25,00 KHz)
; Then   (int)(65536.0 * 40.0e-6 * Frequired)
;****************************************************************************
SetPhaseIncr:
     movlw   HIGH PhaseTblL    ; nPhaseIncL = GetPhaseIncr(nNote)
     movwf   pclath
     decf    nNote,w
     btfsc   fFifths                ; if tuned by fifths, increment index
     addlw   12
     call    PhaseTblL
     movwf   nPhaseIncrL
     movlw   HIGH PhaseTblH    ; nPhaseIncH = GetPhaseIncr(nNote)
     movwf   pclath
     decf    nNote,w
     btfsc   fFifths                ; if tuned by fifths, increment index
     addlw   12
     call    PhaseTblH
     movwf   nPhaseIncrH

     ; adjust phase increment for appropiate octave
     movf    nOctave,w         ; temp = MAX_OCTAVE - nOctave
     sublw   MAX_OCTAVE
     bz      SPI_end
     movwf   temp
SPI_loop:                      ; while (temp > 0) {
     bcf     status,c          ;    phase incr /= 2
     rrf     nPhaseIncrH,f
     rrf     nPhaseIncrL,f
     decfsz  temp,f            ;    temp--
     goto    SPI_loop          ; }
SPI_end:
     return

;
; The phase tables were generated by the program PHASETBL.C leaving the
; output in a file:  PHASETBL.EXE > PTBL.INC
;
; The output file PTBL.INC  was then merged into the source file.

PhaseTblL:
     addwf   pcl,f
; 'Equal temperade tuning' mode
     retlw   06Fh    ; Do   (f=2093.00 Hz, phase incr=5487 [156Fh])
     retlw   0B5h    ; Do#  (f=2217.46 Hz, phase incr=5813 [16B5h])
     retlw   00Fh    ; Re   (f=2349.32 Hz, phase incr=6159 [180Fh])
     retlw   07Dh    ; Re#  (f=2489.02 Hz, phase incr=6525 [197Dh])
     retlw   001h    ; Mi   (f=2637.02 Hz, phase incr=6913 [1B01h])
     retlw   09Ch    ; Fa   (f=2793.83 Hz, phase incr=7324 [1C9Ch])
     retlw   04Fh    ; Fa#  (f=2959.96 Hz, phase incr=7759 [1E4Fh])
     retlw   01Dh    ; Sol  (f=3135.96 Hz, phase incr=8221 [201Dh])
     retlw   005h    ; Sol# (f=3322.44 Hz, phase incr=8709 [2205h])
     retlw   00Bh    ; La   (f=3520.00 Hz, phase incr=9227 [240Bh])
     retlw   030h    ; La#  (f=3729.31 Hz, phase incr=9776 [2630h])
     retlw   075h    ; Si   (f=3951.07 Hz, phase incr=10357 [2875h])
; 'Tuned by fifths' mode
     retlw   05Ch    ; Do   (f=2085.93 Hz, phase incr=5468 [155Ch])
     retlw   081h    ; Do#  (f=2197.52 Hz, phase incr=5761 [1681h])
     retlw   008h    ; Re   (f=2346.67 Hz, phase incr=6152 [1808h])
     retlw   051h    ; Re#  (f=2472.21 Hz, phase incr=6481 [1951h])
     retlw   008h    ; Mi   (f=2640.00 Hz, phase incr=6920 [1B08h])
     retlw   07Bh    ; Fa   (f=2781.23 Hz, phase incr=7291 [1C7Bh])
     retlw   06Ah    ; Fa#  (f=2970.00 Hz, phase incr=7786 [1E6Ah])
     retlw   00Ah    ; Sol  (f=3128.89 Hz, phase incr=8202 [200Ah])
     retlw   0C1h    ; Sol# (f=3296.28 Hz, phase incr=8641 [21C1h])
     retlw   00Bh    ; La   (f=3520.00 Hz, phase incr=9227 [240Bh])
     retlw   0F9h    ; La#  (f=3708.31 Hz, phase incr=9721 [25F9h])
     retlw   08Dh    ; Si   (f=3960.00 Hz, phase incr=10381 [288Dh])
PhaseTblL_End:
;
   if ( (PhaseTblL & 0x0FF) >= (PhaseTblL_End & 0x0FF) )
       MESSG   "Warning: table PhaseTblL crosses page boundry"
    endif
;

PhaseTblH:
     addwf   pcl,f
; 'Equal temperade tuning' mode
     retlw   015h    ; Do   (f=2093.00 Hz, phase incr=5487 [156Fh])
     retlw   016h    ; Do#  (f=2217.46 Hz, phase incr=5813 [16B5h])
     retlw   018h    ; Re   (f=2349.32 Hz, phase incr=6159 [180Fh])
     retlw   019h    ; Re#  (f=2489.02 Hz, phase incr=6525 [197Dh])
     retlw   01Bh    ; Mi   (f=2637.02 Hz, phase incr=6913 [1B01h])
     retlw   01Ch    ; Fa   (f=2793.83 Hz, phase incr=7324 [1C9Ch])
     retlw   01Eh    ; Fa#  (f=2959.96 Hz, phase incr=7759 [1E4Fh])
     retlw   020h    ; Sol  (f=3135.96 Hz, phase incr=8221 [201Dh])
     retlw   022h    ; Sol# (f=3322.44 Hz, phase incr=8709 [2205h])
     retlw   024h    ; La   (f=3520.00 Hz, phase incr=9227 [240Bh])
     retlw   026h    ; La#  (f=3729.31 Hz, phase incr=9776 [2630h])
     retlw   028h    ; Si   (f=3951.07 Hz, phase incr=10357 [2875h])
; 'Tuned by fifths' mode
     retlw   015h    ; Do   (f=2085.93 Hz, phase incr=5468 [155Ch])
     retlw   016h    ; Do#  (f=2197.52 Hz, phase incr=5761 [1681h])
     retlw   018h    ; Re   (f=2346.67 Hz, phase incr=6152 [1808h])
     retlw   019h    ; Re#  (f=2472.21 Hz, phase incr=6481 [1951h])
     retlw   01Bh    ; Mi   (f=2640.00 Hz, phase incr=6920 [1B08h])
     retlw   01Ch    ; Fa   (f=2781.23 Hz, phase incr=7291 [1C7Bh])
     retlw   01Eh    ; Fa#  (f=2970.00 Hz, phase incr=7786 [1E6Ah])
     retlw   020h    ; Sol  (f=3128.89 Hz, phase incr=8202 [200Ah])
     retlw   021h    ; Sol# (f=3296.28 Hz, phase incr=8641 [21C1h])
     retlw   024h    ; La   (f=3520.00 Hz, phase incr=9227 [240Bh])
     retlw   025h    ; La#  (f=3708.31 Hz, phase incr=9721 [25F9h])
     retlw   028h    ; Si   (f=3960.00 Hz, phase incr=10381 [288Dh])
PhaseTblH_End:
;
   if ( (PhaseTblH & 0x0FF) >= (PhaseTblH_End & 0x0FF) )
       MESSG   "Warning: table PhaseTblH crosses page boundry"
    endif
;

;---------------------------------------------------------------------------
; Table of a sine wave values (8 bits) in 256 steps. The table is scaled so
; that value range [-1.0 , 1.0] is mapped to [00h , 0ffh]
;
; Only values for 0 to 90 degrees are stored in order
; to save space, as a full sine table would take up 256 words.
;
; The 8th bit represents the sign, so absolute values are stored as 7 bits.
; Values are generated using the formula:
;                     127*sin(phase*2*pi/256)
; where "phase" goes from 0 to 63
;
; The sine table was generated by the program SINETBL.C leaving the
; output in a file:  SINETBL.EXE > STBL.INC
;
; The output file STBL.INC  was then merged into the source file.

SineTbl:
     addwf  pcl,f
     retlw  080h
     retlw  083h
     retlw  086h
     retlw  089h
     retlw  08Ch
     retlw  090h
     retlw  093h
     retlw  096h
     retlw  099h
     retlw  09Ch
     retlw  09Fh
     retlw  0A2h
     retlw  0A5h
     retlw  0A8h
     retlw  0ABh
     retlw  0AEh
     retlw  0B1h
     retlw  0B3h
     retlw  0B6h
     retlw  0B9h
     retlw  0BCh
     retlw  0BFh
     retlw  0C1h
     retlw  0C4h
     retlw  0C7h
     retlw  0C9h
     retlw  0CCh
     retlw  0CEh
     retlw  0D1h
     retlw  0D3h
     retlw  0D5h
     retlw  0D8h
     retlw  0DAh
     retlw  0DCh
     retlw  0DEh
     retlw  0E0h
     retlw  0E2h
     retlw  0E4h
     retlw  0E6h
     retlw  0E8h
     retlw  0EAh
     retlw  0EBh
     retlw  0EDh
     retlw  0EFh
     retlw  0F0h
     retlw  0F1h
     retlw  0F3h
     retlw  0F4h
     retlw  0F5h
     retlw  0F6h
     retlw  0F8h
     retlw  0F9h
     retlw  0FAh
     retlw  0FAh
     retlw  0FBh
     retlw  0FCh
     retlw  0FDh
     retlw  0FDh
     retlw  0FEh
     retlw  0FEh
     retlw  0FEh
     retlw  0FFh
     retlw  0FFh
     retlw  0FFh
;
     retlw  0FFh      ; not needed
SineTbl_End:
;
   if ( (SineTbl & 0x0FF) >= (SineTbl_End & 0x0FF) )
       MESSG   "Warning: table SineTbl crosses page boundry"
    endif
;

	end



