;************************************************************************************************
;*  MP3 Player
;*  AT90S8515 + VS1001
;*  Xtal Frequency for AVR is 6.144 MHz (1/4 of 24.576 MHz Vs1001 clock)
;*  
;*  Version 0.1, connects to parallel port just for playback
;*  debug and control over RS232
;*  uses a 255 byte FIFO
;* 
;* 
;* 
;* 
;*					
;************************************************************************************************

.include "8515def.inc"

.listmac

.macro ACK			; set ACK signal
	sbi	portb,cs0
.endm

.macro n_ACK			; clear ACK signal
	cbi	portb,cs0	
.endm

.macro wait_Strobe_H			; wait for STROBE HIGH
label_1:
	sbis	pinb,cs1
	rjmp	label_1
.endm

.macro wait_Strobe_L			; wait for STROBE LOW
label_1:
	sbic	pinb,cs1
	rjmp	label_1
.endm

.macro wait_DREQ			; wait for DREQ HIGH
label_1:
	sbis	pind,dreq
	rjmp	label_1
.endm

.macro wait_SPI_end			; wait for SPI Xfer end
wait_SDI:
	sbis	SPSR,SPIF
	rjmp	wait_SDI
.endm

;Pins
		
;		Signal name	Direction	Comment
		
; Port A	D[7:0]		IO		data from parallel port.
;
; Port C	D[15:8]		IO		upper IO data bits for ATA
; 
; Port B	
.equ	CS1 = 0;		I		Strobe from parallel port, change later for ATA version
.equ	CS0 = 1;		O		Handshake to PC.
.equ	BSYNC = 2;		O		Byte Sync
.equ	RES_MP3 = 3;		O		Reset MP3 deoder
.equ	XCS = 4;		O		Select Data/control IF
.equ	MOSI = 5;		O		SPI master out/ slave in
.equ	MISO = 6;		I		SPI master in / slave out
.equ	SCK = 7;		O		SPI clock

; Port D
;	PD0	RxD		I		RS232 data in
;	PD1	TxD		O		RS232 data out
.equ	DREQ = 2;		I		Data request from Vs1001, INT0
.equ	WR = 3;			O		write strobe for ATA
.equ	RD = 4;			O		read strobe for ATA
;	PD5	A2		O		Address 2 ATA
;	PD6	A1		O		Address 1 ATA
;	PD7	A0		O		Address 0 ATA
; 

;******************************************************************************
;
;data segment


.dseg
.org $60
FIFO:		.BYTE	8*32		; FIFO, 8x32=256 Byte	
FIFO_end:				; end of FIFO, address needed 
rph:		.BYTE	1		; read pointer high byte
rpl:		.BYTE	1		; read pointer low byte
wph:		.BYTE	1		; write pointer high byte
wpl:		.BYTE	1		; write pointer low byte
UART_CMD:	.BYTE	1		; UART command
UART_PAR:	.BYTE	1		; UART parameter
UART_PAR1:	.BYTE	1		; UART parameter
.def	cnt = r27			; FIFO content counter
.def	min = r26			; minimum FIFO counter, just for TESTING

;******************************************************************************
;code segment
.cseg
.org 0	
	rjmp	main			; Reset
	rjmp	I_INT0			; Externer Interrupt 0
	rjmp	I_INT1			; Externer Interrupt 1
	rjmp	I_T1_CAP		; Timer 1 capture
	rjmp	I_T1_CMP_A		; Timer 1 compare A
	rjmp	I_T1_CMP_B		; Timer 1 compare B
	rjmp	I_T1_OVF		; Timer 1 overflow
	rjmp	I_T0_OVF		; Timer 0 overflow
	rjmp	I_SPI			; SPI Transmission finished
	rjmp	I_UART_RX		; UART RX complete
	rjmp	I_UART_TX_FIFO		; UART TX FIFO empty
	rjmp	I_UART_TX		; UART TX complete
	rjmp	I_ANA_CMP		; Analog Comparator

main:

;Initialize Stack-Pointer

	ldi	r16,$2
	out	sph,r16
	ldi	r16,$5F			; set SP to upper end of SRAM (512 byte)
	out	spl,r16

;Initialize Ports

	ldi	r16,0			;
	out	ddra,r16		; all inputs on port A
	out	ddrc,r16		; all inputs on port C
	ldi	r16,$ff	
	out	porta,r16		; all Pull-ups active on port A
	out	portc,r16		; all Pull-ups active on port C
	ldi	r16,0b10111110
	out	ddrb,r16		; define port B, in the ATA version, we have to change CS1 to OUTPUT
	ldi	r16,0
	out	portb,r16		; set them to 0
	ldi	r16,0b11111010
	out	ddrd,r16		; define port D, no need to change this later
	ldi	r16,0
	out	portd,r16		; set them to 0
	
