skip navigational linksPJRC
Shopping Cart Download Website
Home Products Teensy Blog Forum
You are here: MIDI Drum Machine Firmware Code Listing

MIDI Drum Machine
Main Page
Features
Photos
Schematic
Circuit Board Layout
Firmware
Overview
selected Code Listing
MIDI Protocol Details

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: February 24, 2005
-- These drum-machine web pages are still under construction --
Questions, Comments?? <paul@pjrc.com>