;tonef84x.asm - a program to generate a tone using a D/A converter
;
; 12/99         Chuck Olson, WB9KZY
;               Jackson Harbor Press
;               http://jacksonharbor.home.att.net
;               jacksonharbor@att.net
;
;
;       VVHATDOESITDO?
;
;  This program uses an 8 bit DAC along with a 16F84 PIC microcontroller to
; generate a keyed sine wave.  The 16F84 uses an RC clock which can be
; varied (with a suitable potentiometer) to allow a variable frequency
; control for the sine wave output.  
;
;  The output from the DAC will require power amplification - one thing
; that I noticed is that the "pure" sine wave has less "hitting" power than
; a more complicated waveform like a square wave - you'll crank up the
; volume to higher levels with a sine wave input than with a square wave.
; This can result in distortion of the waveform if the amplifier is overdriven.
; Best results were obtained with cheap amplified computer speakers - these 
; had 3 inch cones and the amplifier bass boost switch was off - the moral:  
; more bass response, more thump.  Hi-Fi speakers or headphones won't be
; as pleasing.  Also, the larger the output blocking cap in the amplifier,
; the greater the bass response.  
;  The builder might also include some kind of audio filtering on the output
; of the DAC or even the audio amp.  A passive LC filter could be used at
; the higher level of the audio amp - a single 2 cap active filter can be
; used at the DAC output - the idea here is to add a little "ring" to the
; waveform to remove even more of the thump.  At some point additional
; ringing (up/down ramp time of the sine wave) will turn the Morse code 
; tones unpleasantly mushy.
;
;       Possible Enhancements:
;  There are many ways to change and enhance this program - here are a 
; few ideas:
;  1) if you find the sine wave boring try a different waveform - sawtooth 
;       and triangle table files can be inserted in place of the sine wave 
;       table - you could also try adding noise or harmonics or ??
;  2) The Qbasic programs can be altered to generate alternate wavetables.
;       perhaps with more harmonic content for a car horn effect.
;  3) Crystal control could be used and the program changed to offer 
;       accurate frequency tone generation using the conventional DDS
;       approach - kind of an inexpensive audio signal generator.
;  4) The tone could also be made to sweep across a frequency range.
;  5) The number and/or the steepness of the ramp-up/down routines can
;       be altered to the taste of the user.
;  6) A real DAC can be used - possibly with more bits?
;  7) A cheaper 6 bit, 6 resistor DAC can be tried
;
;
;pin    name    function/connection
;---    ----    -----------------------------------------------
;  1    ra2     key input, pulled up to +5V with a 10K resistor
;  2    ra3     no connect
;  3    ra4     no connect
;  4    MCLR    pulled up to +5V with a 10K resistor
;  5    VSS     Ground 
;  6    rb0     connected to 20k dac resistor  LSB
;  7    rb1     connected to 20k dac resistor
;  8    rb2     connected to 20k dac resistor
;  9    rb3     connected to 20k dac resistor
; 10    rb4     connected to 20k dac resistor
; 11    rb5     connected to 20k dac resistor
; 12    rb6     connected to 20k dac resistor
; 13    rb7     connected to 20k dac resistor  MSB
; 14    VDD     +5V
; 15    osc2    no connect
; 16    osc1    connected to the RC timing circuit, R = 10k pot, C = 22 pf
; 17    ra0     no connect
; 18    ra1     true keying output, follows and inverts state of pin 1

        LIST P=16F84, R=DEC 
        INCLUDE "p16F84.inc"
        __FUSES _PWRTE_OFF & _CP_OFF & _WDT_OFF & _RC_OSC
        

;constants
; bit definitions

; memory locations
tabpos  equ     H'0C'   ;step through table position 
tempdat equ     H'0D'   ;temporary sine table data holder
countin equ     H'0E'   ;inside delay loop count
countou equ     H'0F'   ;outside loop count


        ORG     0x000
start   goto  init      ;reset vector
        

        ORG     0x004