;Initialize SPI

	ldi	r16,0b01010000		; configure SPI
	out	spcr,r16		; no SPI interrupts 	(bit 7)
					; SPI enable		(bit 6)
					; MSB first		(bit 5)
					; Master		(bit 4)
					; SCK low when idle	(bit 3)
					; clock phase 0		(bit 2)
					; SCK = 1/4 Xtal	(bit 1,0)
					; = 1.536 MHz  ( 1 byte = 5,2us )
	
	out	spdr,r16		; write a random byte to set the finish bit
	
;Initialize UART

	ldi	r16,0b10011000		; configure RS232
					; Rx interrupt		(bit 7)
					; no Tx interrupt	(bit 6)
					; no Tx FIFO interrupt	(bit 5)
					; Rx enable		(bit 4)
					; Tx enable		(bit 3)
					; 8 bit data		(bit 2)
					; dont care		(bit 1)
					; dont care		(bit 0)
	out	ucr,r16
					
	ldi	r16,39			; 9k6 with 6.144 MHz Xtal
	out	ubrr,r16


; enable Interrupts

	sei

; Initialize watchdog 0 (3s@5V Vcc)

	ldi	r16,$1f
	out	wdtcr,r16
		
decoder_loop:
		
	rcall	VS1001_test
		
	rcall	fifo_init		; initialize FIFO
	
	rcall	MP3_play
		
		
;************************************************************************************
;
; interrupts
;
;************************************************************************************

;**********************************************************
I_INT0:			; Externer Interrupt 0
;**********************************************************
	reti

;**********************************************************	
I_INT1:			; Externer Interrupt 1
;**********************************************************
	reti
	
;**********************************************************	
I_T1_CAP:		; Timer 1 capture
;**********************************************************
	reti

;**********************************************************		
I_T1_CMP_A:		; Timer 1 compare A
;**********************************************************
	reti
	
;**********************************************************	
I_T1_CMP_B:		; Timer 1 compare B
;**********************************************************
	reti
	
;**********************************************************	
I_T1_OVF:		; Timer 1 overflow
;**********************************************************
	reti
	
;**********************************************************	
I_T0_OVF:		; Timer 0 overflow
;**********************************************************
	reti
	
;**********************************************************	
I_SPI:			; SPI Transmission finished	
;**********************************************************
	reti

;**********************************************************
I_UART_RX:		; UART RX complete
;**********************************************************
	push	r16
	push	r17
	push	r21
	push	r22
	in	r16,sreg
	push	r16

	in	r16,udr
	mov	r17,r16
	sts	uart_par,r16
	lds	r16,uart_cmd
	
	cpi	r16,$4C			; compare with 'L'
	brne	no_left
	ldi	r16,$80+$4c		; set MSB in command to trigger SCI access
	sts	uart_cmd,r16		; write back
	rjmp	I_UART_RX_end
no_left:
	cpi	r16,$52			; compare with 'R'
	brne	no_right
	ldi	r16,$80+$52		; set MSB in command to trigger SCI access
	sts	uart_cmd,r16		; write back		
	rjmp	I_UART_RX_end	
no_right:
	cpi	r16,$46			; compare with 'F'
	brne	no_F
	mov	r16,min
	rcall	hex2ascii
	mov	r16,r21
	rcall	put_rs232		; write MINIMUM FIOF counter to RS232	
	mov	r16,r22
	rcall	put_rs232		; write MINIMUM FIOF counter to RS232		
no_F:
	andi	r17,$7f			; so its not L and not R, clear MSB
	sts	uart_cmd,r17
I_UART_RX_end:	
	pop	r16
	out	sreg,r16
	pop	r22
	pop	r21
	pop	r17
	pop	r16
	reti

;**********************************************************	
I_UART_TX_FIFO:		; UART TX FIFO empty
;**********************************************************
	reti

;**********************************************************	
I_UART_TX:		; UART TX complete
;**********************************************************
	reti
	
;**********************************************************	
I_ANA_CMP:		; Analog Comparator
;**********************************************************
	reti
		
;
;************************************************************************************
;
; subroutines
;
;************************************************************************************

;************************************************************************************
halt:
	rjmp	halt		; endless loop, stop processing

