;******************************************************************************
;       SX MultiMedia Card (MMC) VIRTUAL PERIPHERAL AUDIO DEMO
;	(C) Copyright 1997-2000 Scenix Semiconductor
;
;
;       Length: 476 bytes (total)
;       Authors: Craig Webb, Andrian Kouznetsov
;       Written: 97/03/10 to 00/10/30
;
;       This program implements six virtual peripherals on Scenix's SX28/52
;       DEMO board for demonstrating MultiMedia Card (MMC) external memory
;	storage playing its contents as a .WAV file through the audio plug-in PCB.
;	The MMC is accessed through a high level command interpreter subroutine
;	peripheral that controls the MMC by way of an SPI subroutine interface
;	peripheral. Data coming in from the MMC is stored in a circular buffer
;	as it awaits output through the audio harware. The program uses timer
;	clock virtual peripheral in conjunction with a pulse width modulation
;	(PWM) virtual peripheral for output of the stored MMC data as streamed
;	audio samples, and also uses a sigma-delta A/D virtual peripheral to
;	sample external signals that can then be written to the MMC.
;       
;	1) MMC command interpreter interface
;	2) SPI interface subroutine(s)
;	3) Circular storage buffer
;	4) Timer routine
;       5) Pulse-Width Modulated (PWM) outputs (2)
;       6) Adjustable 8-15-bit Analog-to-Digital Converter (ADC) (1)
;
;       These latter four of these virtual peripherals take advantage
;       of the SX's internal RTCC-driven interrupt and operate
;       in the background while the main program loop is executing.
;
;******************************************************************************
; Program Notes:
;
;	-Be careful that the SX SPI port pins are initialized when inserting or
;	  removing the MMC, oherwise peculiar card states can result (in which
;	  case the MMC should be completely removed and powered off, and then
;	  the SYNCHRONIZATION and INITIALIZATION routines must be run again).
;
;	-if the program does encounter any MMC communication error, it is best
;	  to repeat the INITIALIZATION routine, and occasionally the
;	  SYNCHRONIZATION routine also.
;
;==============================================================================
;
;******************************* Program Variables ***************************
;
;
;****** Assembly Options
;
pcb_revision	=	0	;0 or 1		;PCB revision 1.x
compiler	=	0	;0 or 1		;0=SX-Key, 1=SXIDE
stereo		=	0	;0 or 1		;set =1 for stereo PCM data
chip_type	=	1	;0 or 1		;0=SX18/28, 1=SX52
include_adc	=	0	;0 or 1		;0=adc routine not included
;
;****** Adjustable Paramaters
;
sample_freq	=	11025	;6512-44100	;sampling rate of .wav file
MMC_size	=	4	;1-128		;storage size of MMC card
resolution	=	8	;8-15		;adc resolution (8-15 bits)
pwm0_init	=	80h	;0-FFh		;initial pwm0 voltage
pwm1_init	=	80h	;0-FFh		;initial pwm1 voltage
;
;****** Program Variables
;
int_period	=	40+(include_adc*20)+6	;minimum + main routine allowance	
sx_freq_factor	=	1			;this is a time delay scaling factor,
						; for SX running at 50MHz(=1), 100MHz (=2)
;
;****** Program Constants & Dependent Variables
;
;sample_freq= $380 @ 11,025Hz mono, with xtal=50MHz, with adc on
samp_freq	=	$380*int_period/66*sample_freq/11025 ;sets the audio PCM sample rate
		IF stereo=1
samp_freq	=	samp_freq*2		;stereo needs twice the sample output rate
		ENDIF
		IF MMC_size<16
end_of_mem	=	MMC_size<<4-3		;last bank of mmc memory blocks
		ELSE					; for MMC cards >16Meg
end_of_mem	=	MMC_size>>4-3		;last bank of mmc memory blocks
		ENDIF
end_of_mem	=	$37			; or end of audio sample (comment this if needed)			

		IF compiler=0				;assembler directive (for SX-Key)
		 freq	50_000_000*sx_freq_factor	;default clock rate: 50 MHz
		ENDIF
;
;***SPIX timing delays:
;spix_rate	=	(1 + (3 * ( sx_freq_factor - 1)))
spix_rate	=	sx_freq_factor
					;rate factor for SPIX.MMC
					; (may need to be trimmed for different SX clock rates)
sync_duration	=	10 			;?-255	;MMC SYNCHRONIZATION function duration
cmd1_resp_wait	=	$80 * sx_freq_factor	;?-128	;initialization (CMD1) response wait duration
cmd1_delay1	=	$8  ;* sx_freq_factor	;?-128	;initialization (CMD1) internal loop delay
cmd1_delay2	=	$10 ;* sx_freq_factor	;?-128	;initialization (CMD1) internal loop delay
cmd1_delay3	=	$80 * sx_freq_factor	;?-128	;initialization (CMD1) internal loop delay
resp_r1_delay	=	32 * sx_freq_factor	;2-255	;delay for a RESPONSE from MMC
blk_rd_delay1	=	1 * sx_freq_factor	;?-255	;delay for MMC initial block read preparation		"	   written
blk_rd_wait	=	20 * sx_freq_factor	;?-255	;read wait time MMC incoming data bytes
blk_wr_delay	=	20 * sx_freq_factor	;?-255	;delay for MMC initial block write preparation
;
option_init	=	%10001000	;bits: 1=RTCC@01h|0=int_en|0=RTCC_internal|x|psa(1=WDT)|ps(2-0)	
resolution	=	resolution-8		;8-15	;adc resolution (8-15 bits)
;
mmc_blk_size	=	512		;block size for reading/writing MMC (in SPI mode)
mmc_buffsize	=	32		;MMC read/write buffer size: must be =(2^n)*16, n=0,1,2(,3 for SX52)
					; (only 16 and 32 have been tested)

