/*
 * 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>

/*
 * 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) */
}

/*
 * The specified channel is read twice. This is
 * necessary, because each SPI transfer returns
 * the result of the PREVIOUS conversion.
 */
static int
adc(int ch)
{
	INTERN.qsm.qpar	 = 0x7b;		/* enable SPI pins */
	INTERN.qsm.qpdr	 = 0x78;		/* chip selects off */
	INTERN.qsm.qddr	 = 0x7e;		/* set data direction */
	INTERN.qsm.spcr0 = 0xb003;		/* master, 12 bits, SCK =sysclk/6 */
	INTERN.qsm.spcr1 = 0x1909;		/* timing */
	INTERN.qsm.spcr2 = 0x0100;		/* two entries in queue */
	INTERN.qsm.comd_ram[0] =		/* # of bits from spcr0, timing */
	INTERN.qsm.comd_ram[1] = 0x7b;		/*  in spcr1, PCS2 chip select on */
	INTERN.qsm.tran_ram[0] =		/* channel number */
	INTERN.qsm.tran_ram[1] = ch<<8;		/*  as required for ADC */
	INTERN.qsm.spcr1 |= 0x8000;		/* start transfer */

	/*
	 * Wait for transfer complete.
	 */
	while (!(INTERN.qsm.spsr&0x80)) ;

	INTERN.qsm.spsr = 0;			/* clear flags */
	return INTERN.qsm.rec_ram[1];
}

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

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

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