noint   goto  init      ;interrupt vector


; init - the initialization power up entry point of the program
init    bsf     STATUS,5        ;set the bank bit to 1
        movlw   b'00000000'     ; all outputs on PORTB
        movwf   TRISB           ; set the tri-state register
        movlw   b'00000100'     ; RA2 is an input (key)
        movwf   TRISA           ; set the tri-state register
        movlw   b'00000000'     ; no option bits are relevant
        movwf   OPTION_REG      ; set the option register
        bcf     STATUS,5        ;set the bank bit to 0
        ;just load a 1 (for page 2 higher address byte) and put in PCLATH
        ;       since all the table entries are now in page 2.
        movlw   1               ;1  9 load up the high order bits of 2nd page
        movwf   PCLATH          ;1 10 load the high address into the latch
        bcf     PORTA,1 ;set the true output off (inverse of pin 1)


notone  movlw   128     ;load up mid scale 
        movwf   PORTB   ;       for the DAC (an "AC" zero)
        clrf    tabpos          ;      zero the table position
        bcf     PORTA,1 ;set the true output off (inverse of pin 1)
tstsw1  btfsc   PORTA,2         ;see if the sw1 switch is pressed
        goto    tstsw1          ;no, keep looping on sw1
;debounce - execute a no tone delay of 4 tone cycles in length which will        
;       debounce the input pin AND equalize the tone output to the keyed input
;       since the trailing edge of the tone output is 4 tone cycles after
;       the key-up edge.  So, the equalization merely delays the start of
;       the tone output by roughly 4 tone cycles.
;       Each tone cycle should be 19 PIC cycles x 64 table entries = 1216 total
;       Total delay of debounce should be 4 x 1216 = 4864 PIC cycles
;       delay is approximate to 4716 cycles, roughly:
;       18 x 262 = 4716 cycles - this is about 4.7 ms at a PIC clock of 4 MHz.
;Why isn't more accuracy needed here?  Because the tone will always be
;       an integer number of cycles in length, so the length of the tone
;       output won't be exactly the same as the keyed input even if the
;       debounce delay was right on the money - usually the tone will
;       always be a little longer than the keyed delay, so the debounce is 
;       a little shorter on purpose.
        movlw   19              ;1   1  set the outside loop count (18+1)
        movwf   countou         ;1   2  load up the outside loop count
outloop decfsz  countou,1       ;1   1
        goto    ovrend          ;2   3  go around
        goto    dbend           ;       you're done, end the debounce loop
ovrend  movlw   85              ;1   4  load up the inside loop count
        movwf   countin         ;1   5  save it 
        ;total loop count of high loop is 84 x 3 + 2 = 254
inloop  decfsz  countin,1       ;1 (2)  decrement the loop count - skip at 0
        goto    inloop          ;2 259  loop back - inside loop
        goto    outloop         ;2 262  loop back - outside loop
dbend   bsf     PORTA,1 ;yes, set the true output ON (inverse of pin 1)

; The following comment applies to all of the tone generation routines.
; The first number after the semicolon indicates the number of PIC cycles
; that are used to execute the instruction - the second number after the
; semicolon indicates the cumulative total of PIC cycles that have elapsed
; since the start of the routine.  It's important that all the 
; routines execute in exactly the same number of cycles so that the
; tone will have the exactly the same frequency in each routine.

;rup - the 6.25% routine - output 1 cycle of tone at 6.25% of full scale
rupx    clrf    tabpos          ;clear the table position counter  
        goto    rupy            ;branch into the routine
rup     incf    tabpos,1        ;1  1  increment the table position
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rup1x           ;2  3   yes, bail to 25% up loop
rupy    movf    tabpos,0        ;1  4   get ready for the table lookup       
        call    sinetbl         ;6 10   get the sine value from the table
                                ;    the call is 2, addwf is 2 (?), retlw is 2
        movwf   tempdat         ;1 11   store the table data temporarily
        swapf   tempdat,0       ;1 12   divide by 16 by moving hi nibble low
        nop                     ;1 13   equalize the delay
        nop                     ;1 14   equalize the delay
        andlw   d'15'           ;1 15   mask out hi-order bits
        addlw   d'120'          ;1 16   add 112 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rup             ;2 19  Branch to top of the routine

        