;****** Assembler Directives
;
;		DEVICE  turbo
		ID      'MMCAudio'		;program ID label
		RESET   reset_entry             ;set reset/boot address
	
;******************* Configuration DATA and I/O pins *************************
;
;******	Port definitions
;
RA_IO		=	%0010			;port A I/O directions	
RA_init		=	%1111			;port A initial output states	
;---------------
;Pin assingment for SPI_X interface
spix_clk_pin	EQU	RA.3			;SPI clock output
spix_out_pin	EQU	RA.2			;SPI-Master-Out-Slave-In
spix_in_pin	EQU	RA.1			;SPI-Master-In-Slave-Out
spix_cs_pin	EQU	RA.0			;SPI device select
;===============
RB_IO		EQU	%10111111		;port B I/O directions	
RB_init		EQU	%11111111		;port B initial output states	
analog_port1	EQU	RB	
;---------------
LEDs		EQU	RB			;individual LED outputs
;
await_button	EQU	LEDs.3			;await button signal
init_err	EQU	LEDs.2			;signals initialize MMC error
read_err	EQU	LEDs.1			;signals read from MMC error
write_err	EQU	LEDs.0			;signals write to MMC error
;---------------
	  IF pcb_revision=0
;---------------
RC_IO		EQU	%11110110		;port C I/O directions	
RC_init		EQU	%11111111		;port C initial output states	
analog_port2	EQU	RC	
;---------------
;note: make duplicate changes in "analog_buff"
pwm0_pin        EQU     RB.6                    ;Pulse width mod. PWM0 output
pwm1_pin        EQU     RC.3                    ;Pulse width mod. PWM1 output
adc0_in_pin	EQU     RC.2                    ;ADC0 input pin
adc0_out_pin	EQU     RC.0                    ;ADC0 output/calibrate pin
;---------------
	  ELSE	;pcb_revision=1.1
;---------------
RC_IO		EQU	%11111010		;port C I/O directions	
RC_init		EQU	%11111111		;port C initial output states	
analog_port2	EQU	RC	
;---------------
;note: make duplicate changes in "analog_buff"
pwm0_pin        EQU     RB.6                    ;Pulse width mod. PWM0 output
pwm1_pin        EQU     RC.2                    ;Pulse width mod. PWM1 output
adc0_in_pin	EQU     RC.1                    ;ADC0 input pin
adc0_out_pin	EQU     RC.0                    ;ADC0 output/calibrate pin
;===============
	  ENDIF	;revision
;
;******************************** MMC_SPIX RAM ****************************

		ORG	$0E			;global ram
next_samp_ptr	DS	1			;points to next sample in buffer	
mmc_temp_data	DS	1			;temporary storage to move the data
						; between the SX RAM banks
;===============
;SPIX and MMC make use of the same RAM bank			
mmc_spix_ram	ORG	$10			;MMC/SPI control register bank
;
mmc_status	ds	1			;MMC driver status byte
;---------
spix_busy	equ	mmc_status.7		;Busy bit set by SPIX VP to HIGH when byte
						;transfer (SPI) is in progress
mmc_busy	equ	mmc_status.6		;set when the MMC is in BUSY state. Used in WRITE MODE

mmc_read_write_data equ	mmc_status.5		;indicates that BLOCK READ/WRITE cycle is in progress

mmc_error	equ	mmc_status.4		;any error in data transfer
						;MMC_R1 and MMC_R2 contain the error message
mmc_no_response	equ	mmc_status.3		;no response R1 was received
						;within required time
mmc_data_error	equ	mmc_status.2		;data error token received
						;during Block Read/Write command
mmc_no_data	equ	mmc_status.1		;no data block received from MMC

mmc_wrong_command equ	mmc_status.0		;Set by the commnds decoder, indicates not supported command		
;========						
mmc_cmd			ds	1		;mmc command - 6 bytes
mmc_addr_b3		ds	1		
mmc_addr_b2		ds	1
mmc_addr_b1		ds	1
mmc_addr_b0		ds	1
mmc_cmd_crc		ds	1
mmc_r1			ds	1		;mmc response - 2bytes
mmc_r2			ds	1
mmc_temp		ds	1		;temporary storage for MMC driver
mmc_temp1		ds	1
mmc_temp2		ds	1
mmc_data_pointer	ds	1		;pointer to the current address
						;of the SX data RAM
;SPIX data
spix_data_io		ds	1		;one-byte I/O data buffer/shift register
spix_temp		ds	1		;temporary storage
spix_shift_counter	ds	1		;I/O shift counter

;------------------------
;
		org     30h                     ;bank4 variables
analog          =       $                       ;pwm and ADC bank & timer	
;
analog_buff1	DS	1			;analog output buffer
analog_buff2	DS	1			;analog output buffer
	IF pcb_revision=0
;---------------
pwm0_out	EQU	analog_buff1.6		;pwm0 buffer
pwm1_out	EQU	analog_buff2.3		;pwm1 buffer
adc0_out	EQU	analog_buff2.0		;adc0 out buffer
;---------------
	ELSE
;---------------
pwm0_out	EQU	analog_buff1.6		;pwm0 buffer
pwm1_out	EQU	analog_buff2.2		;pwm1 buffer
adc0_out	EQU	analog_buff2.0		;adc0 out buffer
;---------------
	ENDIF
