/*
Filename: MAX_ADC.CPP
Purpose: Controls and displays output from an analogue-to-digital
converter circuit, based on the MAX186DCPP i.c. connected to the parallel port.
Date: Saturday 22 October 2005
Author: Chris Carter, chris.carter@iee.org
Usage: ./io ./max_adc
*/

#include <iostream>
#include <unistd.h>
#include <sys/io.h>
#include <signal.h>
#include <sys/time.h>

// Declare various function prototypes

// 'read_adc' - calls all other functions necessary to initiate a conversion and read the value back

void read_adc(int);

// 'convert' - sends a configuration byte to the ADC, at the end of which conversion begins

int convert(unsigned int channel, bool uni_bip, bool sgl_dif, bool pd1, bool pd0);

// 'read_conversion' - reads two bytes from the ADC, once conversion is complete, calculates coverted value

double read_conversion(void);

// 'read_byte' - reads a single byte back from the ADC (called by 'read_conversion')

unsigned int read_byte(void);

using namespace std;

// Declare global variables

int time_interval_sec;						// The time between samples, in seconds
int j;										// General-purpose loop variable
unsigned int port = 0x378;					// First parallel port is 0x378
unsigned int data_register = port;			// Address of the data register
unsigned int status_register = port + 1;	// Address of the status register

// Sets levels of verbosity, debug info and informativeness

bool verbose = 0;
bool debug = 0;
bool inform = 0;

unsigned int result_mask;

// Bit command masks - used to set/clear individual bits

unsigned int cs_mask = 0x01;	// D0 line (Data register)
unsigned int sclk_mask = 0x02;	// D1 line (Data register)
unsigned int din_mask = 0x04;	// D2 line (Data register)
unsigned int shdn_mask = 0x08;	// D3 line (Data register)

// Bit status mask - used to check the state of individual bits

unsigned int dout_mask = 0x20;			// Paper Out will read the DOUT line (Status register)

// Start of the main function

int main()
{
	// Set an interval timer that calls the conversion routine every so many seconds

	time_interval_sec = 1;	// Calls a conversion from the ADC every second

	struct itimerval itimer;

	signal(SIGALRM, read_adc);	// 'read_adc' is the function that gets called when SIGALRM happens

	itimer.it_value.tv_sec = time_interval_sec;
	itimer.it_value.tv_usec = 0;	// If both are 0, nothing ever happens

	itimer.it_interval.tv_sec = time_interval_sec;
	itimer.it_interval.tv_usec = 0;

	setitimer(ITIMER_REAL, &itimer, NULL);	// Start the timer!

	while(1);
	return(0);
}

void read_adc(int)
{
	if (verbose) cout << hex << "Parallel port address is:  0x" << port << "." << endl;

	// Device setup starts here... start by zeroing the data port

	if (verbose) cout << "Setting data port bits (D0 - D7) to zero...";
	outb(0, data_register);
	if (verbose) cout << "done." << endl;

	// First, set Shutdown (SHDN/) high (takes MAX186 out of shutdown mode)

	if (verbose) cout << "Setting SHDN/ (D3) high...";
	outb((inb(data_register) | shdn_mask), data_register);
	if (verbose) cout << "done." << endl;

	// Call convert() function:
	//
	// First parameter: integer, 0 - 7, specifies the channel to read
	// Second parameter: boolean, 0 - 1, selects unipolar (1) or bipolar (0) measurement mode
	// Third parameter: boolean, 0 - 1, selects single-ended (1) or differential (0) conversion mode
	// Fourth parameter: boolean, 0 - 1, value of control bit PD1 (see datasheet)
	// Fifth parameter: boolean, 0 - 1, value of control bit PD0 (see datasheet)

	convert(0, 1, 1, 1, 0);

	// Read back the conversion from the ADC

	cout << "Conversion value: " << read_conversion() << " V" << endl;
}

// Function definitions

// Function: convert

