/*
 * Copyright(C) Paul und Scherer (mct.de/mct.net)
 *
 * This example demonstrates how to...
 *
 *  ... use the DAC as simple function generator.
 */

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <target.h>

#define F	0xfff				// full-scale
#define X	256				// maximum x
#define Y	0xff				//         y

/*
 * Shape generator
 */
#define make(s)	for (x = 0; x < X; x++) y[x] = clip(s)

#define SLOPE1	x
#define SLOPE2	x*2
#define STAIR1	x|0x1f
#define STAIR2	x|0x3f
#define PSEUDO	x%(rand()%Y+1)? y[x-1]+rand()%5-2: rand()%Y
#define SQUARE	(x < X/2)*Y
#define PULSE1	(x < X/4)*Y
#define PULSE2	(x < X/8)*Y
#define NEEDLE	!x*Y
#define RANDOM	rand()%Y
#define USRDEF	getch()
#define INVERT	Y-y[x]

/*
 * Pre-computed shapes
 */
static char const sin[X] =
{
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  2,
   2,  2,  3,  3,  3,  4,  4,  5,  5,  5,  6,  6,  7,  8,  8,  9,
   9, 10, 10, 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 19, 19, 20,
  21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
  37, 38, 39, 40, 42, 43, 44, 45, 46, 48, 49, 50, 51, 53, 54, 55,
  56, 58, 59, 60, 62, 63, 64, 66, 67, 69, 70, 71, 73, 74, 76, 77,
  79, 80, 81, 83, 84, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99,101,
 103,104,106,107,109,110,112,113,115,117,118,120,121,123,124,126,
 127,129,131,132,134,135,137,138,140,142,143,145,146,148,149,151,
 152,154,156,157,159,160,162,163,165,166,168,169,171,172,174,175,
 176,178,179,181,182,184,185,186,188,189,191,192,193,195,196,197,
 199,200,201,202,204,205,206,207,209,210,211,212,213,215,216,217,
 218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
 234,235,236,236,237,238,239,240,240,241,242,243,243,244,245,245,
 246,246,247,247,248,249,249,250,250,250,251,251,252,252,252,253,
 253,253,254,254,254,254,255,255,255,255,255,255,255,255,255,255
};
static char const exp[X] =
{
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
   2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  5,  5,  5,  5,  5,
   5,  5,  5,  6,  6,  6,  6,  6,  6,  6,  7,  7,  7,  7,  7,  7,
   8,  8,  8,  8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 10, 11,
  11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15,
  16, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 20, 21, 21, 22,
  22, 23, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 30, 31,
  32, 33, 33, 34, 35, 36, 36, 37, 38, 39, 40, 41, 41, 42, 43, 44,
  45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 58, 59, 60, 62, 63,
  64, 66, 67, 69, 70, 72, 73, 75, 77, 78, 80, 82, 84, 86, 87, 89,
  91, 93, 95, 98,100,102,104,106,109,111,114,116,119,121,124,127,
 130,132,135,138,141,144,148,151,154,158,161,165,168,172,176,180,
 184,188,192,196,200,205,209,214,219,223,228,233,238,244,249,255
};

static inline int clip(int v){return v < 0? 0: v > Y? Y: v;}

/*
 * Usage
 */
static void
usage(void)
{
	puts("\n"
	     "ADuC7024 Function Generator\n"
	     "===========================\n"
	     "Space : cycle  shapes\n"
	     " p/P  : pseudo/random pattern\n"
	     " u    : user-defined  pattern\n"
	     " n    : noise (until next key)\n"
	     " i    : invert pattern\n"
	     " m    : toggle mirror\n"
	     " f    : frequency shift\n"
	     " s    : frequency shift left\n"
	     " S    : frequency shift right\n"
	     " g/G  : +/- gain\n"
	     " o/O  : +/- offset\n"
	     " z/Z  : +/- zoom\n"
	     " l/L  : +/- zoom left\n"
	     " r/R  : +/- zoom right\n"
	     " d    : default settings\n"
	);
}

/*
 * How it works:
 *
 * The 8Bit array y is filled with a pattern representing the desired shape.
 * The data in y is scaled with gain and offset and written to the DAC data
 * array d, which is used to update the DAC without further computations.
 *
 * The DAC is continuously written in a loop to get a periodic signal. When
 * shape data has been written and the mirror flag is set, the same data is
 * output again but in reversed order, resulting in a horizontally mirrored
 * signal, with half the frequency (i.e. y contains only the left half-wave
 * of the signal). The signal can be zoomed (=lower frequency), by delaying
 * between DAC outputs (separate delays for both half-waves allow a variety
 * of signal forms).
 *
 * Skipping data points in the DAC data array increases the frequency (e.g.
 * taking only each second entry doubles the frequency) at the cost of loss
 * of x-resolution. In addition, at high frequencies, the program execution
 * time becomes visible in the resulting signal form (time between left and
 * right half-wave, time to restart the main loop, checking for key press).
 * Skipping can also be set independently for each half-wave.
 *
 * Connect a scope to the DAC0 output.
 */