pwm0            DS      1                       ;pwm0 - value
pwm0_acc        DS      1                       ;     - accumulator
pwm1            DS      1                       ;pwm1 - value
pwm1_acc        DS      1                       ;     - accumulator
;
adc0_lo		DS      1                       ;adc0 - value low byte
adc0_hi		DS	1			;         "   high byte
adc0_count_lo	DS      1                       ;     - real-time count (lo)
adc0_count_hi	DS	1			;     -  "    "     "   (hi)
adc0_acc_lo	DS      1                       ;     - accumulator (lo)
adc0_acc_hi	DS	1			;     -     "	    (hi)
;
freq_low        ds      1                       ;frequency value low byte
freq_high       ds      1                       ;frequency value high byte
freq_accl       ds      1                       ;frequency accumulator low byte
freq_acch       ds      1                       ;frequency accumulator high byte
;===============
mmc_data_ram	ORG	$70		;MMC read/write data buffer(length of buffer=mmc_buffsize)
					; (e.g. in the case of 32 bytes it will occupy banks $50 & $70)
;**************************** INTERRUPT CODE *******************************
;
		ORG	0
;
;****** Virtual Peripheral: TIMER/FREQUENCY OUTPUT
;
; This routine adds a programmable value to a 16-bit accumulator (a pair of
;  two 8-bit registers) during each pass through the interrupt. It then
;  copies the value from the high bit of the accumulator to the
;  appropriate output port pin (LED, speaker, etc.)
;
;       Input variable(s) : timer_low,timer_high,timer_accl,timer_acch
;       		    freq_low,freq_high,freq_accl,freq_acch
;       Output variable(s) : LED port pin, speaker port pin
;       Variable(s) affected : timer_accl, timer_acch, freq_accl, freq_acch
;       Flag(s) affected : none
;	Size : 1 byte + 10 bytes (per timer)
;       Timing (turbo) : 1 cycle + 10 cycles (per timer)
;
:frequency
		bank    analog                  ;switch to timer (& adc/pwm) bank
;               clc                             ;only needed if CARRYX=ON
		add     freq_accl,freq_low      ;adjust freq's accumulator
		CLR	W			;zero W reg.
		SNC				;did we get an overflow in low byte?
		MOV	W,#1			;if so, prepare to adjust high byte
		ADD	W,freq_high		; (freq = 16 bits long)
		SZ				;if overflow for adjustment, skip ahead
		ADD	freq_acch,W		;adjust high byte of accumulator 
		SC				;skip ahead if timer triggered
		JMP	:done			;if not, jump ahead to end

:circ_buffer	MOV	W,next_samp_ptr		;load sample pointer
		MOV	FSR,W			; for indirect addressing
		MOV	W,ind			;load next sample
		BANK	analog			;reset bank to analog
		CLRB	FSR.7			; and make sure we're in lower 128reg. block
		IF stereo=0
		 MOV	pwm0,W			;convert it to audio	
		 MOV	pwm1,W			;   "	
		ELSE
		 SB	next_samp_ptr.0		;is this left channel?
		 MOV	pwm0,W			;yes, send out out left
		 SNB	next_samp_ptr.0		;otherwise, if it's right channel
		 MOV	pwm1,W			; send it out right
		ENDIF
		INC	next_samp_ptr		;advance pointer
		IF chip_type=0				;SX18/28
		 MOV	W,#buff_size*2+mmc_data_ram	;last address
		ELSE					;SX52
	 	 MOV	W,#mmc_data_ram+mmc_buffsize	;last address
		ENDIF
		MOV	W,next_samp_ptr-W		;test for end of buffer
		MOV	W,#mmc_data_ram			;pre-load new offset in case
		SNZ					;if not at end, skip ahead
		MOV	next_samp_ptr,W			;reset to start of buffer
		IF	chip_type=0
		 MOV	W,#$10			;keep to odd banks for SX18/28
		 OR	next_sample_ptr,W	;  "
		ENDIF
:done
;
;***** Virtual Peripheral: Pulse Width Modulators
;
; These routines create an 8-bit programmable duty cycle output at the
; respective pwm port output pins whose duty cycle is directly proportional
; to the value in the corresponding pwm register. This value is added to an
; accumulator on each interrupt pass interrupt. When the addition causes a
; carry overflow, the ouput is set to the high part of its duty cycle.
; These routines are timing critical and must be placed before any
; variable-execution-rate code (like the UART, for example).
;
;       Input variable(s) : pwm0,pwm0_acc,pwm1,pwm1_acc
;       Output variable(s) : pwm port pins
;       Variable(s) affected : port_buff, pwm0_acc, pwm1_acc
;       Flag(s) affected : none
;	Size : 3 bytes + 4 bytes (per pwm)
;		+ 4 bytes shared with adc code (see below)
;       Timing (turbo) : 3 cycles + 4 cycles (per pwm)
;			 + 4 cycles shared with adc code (see below)
;
;:set_analog	bank    analog                  ;switch to adc/pwm bank
		clr	analog_buff1		;zero pwm output buffer
		clr	analog_buff2		;zero pwm output buffer
:pwm0           add     pwm0_acc,pwm0           ;adjust pwm0 accumulator
		snc                             ;did it trigger?
		setb    pwm0_out		;yes, toggle pwm0 high
:pwm1		add     pwm1_acc,pwm1           ;adjust pwm1 accumulator
		snc                             ;did it trigger?
		setb	pwm1_out		;yes, toggle pwm1 high
;:update_analog	MOV	analog_port1,analog_buff1 ;update cap. charge/discharge pins
;		MOV	analog_port2,analog_buff2 ;update cap. charge/discharge pins
:end_pwms
;
	
		IF include_adc=1		;is this to be assembled in?

