/*
 * 4/2018 Thom Maughan, tm@mbari.org
 *
 */


/*
 * System.c contains a variety of functions related to sensors and basic system utilities.
 */

#include "crc/crc-32c.h"

#include "main.h"
#include "apf.h"
#include "system.h"
#include "sleep.h"
#include "sample.h"
#include "user_io.h"
#include "ads1248_iso.h"
#include "ads1248_noniso.h"
#include "ctd_aand5860.h"

#include "board_util.h"
#include "ctd_base.h"
#include "ctd_sbe37.h"
#include "optode.h"
#include "sleep.h"
#include "sample.h"
#include "microsd.h"
#include "fatfs/source/ff.h"
#include "uartstdio.h"	// User local version with larger RX buffer
#include "pwm.h"
#include "i2c_fram.h"
#include "config.h"
#include "apf_init.h"
#include "apf_util.h"
#include "serial_cmd_fxn.h"
#include "apf_util.h"
#include "init_defaults.h"

#include "fatfs/source/diskio.h"

#include "inc/hw_sysctl.h"

#include "arm_acle.h"

#include <assert.h>
#include <stdatomic.h>

//EEPROM layout 0x1800 bytes, [0x0000 .. 0x1799]
// 0x0000
// 0x0400 - sys_data @ sizeof(sys_data)
// 0x0A00 - apf_eeprom @ sizeof(apf_eeprom)
// 0x1799
//sizeof(sys_data) is 1400, padding size to nearest multiple of 256 (0x100)
#define SYSDATA_OFFSET   0x0400UL           //size of sys_data is 1408
#define APFEEPROM_OFFSET 0x0A00UL           //size of apf_eeprom is 296
#define SYSQA_OFFSET     0x0C00UL

#define EEPROM_SIZE 1536UL*4UL
static_assert((SYSDATA_OFFSET + sizeof(sys_data)) < APFEEPROM_OFFSET, "sys_data does not fit");
//static_assert((APFEEPROM_OFFSET + sizeof(apf_eeprom)) < EEPROM_SIZE,  "apf_eeprom does not fit");   //apf_eeprom size = 296
static_assert((APFEEPROM_OFFSET + sizeof(apf_eeprom)) < SYSQA_OFFSET,  "apf_eeprom does not fit");   //apf_eeprom size = 296
static_assert((SYSQA_OFFSET + sizeof(sys_qa)) < EEPROM_SIZE,  "sys_qa does not fit");

extern int optode_stateMachine(int optodeState);

extern void adc_onchip_init(void);
extern uint32_t * read_all_adc(void);
extern void fram_store(void);
extern void ADS1248_gpio3(uint32_t state);

#if BOARD_MSC == 3
extern void disk_timerproc(void);
#endif

void fram_retrieve(void);


// Global variables
extern struct optodeDriver optode;
extern struct microCatDriver microCat;
extern struct ctdDriver ctd;   // struct defd in ctd_base.h

uint32_t eeprom_apfcfg_retrievedFlg = 0;

uint32_t sys_data_chksum32;

extern float fBiasPos;  // used for read_bias_bat();
extern float fBiasNeg;

static atomic_ulong timer_10ms = 0;       // ulong is uint32_t on this processor
static atomic_ulong timer_1ms  = 0;

extern uint32_t adc_data[];
extern int dbg_flag;
extern int profileTarget;
extern int profile2Target;
extern int ads1248noniso_flg;

#define TIME_TXSAMPLE_MSEC      30      // was 50

/*****************************************************************************
*
* System tick interrupt handler.  Updated to 1ms for system timing.
* Requires: Requires MAP_SysTickPeriodSet(MAP_SysCtlClockGet() / 1000); in init.c
* Also, provides 10 ms tick for various timing requirements.
* FatFs requires a timer tick every 10 ms for internal timing purposes.
*
*****************************************************************************/
void SysTickHandler(void)
{
    unsigned long temp = atomic_fetch_add(&timer_1ms, 1UL);

    if(((temp+1UL) % 10UL) == 0UL)
    {
#if BOARD_MSC == 3
        disk_timerproc();                 // FatFs
#else
    #warning JLD - fatfs fix equivalent?
#endif
        atomic_fetch_add(&timer_10ms, 1UL); // For Timer_10ms()
    }

    //pwmSoln_stateMachine(-1);         // manages PG1,PG3 GPIO outputs,  -1 says use global state variables
    //pwmPump_stateMachine(-1);
}

/****************************************************************************
 * Timer_10ms()
 *
 * Returns value of 10 ms system tick variable. Can be used for general
 * purpose timing where a simple delay is not workable.
 * Call with "reset" true to reset timer to zero.
 * RCG 4/14
 ***************************************************************************/
unsigned long Timer_10ms(void)
{
    const unsigned long temp = atomic_load(&timer_10ms);

	return temp;
}

/****************************************************************************
 * Timer_1ms()
 *
 * Returns value of 1 ms system tick variable. General purpose timing

 ***************************************************************************/
unsigned long Timer_1ms(void)
{
    const unsigned long temp = atomic_load(&timer_1ms);

    return temp;
}

bool is_timer_1ms_expired(const unsigned long t0, const unsigned long dt_ms)
{
    return (Timer_1ms() - t0) > dt_ms;
}