int
main(void)
{
	int s = 0;				// shape
	int m = 1;				// mirror
	int g = 8;				// gain
	int o = 0;				// offset
	int l = 0;				// zoom left
	int r = 0;				//      right
	int I = 1;				// x-increment
	int D = 1;				// x-decrement

	/*
	 * Reference voltage
	 *
	 * Setting refcon to 1 activates the internal
	 * reference and connects it to the Vref pin.
	 *
	 * Note: A 0.47uF capacitor is required (from
	 * Vref to GND) for proper operation!
	 */
	Intern_refcon = 1;

	Intern_dac0con = 0x12;			// range 0 to Vref

	usage();
	ungetc(' ', stdin);			// select first shape
	while (1) {
		static unsigned long d[X];	// DAC data
		static unsigned char y[X];	// f(x)
		int x;				//   x
		int a;				// amplitude
		int t;				// delay cnt

		if (kbhit()) {
			switch (getchar()) {
			default:
				usage();
				continue;

			case 'n': // noise
				while (!kbhit()) {
					Intern_dac0dat = ((a = rand()%Y*g+o) > F? F: a)<<16;
					for (t = l; t--;) ;
				}
				getch();
				continue;

			case 'm': m = !m		    ; continue;	// toggle mirror
			case 'l': l = (l+1)&0x0ff	    ; continue;	// zoom left  +  1
			case 'L': l = (l-1)&0x0ff	    ; continue;	//            -  1
			case 'r': r = (r+1)&0x0ff	    ; continue;	//      right +  1
			case 'R': r = (r-1)&0x0ff	    ; continue;	//            -  1
			case 'z': l = (l+8)&0x0ff	    ;		//      both  +  8
				  r = (r+8)&0x0ff	    ; continue;
			case 'Z': l = (l-8)&0x0ff	    ;		//      both  -  8
				  r = (r-8)&0x0ff	    ; continue;
			case 's': if ((I <<= 1) > 64) I = 1 ; continue;	// freq. shift left
			case 'S': if ((D <<= 1) > 64) D = 1 ; continue;	//             right
			case 'f': if ((I <<= 1) > 64) I = 1 ;
				  if ((D <<= 1) > 64) D = 1 ; continue;	//             both

			case ' ': switch(s++) {				// cycle shapes
				  default: s = 1	    ;
				  case  0: make(SQUARE)	    ; break;
				  case  1: make(PULSE1)	    ; break;
				  case  2: make(PULSE2)	    ; break;
				  case  3: make(NEEDLE)	    ; break;
				  case  4: make(SLOPE1)	    ; break;
				  case  5: make(SLOPE2)	    ; break;
				  case  6: make(STAIR1)	    ; break;
				  case  7: make(STAIR2)	    ; break;
				  case  8: memcpy(y, sin, X); break;
				  case  9: memcpy(y, exp, X); break;
				  }			      break;

			case 'p': make(PSEUDO)		    ; break;	// pseudo pattern
			case 'P': make(RANDOM)		    ; break;	// random pattern
			case 'u': make(USRDEF)		    ; break;	// user   pattern
			case 'i': make(INVERT)		    ; break;	// invert pattern
			case 'g': g = (g+  1)&0x01f	    ; break;	// gain       +  1
			case 'G': g = (g-  1)&0x01f	    ; break;	//            -  1
			case 'o': o = (o+128)&0xfff	    ; break;	// offset     +128
			case 'O': o = (o-128)&0xfff	    ; break;	//            -128
			case 'd': I = D = m = 1		    ;		// set
				  o = l = r = 0; g = 8	    ; break;	//  defaults
			}

			/*
			 * Prepare DAC data (makes output to DAC as fast as possible).
			 */
			for (x = 0; x < X; x++) d[x] = ((a = y[x]*g+o) > F? F: a)<<16;
		}

		/*
		 * Output left half-wave.
		 * Left zoom delay between DAC data.
		 * Frequency shift by skipping data.
		 */
		for (x = 0; x < X; x += I) {
			Intern_dac0dat = d[x];
			for (t = l; t--;) ;
		}

		/*
		 * If mirror, output right half-wave
		 * (same as left, in reversed order).
		 * Right zoom delay between DAC data.
		 * Frequency shift by skipping data.
		 */
		if (m) while (x) {
			Intern_dac0dat = d[x -= D];
			for (t = r; t--;) ;
		}
	}
}
