8051 Assembly Language Source Code
; The MIDI Drum Set!
;
; Let's go for it, the finished version, NO PROTOTYPE!!
; This code is an original work by Paul Stoffregen, written
; in the Fall of 1991. This code has been placed in the
; public domain. You may use it without any restrictions.
; You may include it in your own projects, even commercial
; (for profit) products.
; This code is distributed in the hope that they will be useful,
; but without any warranty; without even the implied warranty of
; merchantability or fitness for a particular purpose.
; Final production version #2
; This version runs on the finished machine, but has no
; midi merge or harmonize.
.equ location, 0x0000
;Register usage throughout the program:
; r0 = General purpose
; r1 = General purpose
; r2 =
; r3 = Current button state (high 4 bits)
; Previous button state (low 4 bits)
; r4 = Pad being sampled/processed
; r5 = Current pad and option data 0ppp0ooo
; r6 =
; r7 = number of bytes in the serial port buffer
.equ reg0,0
.equ reg1,1
;pointers to arrays of user-defined parameters
.equ chan,0x08 ;location for channel data (special case)
.equ bank,0x10 ;bank data (this is a very special case)
.equ voice,0x10 ;voice data
.equ note,0x18 ;note data
.equ hrmn,0x08 ;harmonize option (special case)
.equ sustain,0x20 ;sustain time data (special case)
.equ release,0x28 ;release data
.equ limit,0x30 ;peak limit data
;pointers to arrays to program variables
.equ ontime,0x40 ;Sustain timers
.equ wait,0x48 ;wait to sample counters (pads resonate
;and cause false triggering w/out)
.equ send,0x50 ;note-on velocity data stored here until
;interrupt driven serial port generates
;note-on codes. zero if no note to play.
.equ buffer,0x58 ;sixteen byte send buffer for the serial
;port routine
;the first byte in the buffer's memory range is the
;first one to send. write into buffer by storing at
;buffer+bufnum and incrementing bufnum. read buffer
;by reading value at buffer, and shifting all the
;bytes down.
;pointers to misc variables
.equ pbtimer, 0x38 ;how much longer to wait?
.equ pbdata, 0x39 ;which switch is waiting
.equ powerid1, 0x3A ;LSB of sum of memory 08h thru 37h
.equ powerid2, 0x3B ;powerid1 XOR 01011010 for double check
.equ serport, 0x3C ;Serial port status byte
; bit0: 0=ok to dump codes into buffer
; 1=in the middle of incomming data
.equ stack, 0x68 ;ouch, only 24 bytes of stack space
.org location
ajmp poweron
.org location+3 ;(external interrupt #0)
ajmp alloff ;(low priority)
.org location+0x0B ;(timer 0)
ajmp timer0 ;(low priority)
.org location+0x13 ;(external interrupt #1)
ajmp poweroff ;(high priority)
.org location+0x23 ;(serial port interrupt)
;(high priority)
poweron:mov sp,#stack ;gotta have the stack in right place
clr psw.3 ;and we must use register bank 0
clr psw.4
clr a ;these things need zeros
mov r4,a
mov r5,a
;check to see if the powerdown saved the old configuration
mov r0,#0x08
clr a
chpwr: add a,@r0 ;add up bytes 08h thru 37h
inc r0
cjne r0,#0x38,chpwr
cjne a,powerid1,initmem ;jump if data didn't check
xrl a,#01011010b
cjne a,powerid2,initmem
jnb p1.4,skipuser ;if they press both up and
jnb p1.5,skipuser ;down, then initmem anyways
initmem:
;initialize the memory
mov r0,#chan ;init channels and harmonize
mov a,#8
initch: mov @r0,a
inc r0
inc a
cjne r0,#chan+8,initch
mov r0,#voice ;init voice and bank (hi bit)
initvc: mov @r0,#0
inc r0
cjne r0,#voice+8,initvc
mov r0,#note ;init note and bank (mid bit)
initnt: mov @r0,#128+60
inc r0
cjne r0,#note+8,initnt
mov r0,#sustain ;init sustain data
mov r1,#release ;and release and bank (LSB)
initsus:mov @r0,#49
mov @r1,#64
inc r0
inc r1
cjne r0,#sustain+8,initsus
mov r0,#limit ;init limit
initlmt:mov @r0,#127
inc r0
cjne r0,#limit+8,initlmt
skipuser: ;jump to here to skip initializing the user-defined
;parameters (because powerdown mode perserved 'em)
;now initialize the program's arrays of data
mov r0,#wait
mov r1,#ontime
initwt: mov @r0,#0
mov @r1,#0
inc r0
inc r1
cjne r0,#wait+8,initwt
mov r0,#send
initsnd:mov @r0,#0
inc r0
cjne r0,#wait+8,initsnd
;now initialize the hardware
;init the 8255 which is used for all output to the display
;(but the four inputs from the pushbuttons come in directly)
mov a,#10000000b ;all 8255 ports are output mode 0
mov dptr,#0xA003 ;8255 address
movx @dptr,a
;init the serial port for MIDI
;p3.5 used to switch serial on
;prototype board
mov tcon,#00000000b ;stop the timers
mov tmod,#00100001b ;timers 0:16 bit, 1:8 bit auto
mov scon,#01010000b ;8 bit UART mode, enable reception
orl pcon,#10000000b ;double baud rate
mov th1,#0xFE ;set for 31250 baud (MIDI)
mov tcon,#01010000b ;start both timers
setb ti ;to prevent it from hanging
clr tr0 ;init timer0 for 10ms intervals
mov th0,0xD8
mov tl0,0xF8
setb tr0
mov a,#8 ;Clear all the capacitors
clr p3.4
clrcaps:dec a ;the capacitors in the sample circuits
mov c,acc.0 ;tend to charge up with time, since
mov p1.2,c ;the LM324 op-amp has PNP input
mov c,acc.1 ;transistors, so we'll clear all the
mov p1.1,c ;capacitors to zero before running.
mov c,acc.2
mov p1.0,c
nop
nop
nop
nop
nop
jnz clrcaps
setb p3.4
mov ip,#00010100b ;turn on the interrupts
mov ie,#10000111b
;now we're done getting ready, so we'll update the front panel
;display, and that will jump into the program.
ajmp print ;do display before running
cout: ;send the Acc value to the serial port
jnb ti,*
mov sbuf,a
clr ti
ret
newline: ; jnb ti,*
; mov sbuf,#13
; clr ti
ret
main: inc r4 ;increment pad #
mov a,r4
anl a,#00000111b
mov r4,a
mov a,#wait
add a,r4
mov r0,a
mov a,@r0
jz doit
dec @r0
acall sample ;so it'll clear the cap
sjmp pushbtn
doit: acall sample
mov b,a
anl a,#11111000b ;check to see if it's less than 8
jz pushbtn
acall sample
acall greater
acall noteon
mov a,#wait
add a,r4
mov r0,a
mov @r0,#8 ;ignore next samples
pushbtn:mov a,r3 ;get data from last time
swap a
mov c,p1.7
mov acc.7,c
mov c,p1.6
mov acc.6,c
mov c,p1.5
mov acc.5,c
mov c,p1.4
mov acc.4,c
mov r3,a
pb7: jnb acc.7,pb6 ;button 7 has highest priority
ajmp button7
pb6: jnb acc.6,pb5
ajmp button6
pb5: jnb acc.5,pb4
ajmp button5
pb4: jnb acc.4,none ;button 4 has lowest priority
ajmp button4
none: ajmp main
noteon: mov r0,a ;Acc has velocity measurement
mov a,#chan
add a,r4
mov r1,a ;r1 is pointer to channel #
mov a,@r1
anl a,#00001111b
add a,#10010000b
acall cout ;send note-on (w/ channel)
mov a,#note
add a,r4
mov r1,a
mov a,@r1
anl a,#01111111b
acall cout ;send note #
mov a,r0
rr a
anl a,#01111111b
mov b,a ;check peak limit
mov a,#limit
add a,r4
mov r1,a
mov a,@r1
anl a,#01111111b
acall greater
xch a,b
acall cout ;send velocity
mov a,#sustain ;set timer to turn off
add a,r4 ;the note...
mov r0,a
mov a,#ontime
add a,r4
mov r1,a
mov a,@r0
inc a
mov @r1,a
acall newline
ret
sample: ;R4 should contain the pad to sample
mov a,r4
mov c,acc.0
mov p1.2,c
mov c,acc.1
mov p1.1,c
mov c,acc.2
mov p1.0,c
mov dptr,#0xE000
mov a,#255
movx @dptr,a
jb p1.3,*
movx a,@dptr
clr p3.4 ;short out the cap
nop
nop
nop
nop
nop
setb p3.4
; mov a,#0 why is this here
ret
greater: ;pass in two values, in A and B, the greater is
;returned in A, lesser returned in B
mov r0,a
clr c
subb a,b
mov a,r0
jnc gtr2
xch a,b
gtr2: ret
voice0: ;r5 indicates which pad......
;sends voice change, and wipes out duplicate voices
mov a,r5
swap a
anl a,#00000111b
mov r0,a ;r0 has pad being changed
mov a,#chan
add a,r0
mov r1,a ;r1 is pointer to channel #
mov a,@r1
anl a,#00001111b
orl a,#11000000b
acall cout ;send program (w/ channel)
mov a,#voice
add a,r0
mov r1,a
mov a,@r1
anl a,#01111111b
acall cout ;send new voice #
acall newline
ret
bank0:
mov a,r5
swap a
anl a,#00000111b
mov r0,a ;r0 has pad being changed
mov a,#0xF0 ;send sys-ex id code
acall cout
mov a,#0x43
acall cout
mov a,#chan
add a,r0
mov r1,a ;r1 is pointer to channel #
mov a,@r1
anl a,#00001111b
orl a,#00010000b
acall cout ;send channel id
mov a,#0x15 ;even more sys-ex codes..
acall cout
mov a,#0x04 ;<--bank indicator
acall cout
mov b,#0
mov a,#voice
add a,r0
mov r1,a
mov a,@r1
mov c,acc.7
mov b.2,c
mov a,#note
add a,r0
mov r1,a
mov a,@r1
mov c,acc.7
mov b.1,c
mov a,#release
add a,r0
mov r1,a
mov a,@r1
mov c,acc.7
mov b.0,c
mov a,b
acall cout ;send new bank #
mov a,#0xF7
acall cout ;End of Exclusive (at last)
acall newline
ret
button7: ;increment pad #
jnb acc.3,btn7a ;do it if was just pressed
ajmp main
btn7a:
mov a,r5
swap a
inc a
anl a,#01110111b
swap a
mov r5,a
acall voice0
acall bank0
ajmp print
button6: ;increment option #
jnb acc.2,btn6a
ajmp main
btn6a:
mov a,r5
inc a
anl a,#01110111b
mov r5,a
ajmp print
button5: ;increase value
jnb acc.1,btn5a
mov a,pbtimer
jnz btn5
mov pbtimer,#8 ;80ms repeat
sjmp btn5b
btn5: ajmp main
btn5a:
mov pbtimer,#30 ;300ms wait
btn5b: acall get
inc a
cjne r1,#5,btn5c
cjne a,#250,btn5c
clr a
btn5c: cjne r1,#1,btn5d
cjne a,#7,btn5d
clr a
btn5d: acall put
ajmp print
button4: ;decrease value
jnb acc.0,btn4a
mov a,pbtimer
jnz btn4
mov pbtimer,#8 ;80ms repeat
sjmp btn4b
btn4: ajmp main
btn4a:
mov pbtimer,#30 ;300ms initial wait
btn4b: acall get
dec a
cjne r1,#5,btn4c
cjne a,#255,btn4c
mov a,#249
btn4c: cjne r1,#1,btn4d
cjne a,#255,btn4d
mov a,#6
btn4d: acall put
ajmp print
get: ;r5 input, a returns with config data
mov a,r5
anl a,#00000111b
mov r1,a
mov dptr,#table3
movc a,@a+dptr
mov r0,a
mov a,r5
swap a
anl a,#00000111b
add a,r0
mov r0,a
mov a,@r0
;now check the special cases
opt1: cjne r1,#0,opt2 ;r1=0 is Channel #
anl a,#00001111b ;only want lower 4 bits
clr c
ret
opt2: cjne r1,#1,opt5 ;r1=1 is Bank #
anl a,#10000000b
mov b,a ;b now has High bit
mov a,r5
swap a
anl a,#00000111b
mov r0,a ;r0 has the pad #
mov a,#note
add a,r0
mov r1,a
mov a,@r1
anl A,#10000000b
rr a
orl a,b
mov b,a
mov a,#release
add a,r0
mov r1,a
mov a,@r1
anl a,#10000000b
rr a
rr a
orl a,b
swap a
rr a
mov r1,#1
clr c
ret
opt5: cjne r1,#4,opt6 ;r1=4 is Harmonize option
swap a
anl a,#00001111b ;only want upper 4 bits
clr c
ret
opt6: cjne r1,#5,normal ;r1=5 is Sustain length
setb c ;want all 8 bits AND leading zeros
ret
normal: clr acc.7 ;standard data is only 7 bits
clr c
getend: ret
put: ;r5 input, a stored into memory
mov b,a
mov a,r5
anl a,#00000111b
mov r1,a ;r1 has option #
mov dptr,#table3
movc a,@a+dptr
mov r0,a
mov a,r5
swap a
anl a,#00000111b
add a,r0
mov r0,a ;r0 is pointer to data
mov a,b
;check the special cases
put1: cjne r1,#0,put2 ;r1=0 is Channel #
anl a,#00001111b ;only want lower 4 bits
mov r1,a
mov a,@r0
anl a,#11110000b
orl a,r1
mov @r0,a
ret
put2: cjne r1,#1,put5 ;r1=1 is Bank #
mov b,a ;b has the 3 bit data
mov a,r5
swap a
anl a,#00000111b
mov r0,a ;r0 had pad #
mov a,#voice
add a,r0
mov r1,a
mov a,@r1
mov c,b.2
mov acc.7,c
mov @r1,a
mov a,#note
add a,r0
mov r1,a
mov a,@r1
mov c,b.1
mov acc.7,c
mov @r1,a
mov a,#release
add a,r0
mov r1,a
mov a,@r1
mov c,b.0
mov acc.7,c
mov @r1,a
ret
put5: cjne r1,#4,put6 ;r1=4 is Harmonize option
swap a
anl a,#11110000b ;only want upper 4 bits
mov r1,a
mov a,@r0
anl a,#00001111b
orl a,r1
mov @r0,a
ret
put6: cjne r1,#5,putnorm ;r1=5 is Sustain length
mov @r0,a ;just put all 8 bits
ret
putnorm:clr acc.7 ;standard data is only 7 bits
mov b,a
mov a,@r0
anl a,#10000000b
orl a,b
mov @r0,a
cjne r1,#2,putend ;skip send voice code
putend: ret
;This routine prints out the configuration data to the screen.
;R5 must point to the correct pad and option
print: mov a,r5
swap a
anl a,#00000111b
mov dptr,#table1
movc a,@a+dptr
mov r0,a ;R0 contains the pad number
mov a,r5
anl a,#00000111b
mov dptr,#table2
movc a,@a+dptr
mov r1,a ;R1 contains the option number
swap a
orl a,r0
rl a ;adjust for wiring error
mov dptr,#0xA000
movx @dptr,a ;Display pad and option values
;if we're displaying a voice or bank, better send codes
mov a,r5
anl a,#00000111b
cjne a,#1,skbank
acall bank0
skbank: mov a,r5
anl a,#00000111b
cjne a,#2,skvoice
acall voice0
skvoice:nop
;get and prepare the data to be displayed on 7-seg's
acall get ;and acc has the data (maybe)
inc a
sevseg: ;acc now has the data to print, c=1 for leading zeros
; Hundreds digit: Port B Upper half
; Tens digit: Port B Lower half
; Ones digit: Port C Lower half
.equ Flag1,0xD5 ;Use PSW.5 for flag to see if
clr Flag1 ;something printed in hundreds
mov b,#100
mov psw.1,c ;store c for a while...
div ab ;divide to find hundred place
mov c,psw.1
jz blank ;skip if leading zero
hund0: setb Flag1 ;We'll need to know if tens is 0
hund: swap a ;Store hundred's digit in r0
mov r0,a ; until it's needed
sjmp tens
blank: jc hund0 ;but don't skip leading zero if C=1
mov a,#15
sjmp hund
tens: mov a,b ;B had remainder
mov b,#10
div ab
jnz dotens ;print tens if it's not zero
jb Flag1,dotens ;or if 0 AND hundreds digit not 0
mov a,#15
dotens: orl a,r0 ;combine hund and tens digits
mov dptr,#0xA001 ;and print 'em to the display
movx @dptr,a
ones: mov a,b ;and now print the ones digit
mov dptr,#0xA002
movx @dptr,a
ajmp main
timer0: clr tr0 ;stop the timer
mov th0,#0xD8 ;reload it
mov tl0,#0xF8 ;for time of 10ms
setb tr0 ;and start it again
push psw
push acc
push reg0
push reg1
mov a,pbtimer ;dec pdtimer, if nesc.
jz time1
dec pbtimer
time1: ;check for note off's
mov r0,#ontime
mov r1,#0
time2: mov a,@r0
jz time3
dec @r0 ;decrement timer
mov a,@r0
jnz time3
;send note off code
mov a,#chan
add a,r1
mov r0,a
mov a,@r0
anl a,#00001111b
orl a,#10000000b
acall cout ;send note-off w/channel
mov a,#note
add a,r1
mov r0,a
mov a,@r0
anl a,#01111111b
acall cout ;send note #
mov a,#release
add a,r1
mov r0,a
mov a,@r0
anl a,#01111111b
acall cout ;off velocity code
mov a,#ontime
add a,r1
mov r0,a
acall newline
time3: inc r0 ;loop to check all 8
inc r1
cjne r1,#8,time2
pop reg1
pop reg0
pop acc
pop psw
reti
alloff: push psw ;all notes off routine
push acc
mov a,#0xF0
acall cout
mov a,#0xF1
acall cout
pop acc
pop psw
reti
poweroff: ;quickly compute a checksum and powerdown
;before the +5v craps out!!
mov r0,#0x08
clr a
pwr1: add a,@r0 ;add up bytes 08h thru 37h
inc r0
cjne r0,#0x38,pwr1
mov powerid1,a ;store result
xrl a,#01011010b
mov powerid2,a
orl pcon,#00000010b ;and go into powerdown mode
orl pcon,#00000010b
table1: .db 0,2,1,3,4,6,5,7 ;compensation for pad light wiring
table2: .db 0,4,2,6,1,5,3,7 ;compensation for option light
table3: .db chan ;location for channel data (special case)
.db bank ;bank data (this is a very special case)
.db voice ;voice data
.db note ;note data
.db hrmn ;harmonize option (special case)
.db sustain ;sustain time data (special case)
.db release ;release data
.db limit ;peak limit data
The MIDI Drum Machine, Paul Stoffregen and Rod Seely.
Designed and constructed Fall, 1991. Project status: Complete.
http://www.pjrc.com/tech/midi-drums/firmware-listing.html
Last updated: November 28, 2003
-- These drum-machine web pages are still under construction --
Questions, Comments?? <paul@pjrc.com>