bool is_timer_10ms_expired(const unsigned long t0, const unsigned long dt_10ms)
{
    return (Timer_10ms() - t0) > dt_10ms;
}

/****************************************************************************
 * compute_sys_data_chksum
 *
 * Compute checksum on sys data struct
 ***************************************************************************/
uint32_t compute_sys_data_chksum(void)
{
    int indx;
    uint8_t *bytePtr;
    uint32_t chkSum32 = 0;

    bytePtr = (uint8_t *)&sys_data;

    for(indx=0; indx<sizeof(sys_data); indx++)
    {
        chkSum32 += *bytePtr;     // add byte at a time, sum into 32 bit checksum (later use a more bulletproof alg)
        bytePtr++;
    }
    return(chkSum32);
}


void initSysDataVolatileVariables(void)
{
    uprintf("\r\nWarning: Initialized volatile sys data variables\r\n");

    sys_samp.state = IDLE;
    sys_samp.nextWakeUp = read_hib_rtcc()+sys_data.sampling_period;
    sys_samp.next_gdata_fptr = 0L;      // deprecated (not used, need to confirm)
    sys_samp.current_sample = 0;
    sys_samp.AD24_std = 0.0;
    //sys_samp.deploy_time = 0;     // TODO

}




/*
 * Retrieves the saved sys_data variables from their storage
 * location in EEPROM (when the device hibernates, sys_data variables
 * are stored in EEPROM if there is a change - 500K write cycle limit).
 * If no value has been assigned to a sys_data variable, a default
 * value is assigned.
 */
//int retrieveSysDataVariables(void)
int eeprom_sys_data_retrieve(void)
{
#if BOARD_MFET == 2
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_EEPROM0);
#endif

	//read the sys_data struct out of EEPROM
	MAP_EEPROMRead((uint32_t*) &sys_data, SYSDATA_OFFSET, sizeof(sys_data));

	//perform 'error checking'  THOMTHOM
	// Criteria for 'bad' EEPROM sys_data
	// uSD is 0xff
	//sys_data_error_check();

	return 1;
}

// return 0 on success
uint32_t eeprom_sys_data_store(void)
{
    uint32_t fram_ret = fram_sys_samp_store();

//    crc32c_t crc = crc32c_init();
//    crc = crc32c_update(crc, &sys_data, sizeof(sys_data));
//    crc = crc32c_finalize(crc);

    // store the sys_data structure (_vol is used to prevent changes in eeprom - this needs to be improved when we drop support for older hardware)
    // returns 0 on success
    uint32_t eeprom_ret = MAP_EEPROMProgram((uint32_t*) &sys_data, SYSDATA_OFFSET, sizeof(sys_data));

    //return ((fram_ret == 0) && (eeprom_ret == 0)) ? (0) : (1);

    return (eeprom_ret);
}

int eeprom_sys_qa_retrieve(void)
{
    //read the sys_data struct out of EEPROM
    MAP_EEPROMRead((uint32_t*) &sys_qa, SYSQA_OFFSET, sizeof(sys_qa));

    return 1;
}

// return 0 on success, APFTODO, change ret vals for consistency.   1 is OK, 0 is BAD is recommended.
uint32_t eeprom_sys_qa_store(void)
{

    // returns 0 on success
    uint32_t eeprom_ret = MAP_EEPROMProgram((uint32_t*) &sys_qa, SYSQA_OFFSET, sizeof(sys_qa));

    return (eeprom_ret);
}

#if BOARD_MSC == 3
int eeprom_apf_cfg_retrieve(void)
{
    //read the sys_data struct out of EEPROM.  Last argument ensures # of bytes is multiple of 4
    MAP_EEPROMRead((uint32_t*) &apf_eeprom, APFEEPROM_OFFSET, sizeof(apf_eeprom));

    eeprom_apfcfg_retrievedFlg = 1;         //what's in memory, matches what's been stored

    return 1;
}

// return 0 on success
uint32_t eeprom_apf_cfg_store(void)
{
//    crc32c_t crc = crc32c_init();
//    crc = crc32c_update(crc, &apf_eeprom, sizeof(apf_eeprom));
//    crc = crc32c_finalize(crc);

    // store the sys_data structure (_vol is used to prevent changes in eeprom - this needs to be improved when we drop support for older hardware)
    // returns 0 on success
    uint32_t ret = MAP_EEPROMProgram((uint32_t*) &apf_eeprom, APFEEPROM_OFFSET, sizeof(apf_eeprom));
    if(ret == 0)
    {
        eeprom_apfcfg_retrievedFlg = 1;         //what's in memory, matches what's been stored
    }

    return ret;
}

#endif // #if BOARD_MSC == 3

//int verifySysDataVariables(void)
int sys_data_verify(void)
{
    if (sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] > EXEC_MAXDEFD)
    {
        sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] = EXEC_DEFAULT;
        return(FALSE);
    }

    return(TRUE);
}


