
// ****************************************************************************
//
//                            Page C - capacitor meter
//
// ****************************************************************************

#include "../include.h"

u32 C_Res = 0;		// result capacitance in pF
u32 C_Tare = 40;	// tare in pF
s32 C_ESR = 0;		// ESR in 0.1 ohm
#define ESR_MAX	 10000	// max value of ESR in 0.1 ohm
#define ESR_INV	 100000	// ESR is invalid

// C filter
#define C_HISTNUM	3	// number of entries
u32 C_Hist[C_HISTNUM];		// C history
u32 C_Hist2[C_HISTNUM];		// ESR history
u64 C_Sum;			// sum of C entries
u32 C_Sum2;			// sum of ESR entries
int C_HistInx;			// index in history

#define LOG_MUL		(1<<13)	// log multiplier (= 8192)

// table of -log(ur)*LOG_MUL
//	ur = relative voltage 0, 1/4096, 2/4096 .. 4094/4096, 4095/4096
//	-log(ur) = values -, 8.3178, 7.6246 .. 4,8840e-4, 2.4417e-4
//	table = values 65535, 65535, 62461 .. 4, 2
// const u16 LogUTab[4096];
#include "page_C_tab.h"

// C initialize
void C_Init()
{
	// R initialize
	R_Init();

	// set ADC divider
	RCC_ADCDivEnable();
	RCC_ADCDiv(RCC_ADCCLK_DIV4);

	// set sampling time
	ADC1_SampTime(RC_ADC, 7);

	// reset filter
	int i;
	for (i = 0; i < C_HISTNUM; i++) C_Hist[i] = C_Tare;
	C_Sum = C_Tare*C_HISTNUM;
	C_HistInx = 0;
}

// C terminate
void C_Term()
{
	// R terminate
	R_Term();
}

// check one type of capacitor
// - method uses logarithmic regression of the capacitor's charging and discharging curves 
// Capacitor charging:
//   v-u (or u) = v * exp(-(t+t0)/RC) ... v = supply voltage, R = charging resistor
//    t = inx*delta ... inx = sample index, delta = time per one sample
// logarithm:  -log(ur) = tinx0*delta/RC + tinx*delta/RC ... ur = relative voltage (v-u)/v or u/v
//   1/RC = (n*sum(tinx*-log(ur)) - sum(tinx)*sum(-log(ur))) / (n*sum(tinx^2) - sum(tinx)^2) / delta
//   C = (n*sum(tinx^2) - sum(tinx)^2) / (n*sum(tinx*-log(ur)) - sum(tinx)*sum(-log(ur))) / R * delta

int C_LoopN;	// loop counter

