PJRC.COM Offline Archive, February 07, 2004
Visit this page on the live site

skip navigational linksPJRC
Shopping Cart Checkout Shipping Cost Download Website
Home MP3 Player 8051 Tools All Projects PJRC Store Site Map
You are here: MIDI Drum Machine Firmware Code Listing Search PJRC

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