// Calculates temperature from Durafet's thermistor. V_therm is the voltage from thermistor, measured using isolated 24-bit ADC.
// TCOffset is an user input, to correct thermistor to be more accurate.
float DuraFET_temp(float V_therm, float TCOffset)
{
	// Declare variables
    float TC;
	double Rtherm;
	double TC4, TC3;
	double c0, c1, c2, c3;
	double A, B, C;
	double dVtherm;

	dVtherm = (double)V_therm;

#ifdef NOCODE
	float c0 = 340.9819863;
	float c1 = -9.10257E-05;
	float c2 = -95.08806667;
	float c3 = 0.965370274;
#endif

	//if(quadratic)
	// Thermistor coefficients, Steinhart-Hart coefficients
    c0 = sys_data.quadTherm_c0;      //340.9819863;
    c1 = sys_data.quadTherm_c1;      //-9.10257E-05;
    c2 = sys_data.quadTherm_c2;      //-95.08806667;
    c3 = sys_data.quadTherm_c3;      //0.965370274;

    //A = sys_data.shTherm_A;
    //B = sys_data.shTherm_B;
    //C = sys_data.shTherm_C;


    A = sys_data.quadTherm_c0;
    B = sys_data.quadTherm_c2;
    C = sys_data.quadTherm_c3;

#define TEMP_ABS    273.15
	// Calculate TC
	Rtherm = 20000 / ((3.3 / dVtherm) - 1);

	//This code came from Scripps
	TC4 = c0 + c1*Rtherm + c2*(log10(Rtherm)) + c3*(pow(log10(Rtherm), 3));   // I suspect conversion to Celsius is built into c0 coefficient.

	// double TC4ln = c0 + c1*Rtherm + c2*(log(Rtherm)) + c3*(pow(log(Rtherm), 3));

	//1/T = A + B(LnR) + C(LnR)^3  (Kelvin) - This Thom has coded from an AVX appnote on NTC thermistors, plus some other research
	TC3 = (double)1 / (double)( A + B*(log(Rtherm)) + C*(pow(log(Rtherm), 3)) );
	//TC3 -= TEMP_ABS;  // convert to Celsius

	// Apply offset to TC; TCOffset is an user input,
	// or derived during pH calibration process.
	TC = 0.0;
    if(sys_data.app_cfg[APPCFG_TEMPC_SH_ALG] == 0)
        TC = (float)TC4;
	if(sys_data.app_cfg[APPCFG_TEMPC_SH_ALG] == 1)
	    TC = (float)TC3;

//uprintf("\r\nDEBUG TC4 = %8.4lf, TC4ln = %8.4lf,  TC3 = %8.4lf\r\n;", TC4, TC4ln, TC3);

	TC = TC + TCOffset;

	//Rtherm =20000./(3.3./Vtherm-1);
	//
	//Steinhart - Hart Equation 1/T = A + B(LnR) + C(LnR)^3  (Kelvin)


	return TC;
}

/*
* Calculate pH using external reference.
* Eext refers to the voltage from the external reference, measured using isolated 24-bit ADC.
* TC should be calculated using fucntion getDurafettemp.
* Salt refers to salinity, extracted from SBE 37.
* RCG 5/14
*/
float calc_pHext(float Eext, float TC, float salt)
{
	float TK;				// Temperature in Kelvin
	float S_T;				// Nernst Slope
	float Eoext;			// Calibration coefficient corrected to in situ temperature
	float Z; 				// Ionic Strength; Dickson 2007
	float SO4_tot; 			// Total sulfate concentration; Dickson 2007
	float mCl; 				// Molality of Chloride; Dickson 2007;
	float K_HSO4; 			// Bisulfate acidity constant; Dickson 1990.
	float DHconst;			// Debye-Huckel; Khoo et al. 1997
	float log10gamma_HCl;	// log10 of mean activity coefficient of HCl in seawater
	float pHext_free;		// pH on free scale calculated from external reference
	float pHext_tot;		// pH on total scale calculated from external reference

	// Start calculations
	TK = TC + 273.15;
	S_T = (R_ * TK) / F_ * LN10;
	//Eoext = sys_data.Eo_ext_25C + dEoEXT_dt * (TC - 25);
	Eoext = sys_data.k0ext + dEoEXT_dt * (TC - 25);

	Z = 19.924 * salt / (1000 - 1.005 * salt); // Ionic strength
	SO4_tot = (0.14 / 96.062) * (salt / 1.80655); // molality sulfate from conservative relationship.
	mCl = 0.99889 / 35.453 * salt / 1.80655; // moallity chloride from conservative relationship.
	K_HSO4 =  exp(-4276.1/(TK)+141.328-23.093*log(TK)+(-13856/(TK)+324.57-47.986*log(TK))*sqrt(Z)+(35474/(TK)-771.54+114.723*log(TK))*Z-2698/(TK)*pow(sqrt(Z), 3)+1776/(TK)*pow(Z, 2)+log(1-0.001005*salt));
	DHconst = 0.00000343 * (pow(TC, 2)) + 0.00067524 * TC + 0.49172143; //Debye-Huckel
	log10gamma_HCl = 2*(-DHconst * sqrt(Z) / (1 + 1.394 * sqrt(Z)) + (0.08885 - 0.000111 * TC) * Z);

	pHext_free =  -( (Eoext - Eext) - S_T * (log10(mCl) + log10gamma_HCl) ) / S_T;
	pHext_tot = pHext_free - log10(1 + SO4_tot / K_HSO4); // pH from external reference electrode on total scale.
	return pHext_tot;

}

