skip navigational linksPJRC
Shopping Cart Checkout Shipping Cost Download Website
Home Products Teensy Blog Forum
You are here: MP3 Player Technical Docs Old MP3 Player Design Firmware Source

PJRC Store
Main Board, $150
LCD & Pushbuttons, $42
LCD/Backlight/PB, $77
IDE Cable, $9
Complete Parts List
MP3 Player
Main Page
Detailed Info
User Photo Gallery
Connecting The Board
Firmware Download
Side Projects
Technical Docs
Freq. Asked Questions
FAQ #2
News And Updates

Old MP3 Player Design


icon: new design
Click For New Design
This new design has replaced the old one. The design shown on this page is obsolete. The new design has more features, is flash upgradable, is much easier to use, costs less, and is actually available if you'd like to buy one! It's better than this old design is almost every way. These old pages are still available, mostly for reference.


Firmware Source Code, For Free...

You may download this code in a ZIP file.

This code is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.

How It Works

The player just reads sectors, in order, from the drive and sends them to the MAS3507D decoder chip. The lba variable stores which sector will be read next. This number is initialized to 256, where the MP3 files are expected to begin, and it's just incremented as each sector is "played". When the last sector is reached, well, the firmware isn't very smart and it just keeps going. This needs to be fixed someday.

