/*
	MG2DMAPW.C
	
   This sample program uses DMA channel 0 for sending data out to PIODA
   of the BL15xx/MicroG2. Clock CKA1 is used to drive \DREQ0 to clock the
   data out.

   For a single shot transfer of a data array, the following call
   is sufficient:

			DMA0_MIO(PIODA, array, size, 0, 0);    // set up the DMA
			outport(CNTLB1, 0x06);					   // start the clock CKA1

   In this sample program, the DMA memory to IO transfer is extended
   to create a crude DAC port. The output pin should be connected to
   an RC circuit which effectively integrates the amount of ON time
   of the output. The realized voltage is proportional to the number
   of ON (high) pulses on the data array. To keep the DAC running
   continuously, 2 copies of the output pattern are kept in contiguous
   memory space. Once the DMA's high page source address (SAR0H) crosses
   into the second page, it is realigned back to its equivalent location
   in the high page source address of the first page. This is done
   without changing the low page source address (SAR0L), which should
   be the same for both pages. The DMA counter is also reloaded.

   Periodic checking of the DMA's high page is needed to make sure that
   the DMA do not count out the 2 copies of the pattern. For example, if
   the size of one pattern is 2048, and the clock rate is set for
   sysclock/640 = 14.4 kh. The dma's should be checked at most every
   2 x 2048 / 14400 = 0.284 seconds. 

   For a data array of 2048, a crude DAC of 2mv resolution is realized.
   Unfortunately, this scheme requires 3 times the size of the pattern array
   to make sure that the low address falls in even boundary so that only
   the high address need be changed.
   
   Note that DMA operates by stealing cycles from the CPU. This effectively 
   slows down the execution speed for the processor. A DMA byte transfer is
   from 7 to 10 clocks. The reduction in CPU computing speed can be
   approximated as: DMA clocks per cycle/ CKA1 clock divide.
   
   At 10 clocks per cycle and CKA1 divide of 640, the reduction in speed
   is 10/640 = 0.0156 or 1.56%. Note that with divide of 40, the % reduction
   becomes 0.25 or 25%. So be careful not to make the clock rate too fast.

   This sample program is tested with the MicroG prototyping board with the
   output measured at TP9 of the RC filter (10K, 4.7uf or 47 ms time constant).

   Connect P1A5 to TP5 and monitor the voltage on TP9 (between the buzzer and
   the capacitor).
*/

#define SIZE 2048   
#define ARRAYSIZE 2048
#use vdriver.lib
#use dma.lib

void set_dutycycle( float dutycycle );
void set_duty(float fraction);	// set the duty cycle as a fraction of 1
void testv(float v);					// set the voltage output
int dma_pattern(int oncount);		// generate pattern based on number of oncount
char pattern[3*ARRAYSIZE];			// allocate 3 times the arraysize to find the
											// even boundary for pages 1 and 2.
unsigned long pattern_base;		// absolute address for start of first page
unsigned int base_offset;			// offset to first page for logical mem address

float logcycle;
float deltacycle;

main()
{
int i;
unsigned int imask;

	VdInit();					// call up virtual drivers for costate

	logcycle = 0.05;
	deltacycle = 1.10;
		
	base_offset = 0;
	pattern_base = phy_adr(pattern);
	imask = ARRAYSIZE - 1;	

	while(pattern_base & imask)
	{
		pattern_base++;		// adjust boundary to next even ARRAYSIZE byte block
		base_offset++;
	}
	
	resPIOCA(0xff);						// PIODA is set as output port
	DMA0_MIO(PIODA, pattern_base, 0xffff, 0, 0);
	outport(CNTLB1,0x06);				// clock is set for sysclock()/640 
	
	while(1)
	{
		costate
		{
			waitfor(DelayMs(5L));		// change duty cycle every 5 ms
			logcycle = logcycle * deltacycle;
			if(logcycle > .9)deltacycle = 0.90;
			else if(logcycle < 0.05) deltacycle = 1.10;
			set_duty(logcycle);
		}

		costate
		{
			waitfor(DelayMs(10L));		// realigned DMA every 10 ms
			realign_dma();
		}
				
		hitwd();
		runwatch();
	}
}