/*
* calc_pHint()
* Function to calculate pH using internal reference.
* Eint refers to the voltage from the internal reference, measured using the isolated 24-bit ADC.
* TC should be calculated using function getDurafettemp.
* RCG 5/14
*/
float calc_pHint(float Eint, float TC)
{
	float TK; 		// Temperature in Kelvin
	float S_T; 		// Nernest Slope
	float Eoint;  	// Calibration coefficient corrected to in situ temperature.
	float pHint;

	TK = TC + 273.15;
	S_T = (R_ * TK) / F_ * LN10;

	//Eoint = sys_data.Eo_int_25C + dEoINT_dt * (TC - 25);
	Eoint = sys_data.k0int + dEoINT_dt * (TC - 25);

	pHint = (Eint - Eoint) / S_T;

	return pHint;
}

/*
 * ph_calib()
 * Calculates DuraFET pH sensorcalibration values
 * using either user entered values, or automatic
 * calibration using temperature and salinity derived
 * from a MicroCAT CTD.
 * RCG 5/14
 */
void ph_calib(void)
{
	int response, i, j, samp, lines;
	char buff[100];
	static float Vint=0, Vext=0, pHcalpt=0, tempC, salt;
	float ftemp, tempK, E0int, S_T, Z, SO4_tot, mCl, K_HSO4, pHint_free, Vtherm, TCOffset;
	float DHconst, log10gamma_HCl, mHfree, aHfree_aCl, E0ext, sqrt_Z, E0int25, E0ext25;	// beta_SO4,
	float StdDev[SD_VALUES][SD_DIFF_SQ+1], diff, sum;
	float Vint_SD, Vext_SD, Vtherm_SD, tempC_SD, salt_SD;

	unsigned char charInput;


	uprintf("\r\nph_calib() not supported in this version\r\n");
	//return;

	// pH Calibration point
	while(1)
	{
		sprintf(buff, "\r\n\r\nEnter pH calibration point [%.3f]: ", pHcalpt);
		uprintf("%s", buff);
		if(getUserInput(buff, strlen(buff)))
		{
			if(sscanf(buff, "%f", &ftemp) == 1)
			{
				if(ftemp >= 0 && ftemp <= 14)
				{
					pHcalpt = ftemp;
					uprintf("pH cal pt = %.3f\r\n", pHcalpt);
					break;
				}
				else uprintf("\r\nEnter between 0 and 14");
				continue;
			}
			else
			{
			    uprintf("DEBUG: sscanf fails to return 1");
			    break;   // if sscanf fails, exit?
			}
		}
		else
		{
		    uprintf("DEBUG: getUserInput fails to return 1");
		    break;         // if
		}
	}

	// Automatic or manual calibration?
	prn_nl(); prn_nl(); 
	response = yesOrNoMenuChoice("Perform automatic DuraFET calibration (Y/N) [N]? ", NO);

	// Manual calibration
	if(response == NO)
	{
		// Vint
		while(1)
		{
			sprintf(buff, "\r\n\r\nEnter Vrsi [%f]: ", Vint);
			uprintf("%s", buff);
			if(getUserInput(buff, strlen(buff)))
			{
				if(sscanf(buff, "%f", &ftemp) == 1)
				{
					if(ftemp >= (-2) && ftemp <= 2)
					{
						Vint = ftemp;
						break;
					}
					else uprintf("Enter number between -2 and 2\r\n\r\n");
					continue;
				}
				else break;
			}
			else break;
		}

		// Vext
		while(1)
		{
			sprintf(buff, "\r\n\r\nEnter Vrse [%f]: ", Vext);
			uprintf("%s", buff);
			if(getUserInput(buff, strlen(buff)))
			{
				if(sscanf(buff, "%f", &ftemp) == 1)
				{
					if(ftemp >= (-2) && ftemp <= 2)
					{
						Vext = ftemp;
						break;
					}
					else uprintf("Enter number between -2 and 2\r\n\r\n");
					continue;
				}
				else break;
			}
			else break;
		}

		// Temperature
		while(1)
		{
			sprintf(buff, "\r\nEnter bath temperature [%f]: ", tempC);
			uprintf("%s", buff);
			if(getUserInput(buff, strlen(buff)))
			{
				if(sscanf(buff, "%f", &ftemp) == 1)
				{
					if(ftemp >= 0 && ftemp <= 100)
					{
						tempC = ftemp;
						break;
					}
					else uprintf("\r\nEnter between 0 and 100");
					continue;
				}
				else break;
			}
			else break;
		}

		// Salinity
		while(1)
		{
			sprintf(buff, "\r\nEnter bath salinity [%f]: ", salt);
			uprintf("%s", buff);
			if(getUserInput(buff, strlen(buff)))
			{
				if(sscanf(buff, "%f", &ftemp) == 1)
				{
					if(ftemp >= 0 && ftemp <= 100)
					{
						salt = ftemp;
						break;
					}
					else uprintf("\r\nEnter between 0 and 100");
					continue;
				}
				else break;
			}
			else break;
		}
	}

	// Automatic calibration
	else
	{
		// Clear standard deviation array
		for(j=0; j<SD_AVG; j++)
		{
			for(i=0; i<5; i++) StdDev[i][j] = 0;
		}

		j = 0;				// Reset standard deviation array counter
		lines = 20; 		// Force program to write column labels before first data line

		//ctd_open();
		openADS1248_iso();
		MAP_SysCtlDelay(ONESEC);	// Let instruments and sensors settle
		prn_nl(); prn_nl(); uprintf("Place DuraFET and MicroCAT in calibration bath.");
		prn_nl(); uprintf("Hit any key when values have stabilized...");
		UARTFlushRx();

		while(1)
		{
			Vint = pollADS1248_iso(1, 6, sys_data.Vrsi_trials, 1, 5);
			Vext = pollADS1248_iso(4, 6, sys_data.Vrse_trials, 1, 5);
			Vtherm = pollADS1248_iso(0, 6, sys_data.Vtherm_trials, 1, 5);	// Chan 0-6, trials = user, gain = 1, sps = 5 Hz
			tempC = DuraFET_temp(Vtherm, sys_data.TCOffset);   // Thom, uncommented this line, future, allow entry of TCOffset as difference between MicroCat temp

			int ctdState;
			ctdState = 1;
			//uprintf("DEBUG: CTD statemachine = 1\r\n");
			while(1)
	        {
			    // ctd will open and close in the loop, might want to mod statemachine for ctd always on operation
	            if(ctdState == 0)            // makes sure ctd statemachine is finished
	                break;                  //ctd.salt and ctd.tempC returned on success
	            //ctdState = ctd_stateMachine(ctdState);

	            if((sys_data.data_cfg[CFG_UART4_INST]) == INST_SBE37_MICROCAT)
	            {
	                ctdState = ctd_sbe37_stateMachine(ctdState);     //defd in ctd_sbe37.c

	            }

	            if((sys_data.data_cfg[CFG_UART4_INST]) == INST_AANDERAA_5860)
	            {
	                ctdState = ctd_aand5860_stateMachine(ctdState);     //defd in ctd_aand5860.c

	            }



                if(UARTRxBytesAvail())
                {
                    charInput = UARTgetc();

                    if(charInput == CTRL_X)  //24
                    {
                        ctd.tempC = 0.0;
                        ctd.salt = 0.0;
                        ctd_close();
                        //uprintf("Debug: reading of ctd terminated by ctrl-x\r\n");
                        break;
                    }

                }

	            // allow user to exit with Ctrl-X

	        }

#ifdef OLDCODE
			ctd_microCat_poll();        //pollMicroCAT();
			MAP_SysCtlDelay(5*ONESEC);	// Wait for instruments to respond
			ctd_microCat_parseData();	// Extract the data from response strings
#endif

			//uprintf("Debug:  tempC = %f, salt = %f", ctd.tempC, ctd.salt);
			tempC = ctd.tempC;
			salt = ctd.salt;

			// Store current sample values
			if(samp < SD_AVG) samp++;
			else samp = 0;
			StdDev[0][samp] = Vint;
			StdDev[1][samp] = Vext;
			StdDev[2][samp] = Vtherm;
			StdDev[3][samp] = tempC;
			StdDev[4][samp] = salt;


			// Find the mean of the last SD_AVG samples for each sensor
			for(i=0; i<SD_VALUES; i++)
			{
				sum = 0;

				for(j=0; j<SD_AVG; j++)
				{
					sum += StdDev[i][j];
				}

				StdDev[i][SD_MEAN] = sum / SD_AVG;	// Current mean for each sensor
			}

			// Find the difference squared for each of the last SD_AVG samples
			// Then find the sum of the squared differences
			for(i=0; i<SD_VALUES; i++)
			{
				sum = 0;

				for(j=0; j<SD_AVG; j++)
				{
					 diff = (StdDev[i][j] - StdDev[i][SD_MEAN]) ;	// Difference for each sample
					 sum += diff * diff;							// Sum of differences squared
				}

				StdDev[i][SD_DIFF_SQ] = sum;
			}

			// Take square root for each sensor
			Vint_SD = sqrt(StdDev[0][SD_DIFF_SQ]);
			Vext_SD = sqrt(StdDev[1][SD_DIFF_SQ]);
			Vtherm_SD = sqrt(StdDev[2][SD_DIFF_SQ]);
			tempC_SD = sqrt(StdDev[3][SD_DIFF_SQ]);
			salt_SD = sqrt(StdDev[4][SD_DIFF_SQ]);

			// Write column headers every 20 lines
			if(lines >= 20)
			{
                prn_nl(); prn_nl(); uprintf("    Vrsi\t Vrsi-SD\t"
                        "    Vrse\t Vrse-SD\t"
                        "  Vtherm\tVthermSD\t"
                        "   TempC\tTempC-SD\t"
                        "Salinity\tSal-SD");

				lines = 1;		// Reset line counter
			}
			else lines++;

			// Write data line
			prn_nl(); uprintf("%8.5f\t%8.5f\t"
					"%8.5f\t%8.5f\t"
					"%8.5f\t%8.5f\t"
					"%8.5f\t%8.5f\t"
					"%8.5f\t%8.5f",
					Vint, Vint_SD,
					Vext, Vext_SD,
					Vtherm, Vtherm_SD,
					tempC, tempC_SD,
					salt, salt_SD);

			// Key hit, so display current averaged values
			if(kbhit())
			{
				prn_nl(); prn_nl(); uprintf("Average of last %d values", SD_AVG);

				Vint = StdDev[0][SD_MEAN];
				prn_nl(); uprintf("Vint = %f V  SD = %f", Vint, Vint_SD);

				Vext = StdDev[1][SD_MEAN];
				uprintf("\r\nVext = %f V  SD = %f", Vext, Vext_SD);

				Vtherm = StdDev[2][SD_MEAN];
				uprintf("\r\nVtherm = %f C  SD = %f", Vtherm, Vtherm_SD);

				tempC = StdDev[3][SD_MEAN];
				uprintf("\r\nMicroCAT Temperature = %f C  SD = %f", tempC, tempC_SD);

				salt = StdDev[4][SD_MEAN];
				uprintf("\r\nMicroCAT Salinity = %f  SD = %f", salt, salt_SD);

				prn_nl(); prn_nl(); 
				response = yesOrNoMenuChoice("Values acceptable (Y/N) [N]? ", NO);
				if(response == NO) continue;
				else break;
			}
		}

		closeADS1248_iso();
		ctd_close(); 	//closeMicroCAT();
	}

	// Calculate E0int25 and E0ext25 using either manual or automatic values
	tempK = tempC + 273.15;  					// Convert temp from C to K
	S_T = (R_ * tempK) / F_ * log(10); 			// Nernst temp dependence
	E0int = Vint - S_T * pHcalpt; 				// Calc E0int from Nernst & pH @ calibration point
	E0int25 = E0int + dEoINT_dt * (25 - tempC);

	Z = 19.924 * salt / (1000 - 1.005 * salt); 			// Ionic strength, Dickson et al. 2007
	sqrt_Z = sqrt(Z);

	SO4_tot = (0.14 / 96.062) * (salt / 1.80655);  	// Total conservative sulfate
	mCl = 0.99889/35.453*salt/1.80655; 				// Conservative chloride

	// Bisulfate equilibrium const., Dickson et al. 2007
	K_HSO4 = exp( -4276.1 / tempK + 141.328 - 23.093 * log(tempK) \
	          + ( -13856 / tempK + 324.57 - 47.986 * log(tempK) ) * sqrt_Z \
	          + ( 35474 / tempK - 771.54 + 114.723 * log(tempK) ) * Z - 2698 / tempK * pow(Z, 1.5) \
	          + 1776 / tempK * Z * Z + log(1 - 0.001005 * salt) );

	pHint_free = pHcalpt + log10( 1 + SO4_tot / K_HSO4);
	DHconst = 0.00000343 * tempC * tempC + 0.00067524 * tempC + 0.49172143; //Debye-Huckel, Khoo et al. 1977
	log10gamma_HCl = 2 * (-DHconst * sqrt_Z / ( 1 + 1.394 * sqrt_Z ) + (0.08885 - 0.000111 * tempC) * Z);
	mHfree = pow(10, (-pHint_free) );
	aHfree_aCl = mHfree * mCl * pow( 10, (log10gamma_HCl) );
	E0ext = Vext + S_T * log10(aHfree_aCl);
	E0ext25 = E0ext + dEoEXT_dt * (25-tempC);

	TCOffset =  tempC - DuraFET_temp(Vtherm, 0);

	uprintf("\r\n\r\nEo Int @ 25C = %f", E0int25);
	uprintf("\r\nEo Ext @ 25C = %f", E0ext25);
	uprintf("\r\nTCOffset = %f", sys_data.TCOffset);

	prn_nl(); prn_nl(); 
	response = yesOrNoMenuChoice("Accept and store calibration values(Y/N) [N]? ", NO);
	if(response == YES)
	{
		//sys_data.Eo_int_25C = E0int25;
		sys_data.k0int = E0int25;
		
		//sys_data.Eo_ext_25C = E0ext25;
		sys_data.k0ext = E0ext25;			// variable rename 2 Apr 2021  Eo_ext_25C to k0ext

		sys_data.TCOffset = TCOffset;

		uprintf("\r\nCalibration values stored");
	}

}