;************************************************************************************
;
; writes 32 bytes to MP3 data interface
;
; r16,r17,r28,29 = scratch for subroutine
;
;************************************************************************************

write_SDI32:
	sbi	portb,pb4	; set XCS to select mp3 data interface
	cpi	cnt,32		; test if there are at least 32 bytes inside the FIFO
	brlo	SDI32_end
	
	wdr			; reset watchdog timer
	
	ldi	r16,32		; byte counter
	lds	r28, rpl
	lds	r29, rph	; load FIFO read pointer
	
write_SDI32_loop:	

	wait_SPI_end	
	
	sbi	portb,bsync	; set BSYNC	
	ld	r17,Y+		; load next data byte
	out	SPDR,r17	; start transmission
	nop			; wait some cycles,
	nop			; to make sure the first bit is Xmitted			
	cbi	portb,bsync	; clear BSYNC	

	dec	r16
	brne	write_SDI32_loop	; next byte Xmission if not Zero	
		
	
	cpi	r28,low(FIFO_end)	; compare actual FIFO write pointer (low byte) against FIFO end
	brne	SDI32_FIFO
	
	cpi	r29,high(FIFO_end)	; compare actual FIFO write pointer (high byte) against FIFO end
	brne	SDI32_FIFO
	
	ldi	r28, low(FIFO)
	ldi	r29, high(FIFO)		; reload FIFO pointer	
SDI32_FIFO:
	sts	rpl,r28
	sts	rph,r29			; write poiter to RAM	
	subi	cnt,32			; decrease FIFO content by 32
SDI32_end:	
	n_ACK				; set ACK low to allow a new Xfer

	cp	min,cnt			; compare FIFO counter with minimum
	brhs	no_min
	mov	min,cnt			; new minimum
no_min:

	ret
;************************************************************************************
;
; writes 1 bytes to MP3 data interface
;
; r16 = byte to send
;
;************************************************************************************

write_SDI:
	sbi	portb,pb4	; set XCS to select mp3 data interface

	wait_SPI_end
	sbi	portb,PB2	; set BSYNC		
	out	SPDR,r16	; start transmission
	nop			; wait some cycles,
	nop			; to make sure the first bit is Xmitted
				; TODO
	cbi	portb,PB2	; clear BSYNC	
	
	ret

;************************************************************************************
;
; write data to control interface
;
; r17 = address
; r18 = High data byte
; r19 = Low data byte
;
;************************************************************************************

write_SCI:			
	wait_SPI_end
	cbi	portb,pb4	; clear XCS to select control interface		
	ldi	r16,2		; code for write acces
	out	SPDR,r16	; write code for write access	
	wait_SPI_end		
	out	SPDR,r17	; write address
	wait_SPI_end
	out	SPDR,r18	; write HIGH data byte	
	wait_SPI_end
	out	SPDR,r19	; write low data byte	
	rcall	wait_5us	; DONT poll SDSR, this will CLEAR the FINISH BIT	
	sbi	portb,pb4	; set XCS HIGH to end access cycle
	rcall	wait_5us	; wait 5 us after a SCI access		
	ret

;************************************************************************************
;
; read data from control interface
;
; r16 = scratch
; r17 = address
; r18 = High data byte (output)
; r19 = Low data byte (output)
;
;************************************************************************************
	
read_SCI:
	wait_SPI_end
	cbi	portb,pb4	; clear XCS to select control interface		
	ldi	r16,3		; code for write acces		
	out	SPDR,r16	; write code for write access	
	wait_SPI_end	
	out	SPDR,r17	; write address	
	wait_SPI_end
	out	SPDR,r17	; write crap, we just need the read data	
	wait_SPI_end
	in	r18,SPDR	; read HIGH byte
	out	SPDR,r17	; write some crap, write data is ignored by VS1001	
	rcall	wait_5us	; DONT poll SDSR, this will CLEAR the FINISH BIT
	in	r19,SPDR	; read LOW byte	
	sbi	portb,pb4	; set XCS HIGH to end access cycle	
	rcall	wait_5us	; wait 5 us after a SCI access	
	ret
	
;************************************************************************************
;
; wait 5 us
;
;************************************************************************************

wait_5us:
	push	r16		; save r16
	ldi	r16,9		; 9 cycles in the loop take 27 clock cycles
				; giving abt. 6.3 us of delay
wait_5u_loop:
	dec	r16
	brne	wait_5u_loop
	pop	r16		; restore r16
	ret	

;************************************************************************************
;
; wait 5 ms
;
;************************************************************************************