The process is basically to request a sector read from the drive, and let the drive work on fetching that sector to its internal buffer while the idle loop transfers the previously read sector to the decoder chip. After all of the available data is sent, the decoder chip has some buffering, which provides enough time to let the drive finish reading (if it didn't already) and copy the sector from the drive to memory. The sector that was transfered from the drive on this pass through the loop will be sent to the decoder on the next pass, immediately after the drive is instructed to begin fetching the next sector.

In a bit more detail, the main_loop check for button presses, and checks if the player is paused. When not paused, the loop calls to read_sector, which actually just tells the drive to read the sector into its buffer, but doesn't wait for the drive to actually have the sector read. Next, the idle loop is responsible for sending the 512 bytes currently in the buffer to the decoder chip. When all the bytes have been sent, the main_loop waits for the drive to have the sector available, and copies it from the drive's buffer to the 8051's external memory buffer. When the process is repeated, the next sector is requested, and the data that was just stored gets sent to the decoder chip.

When the pause button is pressed, the pause flag is toggled and the main_loop will just avoid doing anything while paused. The next/previous track buttons are more difficult. The player does not keep track of which song is playing... it just copies sectors in linear order. When either button is pressed, the current_song routine compares the current sector address to the list of all song starting addresses (which are read into memory at start-up). A number of subroutines are used only by current_song, as it does its search to figure out which song is currently being played. Once the current song is known, the starting sector number of the next or previous song is looked up and the current sector number is updated. Some dummy bits are sent to the decoder chip, to avoid a click sound. When main_loop runs again, it will just skip to the next or previous song, because the lba variable with the sector number was updated.

The Code


; First attempt at a stand-alone MP3 player.
; see http://www.pjrc.com/tech/mp3/ for more info.

;  This code is an original work by Paul Stoffregen, written
;  in December 1999.  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.


;.equ	location, 0x2000	;where this program will exist
.equ	location, 0x8000	;where this program will exist
.equ	sect_buf, 0x2E00	;512 byte buffer, currently playing sector
.equ	list_buf, 0x3000	;4096 byte buffer, list of song addresses

;------------------------------------------------------------------
; Hardware Configuration

;8255 chip.  Change these to specify where the 8255 is addressed,
;and which of the 8255's ports are connected to which ide signals.
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last two are mode setting for the
;8255 to configure its ports, which must correspond to the way that
;the first three lines define which ports are connected.
.equ	ide_8255_lsb, 0x4000	;lower 8 bits
.equ	ide_8255_msb, 0x4001	;upper 8 bits
.equ	ide_8255_ctl, 0x4002	;control lines
.equ	cfg_8255, 0x4003
.equ	rd_ide_8255, 10010010b	;ide_8255_ctl out, ide_8255_lsb/msb input
.equ	wr_ide_8255, 10000000b	;all three ports output

;ide control lines for use with ide_8255_ctl.  Change these 8
;constants to reflect where each signal of the 8255 each of the
;ide control signals is connected.  All the control signals must
;be on the same port, but these 8 lines let you connect them to
;whichever pins on that port.
.equ	ide_a0_line, 0x01	;direct from 8255 to ide interface
.equ	ide_a1_line, 0x02	;direct from 8255 to ide interface
.equ	ide_a2_line, 0x04	;direct from 8255 to ide interface
.equ	ide_cs0_line, 0x08	;inverter between 8255 and ide interface
.equ	ide_cs1_line, 0x10	;inverter between 8255 and ide interface
.equ	ide_wr_line, 0x20	;inverter between 8255 and ide interface
.equ	ide_rd_line, 0x40	;inverter between 8255 and ide interface
.equ	ide_rst_line, 0x80	;inverter between 8255 and ide interface


;------------------------------------------------------------------
; More symbolic constants... these should not be changed, unless of
; course the IDE drive interface changes, perhaps when drives get
; to 128G and the PC industry will do yet another kludge.

;some symbolic constants for the ide registers, which makes the
;code more readable than always specifying the address pins
.equ	ide_data,	ide_cs0_line
.equ	ide_err,	ide_cs0_line + ide_a0_line
.equ	ide_sec_cnt,	ide_cs0_line + ide_a1_line
.equ	ide_sector,	ide_cs0_line + ide_a1_line + ide_a0_line
.equ	ide_cyl_lsb,	ide_cs0_line + ide_a2_line
.equ	ide_cyl_msb,	ide_cs0_line + ide_a2_line + ide_a0_line
.equ	ide_head,	ide_cs0_line + ide_a2_line + ide_a1_line
.equ	ide_command,	ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line
.equ	ide_status,	ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line
.equ	ide_control,	ide_cs1_line + ide_a2_line + ide_a1_line
.equ	ide_astatus,	ide_cs1_line + ide_a2_line + ide_a1_line + ide_a0_line

;IDE Command Constants.  These should never change.
.equ	ide_cmd_recal, 0x10
.equ	ide_cmd_read, 0x20
.equ	ide_cmd_write, 0x30
.equ	ide_cmd_init, 0x91
.equ	ide_cmd_id, 0xEC
.equ	ide_cmd_spindown, 0xE0
.equ	ide_cmd_spinup, 0xE1


;------------------------------------------------------------------
;internal ram usage

.equ	lba, 0x10		;4 bytes, 28 bit Logical Block Address
.equ	song, 0x14		;2 bytes, index of song we're playing
.equ	blk, 0x16		;4 bytes, 32 bit block number (temp usage)
.equ	b0_state, 0x1A		;1 byte, nonzero if button 0 is down
.equ	b1_state, 0x1B		;1 byte, nonzero if button 1 is down
.equ	b2_state, 0x1C		;1 byte, nonzero if button 2 is down
.equ	paused, 0x1D		;1 byte, nonzero if we're paused
.equ	stack, 0x40


;------------------------------------------------------------------
; Main Program, a simple menu driven interface.


.org	location
.db     0xA5,0xE5,0xE0,0xA5     ;signiture bytes
;.db     35,255,0,0              ;id
.db     249,255,0,0              ;id
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;user defined
.db     255,255,255,255         ;length and checksum (255=unused)
.db     "Simple MP3 Player",0
.org    location+64             ;executable code begins here


	mov	sp, #stack
	mov     r1, #0		;wait for any serial data to leave
        djnz    r1, *
        djnz    r1, *
        djnz    r1, *
        djnz    r1, *           ;hope that's long enough

        clr     p1.7            ;bring the MAS3507 out of reset
        clr     p3.4            ;switch the TXD pin to the MAS3507

        mov     scon, #00011100b        ;config uart as shift register
        djnz    r1, *
        setb    ti

	orl	tmod, #00000001b	;config timer0 as 16 bit
	anl	tmod, #11110001b
	setb	tr0
	setb	tf0
	clr	a
	mov	b0_state, a
	mov	b1_state, a
	mov	b2_state, a
	mov	paused, a

	;initialize the drive.  If there is no drive, this may hang
	acall	ide_hard_reset
	acall	ide_init
	mov	a, #ide_sec_cnt
	mov	r2, #1
	acall	ide_wr
	mov	r7, #0

	;read the list of song addresses
	clr	a
	mov	lba+0, a
	mov	lba+1, a
	mov	lba+2, a
	mov	lba+3, a
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0x000
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0x200
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0x400
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0x600
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0x800
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0xA00
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0xC00
	acall	read_data
	acall	read_sector
	acall	inc_lba
	mov	dptr, #list_buf + 0xE00
	acall	read_data

	;start at address of the first song
	clr	a
	mov	song+0, a
	mov	song+1, a
	acall	play_song
	ajmp	main_loop





button_prev:
	mov	a, b0_state
	jnz	any_button
	mov	a, paused
	jnz	any_button
	acall	current_song
	acall	dec_song
	acall	send_dummy_bits
	acall	play_song
	mov	b0_state, #255
	ajmp	any_button

button_pause:
	mov	a, b1_state
	jnz	any_button
	mov	a, paused
	cpl	a
	mov	paused, a
	mov	b1_state, #255
	ajmp	any_button

button_next:
	mov	a, b2_state
	jnz	any_button
	mov	a, paused
	jnz	any_button
	acall	current_song
	acall	inc_song
	acall	send_dummy_bits
	acall	play_song
	mov	b2_state, #255
	ajmp	any_button

any_button:
	mov	tl0, #0
	mov	th0, #120
	clr	tf0
	ajmp	main2





main_loop:
	jnb	tf0, main2		;don't check buttons if recent activity
	clr	a
	jnb	p1.0, button_prev
	mov	b0_state, a
	jnb	p1.1, button_pause
	mov	b1_state, a
	jnb	p1.2, button_next
	mov	b2_state, a
main2:
	mov	a, paused
	jnz	main_loop
	acall	read_sector		;tell the drive to read a sector
	acall	inc_lba
wait_buf:
	acall	idle
	mov	a, r7
	jnz	wait_buf		;wait for buffer to be available

	mov	dptr, #sect_buf
	acall	read_data		;transfer the sector to buffer1
	mov	r7, #sect_buf >> 8
	mov	r6, #0
	sjmp	main_loop





inc_lba:
	mov	a, #1
	add	a, lba+0
	mov	lba+0, a
	clr	a
	addc	a, lba+1
	mov	lba+1, a
	clr	a
	addc	a, lba+2
	mov	lba+2, a
	clr	a
	addc	a, lba+3
	mov	lba+3, a
	ret


msg_1:	.db	"Playing MP3 Data",13,10,13,10,0



	;at 14.7 MHz crystal seems to be fast enough to play
	;MP3's at 192 kbps, but it can't keep up with 11 MHz,
	;so it's unlikely we can do 256 or 320 kbps!


	;this idle loop sends the data to the MP3 chip
idle:
	;push	dpl
	;push	dph
	mov	a, r7
	jz	idle_ret
	mov	dph, a
	mov	dpl, r6
idle_loop:
	jnb     p1.6, idle_end		;2 wait for the chip to want data
        clr     a			;1
        movc    a, @a+dptr		;2
	jz	idle2			;2
	acall	flip_bits		;0 / 6
idle2:  mov     sbuf, a			;1
        inc     dptr			;2
	mov	a, dpl			;1
	jnz	idle_loop		;2
	mov	a, dph
	jb	acc.0, idle_loop
	mov	dph, #0
idle_end:
	mov	r6, dpl
	mov	r7, dph
idle_ret:
	;pop	dph
	;pop	dpl
	ret


	;send 2048 dummy (zero) bits to the MP3 chip.  This is useful
	;when changing songs of moving to a new location in the data,
	;so that the chip will play silence, lose sync, and then
	;re-sync to the new data.... instead of using some of the new
	;data as spectral energy from the old sync'd stream.

send_dummy_bits:
	mov	r2, #0
sdummy:
	jnb     p1.6, sdummy		;2  wait for chip to want data
	clr	a			;1
	mov	sbuf, a			;1
	nop				;1
	nop				;1
	nop				;1
	nop				;1
	nop				;1
	nop				;1
	djnz	r2, sdummy
	ret




flip_bits:
	movc	a, @a+pc
flip2:	ret
	.db	128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136
	.db	72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68
	.db	196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204
	.db	44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34
	.db	162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170
	.db	106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102
	.db	230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238
	.db	30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17
	.db	145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153
	.db	89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85
	.db	213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221
	.db	61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51
	.db	179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187
	.db	123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119
	.db	247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255




	;update "song" to reflect which song we're currently playing
	;just do a (slow) linear search of all the known addresses
current_song:
	clr	a
	mov	song, a
	mov	song+1, a
	mov	dptr, #list_buf
cs_loop:
	mov	r1, #blk
	acall	get_4_bytes		;fetch the next song's address
	mov	r1, #blk
	acall	iszero_4_bytes		;if zero, we hit the end of list
	jz	cs_done			;so assume we're on the last song
	mov	r0, #blk
	mov	r1, #lba
	acall	cmp_4_bytes		;if we're playing a block after the
	jc	cs_next			;beginning of this song, keeping
	mov	r0, #blk		;looking
	mov	r1, #lba
	acall	eq_4_bytes		;if we're playing the first block
	jz	cs_ret			;of this song, then we're done
	sjmp	cs_done			;... or else it's the previous song
cs_next:
	acall	inc_song
	cjne	a, #4, cs_loop
cs_done:acall	dec_song
cs_ret:	ret



	; load "lba" with the block address of "song"
play_song:
	clr	c
	mov	a, song
	rlc	a
	mov	dpl, a
	mov	a, song+1
	anl	a, #3
	rlc	a
	mov	dph, a
	clr	c
	mov	a, dpl
	rlc	a
	mov	dpl, a
	mov	a, dph
	rlc	a
	add	a, #list_buf >> 8
	mov	dph, a
	mov	r1, #lba
	acall	get_4_bytes
	mov	r1, #lba
	acall	iszero_4_bytes
	jz	ps_zero
	ret
ps_zero:
	clr	a
	mov	song+0, a
	mov	song+1, a
	sjmp	play_song



inc_song:
	mov	a, song
	add	a, #1
	mov	song, a
	clr	a
	addc	a, song+1
	mov	song+1, a
	ret


dec_song:
	clr	c
	mov	a, song
	subb	a, #1
	mov	song, a
	mov	a, song+1
	subb	a, #0
	mov	song+1, a
	ret



	;compare (subtract) @r0 and @r1, carry bit is returned
	; carry clr:  @r0 >= @r1
	; carry set:  @r0 < @r1
cmp_4_bytes:
	clr	c
	mov	a, @r0
	subb	a, @r1
	inc	r0
	inc	r1
	mov	a, @r0
	subb	a, @r1
	inc	r0
	inc	r1
	mov	a, @r0
	subb	a, @r1
	inc	r0
	inc	r1
	mov	a, @r0
	subb	a, @r1
	ret


eq_4_bytes:
	clr	c
	mov	a, @r0
	subb	a, @r1
	jnz	eq4n
	inc	r0
	inc	r1
	clr	c
	mov	a, @r0
	subb	a, @r1
	jnz	eq4n
	inc	r0
	inc	r1
	clr	c
	mov	a, @r0
	subb	a, @r1
	jnz	eq4n
	inc	r0
	inc	r1
	clr	c
	mov	a, @r0
	subb	a, @r1
eq4n:	ret




	;read 4 bytes from external memory (@dptr) into
	;internal memory (@r1)
get_4_bytes:
	clr	a
	movc	a, @a+dptr
	mov	@r1, a
	inc	dptr
	inc	r1
	clr	a
	movc	a, @a+dptr
	mov	@r1, a
	inc	dptr
	inc	r1
	clr	a
	movc	a, @a+dptr
	mov	@r1, a
	inc	dptr
	inc	r1
	clr	a
	movc	a, @a+dptr
	mov	@r1, a
	inc	dptr
	ret



iszero_4_bytes:
	mov	a, @r1
	jnz	isz4e
	inc	r1
	mov	a, @r1
	jnz	isz4e
	inc	r1
	mov	a, @r1
	jnz	isz4e
	inc	r1
	mov	a, @r1
isz4e:	ret









;------------------------------------------------------------------
; Routines that talk with the IDE drive, these should be called by
; the main program.



	;read a sector, specified by the 4 bytes in "cylinder",
	;"head" and "sector".
	;Return, acc is zero on success, non-zero for an error
read_sector:
	acall	wr_lba
	mov	a, #ide_command
	mov	r2, #ide_cmd_read
	acall	ide_wr
	acall	ide_drq
	jb	acc.0, rs_err
	clr	a
	ret
rs_err: mov	a, #ide_err
	acall	ide_rd
	mov	a, r2
	jz	rs_err2
	ret
rs_err2:mov	a, #255
	ret



	;initialize the ide drive
ide_init:
	;acall	ide_hard_reset		;usually not necessary
	mov	a, #ide_head
	mov	r2, #10100000b
	acall	ide_wr			;select the master device
	mov	a, #ide_status
	acall	ide_rd
	mov	a, r2
	;should probably check for a timeout here
	jnb	acc.6, ide_init		;wait for RDY bit to be set
	jb	acc.7, ide_init		;wait for BSY bit to be clear
	mov	a, #ide_head
	mov	r2, #0xAF
	acall	ide_wr			;what should this config parm be?
	mov	a, #ide_sec_cnt
	mov	r2, #64
	acall	ide_wr			;what should this config parm be?
	mov	a, #ide_command
	mov	r2, #ide_cmd_init
	acall	ide_wr			;do init parameters command
	acall	ide_busy
	mov	a, #ide_command
	mov	r2, #ide_cmd_recal	;do recal command (is this necessary?)
	acall	ide_wr
	acall	ide_busy
	ret



;------------------------------------------------------------------
; Not quite as low, low level I/O.  These routines talk to the drive,
; using the low level I/O.  Normally a main program should not call
; directly to these.



	;Read a block of 512 bytes (one sector) from the drive
	;and store it in memory @ DPTR
read_data:
	mov	r0, #0
	mov	r1, #2
	mov	r2, dph
	mov	p2, dph
	mov	dptr, #cfg_8255
	mov	a, #rd_ide_8255
	movx	@dptr, a		;config 8255 chip, read mode
rdataloop:
	mov	dptr, #ide_8255_ctl
	;mov	a, #ide_data
	;movx	@dptr, a		;drive address onto control lines
	mov	a, #ide_data | ide_rd_line	
	movx	@dptr, a		;assert read pin
	mov	dptr, #ide_8255_lsb
	clr	a
	movc	a, @a+dptr		;read the lower byte
	movx	@r0, a
	inc	r0
	mov	dptr, #ide_8255_msb
	clr	a
	movc	a, @a+dptr		;read the upper byte
	movx	@r0, a
	inc	r0
	mov	dptr, #ide_8255_ctl
	clr	a
	movx	@dptr, a		;deassert all control pins
	mov	a, r0
	jnz	rdataloop
	mov	a, r2
	inc	a
	mov	p2, a
	djnz	r1, rdataloop
	mov	p2, #255
	ret




	;write the logical block address to the drive's registers
wr_lba:
	mov	a, lba+3
	anl	a, #0x0F
	orl	a, #0xE0
	mov	r2, a
	mov	a, #ide_head
	acall	ide_wr
	mov	a, #ide_cyl_msb
	mov	r2, lba+2
	acall	ide_wr
	mov	a, #ide_cyl_lsb
	mov	r2, lba+1
	acall	ide_wr
	mov	a, #ide_sector
	mov	r2, lba+0
	acall	ide_wr
	;mov	a, #ide_sec_cnt
	;mov	r2, #1
	;acall	ide_wr
	ret




	;Wait for the ide drive to not be busy.
	;Returns the drive's status in Acc
ide_busy:
	mov	a, #ide_status		;wait for RDY bit to be set
	acall	ide_rd
	mov	a, r2
	;should probably check for a timeout here
	jb	acc.7, ide_busy
	ret


	;Wait for the drive to be ready to transfer data.
	;Returns the drive's status in Acc
ide_drq:
	mov	a, #ide_status		;wait for DRQ bit to be set
	acall	ide_rd
	acall	idle
	mov	a, r2
	;should probably check for a timeout here
	jb	acc.7, ide_drq		;wait for BSY to be clear
	jnb	acc.3, ide_drq		;wait for DRQ to be set
	ret


;------------------------------------------------------------------
; Low Level I/O to the drive.  These are the routines that talk
; directly to the drive, via the 8255 chip.  Normally a main
; program would not call to these.

	;Do a read bus cycle to the drive, using the 8255.  This
	;is slow, because we have to manipulate the 8255 and use
	;the 8051's limited moxv and movx to do it (via dptr).
	;Note that the drive is read using MOVC, to run on a board
	;where the 8255 is read using PSEN.  If your board uses
	;RD instead of PSEN, chance the MOVC's to MOVX's.

	;input acc = ide regsiter address
	;output r2 = lower byte read from ide drive
	;output r3 = upper byte read from ide drive
	;dptr is changed
ide_rd:
	push	acc
	mov	dptr, #cfg_8255
	mov	a, #rd_ide_8255
	movx	@dptr, a		;config 8255 chip, read mode
	mov	dptr, #ide_8255_ctl
	pop	acc
	movx	@dptr, a		;drive address onto control lines
	orl	a, #ide_rd_line	
	movx	@dptr, a		;assert read pin
	mov	dptr, #ide_8255_msb
	;clr	a
	;movc	a, @a+dptr		;read the upper byte
	;mov	r3, a
	mov	dptr, #ide_8255_lsb
	clr	a
	movc	a, @a+dptr		;read the lower byte
	mov	r2, a
	mov	dptr, #ide_8255_ctl
	clr	a
	movx	@dptr, a		;deassert all control pins
	ret


	;Do a write bus cycle to the drive, via the 8255

	;input acc = ide register address
	;input r2 = lsb to write
	;input r3 = msb to write
	;dptr is changed
ide_wr:
	;push	acc
	mov	r4, a
	mov	dptr, #cfg_8255
	mov	a, #wr_ide_8255
	movx	@dptr, a		;config 8255 chip, write mode
	mov	dptr, #ide_8255_lsb
	mov	a, r2
	movx	@dptr, a		;drive lower lines with lsb (r2)
	;mov	dptr, #ide_8255_msb
	;mov	a, r3
	;movx	@dptr, a		;drive upper lines with msb (r3)
	mov	dptr, #ide_8255_ctl
	;pop	acc
	mov	a, r4
	movx	@dptr, a		;drive address onto control lines
	orl	a, #ide_wr_line	
	movx	@dptr, a		;assert write pin
	;nop
	clr	a
	movx	@dptr, a		;deassert all control pins
	;mov	dptr, #cfg_8255
	;mov	a, #rd_ide_8255
	;movx	@dptr, a		;config 8255 chip, read mode
	ret


	;do a hard reset on the drive, by pulsing its reset pin.
	;this should usually be followed with a call to "ide_init".
ide_hard_reset:
	mov	dptr, #cfg_8255
	mov	a, #wr_ide_8255
	movx	@dptr, a		;config 8255 chip, write mode
	mov	dptr, #ide_8255_ctl
	mov	a, #ide_rst_line
	movx	@dptr, a		;hard reset the disk drive
	mov	r2, #250
	djnz	r2, *			;delay ^gt; 25 us (reset pulse width)
	clr	a
	movx	@dptr, a		;no ide control lines asserted
	ret


Paul's Homebrew MP3 Player, Paul Stoffregen.
Designed and constructed Winter, 2000.
http://www.pjrc.com/tech/mp3/old_firmware.html
Last updated: February 23, 2005
Status: More info to come... just a couple photos for now.
Questions, Comments?? <paul@pjrc.com>