;rup1 - the 12.5% routine - output 1 cycle of tone at 12.5% of full scale
rup1x   clrf    tabpos          ;clear the table position counter  
        goto    rup1y           ;branch into the routine
rup1    incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rup2x           ;2  3   yes, bail to 25% up loop
rup1y   movf    tabpos,0        ;1  4   get ready for the table lookup       
        call    sinetbl         ;6 10   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 11   store the table data temporarily
        rrf     tempdat,1       ;1 12   divide by 2
        rrf     tempdat,1       ;1 13   divide by 2
        rrf     tempdat,0       ;1 14   divide by 2
        andlw   d'31'           ;1 15   mask out hi-order bits shifted in from low
        addlw   d'112'          ;1 16   add 112 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rup1            ;2 19  Branch to top of the routine

        
;rup2 - the 25% routine - output 1 cycle of tone at 25% of full scale
rup2x   clrf    tabpos          ;clear the table position counter  
        goto    rup2y           ;branch into the routine
rup2    incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rup3x           ;2  3   yes, bail to 50% up loop
rup2y   nop                     ;1  4   waste a cycle for rdown4 compatibility
        movf    tabpos,0        ;1  5   get ready for the table lookup       
        call    sinetbl         ;6 11   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 12   store the table data temporarily
        rrf     tempdat,1       ;1 13   divide by 2
        rrf     tempdat,0       ;1 14   divide by 2
        andlw   d'63'           ;1 15   mask out hi-order bits shifted in from low
        addlw   d'96'           ;1 16   add 96 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rup2            ;2 19  Branch to top of the routine

        
;rup3 - the 50% routine - output 1 cycle of tone at 50% of full scale
rup3x   clrf    tabpos          ;clear the table position counter  
        goto    rup3y           ;branch into the routine
rup3    incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    tabp3           ;2  3   yes, bail to main tone loop
rup3y   nop                     ;1  4   waste a cyc for rdown3
        nop                     ;1  5   waste a cyc for rdown4
        movf    tabpos,0        ;1  6   get ready for the table lookup       
        call    sinetbl         ;6 12   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 13   store the table data temporarily
        bcf     STATUS,0        ;1 14   zero the carry bit
        rrf     tempdat,0       ;1 15   divide by 2
        addlw   d'64'           ;1 16   add 64 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rup3            ;2 19  Branch to top of the routine


;tabp3 - the main tone routine - output tone at full scale until sw1 is released
tabp3   btfsc   PORTA,2         ;2  2 see if the sw1 switch is pressed
        goto    rdown1          ;2  2 no, ramp down the tone
        nop                     ;1  3   waste a cycle for rdown3
        nop                     ;1  4   waste a cyc for rdown3
        goto    p3ml2           ;2  6 waste a cycle for rdown2
p3ml2   incf    tabpos,1        ;1  7   increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  9   have we gone from 63 to 64?
        clrf    tabpos          ;1  9   yes, zero the table position
        movf    tabpos,0        ;1 10   get ready for the table lookup        
        call    sinetbl         ;6 16 Get the sine value
                                ;       the call is 2, addwf is 2, retlw is 2
        movwf   PORTB           ;1 17 Send it to the DAC
        goto    tabp3           ;2 19 Branch to top of the routine


;rdown1 - complete 1 last cycle of tone at full scale
rdown1  nop                     ;1  1 waste a couple of cycles
        nop                     ;1  2    to be consistent with tabp3 loop
        nop                     ;1  3   waste a cycle for rdown3
        nop                     ;1  4   waste a cyc for rdown3
        goto    rdown           ;2  6   waste 2 more cycles for rdown2
        ;first end this particular cycle of tone at zero