;***** Virtual Peripheral: Bitstream Analog to Digital Converters
;
; These routines allow an 8-bit value to be calculated which corresponds
; directly (within noise variation limits) with the voltage (0-5V) present
; at the respective adc port input pins. These routines are timing critical
; and must be placed before any variable-execution-rate code (like the UART,
; for example). The currently enabled routine (version A) has been optimized
; for size and speed, and RAM register usage, however a fixed execution rate,
; yet slightly larger/slower routine (version B) is provided in commented
; (disabled) form to simplify building other timing-critical virtual
; peripheral combinations (i.e. that require fixed rate preceeding code).
;    Note: if version B is selected, version A must be disabled (commented)
;
;       Input variable(s) : adc0_in_pin,adc0_acc_lo/hi,adc0_count_lo/hi
;       Output variable(s) : analog_port1,analog_port2,analog_buff1,analog_buff2
;       Variable(s) affected : adc0_out,adc0_acc_lo/hi,adc0_count_lo/hi
;       Flag(s) affected : none
;	Size :	20 + 4 bytes shared with pwm code (see above)
;       Timing : 5 cycle shared with pwm code (see above) +
;		   (a) [>99.5% of time] 15 cycles
;		   (b) [<0.5% of time] 20 cycles
;
;*** If the PWM routines are removed, the following 2 instructions must
;*** be enabled (uncommented) for the ADC routine to function properly:
;		bank    analog                  ;switch to adc/pwm bank

:adc0           SB	adc0_in_pin		;get current status of adc0
		SETB	adc0_out		;complement input to output
		SNB	adc0_in_pin		; "
		CLRB	adc0_out		; "
		SB	adc0_in_pin		;check if adc0 triggered?
		INCSZ   adc0_acc_lo		;if so, incr. 16-bit accumulator
		DEC     adc0_acc_hi             ; adjusting high byte as necessary
		INC	adc0_acc_hi		; but making sure not to

:adj_count	INCSZ	adc0_count_lo		;adjust 16-bit counter
		DEC	adc0_count_hi		; by skipping this instr.
		INC	adc0_count_hi		; when low byte overflows

:check_trig	SB	adc0_count_hi.resolution ;sample ready?
		JMP	:done_adcs		;if not, exit adc routine

:adc_ready	MOV	adc0_lo,adc0_acc_lo	;yes, copy new value
		MOV	adc0_hi,adc0_acc_hi	; into adc0 registers
		CLR	adc0_count_hi		;clear count (low already=0)		
		CLR	adc0_acc_lo		;clear accumulator (low)		
		CLR	adc0_acc_hi		;clear accumulator (high)		
:done_adcs

		ENDIF				;whether to include adc or not

:update_analog	MOV	analog_port1,analog_buff1 ;update cap. charge/discharge pins
		MOV	analog_port2,analog_buff2 ;update cap. charge/discharge pins

		MOV     W,#-int_period          ;interrupt every 'int_period' clocks
		RETIW				;exit interrupt
:end_int	
;
;******	End of interrupt sequence
;===========================================================================
;
;***************************** PROGRAM DATA ********************************
;
;****************************** SUBROUTINES ********************************
;
Sine_Table	JMP	PC+W			;Lookup sine value (32 bytes)
		RETW	128,153,174,199,219,234,246,254,255,254,246,234,219,199,174,153
		RETW	128,103,79,57,37,22,10,2,0,2,10,22,37,57,79,103
;Sine_Table_16	RETW	0,9,37,79,128,176,218,246,255,246,218,176,128,79,37,9
;
;******************************
;*** 8-BIT SPI Master VP - SPIX
;******************************

;SPIX DATA TRANSFER
;==================
;
;***Send the byte
spix_send		bank	mmc_spix_ram			;select SPIX RAM bank			
			setb	spix_busy			;set the bit indicating that 
								;SPIX started data transfer
			mov	spix_shift_counter,#$08		;set number of shifts
spix_send_loop							;CLK_HIGH
			clrb	spix_clk_pin			;set CLK_LOW

			movb	spix_out_pin,spix_data_io.7	;shift data out. CLK_LOW
			rl	spix_data_io			;CLK_LOW
			mov	spix_temp,#spix_rate		;transfer rate delay
spix_loop2		djnz	spix_temp,spix_loop2		;CLK_LOW

			setb	spix_clk_pin			;set CLK - HIGH
			mov	spix_temp,#spix_rate		;transfer rate delay
spix_loop3		djnz	spix_temp,spix_loop3		;CLK- HIGH

			djnz	spix_shift_counter,spix_send_loop	;check the bit counter
			clrb	spix_busy			;exit if done
			setb	spix_out_pin		
			ret
;get the byte
spix_get		bank	mmc_spix_ram			;select SPIX RAM bank			
			setb	spix_busy			;prepare to start
			mov	spix_data_io,#$ff		;preset all bits high
			STC					;including carry bit
			mov	spix_shift_counter,#$08		;set number of shifts
spix_get_loop							;CLK_High
			clrb	spix_clk_pin			;set CLK_LOW
			rl	spix_data_io			;CLK_LOW
			IF sx_freq_factor>1			;100 MHZ?
			 mov	spix_temp,#spix_rate		;transfer rate delay
spix_loop6		 djnz	spix_temp,spix_loop6		; while CLK_LOW
			ENDIF
			SB	spix_in_pin			;if new bit=1, then do nothing
			CLRB	spix_data_io.0			; otherwise, clear it accordingly
			setb	spix_clk_pin			;set CLK_HIGH

			IF sx_freq_factor>1			;100 MHZ?
			mov	spix_temp,#spix_rate		;transfer rate delay
spix_loop8		djnz	spix_temp,spix_loop8		;while CLK_High
			ENDIF
			djnz	spix_shift_counter,spix_get_loop;check the bit counter
			setb	spix_out_pin			;exit if done
			clrb	spix_busy
			ret
;End of SPIX VP
;***********************
;
;*** SPIX port initialization
;
spix_init	setb	spix_clk_pin
		setb	spix_out_pin
		setb	spix_cs_pin
		RET