int convert(unsigned int channel, bool uni_bip, bool sgl_dif, bool pd1, bool pd0)
{
	// Force Chip Select (CS/) high until we are ready to talk to the device

	if (verbose) cout << "Setting CS/ (D0) high...";
	outb((inb(data_register) | cs_mask), data_register);
	if (verbose) cout << "done." << endl;

	// Error check for invalid configurations

	if (channel > 7 || channel < 0) {
		cout << "Channel to convert must be between 0 and 7." << endl;
		exit(-1);
		}

	// Convert the various individual configuration variables into a 'control byte'

	bool cbyte[8] = {0};
	cbyte[7] = 1;	// Start bit (always 1)

	switch (channel)
	{
		case 0 :	cbyte[6] = 0;	// SEL2
					cbyte[5] = 0;	// SEL1
					cbyte[4] = 0;	// SEL0
					break;
		case 1 :	cbyte[6] = 1;	// SEL2
					cbyte[5] = 0;	// SEL1
					cbyte[4] = 0;	// SEL0
					break;
		case 2 :	cbyte[6] = 0;	// SEL2
					cbyte[5] = 0;	// SEL1
					cbyte[4] = 1;	// SEL0
					break;
		case 3 :	cbyte[6] = 1;	// SEL2
					cbyte[5] = 0;	// SEL1
					cbyte[4] = 1;	// SEL0
					break;
		case 4 :	cbyte[6] = 0;	// SEL2
					cbyte[5] = 1;	// SEL1
					cbyte[4] = 0;	// SEL0
					break;
		case 5 :	cbyte[6] = 1;	// SEL2
					cbyte[5] = 1;	// SEL1
					cbyte[4] = 0;	// SEL0
					break;
		case 6 :	cbyte[6] = 0;	// SEL2
					cbyte[5] = 1;	// SEL1
					cbyte[4] = 1;	// SEL0
					break;
		case 7 :	cbyte[6] = 1;	// SEL2
					cbyte[5] = 1;	// SEL1
					cbyte[4] = 1;	// SEL0
					break;
		default :	cout << "Unable to properly set channel bits." << endl;
					exit(-1);
		}

	cbyte[3] = uni_bip;	// UNI/BIP/
	cbyte[2] = sgl_dif;	// SGL/DIF/
	cbyte[1] = pd1;	// PD 1
	cbyte[0] = pd0;	// PD 0

	if (inform) {	// Print configuration request summary
		cout << endl;
		cout << "Convert channel: " << channel << endl;
		cout << "Conversion type: " << (uni_bip ? "Unipolar" : "Bipolar") << endl;
		cout << "Input mode: " << (sgl_dif ? "Single-ended" : "Differential") << endl;

		cout << "Clock & power-down modes: ";

		if (pd1 && pd0) cout << "External clock";
		else if (!pd1 && pd0) cout << "Fast power-down (Iq = 30 uA)";
		else if (pd1 && !pd0) cout << "Internal clock mode";
		else if (!pd1 && !pd0) cout << "Full power-down (Iq = 2 uA)";
		else {
			cout << "Unknown clock & power-down mode!" << endl;
			exit(-1);
			}

		cout << endl << endl;
		}

	// Clock the control byte into the device to set things up

	if (verbose) cout << "Setting CS/ (D0) low...";
	outb((inb(data_register) & ~cs_mask), data_register);
	if (verbose) cout << "done." << endl;

	// Clock control byte into DIN (D2)

	j = 7;
	while (j != -1)
	{
		if (verbose) cout << "Testing cbyte[" << j << "]" << endl;
		if (cbyte[j])
		{
		outb((inb(data_register) | din_mask), data_register);	// Send '1'
		}
		else
		{
		outb((inb(data_register) & ~din_mask), data_register);	// Send '0'
		}

		outb((inb(data_register) | sclk_mask), data_register);
		if (verbose) cout << "SCLK (D1) high" << endl;

		outb((inb(data_register) & ~sclk_mask), data_register);
		if (verbose) cout << "SCLK (D1) low" << endl;
		j--;
	};	// Conversion starts now

	if (inform) cout << "Control byte sent OK. Starting conversion..." << endl;
	return(0);
}

// Function: read_conversion

double read_conversion(void)
{
	unsigned int high_byte = read_byte();
	unsigned int low_byte = read_byte();
	double conversion = (double)((high_byte * 16) + (low_byte / 16));
	conversion = (conversion / 4096) * 4.096;
	return(conversion);
}

// Function: read_byte

unsigned int read_byte(void)
{
	unsigned int byte = 0;

	// OK, let's clock out the bits

	j = 7;
	result_mask = 0x80;

	while(j != -1)
	{
		outb((inb(data_register) | sclk_mask), data_register);
		if (verbose) cout << "SCLK (D1) high" << endl;

		outb((inb(data_register) & ~sclk_mask), data_register);
		if (verbose) cout << "SCLK (D1) low" << endl;

		if (verbose) cout << "Reading bit " << j << " of the high byte" << endl;
		if ((inb(status_register) & dout_mask) >> 2)
		{
		byte = byte | result_mask;
		result_mask = result_mask >> 1;
		}
		else
		{
		result_mask = result_mask >> 1;
		};
		if (verbose) cout << "Bit " << j << " is " << ((inb(status_register) & dout_mask) >> 2) << endl;
		j--;
	}

	if (verbose) cout << "Byte is " << byte << endl << endl;
	return(byte);
}
// End of file.