// Set the on time of the duty cycle as a fraction of 1; e.g. 0.50 is 50%.

void set_duty(float fraction)
{
	dma_pattern(fraction * ARRAYSIZE);
}

// Set the output voltage measured at the end of an RC filter.

void testv(float v)
{
	set_duty( v/4.96 );		// voltage out is a fraction of max output 
}

// Check the source high address of DMA0 and realign to the first pattern
// copy when it crossed over to the second pattern.		

#asm
realign_dma::
		xor	a							; clear carry flag
		in0	a, (SAR0H)				; read in high address of DMA source address
		ld		hl, pattern_base		; get base address of data array
		inc	hl							; increment to point to location of high address
		sub	(hl)						; a =  (SAR0H) - high address of base
		ld		de, ARRAYSIZE			; get the size of the array
		cp		d							; compare a to d
											; if a > d, the dma high source address is in the second	page
											; if a <= d, the dma high source address is still in first page 
		ret	c							; if a <= d, just return
		in0	a, (SAR0H)				; get the current high address of the second page
		sub	d							; subtract array size to get high address of the first page 
		out0	(SAR0H), a				; set the high address
		ld		a, 0xff					; reload dma counter
		out0	(BCR0H), a				; 
		ret
#endasm

// dma_pattern( int on_count)
// This function distributes the ON and OFF times across the span of the pattern
// instead of just one section of ON and one section OFF. Portions are
// allocated based on the ratio of ON and OFF time until one of them runs out. 
// The pattern is replicated into the second copy for dma output.

int aimore;
int ailess;
char amarkmore;
char amarkless;
unsigned int aratio;

#asm
dma_pattern::
			ld			a, l
			or			h
			jr			z, all_zeroes	; if on_count is zero, zero the whole pattern
			bit		7, h
			jr			nz, all_zeroes ; if negative on_cout, zero the whole pattern
			push		hl					; keep a copy
			ld			de, ARRAYSIZE

			xor		a
			sbc		hl, de
			pop		hl					; restore the count
			jr			z, all_ones		; if on_count == ARRAYSIZE, set whole pattern to 0xff
			jr			nc, all_ones	; if on_count > ARRAYSIZE, set whole pattern to 0xff
			jr			get_ratio		; else get ratio of on to off 
all_ones:
			ld			a, 0xff			; load 0xff to all data
			jr			load_full
all_zeroes:
			xor		a					; load 0x00 to all data
load_full:			
			ld			hl, (base_offset)		; get base offset
			ld			de, pattern		
			add		hl, de					; get logical address of base
			ld			(hl), a
			ld			d, h
			ld			e, l
			inc		de
			ld			bc, ARRAYSIZE-1
			ldir
			jp			merge2

get_ratio:										; get ratio of more against less count
			push		hl							; keep a count
			ld			de, ARRAYSIZE
			xor		a
			rr			d
			rr			e
			xor		a
			sbc		hl, de
			pop		hl							; restore the count
			jr			nc, markmore
markless:
			ld			(ailess), hl
			ld			de, ARRAYSIZE
			ex			de, hl
			xor		a
			sbc		hl, de					; (hl) has more count
			ld			(aimore), hl
			xor		a
			ld			(amarkmore), a			; more data is set to 0x00
			ld			a, 0xff
			ld			(amarkless), a			; less data is set to 0xff
			jr			merge
markmore:
			ld			(aimore), hl
			ld			de, ARRAYSIZE
			ex			de, hl
			xor		a
			sbc		hl, de
			ld			(ailess), hl
			xor		a
			ld			(amarkless), a			; less data is set to 0x00
			ld			a, 0xff
			ld			(amarkmore), a			; more data is set to 0xff
merge:
			xor		a

			ld			hl, (aimore)			; get ratio of more count to less
			ld			de, (ailess)
			ld			bc, 0x0000
loop1:
			sbc		hl, de
			jr			nc, next_dividend
			add		hl, de					; restore last divisor
			ld			(aratio), bc			; store the dividend
			jr			get_residue

next_dividend:
			inc		bc
			ld			a, h
			or			l
			jr			nz, loop1
			ld			(aratio), bc
			exx
			ld			bc, 0x0000				; residue is zero in alternate
			exx
			jr			set_array

