/*
 * Copyright(C) Paul und Scherer (mct.de/mct.net)
 *
 * This example demonstrates how to...
 *
 *  ... read a single ADC channel.
 *
 *  ... reduce signal noise using low pass filtering.
 */

#include <stdio.h>
#include <target.h>
#include "../usleep.h"

/*
 * Pins
 */
#define PIN_CSADC	0x00010000		// P0.16
#define PIN_MS3		0x00400000		// P0.22
#define PIN_CSFLASH	0x00800000		// P0.23
#define PIN_PORT	0xff000000		// P0.24.. 31

/*
 * ADC channel select
 *
 * There are 14 channels (0 to 13). Channels 0 to 10 are physical
 * analog input lines. Channels 11, 12 and 13 are test channels:
 *
 *  Channel 11 : (Vref+ - Vref-)/2
 *          12 :  Vref-
 *          13 :  Vref+
 */
#define CH	0

/*
 * Low pass (numerical simulation of an RC circuit)
 *
 * The behaviour of an RC circuit can be described with the formula
 *
 *  (new)out := (1-K)*out+K*in, with 0<= K <=1, initial out =0
 *
 * In other words, the output is composed of a part of the previous
 * output and a part of the current input. For K =0 the output will
 * never change, and for K =1 it follows the input immediately. The
 * choice for K is a trade-off: signal stability vs. response time.
 *
 * The above formula is rewritten as: (new)out := out-K*out+K*in or
 *
 *  out += K*(in-out)
 *
 * To avoid using floats, this is transformed to integer arithmetic
 * by replacing K with 1/F and multiplying both sides with factor F
 *
 *  fout += in-fout/F, where fout = F*out, F >=1
 *
 * Return out.
 */
#define F	100
static int
lp(int in)
{
	static long fout;			/* initial fout (=F*out) =0 */

	return (fout += in-fout/F)/F;		/* return   out (=fout/F) */
}

/*
 * Initialize hardware.
 */
static void
init(void)
{
	/*
	 * Define I/Os, set all outputs hi.
	 */
	Intern_ioset  = PIN_CSADC|PIN_MS3|PIN_CSFLASH|PIN_PORT;
	Intern_iodir |= PIN_CSADC|PIN_MS3|PIN_CSFLASH|PIN_PORT;

	/*
	 * De-select SPImS
	 */
	Intern_ioclr = PIN_MS3;
	Intern_ioset = PIN_MS3;

	Intern_spcr	= 0x20;			// MSTR
	Intern_spccr	= 8;			// SPI SCK =pclk/8
	Intern_pinsel0 |= 0x5500;		// enable SPI pins
}

/*
 * SPI transfer (no error check!)
 *
 * Return received data.
 */
static int
spi_xfer(int c)
{
	Intern_spdr = c;			// start transfer

	/*
	 * Wait for transfer complete or error
	 * (mask the reserved bits 0, 1 and 2).
	 */
	while (!(Intern_spsr&0xf8)) ;

	return Intern_spdr;
}

/*
 * ADC transfer
 *
 * Transmit data word.
 *
 * Return received word.
 */
static int
adc_xfer(int data)
{
	int r;

	Intern_ioclr = PIN_CSADC;		// select
	r  = spi_xfer(data>>8)<<8;		// MSB
	r |= spi_xfer(data);			// LSB
	Intern_ioset = PIN_CSADC;		// de-select

	return r;
}

/*
 * The specified channel is transmitted, but the
 * received data is not used, as it contains the
 * result of the PREVIOUS conversion.
 *
 * After waiting for end of conversion, a second
 * transfer is needed, to get the result of THIS
 * conversion.
 * 
 * All transfers set the output length to 16Bit.
 */
static unsigned
adc(int ch)
{
	adc_xfer(ch<<12|0x0c00);		// convert channel
	usleep(10);				// wait for EOC
	return adc_xfer(0x0c00)>>4;		// read result
}

/*
 * The selected channel is sampled, filtered through
 * a low pass and displayed (every 5000th loop run).
 */
int
main(void)
{
	long count = 0;

	init();

	/*
	 * Select internal 2.048 reference.
	 */
	adc_xfer(0xf400);
	usleep(20000);

	while (1) {
		int value = lp(adc(CH));

		if (!(++count%5000)) printf("CH%d: %4d\n", CH, value);
	}
}