// check one type of resistor ... takes 10ms or 500ms
void C_CheckCap1(u8 inx, Bool fast)
{
	u8 key;

	// setup ADC
	if (inx == R5_INX)
		ADC1_SampTime(RC_ADC, 7);
	else
		ADC1_SampTime(RC_ADC, 6);

	// prepare sums - charging
	int up_n = 0;		// charging number of samples
	u64 up_sum_tinx = 0;	// charging sum of sample index
	u64 up_sum_tinx2 = 0;	// charging sum of square sample index
	u64 up_sum_ur = 0;	// charging sum of -log(ur)
	u64 up_sum_tinxur = 0;	// charging sum of sample index * -log(ur)

	// prepare sums - discharging
	int dn_n = 0;		// discharging number of samples
	u64 dn_sum_tinx = 0;	// discharging sum of sample index
	u64 dn_sum_tinx2 = 0;	// discharging sum of square sample index
	u64 dn_sum_ur = 0;	// discharging sum of -log(ur)
	u64 dn_sum_tinxur = 0;	// discharging sum of sample index * -log(ur)

	// prepare period
	u32 period = fast ? C_PERIOD_FAST : C_PERIOD_SLOW;
	
	// start time
	u32 t = Time();

	// check start voltage to Udd/4
	u32 period2 = period/2;
	if (ADC1_GetSingle(RC_ADC) < U_LOW) // low voltage - we will be charging to Udd/4
	{
		// set charging resistor
		R_SetResH(inx);

		// wait for charging
		while ((u32)(Time() - t) < period2)
		{
			key = KeyBuf; if ((key == KEY_PREV) || (key == KEY_NEXT)) return;
			if (R_GetADC() >= U_LOW) break;
		}
	}
	else // high voltage - we will be discharging to Udd/4
	{
		// set discharging resistor
		R_SetResL(inx);

		// wait for discharging
		while ((u32)(Time() - t) < period2)
		{
			key = KeyBuf; if ((key == KEY_PREV) || (key == KEY_NEXT)) return;
			if (R_GetADC() < U_LOW) break;
		}
	}

	// set resistor off
	R_SetResOff(inx);

	// start time
	u32 tim1 = Time();

	// measure loop
	u16 u;
	u16 ur;
	int tinx;
	int loop = 0;
	period2 = fast ? C_PERIOD_FAST : C_PERIOD_INT;
	Bool brk;
	while ((u32)(Time() - t) < period)
	{
		// reset time index
		tinx = 0;

		// charging
		while ((u32)(Time() - t) < period)
		{
			brk = False;

			// disable interrupt
			di();

			// start time
			u32 tint = Time();

			// start charge
			R_SetResH(inx);

			// small loop 19ms, to serve SysTick
			while ((u32)(Time() - tint) < period2)
			{
				// get voltage
				u = R_GetADC();

				// end of charging
				if (u >= U_HIGH)
				{
					brk = True;
					break;
				}

				// get -log(1-ur) (2 .. 65535)
				ur = LogUTab[4095-u];

				// sum
				up_n++;				// charging number of samples
				up_sum_tinx += tinx;		// charging sum of sample index
				up_sum_tinx2 += (u64)tinx*tinx; // charging sum of square sample index
				up_sum_ur += ur;		// charging sum of -log(ur)
				up_sum_tinxur += (u64)tinx*ur; // charging sum of sample index * -log(ur)

				// increment time index
				tinx++;
			}

			// set resistor off
			R_SetResOff(inx);

			// enable interrupt
			ei();

			// keyboard break
			key = KeyBuf;
			if ((key == KEY_PREV) || (key == KEY_NEXT)) return;

			if (brk) break;
		}

		// time-out
		if ((u32)(Time() - t) >= period) break;

		// reset time index
		tinx = 0;

		// discharging
		while ((u32)(Time() - t) < period)
		{
			brk = False;

			// disable interrupt
			di();

			// start time
			u32 tint = Time();

			// start discharge
			R_SetResL(inx);

			// small loop 19ms
			while ((u32)(Time() - tint) < period2)
			{
				// get voltage
				u = R_GetADC();

				// end of discharging
				if (u < U_LOW)
				{
					brk = True;
					break;
				}

				// get -log(ur) (65535..2)
				ur = LogUTab[u];

				// sum
				dn_n++;				// discharging number of samples
				dn_sum_tinx += tinx;		// discharging sum of sample index
				dn_sum_tinx2 += (u64)tinx*tinx; // discharging sum of square sample index
				dn_sum_ur += ur;		// discharging sum of -log(ur)
				dn_sum_tinxur += (u64)tinx*ur; // discharging sum of sample index * -log(ur)

				// increment time index
				tinx++;
			}

			// set resistor off
			R_SetResOff(inx);

			// enable interrupt
			ei();

			// keyboard break
			key = KeyBuf;
			if ((key == KEY_PREV) || (key == KEY_NEXT)) return;

			if (brk) break;
		}

		// loop counter
		loop++;
	}

	// stop time
	u32 tim2 = Time();

	// loop counter
	C_LoopN = loop;

	// reset - invalid result
	C_Res = 0;

	// time delta (time per one sample in seconds)
	int n = up_n + dn_n;
	if (n == 0) return;
	double delta = (double)(u32)(tim2 - tim1) / (up_n + dn_n) / (HCLK_PER_US*1000000);

	// resistor value in ohms
	double r = R_Val[inx]*0.1;

	// calculate charging capacitance
	double k = up_n*(double)up_sum_tinxur - (double)up_sum_tinx*up_sum_ur;
	if (k == 0) return; // charging is not valid (no capacitor)
	double cup = (up_n*(double)up_sum_tinx2 - (double)up_sum_tinx*up_sum_tinx) / (k * r) * delta * LOG_MUL;
	u32 resu = (u32)(cup*1e12+0.5); // get result in [pF]
	C_Res = resu;

	// calculate discharging capacitance
	if (dn_n == 0) return; // discharging is not valid
	k = dn_n*(double)dn_sum_tinxur - (double)dn_sum_tinx*dn_sum_ur;
	if (k == 0) return; // discharging is not valid (no capacitor)
	double cdn = (dn_n*(double)dn_sum_tinx2 - (double)dn_sum_tinx*dn_sum_tinx) / (k * r) * delta * LOG_MUL;
	u32 resd = (u32)(cdn*1e12+0.5); // get result in [pF]
	if (resd != 0)
	{
		if (resu == 0)
			C_Res = resd;
		else
			C_Res = (u32)((cup + cdn)/2*1e12+0.5);
	}
}

// measure ESR
void C_CheckESR1()
{
	u32 t = Time(); // start time
	s16 v, v1, v2, v3, r;
	s32 a = 0;

	// loop
	int n = 0;
	while ((u32)(Time() - t) < PERIOD)
	{
		// disable interrupt
		di();

		// discharge capacitor
		R_SetResL(R1_INX);

		// delay 50 us to discharge capacitor
		WaitUs(50);

		// set resistor off
		R_SetResOff(R1_INX);

		// load sample at OFF state
		v1 = R_GetADC();

		// charge capacitor
		R_SetResH(R1_INX);

		// load sample at HIGH state
		v2 = R_GetADC();

		// set resistor off
		R_SetResOff(R1_INX);

		// load sample at OFF state
		v3 = R_GetADC();

		// enable interrupt
		ei();

		// check overflow, capacitor is too small or ESR is too high
		r = v2 - v1;
		if (((v3 - v1) > 3000) || (r > 3000))
		{
			C_ESR = ESR_INV;
			return;
		}

		// calculate resistor value
		// I = (Vcc - v2) / (R1 + R0)
		// R = (v2 - v1) / I = (v2 - v1) * (R1 + R0) / (Vcc - v2)
		v = (4095 + 2) - v2;
		if (r < 0) r = 0; // value 0..3000
		r = (r * (R1_RAW+R0_RAW) + v/2) / v; // result in 0.1 Ohm

		// add to accumulator
		a += r;
		n++;
	}

	// get ESR value
	u32 res = a / n;

	// limit to 100 ohm
	C_ESR = res;
}

