/*
 * probe.c: helping you probe things
 *
 * this file is responsible for figuring out the
 * continuity and diode tests from the raw analog data.
 * 
 * todo: confirm mathematically that continuity values
 *       match the measured values
 *
 * file creation: 20231016 0255
 */

#include "py32f0xx_conf.h"

#include "testo.h"

#include "adc.h"
#include "led.h"
#include "rgbprog.h"
#include "userio.h"

#include "flash.h"



// diode measurements at 3V3
#define DIODE_OPEN				3860		// test unit measures ~4060 no D1, ~3960 with D1
#define DIODE_SCHOTTKY			1960		// vishay 40V 1A SB140
#define DIODE_STANDARD			2330		// fairchild 5V2 zener, forward biased
#define DIODE_LED_BLUE			3190
#define DIODE_LED_GRN			3040		// very dim
#define DIODE_LED_RED			3020
#define DIODE_SHORT				1900		// test unit measures ~1820


// continuity offsets from calibrated short value
#define CONT_47OHM				16
#define CONT_100OHM				34

#define CONT_EXTRA_COUNTS		3		// extra raw counts as a fudge factor

#define CONT_CAL_THRESHOLD		890		// typical short is ~845-860 region

#define CONT_LATCH_THRESHOLD	5		// extra raw counts for latching indicator
#define CONT_LATCH_TIMEOUT		4		// probe measurement passes to hold latched indicator



static uint16_t vref;

static uint8_t cont_cal_timer;
static uint16_t cont_cal_low;
static uint16_t cont_cal_avg;
static const uint16_t cont_thresh_tbl[] = {
	CONT_100OHM, CONT_47OHM
};

static uint8_t latch = 0;



static inline void probe_measure_cont()
{
	uint16_t probe, v_ext;
	uint32_t x, c;
	uint8_t g, b;

	probe = adc_avg[ADC_PROBE];
	v_ext = adc_avg[ADC_VREF_EXT];

	// update vref
	if (!vref) vref = v_ext;
	vref += v_ext;
	vref >>= 1;

	// if the button has been held, change the threshold
	if (userio_get_btn_held() == 60) {
		userio_set_btn_override();
		conf.cont_sensitivity ^= 1;
	}

	// if the button has _really_ been held, clear the calibration
	if (userio_get_btn_held() == 600) {
		conf.cont_shorted = 0;
	}

	// if there is no calibration, try to perform it
	if (!conf.cont_shorted) {
		// until this is performed, show error LEDs
		rgbprog_error_flasher(5, 0, 0, 100);

		// initialize
		if (!cont_cal_timer) {
			cont_cal_low = 0xfff;
			cont_cal_avg = 0;
		}

		// if probe appears to be shorted, calibrate to lowest value read
		if (adc_avg[ADC_PROBE] < CONT_CAL_THRESHOLD) {
			// low value
			if (adc_avg[ADC_PROBE] < cont_cal_low) {
				cont_cal_low = adc_avg[ADC_PROBE];
			}
			// average value
			if (!cont_cal_avg) {
				cont_cal_avg = adc_avg[ADC_PROBE];
			} else {
				cont_cal_avg += adc_avg[ADC_PROBE];
				cont_cal_avg >>= 1;
			}
			cont_cal_timer++;
		} else {
			cont_cal_timer = 0;
		}

		// after held long enough, commit
		if (cont_cal_timer == 180) {
			cont_cal_timer = 0;
			conf.cont_shorted = (cont_cal_avg + cont_cal_low) >> 1;
			flash_commit_conf();
		}
	}

	// right LED remains unused while buzzer is being used,
	// but zero it out anyway
	led_setrgb(1, 0, 0, 0);

	// if no calibration yet, bail here
	if (!conf.cont_shorted) return;
	
	// if the button has been pushed, toggle buzzer on/off
	if (userio_get_btn() > 0) {
		conf.cont_buzzer ^= 1;
	}
	
	if (vref) {
		// note: we don't use VREF anymore...
		// it's too noisy :/
		
		// probe level
		x = probe - conf.cont_shorted;
		if (x > 0xfff) x = 0;

		// threshold level
		c  = cont_thresh_tbl[conf.cont_sensitivity];

		// are we measuring a lower ohm value on the probes than our threshold?
		if (x < (c + CONT_EXTRA_COUNTS)) {	// roughly 100ohm or lower
			latch = CONT_LATCH_TIMEOUT;		// latch any continuity for a while
		}

		// blue led to indicate buzzer is enabled
		b = conf.cont_buzzer ? 20 : 0;

		if (latch) {
			// hysteresis
			if (x >= (c + CONT_LATCH_THRESHOLD)) latch--;

			// indicate continuity
			led_setrgb(0, 0, 250, b);
			led_buzz(0);
			if (conf.cont_buzzer) led_buzz(1);
		} else {
			// idle
			g = conf.cont_sensitivity ? 40 : 0;
			led_setrgb(0, 120, g, b);
			led_buzz(0);
		}
	}
}

static inline void probe_measure_diode()
{
	uint16_t p;

	p = adc_avg[ADC_PROBE];

	led_setrgb(0, 0, 0, 0);

	if (p < DIODE_SHORT) {
		// show off on short
		led_setrgb(1, 0, 0, 0);
	} else
	if (p >= DIODE_OPEN) {
		// show red on open
		led_setrgb(1, 120, 0, 0);
	} else
	if (p >= 1940 && p < 2650) {
		// show green on regular or schottky diodes
		led_setrgb(1, 0, 200, 0);
	} else 
	if (p >= 2650 && p < 3700) {
		// show blue on LEDs
		led_setrgb(1, 0, 0, 500);
	} else
	if (p >= 3701) {
		// show red+blue if between open and LED
		led_setrgb(1, 120, 0, 120);
	}
}

void probe_measure()
{
	uint8_t mode;

	mode = userio_get_mode();

	switch (mode) {
		case MODE_CONT:		probe_measure_cont(); break;
		case MODE_FUN:		break;		// LED program is run elsewhere
		case MODE_DIODE:	probe_measure_diode(); break;
	}
}

/*
 * switch probe mode.
 * sets up LED peripheral and mode pin as necessary.
 */
void probe_mode_switch(uint8_t mode)
{
	switch (mode) {
		case MODE_CONT: {
			// set LED to buzzer mode, set PROBESEL low
			led_mode_buzzer();
			PROBESEL_PORT->BRR  = (1 << PROBESEL_PIN);
			break;
		}
		default: {
			// set LED to dual RGB mode, set PROBESEL high
			led_mode_rgb();
			PROBESEL_PORT->BSRR = (1 << PROBESEL_PIN);
		}
	}

	// save config if moving into LED mode
	if (mode == MODE_FUN) {
		flash_commit_conf();
	}
}