/* Listing 2 */

/*****************************************************
	ADCDMA.C 

	Routines for Data acquisition through DMA on the
	Lab Master AD.

	Copyright Don Bradley, 1991.

	Permission is granted for used of these routines
	in any manner as long as this copyright notice is
	included.

	Tested using Quick C 2.5 and MSC 6.0 on a 
	Toshiba T5200.

 *****************************************************/

#include <io.h>
#include <errno.h>
#include <conio.h>

#include "dma.h"
#include "labmastr.h"

// DMA buffer
static int far *dma_buffer;

// DMA buffer index
static unsigned int dma_buff_index;
// previous DMA buffer index
static unsigned int old_dma_buff_index;

// number of conversions to collect
static long num_to_collect;
// number of conversions collected
static long num_collected;

// DMA finished flag
static int dma_finished;
// DMA channel number
static int dma_chn;

void (*process)(int);

/*****************************************************
	init_adc_dma is a routine used for dma controlled 
	analogue voltage input.

	called by:

	init_adc_dma(num_chn, num_samples, *freq, dma_chn,
				 proc_func);

	where

		int num_chn			# of channels to sample. 
							Start channel is always 
							chn 0. Thus chans collect 
							are 0 ... num_chn-1
		long num_samples	# of samples to collect. 
							A sample is the group of 
							channels
		double *freq		Frequency of sampling in Hz.
		int dma_chn			DMA channel number from 5-7.
		void(*proc_func)(int) process function to be 
		                      called after each value is
							  retrieved.
 *****************************************************/
int init_adc_dma(unsigned int num_chn, 
		long num_samples, double *freq, int dma_channel,
		void (*proc_func)(int))
/*& Setup adc for dma transfer. */
	{
	long length;
	unsigned int i;
	unsigned int dma_mode;

	// check for proper passed values
	if (dma_channel < 5 || dma_channel > 7)
		return (0);

	dma_chn = dma_channel;

	disable_dma(dma_chn);
	
	disable_adc();

	if(num_chn > MAX_CHANNELS)
		return(FALSE);
	
	if (*freq == 0.0)
		return (FALSE);

	process = proc_func;
		
	/* create buffer for storing data in */
	
	if(!(dma_buffer = alloc_dma_buffer(dma_chn, 
		  ADC_DMA_BUFFER_LEN)))
		return (FALSE);

	clr_adc_dma_buffer();
	
	// Frequency adjusted for number of channels.
	// Next version will have a burst mode enabled.
	*freq *= num_chn;

	*freq = timerad(*freq);

	// Readjust frequency for number of channels.
	*freq /= num_chn;


	// Setup the channel gain array.
	for (i = 0;	i < num_chn;	i++) {
		outp(ADC_VIRTCHAN, i);
		outp(ADC_CHN_GAIN_ARRAY, 
			  ADC_GAIN_1 | ADC_BANK_0 | i);
		}

	// set adc control register
	outp(ADC_CONTROL, ADC_FIFO_ENABLE | 
		  ADC_SINGLE_ENDED | ((dma_chn - 4) << 2));

	outp(ADC_LASTCHAN, num_chn-1);

	// Start converstions at channel zero
	outp(ADC_VIRTCHAN, 0);
	
	num_to_collect = num_chn * num_samples;
	num_collected = 0L;

	length = (num_to_collect < (long)ADC_DMA_BUFFER_LEN) ?
		  num_to_collect : (long)ADC_DMA_BUFFER_LEN;

	// Setup dma transfer.
	if(num_to_collect > (long)ADC_DMA_BUFFER_LEN)
		dma_mode = DMA_DEMAND_MODE | DMA_ADDRESS_INC | 
			  DMA_CONTINUOUS_ENABLE | DMA_ADC_TRANSFER;
	else
		dma_mode = DMA_DEMAND_MODE | DMA_ADDRESS_INC | 
			  DMA_CONTINUOUS_DISABLE | DMA_ADC_TRANSFER;

	dma(dma_chn, dma_mode, dma_buffer, 
		  (unsigned int) length);

	dma_finished = FALSE;

	return (TRUE);
	}

void get_next_adc_values()
/*& Returns the pointer to the next memory location, 
	 increments appropriate buffer pointers. */
	{

	if(dma_buffer[dma_buff_index] == NON_ADC_VALUE)
		return;

	// wait until sample has been recorded
	while(dma_buffer[dma_buff_index] != NON_ADC_VALUE) {

		// Check for overrun by looking at last adc 
		// buffer location. If this	location has a valid
		// adc value then an overrun has occurred. An 
		// over run condtion will result in automatic 
		// termination of adc collection. To detect an 
		// overrun the number of samples collected will 
		// be less than those requested.

		if(dma_buffer[old_dma_buff_index] != NON_ADC_VALUE) {
			terminate_adc_dma();
			return;
			}

		process(dma_buffer[dma_buff_index]);

		// clear collected value from DMA buffer
		dma_buffer[dma_buff_index] = NON_ADC_VALUE;

		// increment buffer pointer
		old_dma_buff_index = dma_buff_index;
		if (++dma_buff_index >= ADC_DMA_BUFFER_LEN)
			dma_buff_index = 0;
	
		// Increment number of conversions collected. 
		// Used in automatic termination for repetitive 
		// sampling under DMA.
		if(++num_collected >= num_to_collect) {
			terminate_adc_dma();
			return;
			}
		}
	}

void terminate_adc_dma()
/* Terminates adc dma collection. */
	{
	disable_dma(dma_chn);
	outp(ADC_CONTROL, 0);
	
	free_dma_buffer(dma_chn);
	
	dma_finished = TRUE;
	}

void clr_adc_dma_buffer()
/*& Clears the adc buffer to a non possible value. */
{
	unsigned int i;

	for (i = 0;	i < ADC_DMA_BUFFER_LEN;	i++)
		dma_buffer[i] = NON_ADC_VALUE;

	dma_buff_index = 0;
	old_dma_buff_index = ADC_DMA_BUFFER_LEN-1;
}

int adc_dma_done(void)
/*& Returns TRUE if all conversions are done. */
	{
	return(dma_finished);
	}

long adc_dma_conversion_count()
/*& Returns the number of samples collected. */
	{
	return(num_collected);
	}