/*
 *  send last sample, then take new sample and store to SD card
 * Take measurements and write them to SD card.
 */
void sendlast_takesample(void)
{
    FIL fileObject;
    FRESULT fresult;
#define MAX_DATASAMPSIZE    240     // this is oversized by over 20 bytes
    unsigned char buf[MAX_DATASAMPSIZE];
    unsigned int readSize;

    if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0)        // fix 6 Aug 2021
    {
        uprintf("void sendlast_takesample(void) is not supported with microSD disabled\r\n");
        return;

    }

    if( ! microSD_flg )
    {
        //if(fileDbgFlg == 1) { uprintf("DBG MicroSD is disabled, likely due to f_open error"); prn_nl(); } //APFTODO, remove extraneous prints
        return;
    }


    uprintf("THOM TODO:  Sendlast_takesample is not done yet\r\n");

    // open snapshot.txt, send all until /n
    fresult = f_open(&fileObject, "snapshot.txt", FA_READ);  // Creates a new file. If the file is existing, it will be truncated and overwritten.
    if(fresult != FR_OK)
    {
        // file does not exist, first time reading so previous data not available
        //get_sample(0);      // arg=0 says 'dont tx the result', this will create the snapshot.txt file (after a delay)
        get_sample(0,1);   // THOM 14 May 2021
        fresult = f_open(&fileObject, "snapshot.txt", FA_READ);  // Creates a new file. If the file is existing, it will be truncated and overwritten.
        if(fresult != FR_OK)
        {
            uprintf("f_open error snapshot.txt: %s\r\n", StringFromFresult(fresult));
            error_store("f_open error on snapshot.txt");
            return;
        }

    }

    fresult = f_read(&fileObject, &buf[0], MAX_DATASAMPSIZE-1, &readSize);
    if(fresult != FR_OK)
    {
        uprintf("f_read error snapshot.txt\r\n");
    }
    else
    {

        if(readSize > 4)
        {
            if(readSize >= MAX_DATASAMPSIZE)        // in the odd chance of an error
                readSize = MAX_DATASAMPSIZE-1;
            buf[readSize] = 0x00;       // null terminate

            uprintf("%s", buf);       //uprintf("%s\r\n", buf); print the sample

        }
        else
        {
            uprintf("bad read size (less than 5)\r\n");
        }
    }

    // Close the file
    fresult = f_close( &fileObject );
    if(fresult != FR_OK)
    {
        //uprintf("8\r\n");
        uprintf("f_close error snapshot.txt: %s\r\n", StringFromFresult(fresult));
        error_store("f_close error");
    }

    //get_sample(0);          // get sample for reading with next 'sl'
    get_sample(0,1);
}