wait_5ms:
	push	r16		; save r16
	push	r17		; save r17
	push	r18		; save r18
	
	ldi	r18,4
wait_5ms_loop1:
	ldi	r17,0		; 256x4x6.02 us = 6,1ms
wait_5ms_loop2:
	rcall	wait_5us
	dec	r17
	brne	wait_5ms_loop2
	dec	r18
	brne	wait_5ms_loop1
	pop	r18
	pop	r17
	pop	r16		; restore r18,r17,r16
	ret	

;************************************************************************************
;
; FIFO init, fill FIFO with 255 Byte
;
; r16,r17,r30,r31 = scratch
;
;************************************************************************************

FIFO_init:

; setup some variables
		
	ldi	r30,low(FIFO)
	ldi	r31,high(FIFO)
	sts	rpl,r30			; init read pointer low byte	
	sts	rph,r31			; init read pointer high byte	
	sts	wpl,r30			; init write pointer low byte	
	sts	wph,r31			; init write pointer high byte	
	ldi	cnt,255			; FIFO is full
		
; fill FIFO	

	ldi	r17,255			; byte counter (255 byte to read)
FIFO_loop:
	n_ACK
	wait_strobe_L
	in	r16,pina		; read byte from port
	ack
	st	Z+,r16			; write byte to FIFO
	wait_strobe_H	
	dec	r17
	brne	fifo_loop		; make loop (or not)
						
	ret
	

;************************************************************************************
;
; MP3_play
;
; r16,r30,r31 = scratch
;
;************************************************************************************

; setup some variables
MP3_play:
	ldi	r16,0
	rcall	write_SDI
	ldi	r16,0
	rcall	write_SDI
		
	lds	r30,wpl			; init write pointer low byte	
	lds	r31,wph			; init write pointer high byte
			
MP3_loop:	
	sbic	pind,dreq		; test if we have a request from the VS1001
	rcall	write_SDI32		; yes, write 32 byte to MP3 SDI interface
	
	lds	r16,uart_cmd
	sbrc	r16,7			; test if MSB is set in UART_CMD
	rcall	adj_vol			; adjust volume
	
	sbic	pinb,cs1		; STROBE LOW?
	rjmp	MP3_loop
	
	in	r16,pina		; read byte from port				
	ACK				; ack byte	
	st	Z+,r16			; write byte to FIFO
	
	cpi	r30,low(FIFO_end)	; compare actual FIFO write pointer (low byte) against FIFO end
	brne	MP3_no_over
	
	cpi	r31,high(FIFO_end)	; compare actual FIFO write pointer (high byte) against FIFO end
	brne	MP3_no_over
	
	ldi	r30, low(FIFO)
	ldi	r31, high(FIFO)		; reload FIFO pointer
MP3_no_over:	
	wait_strobe_H
	
	inc	cnt
	cpi	cnt,255
	breq	MP3_full		; check if FIFO is full
	n_ACK				; yes, no further bytes from PC until 32 bytes are read
MP3_FULL:	
	rjmp	MP3_loop
	
	ret
	

;************************************************************************************
;
; get_rs232, read a byte from the serial port,
;
; r16 = read byte
;
;************************************************************************************
	
get_rs232:
	sbis	usr,rxc			; test if we have a byte recieved
	rjmp	get_rs232		; no, wait
	in	r16,udr
	ret
	
;************************************************************************************
;
; put_rs232, write a byte to the serial port,
;
; r16 = byte
;
;************************************************************************************
	
put_rs232:
	sbis	usr,udre		; test if tx buffer is empty
	rjmp	put_rs232		; no, wait
	out	udr,r16
	ret

;************************************************************************************
;
; put_string, write a string to the serial port,
;
; r30,r31 = pointer to string
;
;************************************************************************************
	
put_string:
	push	r0
	push	r16
loop_put_string:	
	lpm				; load programm memory with byte to r0
	ldi	r16,0
	cp	r0,r16
	breq	end_put_string
	adiw	r30,1			; increase pointer by 1
wait_put_string:	
	sbis	usr,udre		; test if tx buffer is empty
	rjmp	wait_put_string		; no, wait
	out	udr,r0
	rjmp	loop_put_string
end_put_string:
	pop	r16
	pop	r0	
	ret

;************************************************************************************
;
; hex to ASCII, converts an 8 bit value to two ASCII chars
;
; r16 = byte
; r21 = high nibble ASCII
; r22 = low  nibble ASCII
;
;************************************************************************************