;*** Set MMC data address = 0
zero_MMC_addr	CLR	mmc_addr_b3			;set the block address to read (hi byte first)
		CLR	mmc_addr_b2			; "
		CLR	mmc_addr_b1			; "
		CLR	mmc_addr_b0			; (lo byte last: always = 0 since 512 bytes/block)
		RET
;***********************  MMC COMMAND INTERPRETER **********************
;
;FUNCTIONS are not MMC card commands, but sequences of the commands. 
;The MMC VP currently implements four separate FUNCTIONS
; - synchronize
; - initialize
; - read the data block
; - write the data block
	
mmc_synchronize		equ	$FF		;send 80 SPI_clk signals for initial synchronization
mmc_initialize		equ	$FE		;card initialization procedure
						; flow chart (exept for synchronization)
mmc_block_read_command	equ	$FD		;Complete implementation of the block read flow chart
						;Send the Read Block Command and set the
						;mmc_read_write_data flag in the MMC_STATUS byte
						;The MMC /CS line is left active (LOW) until the data 
						;are read. Get the data block from MMC and 
						;finishes the read block cycle by setting MMC /CS 
mmc_block_write_command	equ	$FA		;complete implementation of the block writing
;
;MMC COMMANDS. 
;Implemented MMC commands are used as FUNCTION calls
;Other commands listed here (but currently unimplemented) are presented
; mainly for reference. These commands are described in detail in the MMC manual.

mmc_go_idle_state	equ	$40		;CMD0	;these three command are used as a part
mmc_send_op_cond	equ	$41		;CMD1	;of INITIALIZE function
mmc_set_blocklen	equ	$50		;CMD16

mmc_read_single_block	equ	$51		;CMD17
mmc_write_block		equ	$58		;CMD24 

;-----------------------
;MMC commands currently NOT implemented:
;mmc_send_status	equ	$4d		;CMD13 assuming that status data come in the
						;R2 bytes
;mmc_send_csd		equ	$49		;CMD9 
;mmc_send_cid		equ	$4a		;CMD10 

;mmc_program_csd	equ	$5b		;CMD27 
;mmc_set_write_prot	equ	$5c		;CMD28 
;mmc_clr_write_prot	equ	$5d		;CMD29 
;mmc_send_write_prot	equ	$5e		;CMD30 
;mmc_tag_sector_start	equ	$60		;CMD32 
;mmc_tag_sector_end	equ	$61		;CMD33 
;mmc_untag_sector	equ	$62		;CMD34 
;mmc_tag_erase_group_start equ	$63		;CMD35 
;mmc_tag_erase_group_end equ	$64		;CMD36 
;mmc_untag_erase_group	equ	$65		;CMD37 
;mmc_erase		eque	$66		;CMD38 
;mmc_crc_on		equ	$67		;CMD59 

;******************** MMC Subroutines *********************************
;
;*** MMC Wait
;Sometimes a delay is needed for internal MMC processes
mmc_long_wait	MOV	W,#$FF			;longest delay
mmc_wait	MOV	mmc_temp1,W		;selectable delay (loaded in W prior to call)
mloop49		mov	mmc_temp,#$FF		;set wait cycles counter			
mloop69		djnz	mmc_temp,mloop69	;inner delay loop
		djnz	mmc_temp1,mloop49	;outer delay loop
		ret				;exit

;*** MMC Send "DUMMY" Bytes
;This subroutine generates a number of DUMMY byte transfer cycles on the SPI bus.
;The number of cycles should be specified in MMC_TEMP prior to the call.
;These dummy cycles are required by the MMC card data transfer protocol.
;
mmc_delay		bank	mmc_spix_ram
			call	spix_get
			djnz	mmc_temp,mmc_delay
			ret

;*** MMC Send Command
;This Subroutine sends a 6 byte MMC command string starting with the MMC_CMD byte
;and finishing by CRC byte
;
mmc_cmd_send		bank	mmc_spix_ram	;select SPIX RAM bank
			mov	mmc_temp,#mmc_cmd
mmc_loop2		mov	fsr,mmc_temp
			mov	w,ind		;load next COMMAND byte to
						;the SPIX I/O buffer using index addressing mode
			mov	spix_data_io,w
			call	spix_send	;send the byte
			inc	mmc_temp
			cjne	mmc_temp,#(mmc_cmd + 6),mmc_loop2
			setb	spix_out_pin	;loop until all six bytes sent
			ret
			
;*** MMC Get Response R1
;Get the response byte from the MMC. Wait for the response R1 for x=mmc_r1_wait_cycles
;byte cycles. If the R1 is not recieved or R1 is not $00, set the error flag.
;Number of wait cycles is recommended to be 2, but here we use 32 for safety.
;The routine can return two errors: (1) no response at all, (2) non zero response 
;
mmc_r1_get		bank	mmc_spix_ram			;select the RAM bank
			mov	mmc_r1,#$ff			;initialize the byte to FF
			mov	mmc_temp,#resp_r1_delay		;set delay counter			
mmc_loop3		call 	spix_get			;read the byte
			cjne	spix_data_io,#$FF,mmc_loop4	;if the byte is "FF" then repeate
								;mmc_r1_wait_cycles times
			djnz	mmc_temp,mmc_loop3
			setb	mmc_no_response			;no response
			ret

mmc_loop4		test	spix_data_io			;test if not zero
			sz
			setb	mmc_error			;set an error flag	
			mov	mmc_r1,spix_data_io
			ret