/****************************************************************
 * Get the current time according to the RTC.
 * returns in time-strcut
 ****************************************************************/
struct tm *get_RTC_time(void)
{
    //char timeStamp[TIMESTAMPLENGTH];
    uint32_t ticks;
    struct tm *time_struct;

    //get the time from the from the RTC so we can print it
    ticks = read_hib_rtcc();

    time_struct = localtime((const time_t*)&ticks);

    // DEBUG to print the time
    //strftime(timeStamp, sizeof(timeStamp),"%m/%d/%y %H:%M:%S", time_struct);
    //uprintf("%s", timeStamp);

    return(time_struct);
}

void init_timer(TimerState* const t_ptr, unsigned long dt)
{
    t_ptr->t0_ms = Timer_1ms();
    t_ptr->dt_ms = dt;
}
bool is_timer_expired(TimerState const * const t_ptr)
{
    // use > to try to not under sleep, bias towards oversleeping, subtract to handle wrap around
    return (Timer_1ms() - t_ptr->t0_ms) > t_ptr->dt_ms;
}

/*
 * error_store()
 * Stores error messages with timestamp.
 * Appends a newline character to message.
 * Function is useful during deployment when console is disconnected
 * RCG 5/14
 */
void error_store(const char *error_buff)
{
#if BOARD_MSC == 3
	return;     //remove outof band serial messages for MSC3
#else
    char timestamp[TIMESTAMPLENGTH];
    uint32_t ticks;
    struct tm *time_struct;
    FIL fil;        // File object
    FRESULT fresult;
    uint32_t bw;

    if( ! microSD_flg )
    {
        //if(fileDbgFlg == 1) { uprintf("DBG MicroSD is disabled, likely due to f_open error"); prn_nl(); } //APFTODO, remove extraneous prints
        //error_store("MicroSD is disabled");
        return;
    }

	if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0)        // 20 Jan 2021 TGM
	{
	    uprintf("error_stor() not supported with microSD_flg=0 disabled\r\n");
	    return;
	}

	// Open a file
	fresult = f_open(&fil, "Error.txt", FA_WRITE |FA_OPEN_ALWAYS);
	if(fresult != FR_OK)
	{
		uprintf("Error.txt f_open error: %s\r\n", StringFromFresult(fresult));
	}

	// Seek to the end, to append our file
	fresult = f_lseek(&fil, f_size(&fil));
	if(fresult != FR_OK)
	{
		uprintf("Error.txt f_lseek error: %s\r\n", StringFromFresult(fresult));
	}

	// Write timestamp to file
	ticks = read_hib_rtcc();
	time_struct = localtime(&ticks);
	strftime(timestamp, sizeof(timestamp),"\r\n%Y/%m/%d %H:%M:%S ", time_struct);

	fresult = f_write( &fil, timestamp, strlen(timestamp), &bw );
	if(fresult != FR_OK)
	{
		uprintf("Error.txt f_write error: %s\r\n", StringFromFresult(fresult));
	}

	// Write error message to file
	fresult = f_write( &fil, error_buff, strlen(error_buff), &bw );
	if(fresult != FR_OK)
	{
		uprintf("Error.txt f_write error: %s\r\n", StringFromFresult(fresult));
	}

	// Close the file
	fresult = f_close( &fil );
	if(fresult != FR_OK)
	{
		uprintf("Error.txt f_close error: %s\r\n", StringFromFresult(fresult));
	}

