/****************************************************************************/
/* Copyright 2016 MBARI                                                     */
/****************************************************************************/
/* Summary  : A/D Converter Routines for OASIS5 on PIC32MX470F512L          */
/* Filename : adc.c                                                         */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 02/23/2016                                                    */
/*                                                                          */
/* MBARI provides this documentation and code "as is", with no warranty,    */
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium  */
/* Research Institute to assist in its use, correction, modification, or    */
/* enhancement. This information should not be published or distributed to  */
/* third parties without specific written permission from MBARI.            */
/*                                                                          */
/****************************************************************************/
/* Modification History:                                                    */
/* 23feb2016 rah - created                                                  */
/****************************************************************************/
/* Using this module:                                                       */
/* 1. Call adcSetup{Single,Mult}                                            */
/* 2. Wait 10 ms for ADC power to stabilize                                 */
/* 3. Repeat as necessary:  call adcConvert{Single,Mult}                    */
/* 3b. Delay as needed, go to 3                                             */
/* 4. Call adcStop()                                                        */
/****************************************************************************/

#include <xc.h>

#include <mbariTypes.h>
#include <oasis.h>						/* OASIS5 controller definitions	*/
#include <adc.h>						/* OASIS5 Analog definitions		*/
#include <dig_io.h>
#include <sem.h>
#include <otask.h>
#include <utils.h>
#include <stdio.h>


/****************************************/
/*		External Data					*/
/****************************************/

Extern Parm_t	adcStartupMs;


/****************************************/
/*		Module Local Data				*/
/****************************************/

MLocal Semaphore	adcSem;					/* Mutex sem for ADC	*/

/* Can be static, because print routine protected by adcSem				*/
MLocal char			adcMsgBuf[128];

MLocal volatile unsigned int *adcResult[] =
    {&ADC1BUF0, &ADC1BUF1, &ADC1BUF2, &ADC1BUF3,
     &ADC1BUF4, &ADC1BUF5, &ADC1BUF6, &ADC1BUF7,
     &ADC1BUF8, &ADC1BUF9, &ADC1BUFA, &ADC1BUFB,
     &ADC1BUFC, &ADC1BUFD, &ADC1BUFE, &ADC1BUFF
    };

MLocal AnalogDesc analogDesc[] = {
    {"PRESSURE",   "psiA",    14.5038, -0.2901},
    {"BATT_VOLTS", "Volts",   8.00, 0.0},
    {"GF_VOLTS",   "Volts",   1.00, 0.0},
    {"TEMP",       "deg C",   160., -67.84},
    {"HUMIDITY",   "%rh",     31.4465, -23.8208}
};


/************************************************************************/
/* Function    : adcInit                                                */
/* Purpose     : Initialize this module                                 */
/* Inputs      : None                                                   */
/* Outputs     : OK if successful, else ERROR							*/
/************************************************************************/
Errno adcInit()
{
    AD1CON1 = 0x0010;
    AD1CON2 = 0x0000;
    AD1CON3 = 0x0000;
    AD1CON3bits.SAMC = 12;
    AD1CON3bits.ADCS = 12;

	if (semCreateRecursiveMutex(&adcSem) == NULL)
		return(ERROR);

	semName(&adcSem, "ADCsem");
	return(OK);
}


/************************************************************************/
/* Function    : adcSetupSingle                                         */
/* Purpose     : Setup ADC module to convert one channel                */
/* Inputs      : ADC Channel                                            */
/* Outputs     : None                                                   */
/************************************************************************/
void adcSetupSingle(int chan)
{
	if (rtosIsRunning())
		semTake(&adcSem);
    digEnvEnable();
    AD1CON1 = 0x0005;
    AD1CON2 = 0x0000;
    AD1CON3 = 0x0000;
    AD1CON3bits.SAMC = 12;
    AD1CON3bits.ADCS = 12;
    AD1CHS = ((chan & 0x1f) << 16);
    AD1CSSL = 0;
    AD1CON1SET = _AD1CON1_ON_MASK;
}


/************************************************************************/
/* Function    : adcSetupMult                                           */
/* Purpose     : Setup ADC module to convert multiple channels          */
/* Inputs      : First ADC Channel, number of channels                  */
/* Outputs     : None                                                   */
/************************************************************************/
void adcSetupMult(int chan, int nchans)
{
	if (rtosIsRunning())
		semTake(&adcSem);
    digEnvEnable();
    AD1CON1 = 0x00e5;
    AD1CON2 = 0;
    AD1CON2bits.CSCNA = 1;
    AD1CON2bits.SMPI = nchans - 1;
    AD1CON3 = 0;
    AD1CON3bits.SAMC = 12;
    AD1CON3bits.ADCS = 12;
    AD1CHS = ((chan & 0x1f) << 16);
    AD1CSSL = ((1 << nchans) - 1) << chan;
    AD1CON1SET = _AD1CON1_ON_MASK;
}


