;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; A piano key transposer ;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;This program takes in a key input from the octave-long keyboard. ;It then stores the program at location 2000h in the green light ;mode. The notes of the song is echoed back to the screen after ;a done signal is input by the user. Then based on the users ;key input, the program can do different things. It can play ;the original song back, again the notes are displayed on the PC ;screen. The minor of the song is played assuming the input ;song is in C major. The notes of the transposed minor is displayed ;on the PC screen. Also, the user can input the key that the original ;song is played in, and the key the user wants the song to be ;transposed to. The program can transpose the music to the ;corresponding key. The tranposed song is played, and the ;notes are displayed on the PC screen. ;P3.4 is connected to output enable on the keypad encoder ;P3.5 is connected to data available on the keypad encoder ;the keypad output lines are connected to port 1, lower 4 bits ;so we read in the info about the key press from P1.0 to P1.3 ;R0 has the number corresponding to the new numbering system ;It also is a temporary storage for storing the difference when transposing keys. ;R1 stores the dpl when reading the info of the song starting from location 2000h ;R2 stores the low byte of the skipping step corresponding to the frequency ;R3 stores the high byte of the skipping step corresponding to the frequency ;R4 has the low byte of the table pointer for the frequency ;R5 has the high byte of the table pointer for the frequency ;R6 is to count the number of interrupts happened within a time interval ;R7 is to keep track of dpl when storing the information of a song to 2000h ;information about the song is stored at location 2000h ;Timer 1 is for serial communication. ;Timer 0 is for interrupt ;2 External interrupts are basically timer interrupts. Both external 0 ;and external 1 get interrupted at a frequency of 28.125Hz org 00h ;jump to start ljmp start org 003h ;external 0 interrupt, for retrieving time interval ljmp countdn ;count down until the counter is zero org 000BH ;timer 0 interrupt vector ljmp processdata ;go to the next table read, for generating the sine wave org 013h ;external 1 interrupt, for counting the timer interval ljmp counttime ;count up until the time interval ends org 100h start: ;starts from here mov P1, #0fh ;clear the highest 4 bits in P1, because the lower nibble ;of P1 is used as inputs lcall init ;initialize timer 0 so that it gets interrupted ;every 70 machine cycles when needed, set up 8254, timer 1 for ;serial communications. Set up the interrupts lcall print ;print out the prompt message db 0ah, 0dh, "Please input a song. Press done after you are done", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed chkdata: ;check the piano keypad to see if ;there's any keypress available jnb P3.5, chkdata ;if there's no key input, keep checking clr IE.2 ;if there is, stop the external interrupt 1 mov a, R6 ;store the interrupt counter value into A lcall store ;store the timer interval to location 2000h mov R6, #0 ;reinitialize the counter to zero clr P3.4 ;get the input lcall getkey ;the key is in the accumulator now lcall numbering ;find out the label in the numbering system jz read ;0 corresponds to play the original song back push acc ;save the accumulator dec a jz minorbranch ;1 corresponds to branch to minor dec a jz jump ;2 corresponds to jump to transpose dec a jz done ;3 corresponds to the user is done inputting the song pop acc ;retrieve the accumulator mov R0, a ;save the byte into R0 lcall store ;store the note mov A, R0 ;restore the label anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct4high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct4low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step setb IE.2 ;enable external interrupt 1 to count the time the note lasts setb TR0 ;start timer 0 to generate sound mov R4, #00h ;initialize both the table pointers, low mov R5, #00h ;and high loop: ;the loop is to check whether the key is still pressed. jb P3.5, loop ;if it is, keep waiting until is released clr TR0 ;stop timer 0 (stop generating sound) after the key is released clr IE.2 ;stop external interrupt (stop counting the time interval) mov a, R6 ;move the timer interval counter to a lcall store ;store the counter value mov R6, #0 ;reinitialize the timer interval value to zero setb IE.2 ;start external interrupt 1 (start counting another ;timer interval) ljmp chkdata ;jump back to check the next key press ;the following three labels are to solve the problem that jz can't jump too long jump: ;jump to transpose ljmp transpose minorbranch: ;jump to minor ljmp minor done: ;jump to respond to done ljmp processdone read: ;play the original song back setb B.1 ;B.1 is set at the beginning of a time interval. B.1 is cleared ;after the time interval is done. Set it by default mov R1, #00h ;initialize the dpl to be 0 readin: ;read in values from the table mov dph, #20h ;we are reading starting from 2000h mov dpl, R1 ;R1 keeps track of dpl movx a, @dptr ;read in the entry inc a ;when the entry is FFh, we know we are done reading jz doneread ;then we jump to doneread dec a ;get back the original accumulator value anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct4high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct4low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step inc R1 ;increment dpl by 1 mov dph, #20h ;load the accumulator with the next entry of the stored data table mov dpl, R1 movx a, @dptr ;this is supposed to be the value of the time interval counter ;that counts how much time the note lasts mov R6, a ;move the count to R6 setb IE.0 ;start external interrupt 0 to count down time interval setb B.1 ;B.1 is set to signal that we are counting down a time interval setb TR0 ;start timer 0 so we start generating sound mov R4, #00h ;initialize both the table pointers, low mov R5, #00h ;and high loop1: ;the loop waits until the counter counts down to zero, in jb B.1, loop1 ;which case B.1 is cleared inc R1 ;increment R1 to get the next entry mov dph, #20h ;still read from the table we stored earlier mov dpl, R1 ;this is supposed to be the value of the timer interval counter movx a, @dptr ;that counts the rest time between notes mov R6, a ;load R6 with the counter value setb IE.0 ;start external interrupt to start counting down the counter setb B.1 ;B.1 is set to signal that we are counting down a time interval loop2: ;this loop waits until the counter counts down to zero, in jb B.1, loop2 ;which case B.1 is cleared inc R1 ;increment dpl ljmp readin ;jump back to read the next entry doneread: ;this piece of code handles what happens after the user ;is done inputting the song. All the notes are displayed ;on the PC screen, and an FFh is stored at the very end ;to signal that it's the end clr EA ;stop all the interrupt mov R6, #0h ;reinitilze the counter to be zero lcall print ;print some prompt db 0ah, 0dh, "The wonderful song you just played is:", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed mov dptr, #2000h ;move the dptr to 2000h to read from the table ljmp doneloop ;jump to done loop to display the notes on the PC screen minor: lcall print print out the prompt message db 0ah, 0dh, "Please note the song that's transposed must be in C major", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed setb B.1 ;B.1 is set at the beginning of a time interval. B.1 is cleared ;after the time interval is done. Set it by default mov R1, #00h ;initialize the dpl to be 0 minor2: ;read in the values from the table and transpose the notes to minor mov dph, #20h ;we are reading starting from 2000h mov dpl, R1 ;R1 keeps track of dpl movx a, @dptr ;read in the entry inc a ;when the entry is FFh, we know we are done reading jz minordone ;then we jump to minordone dec a ;get back the original accumulator value anl A, #0Fh ;get the lower nibble of A lcall minoradjust ;flat the E and A anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct4high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct4low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step inc R1 ;increment dpl by 1 mov dph, #20h ;load the accumulator with the next entry of the stored data table mov dpl, R1 movx a, @dptr ;this is supposed to be the value of the time interval counter ;that counts how much time the note lasts mov R6, a ;move the count to R6 setb IE.0 ;start external interrupt 0 to count down time interval setb B.1 ;B.1 is set to signal that we are counting down a time interval setb TR0 ;start timer 0 so we start generating sound mov R4, #00h ;initialize both the table pointers, low mov R5, #00h ;and high loop4minor: ;the loop waits until the counter counts down to zero, in jb B.1, loop4minor ;which case B.1 is cleared inc R1 ;increment R1 to get the next entry mov dph, #20h ;still read from the table we stored earlier mov dpl, R1 ;this is supposed to be the value of the timer interval counter movx a, @dptr ;that counts the rest time between notes mov R6, a ;load R6 with the counter value setb IE.0 ;start external interrupt to start counting down the counter setb B.1 ;B.1 is set to signal that we are counting down a time interval loop4minor2: ;this loop waits until the counter counts down to zero, in jb B.1, loop4minor2 ;which case B.1 is cleared inc R1 ;increment dpl ljmp minor2 ;jump back to read the next entry minordone: ;after the sound echoing is done clr EA ;stop all the interrupt mov R6, #0h ;reinitilze the counter to be zero lcall print ;print some prompt db 0ah, 0dh, "The transposed minor version of the song you input is:", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed mov dptr, #2000h ;move the data pointer to the beginning of the table minordoneloop: ;this piece of code puts the notes of the transposed minor ;song on the PC screen movx a, @dptr ;get the entry in the table push acc ;save the entry inc a ;check to see if the entry is FFh, in which case, it's the end of the song jz minorfinish ;if it is, go to minorfinish pop acc ;retrieve the entry anl a, #0Fh ;get the lower nibble lcall minoradjust ;flat the E and A anl a, #0Fh ;get the lower nibble again push acc ;save the value lcall readkeyhi ;get the corresponding ASCII code of the note lcall sndchr ;display the note on the screen pop acc ;retrieve the lower nibble lcall readkeylo ;get the corresponing ASCII code of flats or space lcall sndchr ;display the note on the screen lcall crlf ;issue a carriage return and line feed inc dpl ;increment data pointer 3 times, because we don't need to read inc dpl ;the time intervals in this case inc dpl ljmp minordoneloop ;jump back to minordoneloop to read the next entry minorfinish: ;after all the minor notes are displayed on the PC screen jb P3.5, minorfinish ;check if the key press is still held. wait unti ;it's released setb EA ;enable the interrupts again ljmp start ;jump back to start transpose: ;this piece of code handles the transposing key part lcall print ;print prompt db 0ah, 0dh, "Please input the key the song is originally in: ", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed lcall getchr ;wait for the user input (the key the song is in) clr acc.5 ;make it upper case lcall sndchr ;echo the character push acc ;save the ASCII code of upper case character A-G clr c ;range check subb A, #65 ;check if below A in the ASCII table jc illegal1 ;if it is, call illegal1 pop acc ;retrieve acc push acc ;save acc again clr c ;clear the carry bit subb A, #72 ;check if above G in the ASCII table jnc illegal1 ;if it is, call illegal1 pop acc ;get back the accumulator value anl A, #0fh ;get the lower nibble only lcall match ;call match to find the correspondance in the numbering system push B ;save B mov B, A ;save A to B lcall getchr ;get second key input, which should be #, b or space lcall sndchr ;echo the character cjne a, #35, flat ;if it's not sharp, go to branch flat inc B ;if the input is sharp, increment B by one ljmp get2ndinput ;now we are ready to get the second input illegal1: ;handling illegal input lcall print ;print out the prompt message db 0ah, 0dh, "illegal input. The song isn't written in that key!", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed ljmp transpose ;jump back to transpose for a valid user input flat: ;check to see if the second character is flat cjne a, #98, space1 ;if the input is not flat, check if it's space dec B ;if the 2nd character is flat, decrement B by 1 ljmp get2ndinput ;now we are ready to get the 2nd input illegal4: ;handling illegal input lcall print ;print out the prompt message db 0ah, 0dh, "illegal input. I don't know what you are doing!", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed ljmp transpose ;jump back to transpose for a valid user input space1: ;check to see if the second character is space cjne a, #32, illegal4 ;if not, jump to illegal4 get2ndinput: ;getting the second input lcall print ;print prompt db 0ah, 0dh, "Please input the key you want the song to be transposed to: ", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed lcall getchr ;wait for the user input (the key the song is in) clr acc.5 ;make it upper case lcall sndchr ;echo the character push acc ;save the ASCII code of upper case character A-G clr c ;range check subb A, #65 ;check if below A in the ASCII table jc illegal2 ;if it is, call illegal2 pop acc ;retrieve acc push acc ;save acc again clr c ;clear the carry bit subb A, #72 ;check if above G in the ASCII table jnc illegal2 ;if it is, call illegal2 pop acc ;get back the accumulator value anl A, #0fh ;get the lower nibble only lcall match ;call match to find the correspondance in the numbering system push acc ;save the note lcall getchr ;get second key input, which should be #, b or space lcall sndchr ;echo the character cjne a, #35, flat2 ;if it's not sharp, go to branch flat pop acc ;retrieve the accumulator inc acc ;if the input is sharp, increment A by one ljmp trans ;now we are ready to calculate the difference of the two keys illegal2: ;handling illegal input lcall print ;print out the prompt message db 0ah, 0dh, "illegal input. I can't transpose the song to that key!", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed ljmp transpose ;jump back to transpose for a valid user input flat2: ;check to see if the second character is flat cjne a, #98, space ;if the input is not flat, check if it's space pop acc ;retrieve the accumulator value dec acc ;if the 2nd character is flat, decrement A by 1 ljmp trans ;now we are ready to calculate the difference of the two keys space: ;check to see if the second character is space cjne a, #32, illegal3 ;if not, jump to illegal3 pop acc ;retrieve the accumulator value ljmp trans ;now we are ready to calculate the difference of the two keys illegal3: ;handling illegal input lcall print ;print out the prompt message db 0ah, 0dh, "illegal input. I don't know what you are doing!", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed ljmp transpose ;jump back to transpose for a valid user input trans: ;calculate the difference between the two keys amd store the value in R0 clr c ;clear the carry bit subb A, B ;subtract the original key from the key that we want to transpose to mov R0, A ;R0 as the temporary storage for the difference pop B ;get back the original value of B setb B.1 ;B.1 is set at the beginning of a time interval. B.1 is cleared ;after the time interval is done. Set it by default mov R1, #00h ;initialize the dpl to be 0 transpose1: ;calculating the difference mov dph, #20h ;we are reading starting from 2000h mov dpl, R1 ;R1 keeps track of dpl movx a, @dptr ;read in the entry push acc ;save the accumulator value inc a ;when the entry is FFh, we know we are done reading jz transposedone ;then we jump to transposedone dec a ;get back the original accumulator value anl A, #00Fh ;get the lower nibble that shows the position in the lookup table clr C ;clear the carry bit add A, R0 ;add the diffrence to the lower nibble clr c ;clear the carry bit subb a, #12 ;see if the sum is bigger than 12 jc goon ;if it is small than 12, go on to add the difference pop acc ;if not, retrieve the accumulator value clr c ;clear the carry bit add A, R0 ;add the difference to the original note add a, #4 ;add a 4 to it so that it moves to the next octave ljmp goon2 ;then we can go on to make sound goon: ;go on to make sound pop acc ;retrieve the accumulator value clr c ;clear the carry bit add a, R0 ;add the difference to the original value goon2: ;now we can go on and make sound push acc ;save the note value anl A, #0F0h ;get the higher nibble swap A ;swap the lower and higher nibble clr c ;clear the carry bit subb A, #03h ;see if a is 3 jz gooctave3 ;if yes, go read from octave 3 table dec A ;decrement a by 1 jz gooctave4 ;see is a is 4. if yes, go read from octave 4 table dec A ;decrement a by 1 jz gooctave5 ;see if a is 5. if yes, go read from octave 5 table ljmp octave4 ;by default, read from octave 4 gooctave3: ;the following lines are to accomodate the range of jz ljmp octave3 ;go to octave3 gooctave4: ;go to octave3 ljmp octave4 gooctave5: ;go to octave3 ljmp octave5 transposedone: ;after the transposed song is played clr EA ;stop all the interrupt mov R6, #0h ;reinitilze the counter to be zero lcall print ;print some prompt db 0ah, 0dh, "The transposed version of the song you input is:", 0ah, 0dh,"TINA> ", 0h lcall crlf ;the message is followed by a carriage return and line feed mov dptr, #2000h ;move the data pointer to the beginning of the table transposedoneloop: ;this piece of code puts the notes of the transposed ;song on the PC screen movx a, @dptr ;get the entry in the table push acc ;save the entry inc a ;check to see if the entry is FFh, in which case, it's the end of the song jz transposefinish ;if it is, go to transposefinish pop acc ;retrieve the entry push acc ;save the entry again anl A, #00Fh ;get the lower nibble that shows the position in the lookup table clr C ;clear the carry bit add A, R0 ;add the diffrence to the lower nibble clr c ;clear the carry bit subb a, #12 ;see if the sum is bigger than 12 jc trans1 if it is small than 12, go on to add the difference pop acc ;if not, retrieve the accumulator value clr c ;clear the carry bit add A, R0 ;add the difference to the original note add a, #4 ;add a 4 to it so that it moves to the next octave ljmp trans2 ;then we can go on and display notes trans1: ;for numbers that are lower than 12 op acc ;retrieve the accumulator value clr c ;clear the carry bit add a, R0 ;add the difference to the original value trans2: ;display the characters on PC screen anl a, #0Fh ;get the lower nibble again push acc ;save the value lcall readkeyhi ;get the corresponding ASCII code of the note lcall sndchr ;display the note on the screen pop acc ;retrieve the lower nibble lcall readkeylo ;get the corresponing ASCII code of flats or space lcall sndchr ;display the note on the screen lcall crlf ;issue a carriage return and line feed inc dpl ;increment data pointer 3 times, because we don't need to read inc dpl ;the time intervals in this case inc dpl ljmp transposedoneloop ;jump back to transposedoneloop to read the next entry transposefinish: ;after all the transposed notes are displayed on the PC screen jb P3.5, transposefinish;check if the key press is still held. wait unti ;it's released setb EA ;enable the interrupts again ljmp start ;jump back to start octave3: ;for octave 3 pop acc ;retrieve the accumulator value anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct3high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct3low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step ljmp processtranspose ;go on transposing octave4: ;for octave 4 pop acc ;retrieve the accumulator value anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct4high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct4low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step ljmp processtranspose ;go on transposing octave5: ;for octave 5 pop acc ;retrieve the accumulator value anl A, #00Fh ;get the lower nibble that shows the position in the lookup table push acc ;save the lower nibble lcall oct5high ;find the high byte of the skipping step corresponding to the frequency mov R3, a ;R3 has the high byte skipping step pop acc ;get the lower nibble lcall oct5low ;find the low byte of the skipping step corresponding to the frequency mov R2, a ;R2 has the low skipping step processtranspose: inc R1 ;increment R1 to get the next entry mov dph, #20h ;still read from the table we stored earlier mov dpl, R1 ;this is supposed to be the value of the timer interval counter movx a, @dptr ;that counts the rest time between notes mov R6, a ;load R6 with the counter value setb IE.0 ;start external interrupt to start counting down the counter setb B.1 ;B.1 is set to signal that we are counting down a time interval setb TR0 ;stop timer 0 mov R4, #00h ;initialize both the table pointers, low mov R5, #00h ;high transposeloop1: ;the loop waits until the counter counts down to zero, in jb B.1, transposeloop1 ;which case B.1 is cleared inc R1 ;increment R1 to get the next entry mov dph, #20h ;still read from the table we stored earlier mov dpl, R1 ;this is supposed to be the value of the timer interval counter movx a, @dptr ;that counts the rest time between notes mov R6, a ;load R6 with the counter value setb IE.0 ;start external interrupt to start counting down the counter setb B.1 ;B.1 is set to signal that we are counting down a time interval transposeloop2: ;this loop waits until the counter counts down to zero, in jb B.1, transposeloop2 ;which case B.1 is cleared inc R1 ;increment dpl ljmp transpose1 ;jump back to read the next entry processdata: ;this interrupt survice routine is to process data, and output the ;digital added sine wave to the DAC mov dptr, #1000h ;set the data pointer offset to be where the sine table is, 1000h mov a, R2 ;get the low byte of the advance for row frequency add a, R4 mov R4, a ;update the low table pointer mov a, R3 ;get the high byte of the advance for row frequnecy addc a, R5 ;addc is to take into account the low byte addition carry mov R5, a ;update the row high table pointer movc a, @a+dptr ;read the value from the sine table mov dptr, #0FE08h ;set the digital value to the DAC movx @dptr, a reti ;return from interrupt counttime: ;external 1 interrupt inc R6 ;only increment the time interval counter every time an interrupt happens reti ;return from interrupt init: ;this subroutine sets up timer 0 so that it overflows every 76 microseconds ;it also sets up 8254 so that it makes a square wave with a frequency of 28.125Hz ;timer 1 is set up for serial communication ;set up for serial communication mov tmod, #22h ;0b00100010 select for timer operation, 8-bit auto-reload mov tcon, #41h ;run counter 1 and set edge trig ints mov th1, #0fdh ;set 9600 baud with xtal=11.059mhz mov scon, #50h ;set serial control reg for 8 bit data ;and mode 1 mov th0, #0BAh ;timer0 counts up 70 times, so that it interrupts every 76 microseconds mov dptr, #0FE03h ;configure the 8254 mov a, #0B4h ;select counter 2, read/write least significant byte first, then most significant byte movx @dptr, a ;in mode 2 mov dptr, #0FE02h ;load counter 2 with FFFFh mov a, #0FFh movx @dptr, a mov dptr, #0FE02h mov a, #0FFh movx @dptr, a mov dptr, #0FE03h ;select counter 1, read/write least significant byte first, then most significant byte mov a, #072h ;in mode 1 movx @dptr, a mov dptr, #0FE01h ;load counter 1 with 80FFh mov a, #0FFh movx @dptr, a mov dptr, #0FE01h mov a, #080h movx @dptr, a setb IT1 ;set external interrupt 1 to be edge triggered setb IT0 ;set external interrupt 0 to be edge triggered clr B ;clear the B register for later use mov R6, #0h ;initiailize the counter of interrupts to be zero mov R7, #0FEh ;R7 keeps track of dpl when storing data. dpl points to 255 initially for data storing mov IE, #82h ;0b10000010, enable timer 0 interrupt ret getkey: ;getkey gets the keyboard input from the 74C922 mov a, P1 ;move the port 1 value to the accumulator anl a, #0fh ;only gets the lower nibble ret return store: ;the reason why we are not echoing the notes to the PC screen ;in real time is that it would take too much time, and thus the ;sampling rate would be too low to generate the sine wave with ;the right frequency inc R7 ;increment dpl mov dph, #20h ;move dph to be 20h mov dpl, R7 ;R7 has dpl movx @dptr, a ;store the accumulator value into the place where dptr points ret countdn: ;external 0 interrupt, counts down the counter value until it's 0 djnz R6,returni ;decrement the counter, check to see if it's zero clr IE.0 ;stop external 0 interrupt clr B.1 ;clear the still-counting-down bit clr TR0 ;stop timer 0 returni: ;return from interrupt reti ;after the user hits the "done" button processdone: ;FFh signifies a done signal, a random choice clr EA ;stop all the interrupts mov R6, #0h ;reset the counter to be zero lcall print ;print prompt db 0ah, 0dh, "Here is the beautiful song you just played:", 0ah, 0dh,"TINA> ", 0h lcall crlf ;issue a carriage return and line feed mov a, #0FFh ;move the done signal to A lcall store ;store the value to the next two positions lcall store mov dptr, #2000h ;reset the dptr to be 2000h doneloop: ;doneloop displays the notes that are stored in the table starting from 2000h movx a, @dptr ;get the entry in the table push acc ;save the entry inc a ;check to see if the entry is FFh, in which case, it's the end of the song jz finish ;if it is, go to finish pop acc ;retrieve the entry anl a, #0Fh ;get the lower nibble push acc ;save the lower nibble lcall readkeyhi ;get the corresponding ASCII code of the note lcall sndchr ;display the note on the screen pop acc ;retrieve the lower nibble lcall readkeylo ;get the corresponing ASCII code of flats or space lcall sndchr ;display the note on the screen lcall crlf ;issue a carriage return and line feed inc dpl ;increment data pointer 3 times, because we don't need to read inc dpl ;the time intervals in this case inc dpl ljmp doneloop ;jump back to doneloop to read the next entry finish: ;after all the notes are displayed on the PC screen jb P3.5, finish ;check if the key press is still held. wait unti ;it's released setb EA ;enable the interrupts again ljmp start ;jump back to start ;this table stores the first letters of all the notes readkeyhi: inc a movc a, @a+pc ret db 43h, 44h, 44h, 45h db 45h, 46h, 47h, 47h db 41h, 41h, 42h, 42h db 00h, 00h, 00h, 00h ;this table stores the second part of all notes, that are, ;flats or spaces readkeylo: inc a movc a, @a+pc ret db 20h, 62h, 20h, 62h db 20h, 20h, 62h, 20h db 62h, 20h, 62h, 20h db 00h, 00h, 00h, 00h ;this table matches the lower nibble of A-G in the ASCII table ;to the numbering system that I use match: inc a movc a, @a+pc ret db 040h, 049h, 04bh, 040h db 042h, 044h, 045h, 047h db 048h, 049h, 04ah, 04bh db 003h, 002h, 001h, 000h ;numbering reading in the notes corresponding to the key presses. ;they are all in octave 4, which is shown in the higher nibble. ;the lower nibble shows which note it is in the octave numbering: inc a movc a, @a+pc ret db 040h, 041h, 042h, 043h db 044h, 045h, 046h, 047h db 048h, 049h, 04ah, 04bh db 003h, 002h, 001h, 000h ;minoradjust flats the E and the A minoradjust: inc a movc a, @a+pc ret db 040h, 041h, 042h, 043h db 043h, 045h, 046h, 047h db 048h, 048h, 04ah, 04bh db 003h, 002h, 001h, 000h ;The following table contains the high byte of the ;skipping steps corresponding to entries of notes in octave 3. oct3high: inc a movc a, @a+pc ret db 002h, 002h, 002h, 003h db 003h, 003h, 003h, 003h db 004h, 004h, 004h, 004h db 000h, 000h, 000h, 000h ;The following table contains the low byte of the ;skipping steps corresponding to entries of notes in octave 3. oct3low: inc a movc a, @a+pc ret db 08Bh, 0B2h, 0DBh, 006h db 034h, 065h, 099h, 0D0h db 00Ah, 047h, 088h, 0CDh db 000h, 000h, 000h, 000h ;The following table contains the high byte of the ;skipping steps corresponding to entries of notes in octave 4. oct4high: inc a movc a, @a+pc ret db 005h, 005h, 005h, 006h db 006h, 006h, 007h, 007h db 008h, 008h, 009h, 009h db 000h, 000h, 000h, 000h ;The following table contains the low byte of the ;skipping steps corresponding to entries of notes in octave 4. oct4low: inc a movc a, @a+pc ret db 016h, 064h, 0B6h, 0Dh db 069h, 0CAh, 032h, 09Fh db 013h, 08Eh, 011h, 09Bh db 000h, 000h, 000h, 000h ;The following table contains the high byte of the ;skipping steps corresponding to entries of notes in octave 5. oct5high: inc a movc a, @a+pc ret db 00Ah, 00Ah, 00Bh, 00Ch db 00Ch, 00Dh, 00Eh, 00Fh db 010h, 011h, 012h, 013h db 000h, 000h, 000h, 000h ;The following table contains the low byte of the ;skipping steps corresponding to entries of notes in octave 5. oct5low: inc a movc a, @a+pc ret db 02Dh, 0C8h, 06Ch, 01Ah db 0D2h, 095h, 064h, 03Fh db 027h, 01Dh, 021h, 035h db 000h, 000h, 000h, 000h ;=============================================================== ; subroutine prthex ; this routine takes the contents of the acc and prints it out ; as a 2 digit ascii hex number. ;=============================================================== prthex: push 2 push acc lcall binasc ; convert acc to ascii lcall sndchr ; print first ascii hex digit mov a, r2 ; get second ascii hex digit lcall sndchr ; print it pop acc pop 2 ret ;=============================================================== ; subroutine binasc ; binasc takes the contents of the accumulator and converts it ; into two ascii hex numbers. the result is returned in the ; accumulator and r2. ;=============================================================== binasc: mov r2, a ; save in r2 anl a, #0fh ; convert least sig digit. add a, #0f6h ; adjust it jnc noadj1 ; if a-f then readjust add a, #07h noadj1: add a, #3ah ; make ascii xch a, r2 ; put result in reg 2 swap a ; convert most sig digit anl a, #0fh ; look at least sig half of acc add a, #0f6h ; adjust it jnc noadj2 ; if a-f then re-adjust add a, #07h noadj2: add a, #3ah ; make ascii ret ;=============================================================== ; subroutine sndchr ; this routine takes the chr in the acc and sends it out the ; serial port. ;=============================================================== sndchr: clr scon.1 ; clear the tx buffer full flag. mov sbuf,a ; put chr in sbuf txloop: jnb scon.1, txloop ; wait till chr is sent ret ;=============================================================== ; subroutine crlf ; crlf sends a carriage return line feed out the serial port ;=============================================================== crlf: mov a, #0ah ; print lf lcall sndchr cret: mov a, #0dh ; print cr lcall sndchr ret ;=============================================================== ; subroutine print ; print takes the string immediately following the call and ; sends it out the serial port. the string must be terminated ; with a null. this routine will ret to the instruction ; immediately following the string. ;=============================================================== print: pop dph ; put return address in dptr pop dpl prtstr: clr a ; set offset = 0 movc a, @a+dptr ; get chr from code memory cjne a, #0h, mchrok ; if termination chr, then return sjmp prtdone mchrok: lcall sndchr ; send character inc dptr ; point at next character sjmp prtstr ; loop till end of string prtdone: mov a, #1h ; point to instruction after string jmp @a+dptr ; return ;=============================================================== ; subroutine getchr ; this routine reads in a chr from the serial port and saves it ; in the accumulator. ;=============================================================== getchr: jnb ri, getchr ; wait till character received mov a, sbuf ; get character anl a, #7fh ; mask off 8th bit clr ri ; clear serial status bit ret ;There are 256 samples of a period of a sine wave. The ;sine table needs to be read to create a sine wave org 1000h ;sine table db 080h, 083h, 086h, 089h, 08Ch, 08Fh, 092h, 095h db 099h, 09Ch, 09Fh, 0A2h, 0A5h, 0A8h, 0ABh, 0AEh db 0B1h, 0B4h, 0B6h, 0B9h, 0BCh, 0BFh, 0C2h, 0C4h db 0C7h, 0C9h, 0CCh, 0CFh, 0D1h, 0D3h, 0D6h, 0D8h db 0DAh, 0DCh, 0DFh, 0E1h, 0E3h, 0E5h, 0E7h, 0E8h db 0EAh, 0ECh, 0EEh, 0EFh, 0F1h, 0F2h, 0F3h, 0F5h db 0F6h, 0F7h, 0F8h, 0F9h, 0FAh, 0FBh, 0FCh, 0FDh db 0FDh, 0FEh, 0FEh, 0FFh, 0FFh, 0FFh, 0FFh, 0FFh db 0FFh, 0FFh, 0FFh, 0FFh, 0FFh, 0FEh, 0FEh, 0FDh db 0FDh, 0FCh, 0FBh, 0FBh, 0FAh, 0F9h, 0F8h, 0F7h db 0F5h, 0F4h, 0F3h, 0F1h, 0F0h, 0EEh, 0EDh, 0EBh db 0E9h, 0E8h, 0E6h, 0E4h, 0E2h, 0E0h, 0DEh, 0DBh db 0D9h, 0D7h, 0D5h, 0D2h, 0D0h, 0CDh, 0CBh, 0C8h db 0C6h, 0C3h, 0C0h, 0BDh, 0BBh, 0B8h, 0B5h, 0B2h db 0AFh, 0ACh, 0A9h, 0A6h, 0A3h, 0A0h, 09Dh, 09Ah db 097h, 094h, 091h, 08Eh, 08Bh, 087h, 084h, 081h db 07Eh, 07Bh, 078h, 074h, 071h, 06Eh, 06Bh, 068h db 065h, 062h, 05Fh, 05Ch, 059h, 056h, 053h, 050h db 04Dh, 04Ah, 047h, 044h, 042h, 03Fh, 03Ch, 039h db 037h, 034h, 032h, 02Fh, 02Dh, 02Ah, 028h, 026h db 024h, 021h, 01Fh, 01Dh, 01Bh, 019h, 017h, 016h db 014h, 012h, 011h, 00Fh, 00Eh, 00Ch, 00Bh, 00Ah db 008h, 007h, 006h, 005h, 004h, 004h, 003h, 002h db 002h, 001h, 001h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 001h, 001h, 002h db 002h, 003h, 004h, 005h, 006h, 007h, 008h, 009h db 00Ah, 00Ch, 00Dh, 00Eh, 010h, 011h, 013h, 015h db 017h, 018h, 01Ah, 01Ch, 01Eh, 020h, 023h, 025h db 027h, 029h, 02Ch, 02Eh, 030h, 033h, 036h, 038h db 03Bh, 03Dh, 040h, 043h, 046h, 049h, 04Bh, 04Eh db 051h, 054h, 057h, 05Ah, 05Dh, 060h, 063h, 066h db 06Ah, 06Dh, 070h, 073h, 076h, 079h, 07Ch, 07Fh