#endif  // BOARD_MSC is not 3
}

#if 0
void delay_sec_coarse(unsigned long secs)
{
    delay_msec_coarse(secs * 1000U);
}
void delay_msec_coarse(unsigned long msec)
{
    if(msec > 0UL)
    {
        const unsigned long t_start = Timer_1ms();
        do
        {
            // wait for next interrupt
            // this gates internal clocks and should use somewhat less power than delay_msec
            // at the expense of accuracy

            // Spinning on WFI can crash, as not all peripherals are set up correctly for sleep mode
            // TODO: configure needed peripherals to be awake in sleep using MAP_SysCtlPeripheralSleepEnable
            // See tm4c manual caution in section 5.2.6.2
            // const bool was_disabled = MAP_IntMasterDisable();
            // _isb();
            // _dsb();
            // _isb();
            // __wfi();
            // _isb(); // WFI can crash on some cortex-m if a branch is near the wfi, ppl think it is a bug in the prefetcher
            // if( ! was_disabled )
            // {
            //    MAP_IntMasterEnable();
            //   _isb(); // to ensure that ISRs fire, we need to ISB between the enable / disable ISR calls. See ARM DAI0321A. It is not totatally required for cortex-m but be conservative.
            // }
        } while((Timer_1ms() - t_start) <= msec); // use <= to try to not under sleep, bias towards oversleeping, subtract to handle wrap around
    }
}
#endif
void delay_sec(unsigned int secs)
{
    unsigned int cnt;
    for(cnt = 0; cnt < secs; cnt++)
    {
        delay_msec(1000);
    }
}