;******************** MMC Command Function Interpreter *******************
;This is the MMC command decoder subroutine. This is an optional structure
;The actual commands can be executed separately. The use of this structure
;is to reduce the number of subroutines (i.e. CALLs) and replace them by JMPs
;
mmc_execute		bank	mmc_spix_ram
			cje	mmc_cmd,#mmc_block_read_command,mmc_cmd_block_read
			cje	mmc_cmd,#mmc_block_write_command,mmc_cmd_block_write
			cje	mmc_cmd,#mmc_synchronize,mmc_cmd_synchronize
			cje	mmc_cmd,#mmc_initialize,mmc_cmd_initialize
	
			setb	mmc_wrong_command
mmc_exit		ret

;****************************** MMC Functions ****************************

;*** Read Data Block (Function $51)
;upon the reception of this command the MMC will start to access the data block.
;the data will be ready within 1.5 msec
;
mmc_cmd_block_read	
			bank	mmc_spix_ram		;Select the RAM Bank
			clrb	spix_cs_pin		;set MMC_CS active (LOW)
;			mov	mmc_temp,#blk_rd_delay1	;do a DUMMY read cycle delay
;			call	mmc_delay		;
			clr	mmc_status
:do_read		mov	mmc_cmd,#$51		;send the BLOCK READ Command
			call	mmc_cmd_send		
			call	mmc_r1_get
			test	mmc_r1			;MMC response is not Zero, exit	
			jnz	mmc_read_exit	

;******************* Continue Read Cycle, Read The Data *********************
;Wait for the Start Byte.
;Wait time is set to (mmc_read_wait_cycles * $FF).  Total wait time sould be 1.5ms+.

mmc_read_data_block	bank	mmc_spix_ram		;select the RAM bank
			setb	mmc_read_write_data

;await data start byte = $FE
			mov	mmc_temp1,#blk_rd_wait	 	;load delay value	
mmc_label40		mov	mmc_temp,#$FF			;set wait counter			
mmc_label41		call 	spix_get			;wait for data start byte = $FE
			cje	spix_data_io,#$fe,mmc_label42	;End waiting if Start Byte
			cjne	spix_data_io,#$ff,mmc_label44	;End waiting if Error Message
			djnz	mmc_temp,mmc_label41		;inner wait loop
			djnz	mmc_temp1,mmc_label40		;outer wait loop

			setb	mmc_no_data			;waiting time expiered - error
			jmp	mmc_read_exit			;exit

mmc_label44		mov	mmc_r2,spix_data_io		;Error message is in R2
			setb	mmc_data_error			;MMC sent the Data Error Token
			jmp	mmc_read_exit			;the token will be placed in MMC_R2
				
;Read MMC Data (if no errors occurred): 
;This routine should be used as an example only. It transfers a block of data from MMC card
;placing the data bytes in the data storage buffer. Since the buffer is smaller than the MMC
;block size (i.e. data not being streamed), the 512 bytes coming from the MMC will
;repeatedly overwrite the buffer locations with the data steaming from MMC card.
;At the end of the cycle the last 16 (32) bytes of 512 MMC data block will be located
;in the SX data buffer

mmc_label42		mov	mmc_temp2,#mmc_blk_size/mmc_buffsize
								;set number of cycles 
mmc_label43		mov	mmc_temp,#mmc_buffsize		;set the byte counter and 
			IF chip_type=0				;SX18/28
			 MOV	W,#buff_size*2+mmc_data_ram-1	;last address
			ELSE					;SX52
			 MOV	W,#mmc_data_ram+mmc_buffsize-1	;last address
			ENDIF
			mov	mmc_data_pointer,W		;data pointer for incoming data

:mmc_read_byte		call	spix_get			;data read loop

;store incoming bytes into the data (circular) buffer
			INC	mmc_data_pointer
			IF chip_type=0				;SX18/28
			 MOV	W,#buff_size*2+mmc_data_ram	;last address
			ELSE					;SX52
		 	 MOV	W,#mmc_data_ram+mmc_buffsize	;last address
			ENDIF
			MOV	W,mmc_data_pointer-W		;test for end of buffer
			MOV	W,#mmc_data_ram			;pre-load new offset in case
			SNZ					;if not at end, skip ahead
			MOV	mmc_data_pointer,W			;reset to start of buffer
			IF	chip_type=0
			 MOV	W,#$10				;keep to odd banks for SX18/28
			 OR	mmc_data_pointer,W		;  "
			ENDIF
:wait_buff_space	MOV	W,next_samp_ptr			;load output pointer
			MOV	W,mmc_data_pointer-W		;and check if buffer full
			SNZ					;if not, skip ahead
			JMP	:wait_buff_space		;wait till there's room

			mov	mmc_temp_data,spix_data_io	;move data to global storage	
			mov	fsr,mmc_data_pointer		;use index register and W
			mov	w,mmc_temp_data			;to move the date from SPIX temp data reg.
			mov	ind,w				; to MMC data buffer

			BANK	mmc_spix_ram			;reset to variables bank
			CLRB	FSR.7				; and make sure we're in lower 128reg. block
			djnz	mmc_temp,:mmc_read_byte		;decrement byte counter
			djnz	mmc_temp2,mmc_label43		;decrement block counter
			call	spix_get			;"read" two bytes CRC
			call	spix_get			;the CRC is not implemented and thus the data are not used

mmc_read_exit		call 	spix_get			;DUMMY read cycle
;			call 	spix_get			;DUMMY read cycle
			clrb	mmc_read_write_data		;end the transfer and exit
			setb	spix_cs_pin			;set MMC to inactive state
			jmp	mmc_exit			;done
				
;******************* Send CMD24 ($58) (WRITE BLOCK) to MMC  *********************
;this is the first part of the BLOCK WRITE sequence - the command itself and the R1
;response from the MMC
	