hex2ASCII:
	mov	r21,r16		; just copy byte
	mov	r22,r16
	lsr	r21
	lsr	r21
	lsr	r21
	lsr	r21		; shift 4 bit right to get high nibble			
	andi	r22,$0F		; mask high nibbles
	ldi	r16,48		; ASCII code for "0"
	add	r21,r16
	add	r22,r16
	cpi	r21,58		; check if code ist A...F
	brlo	no_add1
	ldi	r16,7
	add	r21,r16		; yes, add 17
no_add1:
	cpi	r22,58		; check if code ist A...F
	brlo	no_add2
	ldi	r16,7
	add	r22,r16		; yes, add 17
no_add2:
	
	ret

	
;************************************************************************************
;
; VS1001 init
;
; r16,r30,r31 = scratch
;
;************************************************************************************

VS1001_test:
	
	cbi	portb,res_mp3		; assert hardware reset of mp3 decoder
	rcall	wait_5ms
	sbi	portb,res_mp3		; release hardware reset of mp3 decoder	
	rcall	wait_5ms	
	sbis	pind,dreq		; check id DREQ is set		
	rjmp	halt			; test failed		

	ldi	r30,low(s1 <<1)		; IMPORTANT, shift count 1 bit left
	ldi	r31,high(s1 <<1)	; because S1 is the WORD address, but we need the BYTE address
	rcall	put_string

	ret


; for testing		

f_wob:
	ldi	r21,49	

test_SDI:				; intialize sine test
	ldi	r16,$53
	rcall	write_SDI
	ldi	r16,$ef
	rcall	write_SDI
	ldi	r16,$6e
	rcall	write_SDI
	mov	r16,r21
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI

	ldi	r17,11
	ldi	r18,60
	ldi	r19,60
	rcall	write_SCI		; set vol

	ldi	r16,100			; wobble frequency
f_wob_loop:
	rcall	wait_5ms
	dec	r16
	brne	f_wob_loop

	ldi	r16,$45
	rcall	write_SDI
	ldi	r16,$78
	rcall	write_SDI
	ldi	r16,$69
	rcall	write_SDI
	ldi	r16,$74
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	ldi	r16,$0
	rcall	write_SDI
	
	inc	r21
	cpi	r21,120
	brne	test_SDI
	
	rjmp	f_wob

	
	
	
	
test_wob:
	ldi	r20,0			; wobble volume
test_SCI:		
	ldi	r17,$b
	mov	r18,r20
	mov	r19,r20
	rcall	write_sci	
	ldi	r16,3
wob_loop:
	rcall	wait_5ms
	dec	r16
	brne	wob_loop		
	dec	r20	
	brne	test_SCI
	
	
	rjmp	test_wob


;************************************************************************************
;
; VS1001 REGISTER DUMP
;
; r16,r23,r30,r31 = scratch
;
;************************************************************************************


REG_dump:

	ldi	r16,$a
	rcall	put_rs232
	ldi	r16,$d
	rcall	put_rs232		; new line + carrige return
	
	ldi	r23,0
read_regs:
	mov	r17,r23
	rcall	read_SCI
	
	mov	r16,r18
	rcall	hex2ascii
	mov	r16,r21
	rcall	put_rs232
	mov	r16,r22
	rcall	put_rs232
		
	mov	r16,r19
	rcall	hex2ascii
	mov	r16,r21	
	rcall	put_rs232
	mov	r16,r22	
	rcall	put_rs232
	
	ldi	r16,$a
	rcall	put_rs232
	ldi	r16,$d
	rcall	put_rs232		; new line + carrige return
	
	inc	r23
	cpi	r23,12
	brne	read_regs		

	ret

;************************************************************************************
;
; adjust volume
;
;************************************************************************************

adj_vol:

	ldi	r17,11			; volume register
	rcall	read_SCI		; read volume
	
	lds	r16,uart_cmd		; load uart cmd
	andi	r16,$7f			; clear MSB

	cpi	r16,$4C			; compare with 'L'
	brne	adj_vol_no_left
	lds	r18,uart_par		; load new value for left channel
	rjmp	adj_vol_go
adj_vol_no_left:			; no need to compare with R, this MUST be R
	lds	r19,uart_par		; load new value for right channel
adj_vol_go:

	ldi	r17,11
	rcall	write_sci

	ldi	r16,0
	sts	uart_cmd,r16	
	ret
	
	
;*************************************************************************
; 
; String definitions
;
;*************************************************************************

s1:
.db	$A,$D,"Hello World",$A,$D,"MpTriTium V0.1",$A,$D,"by F.B.I. in 09/2001",$A,$d,0
	