;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