rdown   incf    tabpos,1        ;1  7  no, increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  9   have we gone from 63 to 64?
        goto    rdown2x         ;2  9   yes, bail to 50% down loop
        movf    tabpos,0        ;1 10   get ready for the table lookup       
        call    sinetbl         ;6 16   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rdown1          ;2 19  Branch to top of the routine


;rdown2 - the 50% routine - output 1 cycle of tone at 50% of full scale
rdown2x clrf    tabpos          ;clear the table position counter  
        goto    rdown2y         ;branch into the routine
rdown2  incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rdown3x         ;2  3   yes, bail to 25% down loop
rdown2y nop                     ;1  4   waste a cyc for rdown3
        nop                     ;1  5   waste a cyc for rdown4
        movf    tabpos,0        ;1  6   get ready for the table lookup       
        call    sinetbl         ;6 12   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 13   store the table data temporarily
        bcf     STATUS,0        ;1 14   zero the carry bit
        rrf     tempdat,0       ;1 15   divide by 2
        addlw   d'64'           ;1 16   add 64 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rdown2          ;2 19  Branch to top of the routine


;rdown3 - the 25% routine - output 1 cycle of tone at 25% of full scale
rdown3x clrf    tabpos          ;clear the table position counter  
        goto    rdown3y         ;branch into the routine
rdown3  incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rdown4x         ;2  3   yes, bail to 12.5% loop
rdown3y nop                     ;1  4   waste a cycle for rdown4 compatibility
        movf    tabpos,0        ;1  5   get ready for the table lookup       
        call    sinetbl         ;6 11   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 12   store the table data temporarily
        rrf     tempdat,1       ;1 13   divide by 2
        rrf     tempdat,0       ;1 14   divide by 2
        andlw   d'63'           ;1 15   mask out hi-order bits shifted in from low
        addlw   d'96'           ;1 16   add 96 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rdown3          ;2 19  Branch to top of the routine

        
;rdown4 - the 12.5% routine - output 1 cycle of tone at 12.5% of full scale
rdown4x clrf    tabpos          ;clear the table position counter  
        goto    rdown4y         ;branch into the routine
rdown4  incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    rdwnx           ;2  3   yes, bail to 6.25% routine
rdown4y movf    tabpos,0        ;1  4   get ready for the table lookup       
        call    sinetbl         ;6 10   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 11   store the table data temporarily
        rrf     tempdat,1       ;1 12   divide by 2
        rrf     tempdat,1       ;1 13   divide by 2
        rrf     tempdat,0       ;1 14   divide by 2
        andlw   d'31'           ;1 15   mask out hi-order bits shifted in from low
        addlw   d'112'          ;1 16   add 112 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rdown4          ;2 19  Branch to top of the routine

        
;rdwn - the 6.25% routine - output 1 cycle of tone at 6.25% of full scale
rdwnx   clrf    tabpos          ;clear the table position counter  
        goto    rdwny           ;branch into the routine
rdwn    incf    tabpos,1        ;1  1  increment the table position
;12/27/99 - add a test of bit 7 of the table position to see if we've        
        ;rolled into the next sine wave cycle
        btfsc   tabpos,6        ;2  3   have we gone from 63 to 64?
        goto    notone          ;2  3   yes, bail to the no tone routine
rdwny   movf    tabpos,0        ;1  4   get ready for the table lookup       
        call    sinetbl         ;6 10   get the sine value from the table
                                ;    the call is 2, addwf is 2, retlw is 2
        movwf   tempdat         ;1 11   store the table data temporarily
        swapf   tempdat,0       ;1 12   divide by 16 by moving hi nibble low
        nop                     ;1 13   equalize the delay
        nop                     ;1 14   equalize the delay
        andlw   d'15'           ;1 15   mask out hi-order bits
        addlw   d'120'          ;1 16   add 112 to center the waveform on 128
        movwf   PORTB           ;1 17  Send it to the DAC
        goto    rdwn            ;2 19  Branch to top of the routine

        
        org     0xFF