mmc_cmd_block_write	bank	mmc_spix_ram		;select the RAM bank
			clr	mmc_status		;clear the Status byte
			clrb	spix_cs_pin		;set MMC_CS active (LOW)

;execute CMD16
:set_block_length	mov	mmc_temp,#$10		;fixed delay
			call	mmc_delay
			call	spix_get		;"DUMMY" byte
			mov	mmc_cmd,#$58		;send the command
			call	mmc_cmd_send
			call	mmc_r1_get		;get the response
			call	spix_get		;"DUMMY" byte
			test	mmc_r1			;if response was 00 then start
			jz	mmc_write_data_block
			jmp	mmc_write_exit		;end the transfer and exit

;*** WRITE CYCLE: Write the data block to MMC
;Send the Start Byte=$FE, wait for response (R1 type) 
;Wait time= (mmc_write_wait_cycles * $FF).  Total wait time should be =>1.5ms
;
mmc_write_data_block	call	spix_get
			mov	spix_data_io,#$fe	;send the start byte $FE
			call	spix_send

;send the 512 data bytes the from the SX write data buffer. The buffer can be 16 or 32 bytes
;long. 
;In a real wold when the buffer is empty, the program should wait untill the MAIN fills the buffer 
;and returns to the black transfer again
;This program just sends the incrementing counter to the the MMC card
;It is intended to demonstrate the timing and the logic of the data transfer
;This part of the program heavily depends on the higher level program artichecture 

;			clr	mmc_temp_data			;This is temp location for the Demo
			mov	mmc_temp1,#mmc_blk_size/mmc_buffsize
mmc_label38		mov	mmc_temp,#mmc_buffsize		;set the byte counter and 
			mov	mmc_data_pointer,#mmc_data_ram	;data pointer for the data
mmc_label39		
;this part of the program was tested and it works, but is not used for this demo
;			mov	fsr,mmc_data_pointer		;move the byte from MMC_DATA_RAM 
;			mov	w,ind				;to MMC_SPIX_RAM using global
;			mov	mmc_temp_data,w			;location at $08 - MMC_TEMP_DATA
;			bank	mmc_spix_ram			
;			mov	spix_data_io,mmc_temp_data		

;this is just for the Demo - write incrementing values to MMC offset addresses
			bank	mmc_spix_ram			;switch to spix control ram bank
			mov	spix_data_io,mmc_temp_data	;Increment the counter and sent it to MMC card		
			INC	mmc_temp_data			;write increading values: DEMO only
	
			call	spix_send			;send the byte			
			inc	mmc_data_pointer		;point to next data address
			or	mmc_data_pointer,#$10		;keep bank address in %xxx1000 range
			djnz	mmc_temp,mmc_label39		;decrement byte counter
			djnz	mmc_temp1,mmc_label38		;decrement SX RAM blocks counter
			call	spix_get			;read the two byte "CRC"
			call	spix_get			; "											
			call	mmc_r1_get			;get the response on the data block
			call	spix_get			;the R1 in this case is not a typical one
			clrb	mmc_error			;error detection in MMC_GET_R1 is not
			and	mmc_r1,#$0f			;applicable
			cje	mmc_r1,#$05,mmc_label37		
			jmp	mmc_write_error			;error in the data - exit

;continue BLOCK WRITE dummy read cycles until MMC's BUSY signal is clear
mmc_label37		setb	mmc_busy			;set wait cycles counter
			mov	mmc_temp1,#blk_wr_delay	
mmc_label36		mov	mmc_temp,#$FF
mmc_label35		call	spix_get					
			jb	spix_in_pin,mmc_write_done	;wait for the BUSY signal to clear
			djnz	mmc_temp,mmc_label35
			djnz	mmc_temp1,mmc_label36
			jmp	mmc_write_error			;error exit - time expired
mmc_write_done		clr	mmc_status			;no-error exit
			jmp	mmc_write_exit
mmc_write_error		setb	mmc_error			;exit on error
			setb	mmc_data_error
mmc_write_exit		call	spix_get			;DUMMY read cycle
			clrb	mmc_read_write_data
			setb	spix_cs_pin
			jmp	mmc_exit
	
;*** MMC Synchronization
;send $FF data to the MMC 10 times. Only called after power-up
;
mmc_cmd_synchronize	bank 	mmc_spix_ram			;select the RAM bank		
			CALL	spix_init			;initialize SPI	I/O pins	
			clr	mmc_status			;initialize the MMC driver
			clr	mmc_r1
			clr	mmc_r2
			mov	mmc_temp,#sync_duration		;get n bytes as a delay
mmc_lable1		setb	spix_out_pin			;in case of non-zero response from MMC	
			call	spix_get
			cjne	spix_data_io,#$ff,mmc_lable2
			djnz	mmc_temp,mmc_lable1
mmc_lable2		mov 	mmc_r1,spix_data_io
			call 	spix_get			;DUMMY read cycle
			jmp	mmc_exit
			
;*** MMC Initialization
;This function executes CMD0 and CMD1 with corresponding R1 MMC response detection
;
mmc_cmd_initialize	clrb	spix_cs_pin		;set MCC_CS active (low)
			bank	mmc_spix_ram		;init all the variables
			clr	mmc_status		;clear the status byte
			CALL	zero_MMC_addr		;set MMC data address to 0
			mov	mmc_cmd_crc,#$FF

							;execute the CMDO command, exit if R1 is not equal to $01
			mov	mmc_temp,#$08
			call	mmc_delay		;execute 8 DUMMY cycles

			mov	mmc_cmd,#$40		;execute CMD0			
			call	mmc_cmd_send
			call	mmc_r1_get		;get a response byte back

			mov	mmc_temp,#$10
			call	mmc_delay		;execute 16 DUMMY cycles
			cjne	mmc_r1,#$01,mmc_label14	;correct R1 is equal to #$01
