📄 mp3drq.asm
字号:
.TITLE (STA013 Data Request Interrupt Service)
.sbttl Copyright (C) 2005 by Spare Time Gizmos. All rights reserved.
;++
; mp3drq.a51
;
; Copyright (C) 2005 by Spare Time Gizmos. All rights reserved.
;
; This file is part of the Spare Time Gizmos' MP3 Player firmware.
;
; This firmware is free software; you can redistribute it and/or modify it under
; the terms of the GNU General Public License as published by the Free Software
; Foundation; either version 2 of the License, or (at your option) any later
; version.
;
; This program 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. See the GNU General Public License for
; more details.
;
; You should have received a copy of the GNU General Public License along with
; this program; if not, write to the Free Software Foundation, Inc., 59 Temple
; Place, Suite 330, Boston, MA 02111-1307 USA
;
; DESCRIPTION:
; This module contains the STA013 data request (MP3 data, that is) interrupt
; service. The STA013 wants its MP3 data delivered bit serially with a synch-
; ronous clock. In DEBUG mode we do this the hard way, by bit banging two
; port pins. Needless to say, this is a little slow and limits the bit rate
; of the MP3s we can play to around 32kbps or so.
;
; In non-DEBUG mode we can use the 8051's built in UART in mode 0 to shift
; out the bits; this is vastly faster and is able to play 128kbps MP3s without
; missing a beat (literally!), however there is a small downside. The 8051's
; UART sends data LSB first, which is the opposite of the way the STA013 wants
; to see it. There's no choice but to reverse the order of the bits in each
; byte - to prevent this expensive operation from eating up all the time we
; save, we use a 256 byte lookup table to handle the reversal.
;
; The MP3 file data is buffered in a very simple circular buffer stored in
; xdata RAM. In practice this buffer is always an exact multiple of 512 bytes
; so that IDE sectors can be read directly without any nasty wrap around
; problems, but this code actually doesn't care what size the buffer is.
;
; REFERENCES:
; http://www.pjrc.com/tech/mp3/sta013.html
;
; REVISION HISTORY:
; dd-mmm-yy who description
; 29-May-05 RLA New File
; 12-Jul-05 DJA ported to ASX8051/SDCC
; 13-Jul-05 RLA Unfortunately you can't test for the play list empty
; simply by saying "if (g_pPlayListHead == NULL)"
; because MP3DRQ always holds the last BCB pointer in
; R0. Invent IsPlayListEmpty() to fix this problem.
; 20-Jul-05 DJA Port attempt II
; 20-Oct-05 RLA Correct for SDCC's little endian byte ordering
; RLA Missing a # (immedate mode) when setting SCON!
;--
.module MP3DRQ
.optsdcc -mmcs51 --model-small
.radix d
.include "player.inc"
.include "fastsdi.inc"
.globl _InitializeMP3DRQ, _FlushPlayList, _IsPlayListEmpty
.globl _g_pPlayListHead, _g_pPlayListEnd, _MP3DRQ
; These two global variables keep track of the current queue of MP3 data buffers
; to be played. The background code pulls buffers off the free list, reads data
; blocks from the CF card, and puts full buffers on this play list. This code pulls
; a full buffer off the list, sends it to the STA013, and then puts the buffer
; back on the free list.
.area DSEG (DATA)
_g_pPlayListHead: ; PUBLIC BUFFER_CONTROL_BLOCK idata *g_pPlayListHead;
.ds 1
_g_pPlayListEnd: ; PUBLIC BUFFER_CONTROL_BLOCK idata *g_pPlayListEnd;
.ds 1
; Not only do we need the register banks defined, but in this case we
; actually need _two_ banks, zero and one. To avoid confusion, we actually
; define different (abeit non-standard) names for each bank... See my
; insightful comments in the ide.asm module for more details...
.area REG_BANK_0 (REL,OVR,DATA)
.ds 8
AR00=0x00
AR01=0x01
AR02=0x02
AR03=0x03
AR04=0x04
AR05=0x05
AR06=0x06
AR07=0x07
.area REG_BANK_1 (REL,OVR,DATA)
.ds 8
AR10=0x08
AR11=0x09
AR12=0x0A
AR13=0x0B
AR14=0x0C
AR15=0x0D
AR16=0x0E
AR17=0x0F
.area CSEG (CODE)
; When we're using the 8051 UART to send data to the STA013, we need to reverse
; the order of the bits in each byte first. This 256 byte table does the job
; without wasting lots of valuable CPU time (e.g. m_abReverseBits[i] = <the reverse
; of i>).
.if FAST_SDI
m_abReverseBits:
; 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
.db 0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0 ; 00
.db 0x08,0x88,0x48,0xc8,0x28,0xa8,0x68,0xe8,0x18,0x98,0x58,0xd8,0x38,0xb8,0x78,0xf8 ; 10
.db 0x04,0x84,0x44,0xc4,0x24,0xa4,0x64,0xe4,0x14,0x94,0x54,0xd4,0x34,0xb4,0x74,0xf4 ; 20
.db 0x0c,0x8c,0x4c,0xcc,0x2c,0xac,0x6c,0xec,0x1c,0x9c,0x5c,0xdc,0x3c,0xbc,0x7c,0xfc ; 30
.db 0x02,0x82,0x42,0xc2,0x22,0xa2,0x62,0xe2,0x12,0x92,0x52,0xd2,0x32,0xb2,0x72,0xf2 ; 40
.db 0x0a,0x8a,0x4a,0xca,0x2a,0xaa,0x6a,0xea,0x1a,0x9a,0x5a,0xda,0x3a,0xba,0x7a,0xfa ; 50
.db 0x06,0x86,0x46,0xc6,0x26,0xa6,0x66,0xe6,0x16,0x96,0x56,0xd6,0x36,0xb6,0x76,0xf6 ; 60
.db 0x0e,0x8e,0x4e,0xce,0x2e,0xae,0x6e,0xee,0x1e,0x9e,0x5e,0xde,0x3e,0xbe,0x7e,0xfe ; 70
.db 0x01,0x81,0x41,0xc1,0x21,0xa1,0x61,0xe1,0x11,0x91,0x51,0xd1,0x31,0xb1,0x71,0xf1 ; 80
.db 0x09,0x89,0x49,0xc9,0x29,0xa9,0x69,0xe9,0x19,0x99,0x59,0xd9,0x39,0xb9,0x79,0xf9 ; 90
.db 0x05,0x85,0x45,0xc5,0x25,0xa5,0x65,0xe5,0x15,0x95,0x55,0xd5,0x35,0xb5,0x75,0xf5 ; a0
.db 0x0d,0x8d,0x4d,0xcd,0x2d,0xad,0x6d,0xed,0x1d,0x9d,0x5d,0xdd,0x3d,0xbd,0x7d,0xfd ; b0
.db 0x03,0x83,0x43,0xc3,0x23,0xa3,0x63,0xe3,0x13,0x93,0x53,0xd3,0x33,0xb3,0x73,0xf3 ; c0
.db 0x0b,0x8b,0x4b,0xcb,0x2b,0xab,0x6b,0xeb,0x1b,0x9b,0x5b,0xdb,0x3b,0xbb,0x7b,0xfb ; d0
.db 0x07,0x87,0x47,0xc7,0x27,0xa7,0x67,0xe7,0x17,0x97,0x57,0xd7,0x37,0xb7,0x77,0xf7 ; e0
.db 0x0f,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,0x1f,0x9f,0x5f,0xdf,0x3f,0xbf,0x7f,0xff ; f0
.endif
;++
; PRIVATE void MP3DRQ (void) interrupt 0 using 1
;
; As you've probably figured out by now, this routine is the interrupt service
; for STA013 MP3 data requests. In twenty words or less, what we want to do is
; to pull a buffer of MP3 data from the m_pPlayList and send it, synchronous bit
; serially, to the STA013. When we've finished sending the buffer we return it
; to the free buffer pool, m_pFreeBufferList, and then go back to m_pPlayList to
; look for another buffer to play.
;
; There are only two things that make our life difficult - the first is that
; the STA013's internal buffer can only hold 200 bytes or so (the datasheet is
; a little vague on that topic!) where as our buffers hold 512. That means it'll
; take several MP3 DRQ interrupts to transfer a single buffer.
;
; Second, if the background code is too slow it's possible that we'll run out
; of buffers on the m_pPlayList. We can't simply return if that happens because
; the STA013 will continue to assert DRQ which will continue to hold the 8051's
; INT0 low. Since INT0 is configured as level sensitive, we'll never get out of
; this interrupt routine! The only solution is to mask INT0 interrupts with the
; EX0 bit when this happens. The background code will turn EX0 back on when it
; reads the next buffer.
;
; This routine has its own register bank allocated to it, so when we enter
; routine we can depend on R0..R7 having the same values we left there the last
; time we were here. In general, the register usage is:
;
; R0 - points to current buffer control block (BCB)
; R1 - temporary (copy of R0, and loop counter)
; R2
; R3
; R4 - count of bytes remaining in current buffer (high)
; R5 - " " " " " " " " " " " (low)
; R6 - address (xdata) of next byte in current buffer (DPH)
; R7 - " " " " " " " " " " " (DPL)
;--
; Unlike A51, with sdcc/asx8051 there's no need (and indeed, no way!) to set
; the interrupt vector here to point to MP3DRQ. The SDCC compiler will take
; care of it for us in the "main" module (player.c in this case) SO LONG AS
; we include a function prototype for the MP3DRQ routine. The compiler looks
; for the "interrupt" attribute on the prototype declaration and automatically
; generates the appropriate vector.
; A further unpleasant business is that asx8051 has nothing equivalent to
; the "USING" directive. The only way to handle different register banks is
; to actually equate the AR0..7 symbols to different addresses. Since this
; module actually uses both bank zero (for code that's called directly from
; C) and bank one (for this ISR) we actually define a different set of symbols
; (e.g. AR01 for register 1, bank 0 - AR10 for register 0, bank 1) to avoid
; confusion... Of course, it's still up to us, the programmers, to ensure
; that the correct bank is selected by the PSW at the correct time!
_MP3DRQ:
PUSH ACC ; save the accumulator
PUSH DPH ; ... data pointer
PUSH DPL ; ...
PUSH PSW ; register bank and flags
SETB LED_BIT ; (turn the LED on for timing purposes)
MOV PSW, #0x08 ; and select register bank #1
; If there's no current buffer, try to remove the next one from the
; play list. If the play list is empty, then disable the DRQ interrupts
; and wait for more to arrive. As long as we leave with R0=0, we'll try
; to obtain a play buffer next time we come here.
ISR10: CJNE R0, #0, ISR20 ; jump if the current BCB is not null
MOV R0, _g_pPlayListHead ; remove the next BCB from the play list
CJNE R0, #0, ISR11 ; jump if we really got a buffer
CLR EX0 ; no more buffers - disable future interrupts
SJMP ISR99 ; and go dismiss this one
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -