
// ****************************************************************************
//
//                         Page NG - noise digital
//
// ****************************************************************************

#include "../include.h"

#define NGRANGE_NUM	28	// number of range values
#define NGBUFSIZE	2048	// number of u16 entries in the Buf[] buffer (must be power of 2)

Bool NGSet = False;		// current generator setup set False=Min or True=Max
u8 NGMinInx = 10;		// index of minimal value
u8 NGMaxInx = 16;		// index of maximal value
u16 NGMin;			// minimal value
u16 NGMax;			// maximal value (must be NGMax >= NGMin)
u32 NGRandSeed = 123456;	// random seed
int NGBufInx;			// next buffer index
u32 NGAcc = 0;		 	// accumulator

// range setup, in [us]
const u16 NG_Range[NGRANGE_NUM] = {
			2,	3,	5,	7,
	10,	15,	20,	30,	50,	70,
	100,	150,	200,	300,	500,	700,
	1000,	1500,	2000,	3000,	5000,	7000,
	10000,	15000,	20000,	30000,	50000,	65000,
};

// range setup text
const char* const NG_RangeTxt[NGRANGE_NUM] = {
			"2us",	"3us",	"5us",	"7us",
	"10us",	"15us",	"20us",	"30us",	"50us",	"70us",
	"100us","150us","200us","300us","500us","700us",
	"1ms",	"1.5ms","2ms",	"3ms",	"5ms",	"7ms",
	"10ms",	"15ms",	"20ms",	"30ms",	"50ms",	"65ms",
};

// generate random interval
u16 NG_Rand()
{
	// shift seed
	u32 k = NGRandSeed;
	k = k*214013 + 2531011;
	NGRandSeed = k;

	// get min and max (max >= min)
	u16 min = NGMin;
	u16 max = NGMax;

	// range
	u64 mul = (u64)k * (u32)(max - min + 1);
	return (u16)((u32)(mul >> 32) + min);
}

// NG terminate
void NG_Term()
{
	// disable timer
	TIM1_Disable();

	// disable DMA tranfer
	DMA_ChanDisable(DMA1_Chan(NG_DMA));

	// reset Timer 1
	TIM1_Reset();

	// Timer 1 clock disable
	RCC_TIM1ClkDisable();

	// reset GPIO pin
	GPIO_PinReset(NG_GPIO);
}

// setup timer (shared with REP page)
//  reppage ... use REP page
void NG_SetupTimer(Bool reppage)
{
	// Enable timer clock source
	TIM1_ClkEnable();

	// Reset timer to default setup
	TIM1_Reset();

	// select input from internal clock CK_INT
	TIM1_InMode(TIM_INMODE_INT);

	// enable compare output
	TIM1_CC4Enable();

	// setup prescaler, to get 1us time base
	TIM1_Presc((reppage ? (HCLK_PER_US*2) : HCLK_PER_US) - 1);

	// setup loader to maximal value
	TIM1_Load(65535);

	// direction up
	TIM1_DirUp();

	// setup compare
	TIM1_OC4Mode(TIM_COMP_FLIP);	// set compare mode to flip output
	TIM1_OC4PreDisable();		// disable preload compare register - update compare register immediately

	// enable main output (not necessary, but just to be safe)
	TIM1_OCEnable();

	// reload immediately - apply prescaler
	TIM1_Update();

	// reset counter
	TIM1_Cnt(0);

	// select capture DMA request on compare
	TIM1_CCDMAOnComp();

	// enable compare channel 4 DMA request (use channel 4: TIM1_CH4 request)
	TIM1_CC4DMAReqEnable();

	// remap Timer 1
	// 0 ... PC5:ETR, PD2:CH1, PA1:CH2, PC3:CH3, >>> PC4:CH4 <<<, PC2:BKIN, PD0:CH1N, PA2:CH2N, PD1:CH3N
	GPIO_Remap_TIM1(0);

	// setup GPIO pin
	GPIO_Mode(NG_GPIO, GPIO_MODE_AF);
}

// setup DMA
void NG_SetupDMA()
{
	// enable DMA1 clock
	RCC_DMA1ClkEnable();

	// get address of DMA1 channel (channel 4 with TIM1_CH4 request)
	DMAchan_t* chan = DMA1_Chan(NG_DMA);

	// set destination address - TIM1 compare register od channel 4 (as peripheral address)
	DMA_PerAddr(chan, &TIM1->CH4CVR);

	// set source addres (as memory address)
	DMA_MemAddr(chan, Buf);

	// set number of entries
	DMA_Cnt(chan, NGBUFSIZE);		// 2048 u16 entries (= 4096 bytes buffer size)

	// configure DMA channel
	DMA_Cfg(chan,
	//	DMA_CFG_EN |			// channel enable
	//	DMA_CFG_COMPINT |		// completion interrupt enable
	//	DMA_CFG_HALFINT |		// over half interrupt enable
	//	DMA_CFG_TRANSERRINT |		// transmission error interrupt enable
		DMA_CFG_DIRFROMMEM |		// transfer direction from memory
	//	DMA_CFG_DIRFROMPER |		// ... transfer direction from peripheral
		DMA_CFG_CIRC |			// circular mode enabled
	//	DMA_CFG_PERINC |		// peripheral address increment
		DMA_CFG_MEMINC |		// memory address increment
	//	DMA_CFG_PSIZE(size) |		// peripheral data size DMA_SIZE_*
	//	DMA_CFG_PSIZE_8 |		// ... peripheral data size 8 bits
		DMA_CFG_PSIZE_16 |		// ... peripheral data size 16 bits
	//	DMA_CFG_PSIZE_32 |		// ... peripheral data size 32 bits
	//	DMA_CFG_MSIZE(size) |		// memory data size DMA_SIZE_*
	//	DMA_CFG_MSIZE_8 |		// ... memory data size 8 bits
		DMA_CFG_MSIZE_16 |		// ... memory data size 16 bits
	//	DMA_CFG_MSIZE_32 |		// ... memory data size 32 bits
	//	DMA_CFG_PRIOR(prior) |		// channel priority 0..3
	//	DMA_CFG_PRIOR_LOW |		// ... channel priority 0 low
	//	DMA_CFG_PRIOR_MED |		// ... channel priority 1 medium
	//	DMA_CFG_PRIOR_HIGH |		// ... channel priority 2 high
		DMA_CFG_PRIOR_VERYHIGH |	// ... channel priority 3 very high
	//	DMA_CFG_MERM2MEM |		// memory-to-memory mode enable
		0);
}