;The above code segment seems to go against MMC specs. The MMC isn't supposed to give
; a $00 response on this command but it seems to if we try to repeat it.
;The correct (from the manual point of view) sequence should look like following, 
; (but it does not work):
;			cje	mmc_r1,#$01,mmc_label15	;correct R1 is equal to #$01
;			setb	mmc_error		;if not - set the error flag
;			jmp	mmc_label14		;and exit
			
;execute the CMD1 command until R1==00 or MMC_CMD1_WAIT times

mmc_label15		mov	mmc_temp2,#cmd1_resp_wait ;load wait duration for response
mmc_label13		clr	mmc_status		;reset error status
			mov	mmc_temp,#cmd1_delay1	;send n (=8?) DUMMY bytes
			call	mmc_delay		
			mov	mmc_cmd,#$41		;send CMD1 code		
			call	mmc_cmd_send
			call	mmc_r1_get		;get the response
			mov	mmc_temp,#cmd1_delay2	;load delay
			call	mmc_delay		;send n (=16?) DUMMY bytes
			MOV	W,#cmd1_delay3		;load delay value
			call 	mmc_wait		;wait for some time
			test	mmc_r1			;check for response
			jz	mmc_label14		;correct R1 is equal to #$00
			djnz	mmc_temp2,mmc_label13	;if not - repeat mmc_cmd1_wait times
			
mmc_label14		setb	spix_cs_pin		;set MMC_CS to inactive (HIGH)
			jmp	mmc_exit		;exit
			
;===========================================================================
;
;*************************** Main Program Code *****************************
;
;********
;* Main *
;********
;
;		ORG     100h
;
Reset_entry
		IF chip_type=1			;SX52
		 MOV	FSR,#$0A		;load first offset
		ELSE				;SX52
		 CLR	FSR			;point to beginning of RAM
		ENDIF
:zero_ram       
		IF chip_type=0
		 SB	FSR.4			;are we on low half of bank?
		 SETB	FSR.3			;If so, don't touch regs 0-7
		ENDIF
		CLR     IND                     ;clear using indirect addressing
		IJNZ    FSR,:zero_ram           ;repeat until done

:setup_regs	MOV	RA,#RA_init		;initialize port RA
		MOV     !RA,#RA_IO		;Set RA in/out directions
		MOV	RB,#RB_init		;initialize port RB
		MOV     !RB,#RB_io		;Set RB in/out directions
		MOV	RC,#RC_init		;initialize port RC
		MOV     !RC,#RC_io		;Set RC in/out directions
		MOV     !OPTION,#option_init	;initialize OPTION reg.

:initialize
		BANK	analog                  ;switch to adc/pwm/timer bank
		MOV	W,#pwm0_init		;get initial pwm0 voltage
		MOV	pwm0,W			; and store it
		MOV	W,#pwm1_init		;get initial pwm0 voltage
		MOV	pwm1,W			; and store it
		MOV	W,#samp_freq&$FF	;keep low byte of sample rate
		MOV	freq_low,W		;store it
		MOV	W,#samp_freq>>8		;keep high byte of sample rate
		MOV	freq_high,W		;store it
		IF chip_type=0				;SX18/28
		 MOV	W,#buff_size*2+mmc_data_ram-2	;last address (-2 to keep stereo syncing)
		ELSE					;SX52
		 MOV	W,#mmc_data_ram+mmc_buffsize-2	;last address (-2 to keep stereo syncing)
		ENDIF
		MOV	next_samp_ptr,W			;set sample pointer to start

		CALL	spix_init		;SPIX port pin default states
		CALL	zero_MMC_addr		;reset MMC data address pointers
;
;************** Main program loop
Mainloop	

:sync		bank	mmc_spix_ram
		mov	mmc_cmd,#$ff		;execute the SYNCHRONIZE function
		call	mmc_execute
:init		mov	mmc_cmd,#$fe		;execute the INITIALIZE function
		call	mmc_execute
		test	mmc_status	
		jnz	:sync			;repeat in case of syncronization/initialization error

:write		mov 	mmc_cmd,#$FA		;execute the 512 byte block write command
;		call	mmc_execute		;uncomment this to write the block
		test	mmc_status		;check how write procedure went
		sz
		JMP	:error			;exit if we got an error writing data block

:read_init	CALL	zero_MMC_addr		;reset MMC data address pointer => 0
:read_loop	mov	mmc_cmd,#$FD		;execute the block read command
		call	mmc_execute
		test	mmc_status		;check how read procedure went
		SZ				;
		JMP	:error			;exit if we got an error reading data block

		INC	mmc_addr_b1		;count through multiple blocks
		INCSZ	mmc_addr_b1		; (b1=b1+2 => advance 512 bytes at a time)
		JMP	:read_loop 		;keep reading

	IF MMC_size<16
		INC	mmc_addr_b2		;continue through multiple blocks
		MOV	W,#end_of_mem		;load end of storage memory (or sample size)
		MOV	W,mmc_addr_b2-W		; and see if we're there yet
	ELSE					; for MMC cards >16Meg
		INCSZ	mmc_addr_b2		;continue through multiple blocks
		JMP	:read_loop 		;keep reading
		INC	mmc_addr_b3		;continue through multiple blocks
		MOV	W,#end_of_mem		;load end of storage memory (or sample size)
		MOV	W,mmc_addr_b3-W		; and see if we're there yet
	ENDIF
		SZ				;if so, skip ahead
		JMP	:read_loop 		;otherwise, keep looping

:done_read	JMP	:read_init		;run through the whole read again
		
:error		JMP	MainLoop		;in case of read/write error, re-synchronize
;
;***********************************************
		END