; sinetbl - a table of triangle values - 64 bytes in length
;generated table with sine11.bas Qbasic program
; Question:  Why does this routine start at address FF?
; Answer:  Because the table was originally 256 entries long - the only
;       easy/quick way to access the table is to have all the entries on
;       the same 256 word memory page.  The register PCLATH is preset
;       during initialization to always point to page 1 (address 100h) so
;       that the result of the addwf to PCL always points to page 1.
;       Later, the table was cut back to 128 and then 64 entries for a
;       higher tone output frequency with a given chip clock frequency.
;       Note that the table could also have been trimmed in size by 
;       using only 1/4 of the waveform and then performing a little
;       simple math to recreate the other 3/4 of the table depending on
;       where the routine is within a given waveform cycle.  This idea
;       wasn't used to save time during the routine.
sinetbl addwf   PCL,1   ;compute the jump value
        retlw     128 ; 0    0 
        retlw     140 ; 1    9.801706E-02 
        retlw     152 ; 2    .1950902 
        retlw     165 ; 3    .2902845 
        retlw     176 ; 4    .3826831 
        retlw     188 ; 5    .4713964 
        retlw     198 ; 6    .5555698 
        retlw     208 ; 7    .6343929 
        retlw     218 ; 8    .7071064 
        retlw     226 ; 9    .77301 
        retlw     234 ; 10   .8314692 
        retlw     240 ; 11   .8819209 
        retlw     245 ; 12   .9238791 
        retlw     250 ; 13   .9569401 
        retlw     253 ; 14   .9807851 
        retlw     254 ; 15   .9951846 
        retlw     255 ; 16   1 
        retlw     254 ; 17   .9951848 
        retlw     253 ; 18   .9807855 
        retlw     250 ; 19   .9569408 
        retlw     245 ; 20   .9238802 
        retlw     240 ; 21   .8819221 
        retlw     234 ; 22   .8314706 
        retlw     226 ; 23   .7730116 
        retlw     218 ; 24   .7071081 
        retlw     208 ; 25   .6343948 
        retlw     198 ; 26   .555572 
        retlw     188 ; 27   .4713986 
        retlw     176 ; 28   .3826855 
        retlw     165 ; 29   .2902869 
        retlw     152 ; 30   .1950926 
        retlw     140 ; 31   9.801959E-02 
        retlw     128 ; 32   2.535182E-06 
        retlw     115 ; 33  -9.801454E-02 
        retlw     103 ; 34  -.1950877 
        retlw     90 ; 35   -.290282 
        retlw     79 ; 36   -.3826808 
        retlw     67 ; 37   -.4713942 
        retlw     57 ; 38   -.5555677 
        retlw     47 ; 39   -.6343909 
        retlw     37 ; 40   -.7071046 
        retlw     29 ; 41   -.7730084 
        retlw     21 ; 42   -.8314677 
        retlw     15 ; 43   -.8819197 
        retlw     10 ; 44   -.9238782 
        retlw     5 ; 45    -.9569393 
        retlw     2 ; 46    -.9807846 
        retlw     1 ; 47    -.9951844 
        retlw     0 ; 48    -1 
        retlw     1 ; 49    -.9951851 
        retlw     2 ; 50    -.980786 
        retlw     5 ; 51    -.9569415 
        retlw     10 ; 52   -.9238811 
        retlw     15 ; 53   -.8819233 
        retlw     21 ; 54   -.831472 
        retlw     29 ; 55   -.7730132 
        retlw     37 ; 56   -.7071099 
        retlw     47 ; 57   -.6343968 
        retlw     57 ; 58   -.5555741 
        retlw     67 ; 59   -.4714009 
        retlw     79 ; 60   -.3826878 
        retlw     90 ; 61   -.2902893 
        retlw     103 ; 62  -.1950951 
        retlw     115 ; 63  -9.802211E-02 


        END