// NG setup
void NG_Setup()
{
	// NG terminate
	NG_Term();

	// prepare Min and Max
	int min = NG_Range[NGMinInx];
	int max = NG_Range[NGMaxInx];
	if (min > max)
	{
		int k = min;
		min = max;
		max = k;
	}
	NGMin = min;
	NGMax = max;

	// generate buffer
	u16* d = (u16*)Buf;
	int i;
	u32 acc = 0;
	for (i = NGBUFSIZE; i > 0; i--)
	{
		acc += NG_Rand();
		*d++ = (u16)acc;
	}
	NGAcc = acc;		// accumulator
	NGBufInx = 0;		// next buffer index

	// setup DMA
	NG_SetupDMA();

	// setup timer
	NG_SetupTimer(False);

	// start DMA tranfer
	DMA_ChanEnable(DMA1_Chan(NG_DMA));

	// enable timer
	TIM1_Enable();

	// waiting for a short time to ensure that the DMA moves to the next item
	while (DMA_GetCnt(DMA1_Chan(NG_DMA)) != NGBUFSIZE) {}
}

// update buffer
void NG_Update()
{
	// start of the buffer
	u16* buf = (u16*)Buf;

	// current pointer and index
	int inx = NGBufInx;
	u32 acc = NGAcc;

	// get address of DMA1 channel (channel 4 with TIM1_CH4 request)
	DMAchan_t* chan = DMA1_Chan(NG_DMA);

	// generate data
	int dma, rem;
	while (True)
	{
		// check reserve to DMA
		if (((DMA_GetCnt(chan) + inx) & (NGBUFSIZE-1)) >= NGBUFSIZE-10) break;

		// generate next sample
		acc += NG_Rand();
		buf[inx] = (u16)acc;
		inx++;
		if (inx >= NGBUFSIZE) inx = 0;
	}

	// store new index and accumulator
	NGBufInx = inx;
	NGAcc = acc;
}

// NG display
void NG_Disp()
{
	// clear area
	DrawRectClrFast(0, TITLE_H, WIDTH, HEIGHT-TITLE_H);

	// select font 8x12
	SelFont12();

	// display range
	const char* t1 = NG_RangeTxt[NGMinInx];
	int len1 = StrLen(t1);
	const char* t2 = NG_RangeTxt[NGMaxInx];
	int len2 = StrLen(t2);
	int len = len1 + 3 + len2;
	int x = (WIDTH - len*8)/2;
	DrawText(t1, x, ROW1_Y);
	x += len1*8+8;
	DrawChar('-', x, ROW1_Y);
	x += 2*8;
	DrawText(t2, x, ROW1_Y);

	// display frequency
	u32 d = NGMin + NGMax;
	u32 f = (1000000000 + (d>>1))/d;
	x = DispUVal(8, ROW2_Y, f, 5, -3, 'H', True, False);
	DrawChar('z', x, ROW2_Y+SMALL_Y);
	
	// display setup mode 'setup: min'
	DrawText("setup:", 3*8, ROW4_Y);
	DrawText(NGSet ? "max" : "min", 10*8, ROW4_Y);

	// display update
	DispUpdate();
}

// Page NG (returns key PREV/NEXT)
u8 PageNG()
{
	u8 key;
	int i;

	// NG setup
	NG_Setup();

	// display NG state
	NG_Disp();

	while (True)
	{
		// reload watchdog counter
		IWDG_Reload();

		// update buffer
		NG_Update();

		// get keyboard
		key = KeyGet();
		switch (key)
		{
		// Prev
		case KEY_PREV:
		// Next
		case KEY_NEXT:
			// NG terminate
			NG_Term();
			return key;

		// Fast
		case KEY_FAST:
			if (NGSet)
			{
				// setup Max
				i = NGMaxInx;
				if ((i > 0) && ((i != 1) || (NGMinInx != 0))) // avoid 2us+2us
				{
					NGMaxInx = i-1;
					NG_Setup();
					NG_Disp();
				}
			}
			else
			{
				// setup Min
				i = NGMinInx;
				if ((i > 0) && ((i != 1) || (NGMaxInx != 0))) // avoid 2us+2us
				{
					NGMinInx = i-1;
					NG_Setup();
					NG_Disp();
				}
			}
			break;		

		// Slow
		case KEY_SLOW:
			if (NGSet)
			{
				// setup Max
				i = NGMaxInx;
				if (i < NGRANGE_NUM-1)
				{
					NGMaxInx = i+1;
					NG_Setup();
					NG_Disp();
				}
			}
			else
			{
				// setup Min
				i = NGMinInx;
				if (i < NGRANGE_NUM-1)
				{
					NGMinInx = i+1;
					NG_Setup();
					NG_Disp();
				}
			}
			break;

		// Hold
		case KEY_HOLD:
			NGSet = !NGSet;
			NG_Disp();
			break;
		}
	}
}