/************************************************************************/
/* Function    : adcStop                                                */
/* Purpose     : Inverse of adcSetupxxx, stop using ADC                 */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void adcStop(void)
{
    AD1CON1CLR = _AD1CON1_ON_MASK;
    digEnvDisable();
	if (rtosIsRunning())
		semGive(&adcSem);
}


/************************************************************************/
/* Function    : adcConvertSingle                                       */
/* Purpose     : Convert one A/D channel                                */
/* Inputs      : None                                                   */
/* Outputs     : A/D value, 0-1023                                      */
/* Comments    : Must first call adcSetupSingle()                       */
/************************************************************************/
Nat16	adcConvertSingle(void)
{
    AD1CON1CLR = _AD1CON1_SAMP_MASK;

    while(!AD1CON1bits.DONE)
        ;
    return(ADC1BUF0);
}


/************************************************************************/
/* Function    : adcConvertMult                                         */
/* Purpose     : Convert one A/D channel                                */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comments    : Must first call adcSetupMult()                         */
/*               Results are in ADC1BUF0 - ADC1BUFn                     */
/************************************************************************/
void adcConvertMult(void)
{
    IFS0CLR = _IFS0_AD1IF_MASK;
    AD1CON1CLR = _AD1CON1_SAMP_MASK;

    while(!IFS0bits.AD1IF)
        ;
}


/************************************************************************/
/* Function    : adcGetResult                                           */
/* Purpose     : Get Result of adcConvertMult for one channel           */
/* Inputs      : Offset from first channel converted (chan parm to adcSetupMult())*/
/* Outputs     : Result                                                 */
/************************************************************************/
Nat16	adcGetResult(Nat16 chanOfs)
{
    return(*adcResult[chanOfs]);
}


/************************************************************************/
/* Function	   : adcConvertChans										*/
/* Purpose	   : Read analog value for one or more A/D ports			*/
/* Inputs	   : A/D port, number of ports, number of samples to average*/
/*				 ticks to delay between samples, buffer to put results	*/
/* Output	   : OK or ERROR											*/
/* Comments	   : Used by analog_drv() and analog() user function		*/
/*				 Returns A/D values in array result[]					*/
/*               Averaged results normalized to 16 bits					*/
/************************************************************************/
Errno adcConvertChans(int stChan, int numChans, int numAvg, Nat16 *result)
{
#define AdcAvgMs 10
    Nat32	i, j;
    Nat32	accum[ADC_CHANS];

	if ((stChan + numChans) > ADC_CHANS)
		return(ERROR);

    memset(accum, 0, sizeof(accum));

	if (numChans == 1)
	{
		adcSetupSingle(stChan + ADC_CHAN_OFFSET);
		tmrDelayMs(adcStartupMs);

		for (i = 0; i < numAvg; i++)
		{
			accum[0] += (adcConvertSingle() & 0x3ff);
			tmrDelayMs(AdcAvgMs);
		}
	}
	else
	{
		adcSetupMult(stChan + ADC_CHAN_OFFSET, numChans);
		tmrDelayMs(adcStartupMs);

		for (i = 0; i < numAvg; i++)
		{
			adcConvertMult();
			for (j = 0; j < numChans; j++)
				accum[j] += (adcGetResult(j) & 0x3ff);
			tmrDelayMs(AdcAvgMs);
		}
	}

    adcStop();

	for ( i = 0; i < numChans; i++ )	/* Calculate average values */
		result[i] = (Nat16)((64*accum[i] + (numAvg/2))/numAvg);
	
	return(OK);

} /* adcConvertChans() */


/************************************************************************/
/* Function    : printAnalogChan                                        */
/* Purpose     : Print one analog channel                               */
/* Inputs      : Analog channel number, A/D counts, print buffer		*/
/* Outputs     : None                                                   */
/************************************************************************/
void printAnalogChan(Nat32 chan, Nat16 cnt)
{
    AnalogDesc	*adp;
	double		volt;

	if (rtosIsRunning())
		semTake(&adcSem);
	adp = &analogDesc[chan];
	volt = AdcVolts(cnt);

	/* We have to use sprintf and xputs because xprintf/iprintf don't	*/
	/* support floating point.											*/
    sprintf(adcMsgBuf, "Ch %2d  %-12s %5u cnts %6.3f ADC volts %6.3f %s\r\n",
            chan, adp->name, cnt, volt,
            volt * adp->mult + adp->offset, adp->units);

    xputs(adcMsgBuf);
	if (rtosIsRunning())
		semGive(&adcSem);
}


/************************************************************************/
/* Function    : getAnalogDesc											*/
/* Purpose     : Return pointer to array of AnalogDesc's				*/
/* Inputs      : None													*/
/* Outputs     : Ptr to array of AnalogDesc's for native analog channels*/
/************************************************************************/
AnalogDesc	*getAnalogDesc(void)
{
    return(analogDesc);
}
