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

 Conrad 8-Channel 10-Bit Digitizer Board
 Version 1.0 Dominik Kuhlen <dh0dom@amsat.org>


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

#ifndef __LTC1090_H_
#define __LTC1090_H_

#ifdef __linux__
#include "sys/types.h"
#include "fcntl.h"
#include "unistd.h"
#include "sys/ioctl.h"
#else
#error You need to add low level serial port access....
#endif

/*
 Conrad 8Channel 10-Bit ADC Board:
 Current: 2.2 mA
 Reference Voltage: 5.00 V
 AClk: ~336 kHz

	Serial <-> LTC1090
 -------------------
	8(CTS) <-> Dout
	7(RTS) <-> Din
	4(DTR) <-> SClk
	FF <-> CS

	Serial Port : 0x3f8 (default)
	all signals are active low (refer to 8250 (or 16550) data-sheet for details)!
  Modem Control Register: 0x3fc (bit 0:DTR bit 1:RTS)
	Modem Status Register:  0x3fe (bit 4:CTS)

	SClk lo -> CS Low (DFF Reset)
	SClk hi and rising edge on Din -> CS High
*/

// single differential
#define LTC1090_DIFF   (0 << 0)
#define LTC1090_SINGLE (1 << 0)

// Uni
#define LTC1090_UNI    (1 << 4)
// MSB First
#define LTC1090_MSBF   (1 << 5)
// Word len (warning ! Bit reversal)
#define LTC1090_WL8    (0 << 6)
#define LTC1090_WL10   (2 << 6)
#define LTC1090_WL12   (1 << 6)
#define LTC1090_WL16   (3 << 6)

// in : ABC A:MSB ->  C->1 A->2 B->3 
#define LTC1090_CHANNEL(x) ( ((x & 1) << 1) | ((x & 2) << 2) | ((x & 4)))

class Ltc1090 {
public:
	/**
	 \brief 
	*/
	Ltc1090(double r = 5.000) {
		Ref = r;	// Reference Voltage / V
		setSclk();
	}

	~Ltc1090() {
		closeSerial();
	}

	/**
	 \brief call this to setup the serial port
	 \param port the port number: 0 means first port
	*/
	int init(int port = 0) {
		return initSerial(port);
	}

	/**
	 \brief read previously selected channel from ltc1090.
	 \param next_ch Channel which should be read by next call
	 \return Voltage from previously selected input
	*/
	double readVol(int next_ch) {
		int i, v = 0;
		int Cfg = LTC1090_SINGLE | LTC1090_UNI | LTC1090_MSBF | LTC1090_WL10 | LTC1090_CHANNEL(next_ch);
		for (i = 0; i < 10; i++) {
			setSclk(false);	// set SClk to low (also CS low)
			setDin(Cfg & (1 << i)); // prepare data input bit
			setSclk(); // rise SClk
			v |= (!getDout()); // read data output bit
			v <<= 1; // shift  
		}
		// SClk still hi -> reset FF with rising edge on Din
		setDin(false);
		setDin(true);
		usleep(175);  // conversion time
		return Ref*((v>>1)/1024.0); // return 
	}

private:
	double Ref;

	/**
	 \brief control SClk line. Setting to low will also set CS to low (enable).
	 To disable CS set SClk to high and send rising edge on Din
	*/
	void setSclk(int set = 1) {
		setDtr(set);
	}

	/**
	 \brief Control Din line. Also Clock for Flipflop to rise CS (disable)
	*/
	void setDin(int set = 1) {
		setRts(set);
	}

	/**
	 \brief Dout line. This line has no special behaviour.
	*/
	int getDout() {
		return readCts();
	}


#ifdef __linux__
	int SerialFd;	// file descriptor

	void closeSerial() {
		if (SerialFd > 0) close(SerialFd);
	}

	int initSerial(int p) {
		char dev[16];
		sprintf(dev, "/dev/ttyS%d", p);
		SerialFd = open(dev, O_RDWR);
		if (SerialFd < 0) return SerialFd;
		return 0;
	}

	int setDtr(int s) {
		return setLine(s, TIOCM_DTR);
	}

	int setRts(int s) {
		return setLine(s, TIOCM_RTS);
	}


	int setLine(int set, int line) {
		int r, lines;
		if (SerialFd == -1) return -100;
		r = ioctl(SerialFd, TIOCMGET, &lines);
		if (r != 0) return r;
		lines &= ~line;
		if (set) lines |= line;
		r = ioctl(SerialFd, TIOCMSET, &lines);
		if (r != 0) return r;
		return 0;
	}

	int readCts() {
		int r, lines;
		if (SerialFd == -1) return -100;
		r = ioctl(SerialFd, TIOCMGET, &lines);
		if (r != 0) return r;
		return ((lines & TIOCM_CTS) != 0);
	}

#else
#error You need to add low level serial port access....
#endif
}; // class Ltc1090


#endif