get_residue:

			xor		a							; get residue of more count to less
			ld			b, 16
			exx									; use alternate
			ld			bc, 0x0000				; residue is in alternate bc
			exx									; back to regular
	
loop2:
			xor		a
			rl			l
			rl			h
			exx									; use alternate
			xor		a
			rl 		c
			rl			b
			exx									; use regular
			xor		a							; clear carry
			sbc		hl, de
			jr			nc, mask_1
mask_0:
			xor		a							; clear carry
			add		hl, de					; restore current value of hl
			jr			nxt_dec		
mask_1:
			ld			a, 0x01
			exx									; use alternate
			or			c
			ld			c, a
			exx									; use regular
nxt_dec:
			djnz 		loop2				


set_array:										; set the pattern base on ratio
			ld			de, pattern				; of more count to less, adjusting
			ld			hl, (base_offset)		; for accumulation if the ratio is
			add		hl, de					; is not a whole number
			push		hl
			exx									; use alternate
			pop		hl							; pointer to array is in alternate hl
			ld			de, 0x0000				; use alternate for accum
													; alternate bc has the residue
			exx
						
			ld			bc, ARRAYSIZE			; size of one copy
loop3:
			exx									; use alternate
			ex			de, hl					; get residue accumulation to hl
			add		hl, bc					; add residue to accumulation
			ex			de, hl					; copy back to hl
			exx
			jr			nc, load_more			; if no carry, update more count

			ld			a, (amarkmore)			; get the data for mark more
			exx									; use alternate
			ld			(hl), a					; copy to array location
			inc		hl							; increment array pointer
			exx									; use regular
			dec		bc							; decrement loop counter
			ld			hl, (aimore)			; get the more counter
			dec		hl							; decrement
			ld			(aimore), hl			; copy to (aimore)
			ld			a, l						; check if done for this counter			
			or			h
			jr			nz, load_more			; if not load_more
			jr			stuff_less				; else stuff with less count	

load_more:
			ld			a, b						; check if there is more count left
			or			c
			jr			z, merge2				; if no more, go to merge2			
			
			ld			de, (aratio)			; get whole ratio of more to less
loop4:
			ld			a, (amarkmore)			; load aratio number of more per 1 of less
			exx
			ld			(hl), a					; load more data to array
			inc		hl							; increment array pointer
			exx
			dec		bc
			ld			hl, (aimore)
			dec		hl
			ld			(aimore), hl
			ld			a, l
			or			h
			jr			z, stuff_less			; if more is done, jump to stuff_less
			dec		de
			ld			a, e
			or			d
			jr			nz, loop4

load_less:
			ld			a, b
			or			c
			jr			z, merge2				; make sure we did not run out of count
			ld			a, (amarkless)			; less data
			exx	
			ld			(hl), a					; load less data to array
			inc		hl							; increment array pointer
			exx
			dec		bc							; decrement array counter
			ld			hl, (ailess)
			dec		hl							; decrement less counter
			ld			(ailess), hl
			ld			a, l
			or			h
			jr			z, stuff_more			; if less count is 0, fill rest with more data		
			ld			a, b
			or			c							
			jr			nz, loop3				; continue to loop if array count is not zero

stuff_more:
			ld			a, (amarkmore)			; use more data to fill rest of array
			ld			e, a
			jr			merge_stuff
stuff_less:
			ld			a, (amarkless)			; use less data to fill rest of array
			ld			e, a
merge_stuff:
			ld			a, b
			or			c
			jr			z, merge2
			
			exx
			push		hl
			exx
			pop		hl
loop5:											; fill in whatever is left in the data array
			ld			(hl), e
			inc		hl
			dec		bc
			ld			a, b
			or			c
			jr			nz, loop5
			
merge2:											; copy first page pattern to seconde page
			ld			bc, ARRAYSIZE
			ld			hl, (base_offset)
			ld			de, pattern
			add		hl, de
			push		hl					; save a copy of source
			ld			de, ARRAYSIZE
			add		hl, de
			ex			de, hl			; destination in de
			pop		hl
			ldir							; copy first page to second page
			ret
#endasm