// update capacitor with auto select range ... takes 500ms
void C_Update()
{
	u8 key;
	int inx, n;
	u64 c;
	u64 cu;
	u64 cd;

	// prepare resistor index
	inx = R1_INX;
	C_CheckCap1(inx, True);
	if (C_Res < 900000)	// < 0.9 uF
	{
		inx = R2_INX;
		C_CheckCap1(inx, True);
		if (C_Res < 90000) // < 90 nF
		{
			inx = R3_INX;
			C_CheckCap1(R3_INX, True);
			if (C_Res < 9000) // < 9 nF
			{
				inx = R4_INX;
				C_CheckCap1(R4_INX, True);
				if (C_Res < 900) // < 900 pF
				{
					inx = R5_INX;
					C_CheckCap1(R5_INX, True);
				}
			}
		}
	}

	// slow measure	
	C_CheckCap1(inx, False);

	// measure ESR
	C_CheckESR1();

	// filter
	inx = C_HistInx;
	C_Sum -= C_Hist[inx];
	C_Sum2 -= C_Hist2[inx];
	C_Hist[inx] = C_Res;
	C_Hist2[inx] = C_ESR;
	C_Sum += C_Res;
	C_Sum2 += C_ESR;
	inx++;
	if (inx >= C_HISTNUM) inx = 0;
	C_HistInx = inx;
}

// display tare correction
void C_DispTare()
{
	// get tare correction in pF
	char* s = DecNumBuf;
	int len = DecNum(s, C_Tare, '\'');

	// select font 8x12
	SelFont12();

	// display tare correction "tare 1'234pF"
	int x = (WIDTH - (len+7)*8)/2;
	DrawText("tare", x, ROW4_Y);
	x += 5*8;
	DrawText(s, x, ROW4_Y);
	x += len*8;
	DrawText("pF", x, ROW4_Y);
}

// C display
void C_Disp()
{
	DrawRectClrFast(0, TITLE_H, WIDTH, HEIGHT-TITLE_H);

	// select font 8x12
	SelFont12();

	// display result
	//  x ... X coordinate
	//  y ... Y coordinate
	//  val ... value
	//  dig ... number of valid digits
	//  ex ... decimal exponent related to base unit, in range -18..+8
	//  unit ... unit character, 0=none
	//  small ... use small unit characters
	u32 r = (u32)(((u64)C_Sum + C_HISTNUM/2)/C_HISTNUM);
	int y = ROW2_Y;
	int x = 0;
	if (r < C_Tare)
	{
		r = C_Tare - r;
		DrawChar2('-', 0, y);
		x += 16;
	}
	else
	{
		r -= C_Tare;
		x += 8;
	}

	x = DispUVal(x, y, r, 4, -12, 'F', False, False);

	// display ESR
	y = ROW1_Y;
	u32 esr = (C_Sum2 + C_HISTNUM/2)/C_HISTNUM;
	if (esr > ESR_MAX)
	{
		DrawText("ESR -", (WIDTH-5*8)/2, y);
	}
	else
	{
		char* s = DecNumBuf;
		int len = DecUNum(s, esr, 0);

		// correction to fixed point
		if (len == 1)
		{
			// "5" -> "0.5"
			s[3] = 0;
			s[2] = s[0];
			s[1] = '.';
			s[0] = '0';
			len = 3;
		}
		else
		{
			s[len+1] = 0;
			s[len] = s[len-1];
			s[len-1] = '.';
			len += 1;
		}

		// display ESR "ESR 1.2ohm"
		x = (WIDTH - (len+7)*8)/2;
		DrawText("ESR", x, y);
		x += 4*8;
		DrawText(s, x, y);
		x += len*8;
		DrawText("ohm", x, y);
	}

	// display tare correction
	C_DispTare();

	// display update
	DispUpdate();
}

// tare
void C_DoTare()
{
	// setup tare
	C_Tare = (u32)(((u64)C_Sum + C_HISTNUM/2)/C_HISTNUM);

	// message
	TareMsg();
}

// Page C (returns key PREV/NEXT)
u8 PageC()
{
	int i;
	u8 key;
	s64 sum;

	// C initialize
	C_Init();

	// C display
	C_Disp();

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

		// C update
		C_Update();

		// C display
		C_Disp();

		// keyboard input
		key = KeyGet();
		if (key == KEY_HOLD)
		{
			// do tare
			C_DoTare();
		}
		else if ((key == KEY_PREV) || (key == KEY_NEXT))
		{
			// C terminate
			C_Term();
			return key;
		}
	}
}