void delay_msec(unsigned int msec)
{
    uint32_t indx;
    for(indx=0; indx<msec; indx++)
    {
        wdog_pet();     //defd in board_util.c
        MAP_SysCtlDelay(MILLISECOND);
    }
}

void delay_msec_noDog(unsigned int msec)
{
    uint32_t indx;
    for(indx=0; indx<msec; indx++)
    {
        MAP_SysCtlDelay(MILLISECOND);
    }
}

void delay_usec(unsigned int usec)
{
    MAP_SysCtlDelay(MICROSECOND*usec);
    wdog_pet();
}

//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line)
{
}
#endif



FRESULT open_append (
    FIL* fp,            /* [OUT] File object to create */
    const char* path    /* [IN]  File name to be opened */
)
{
    /* Opens an existing file. If not exist, creates a new file. */
    fRes = f_open(fp, path, FA_WRITE | FA_OPEN_ALWAYS);
    if (fRes == FR_OK) {
        /* Seek to end of the file to append data */
        fRes = f_lseek(fp, f_size(fp));
        if (fRes != FR_OK)
            f_close(fp);
    }
    return fRes;
}

int version = 0;
int didClass = 0;
int didMajor = 0;
int didMinor = 0;
//void printSiliconDetails(void)
//{
//    uprintf("Silicon details: %d %d %d %d", version, didClass, didMajor, didMinor); prn_nl();
//}

bool check_silicon_revision_matches_tiva_build()
{
    const uint32_t did0 = HWREG(SYSCTL_DID0);
    const uint32_t did0_ver = (did0 & 0x70000000U) >> 28;

    if(did0_ver != 0x01)
    {
        // We only know how to decode DID0 version 2
        return false;
    }
    version = 1;
    // 0x18 05 01 01
    const uint32_t did0_class = (did0 & 0x00FF0000U) >> 16;
    const uint32_t did0_major = (did0 & 0x0000FF00U) >> 8;
    const uint32_t did0_minor = (did0 & 0x000000FFU) >> 0;

#if defined(TARGET_IS_TM4C123_RA1)
    if(did0_class != 0x05U)
    {
        return false;
    }
    didClass = 1;
#warning Whats up with did0_major reports 01 here!
    if(did0_major != 0x01U)             // JLD - changed from 00 to get MpHOx to run past this check!!!
    {
        return false;
    }
    didMajor = 1;
    if(did0_minor != 0x01U)
    {
        return false;
    }
    didMinor = 1;
#elif defined(TARGET_IS_TM4C123_RA3)
    if(did0_class != 0x05U)
    {
        return false;
    }
    if(did0_major != 0x00U)
    {
        return false;
    }
    if(did0_minor != 0x03U)
    {
        return false;
    }
#elif defined(TARGET_IS_TM4C123_RB1)
    if(did0_class != 0x05U)
    {
        return false;
    }
    if(did0_major != 0x01U)
    {
        return false;
    }
    if(did0_minor != 0x01U)
    {
        return false;
    }
#elif defined(TARGET_IS_TM4C123_RB2)
    if(did0_class != 0x05U)
    {
        return false;
    }
    if(did0_major != 0x01U)
    {
        return false;
    }
    if(did0_minor != 0x02U)
    {
        return false;
    }
#elif defined(TARGET_IS_TM4C129_RA0)
    if(did0_class != 0x0AU)
    {
        return false;
    }
    if(did0_major != 0x00U)
    {
        return false;
    }
    if(did0_minor != 0x00U)
    {
        return false;
    }
#elif defined(TARGET_IS_TM4C129_RA1)
    if(did0_class != 0x0AU)
    {
        return false;
    }
    if(did0_major != 0x00U)
    {
        return false;
    }
    if(did0_minor != 0x01U)
    {
        return false;
    }
#elif defined(TARGET_IS_TM4C129_RA2)
    if(did0_class != 0x0AU)
    {
        return false;
    }
    if(did0_major != 0x00U)
    {
        return false;
    }
    if(did0_minor != 0x02U)
    {
        return false;
    }
#else
    #error "Unknown build target"
#endif

    return true;
}

