/****************************************************************************/
/* Copyright 2013 MBARI.                                                    */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Written by Bob Herlien for SensorNode for xFOCE, May 2013                */
/****************************************************************************/
#include "adc.h"
#include "dig_io.h"
#include "sys_timer.h"
#include "spi.h"
#include "config.h"
#include "p24Fxxxx.h"
#include "mb.h"
#include "port.h"
#include <string.h>

//#define DEBUG 1

#ifdef DEBUG
#include <stdio.h>
#include "serial.h"
#endif

#define MAX_TOT_DELAY	500

static ADCRegs	parmRegs;
static int	whichAdc = 1;
static BOOL     adcIsOn = FALSE;
static ADCWork	adcWork[ADC_CHANS];

#ifdef DEBUG
static char     adcDbgBuff[256];
#endif

static USHORT ads8344Convert(USHORT chan);
static USHORT picAdcConvert(USHORT chan);

/* convert chan to single-ended selector	*/
const USHORT ADS8344SglChSel[8] =
    { 0x0000, 0x4000, 0x1000, 0x5000, 0x2000, 0x6000, 0x3000, 0x7000 };

/* init registers */
void adcInit()
{
    USHORT	i;

    _ADC1MD = 0;

    for (i = 0; i < 2*ADC_CHANS; i++)
	parmRegs.adcRegs[i] = 1;

    /* Note: the AD1PCFGL and TRIS registers have to be set on 
    ADC pins if you want to use them as digital I/O pins */    
	
    /* Configure all analog/digital sharing pins to digital */
    // 1 = digital, 0 = analog (n=ADC#)

    AD1PCFGbits.PCFG0 = 1;
    AD1PCFGbits.PCFG1 = 1;
    AD1PCFGbits.PCFG2 = 1;
    AD1PCFGbits.PCFG3 = 1;
    AD1PCFGbits.PCFG4 = 1;
    AD1PCFGbits.PCFG5 = 1;
    AD1PCFGbits.PCFG6 = 1;
    AD1PCFGbits.PCFG7 = 1;
    AD1PCFGbits.PCFG8 = 1;
    AD1PCFGbits.PCFG9 = 1;
    AD1PCFGbits.PCFG10 = 0;	// INSTR_CURR
    AD1PCFGbits.PCFG11 = 0;	// INSTR_VOLT
    AD1PCFGbits.PCFG12 = 0;	// INSTR_1_CURR
    AD1PCFGbits.PCFG13 = 0;	// INSTR_2_CURR
    AD1PCFGbits.PCFG14 = 0;	// INSTR_3_CURR
    AD1PCFGbits.PCFG15 = 0;	// INSTR_4_CURR

    TRISBbits.TRISB10 = 1;	// INSTR_CURR
    TRISBbits.TRISB11 = 1;	// INSTR_VOLT
    TRISBbits.TRISB12 = 1;	// INSTR_1_CURR
    TRISBbits.TRISB13 = 1;	// INSTR_2_CURR
    TRISBbits.TRISB14 = 1;	// INSTR_3_CURR
    TRISBbits.TRISB15 = 1;	// INSTR_4_CURR

    AD1CON1 = 0x20e0;		// ~ADON, ADSIDL, Integer, auto-convert
    AD1CON2 = 0x2000;		// VRef+, Gnd for -
    AD1CON3 = 0x0301;		// TAD = 2*Tcy, sample 3 TADs
    AD1CHS  = 0x000A;		// Gnd for -, preselect AN10
    AD1CSSL = 0xFC00;		// Unused, but if we scan, do chans 10-15
    AD1CSSH = 0x00;

    TRISGbits.TRISG2 = 0;	//ADC_SHDN
    ADC_SHDN = 0;
    TRISGbits.TRISG3 = 1;	//ADC_BUSY
    TRISGbits.TRISG6 = 0;	//SPI_CS
    SPI_CS = 1;
    TRISGbits.TRISG7 = 1;	//SPI_SCK
    TRISGbits.TRISG8 = 1;	//SPI_MISO
    TRISGbits.TRISG9 = 1;	//SPI_MOSI

}

static void turnOnADC(void)
{
    if (!adcIsOn)
    {
	if (whichAdc)
	{
//	    _SPI1MD = 0;
	    ADC_SHDN = 1;
	}
	else
	{
//	    _ADC1MD = 0;
	    AD1CON1bits.ADON = 1;
	}

	adcIsOn = TRUE;
	tmrDelayUs(50);
    }
}

static void turnOffADC(void)
{
    ADC_SHDN = 0;
    AD1CON1bits.ADON = 0;
//    _SPI1MD = 1;
    adcIsOn = FALSE;
}

void adcSelectADC(int val)
{
#ifdef DEBUG
    serPutStringBool(CFG_SERPORT, "adcSelectADC ", val);
#endif
    whichAdc = val;
    turnOffADC();
}

int adcGetWhichADC(void)
{
    return(whichAdc);
}

/* Accumulate A/D samples for averaging		*/
void adcAccum(USHORT stChan, USHORT nchans)
{
    USHORT  chan, curMs, nextDelay, chanDelay, sample, moreWork;
    ADCWork *workp;
    struct adcChanStruct *chanp;

    memset(adcWork, 0, sizeof(adcWork));
    curMs = 0;

    for (moreWork = 1; moreWork; )
    {
	nextDelay = 500;
	moreWork = 0;
	for (chan = stChan ; chan < stChan+nchans; chan++)
	{
	    workp = &adcWork[chan];
	    chanp = &parmRegs.adcChan[chan];

	    /* Chan needs more samples?		*/
	    if ((workp->nSamples < chanp->adcNumAvg) &&	(curMs >= workp->nextMs))
	    {
		sample = whichAdc ? ads8344Convert(chan) : picAdcConvert(chan);
		workp->accum += (ULONG)sample;
		workp->nSamples++;
		workp->nextMs += chanp->adcAvgMs;
#ifdef DEBUG
		sprintf(adcDbgBuff, "adcAccum(%u) = %u sample %u time %u next %u\r\n", 
			chan, sample, workp->nSamples, curMs, workp->nextMs);
		serPutString(CFG_SERPORT, adcDbgBuff);
#endif
	    }

	    if (workp->nSamples < chanp->adcNumAvg)
	    {
		moreWork = 1;
		chanDelay = workp->nextMs - curMs;
		if (chanDelay < nextDelay)
		    nextDelay = chanDelay;
	    }
	}

	/* If have more channels, delay for next avg time	*/
	if (moreWork)
	{
#ifdef DEBUG
	    sprintf(adcDbgBuff, "Delay %u ms\r\n", nextDelay);
	    serPutString(CFG_SERPORT, adcDbgBuff);
#endif
	    tmrDelayMs(nextDelay);
	    curMs += nextDelay;
	}
    }
}


/* Modbus registers for A/D conversions		*/
void adcConvert(USHORT address, USHORT nRegs)
{
    USHORT	curAddr, nCurRegs, offset;

#ifdef DEBUG
    sprintf(adcDbgBuff, "adcConvert(0x%x, %u)\r\n", address, nRegs);
    serPutString(CFG_SERPORT, adcDbgBuff);
#endif

    if (((address + nRegs) < ADC_CONVERT_START) || (address > ADC_CONVERT_END))
	return;

    turnOnADC();

    curAddr = address;
    offset = 0;
    nCurRegs = nRegs;

    if (address < ADC_CONVERT_START)
    {
	curAddr = ADC_CONVERT_START;
	offset = ADC_CONVERT_START - address;
	nCurRegs = nRegs - offset;
    }

    if ((curAddr + nCurRegs) > ADC_CONVERT_END1)
	nCurRegs = ADC_CONVERT_END1 - curAddr;

    adcAccum(curAddr-ADC_CONVERT_START, nCurRegs);

    turnOffADC();
}

USHORT adcReadReg(USHORT address)
{
    ADCWork	*wp;

    if ((address >= ADC_CONVERT_START) && (address <= ADC_CONVERT_END))
    {
	wp = &adcWork[address - ADC_CONVERT_START];

	if (wp->nSamples <= 0)
	    return(0);

	if (whichAdc)
	    return((USHORT)(wp->accum / wp->nSamples));
	else
	    //PIC ADC is 10 bit, so shift left result by 6
	    //Do before divide to capture rounding.
	    //OK since number samples <= 500, so can't overflow a ulong
	    //MUST CHANGE THIS if you make MAX_TOTAL_DELAY larger than 1024
	    return((USHORT)((wp->accum<<6) / wp->nSamples));
    }
    else if ((address >= ADC_PARM_START) && (address <= ADC_PARM_END))
	return(parmRegs.adcRegs[address - ADC_PARM_START]);
    else
        return(0);

}

eMBErrorCode adcWriteRegs(UCHAR *pRegBuffer, USHORT address, USHORT nRegs)
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          i, val;
    ADCRegs	    newParms;

    memcpy(&newParms, &parmRegs, sizeof(ADCRegs));

    while( nRegs > 0 )
    {
	if ((address >= ADC_PARM_START) && (address <= ADC_PARM_END))
	{
	    /* get a register from the reg buffer */
	    val = *(pRegBuffer++) & 0xff;
	    val <<= 8;
	    val += *(pRegBuffer++) & 0xff;

	    if (val < 1)	//adcNumAvg and adcAvgMs must both be >= 1
		val = 1;

	    newParms.adcRegs[address - ADC_PARM_START] = val;
	}

	/* increment register index and address */
	--nRegs;
	++address;
    }

    /* Check new parameters for valid delays	*/
    for (i = 0; i < ADC_CHANS; i++)
    {
	if (newParms.adcChan[i].adcNumAvg * newParms.adcChan[i].adcAvgMs <= MAX_TOT_DELAY)
	{
	    parmRegs.adcChan[i].adcNumAvg = newParms.adcChan[i].adcNumAvg;
	    parmRegs.adcChan[i].adcAvgMs = newParms.adcChan[i].adcAvgMs;
#ifdef DEBUG
	    sprintf(adcDbgBuff, "ADC Chan %u parms = %u, %u\r\n",
		    i, parmRegs.adcChan[i].adcNumAvg, parmRegs.adcChan[i].adcAvgMs);
	    serPutString(CFG_SERPORT, adcDbgBuff);
#endif
	}
	else
	{
	    eStatus = MB_EINVAL;
#ifdef DEBUG
	    sprintf(adcDbgBuff, "Convert time too long for chan %u\r\n", i);
	    serPutString(CFG_SERPORT, adcDbgBuff);
#endif
	}
    }

    return(eStatus);

}

static USHORT ads8344Convert(USHORT chan)
{
    USHORT  val = 0;

    if (chan > ADC_CHANS)
	return(0);

    SPI_CS = 0;

    val = (spiReadWrite(ADS8344SglChSel[chan] | ADS8344START | ADS8344SGL | ADS8344EXT)
	   & 0x7f) << 9;

    val |= ((spiReadWrite(0) >> 7) & 0x1ff);

    SPI_CS = 1;

#ifdef DEBUG
    sprintf(adcDbgBuff, "ads8344Convert(%u) = %u\r\n", chan, val);
    serPutString(CFG_SERPORT, adcDbgBuff);
#endif
    return(val);
}

static USHORT picAdcConvert(USHORT chan)
{
    USHORT	val;

    if (chan > ADC_CHANS)
	return(0);

    AD1CHS = chan + FIRST_PIC_ADC_CHAN;
    AD1CON1bits.SAMP = 1;

    while (!AD1CON1bits.DONE)
	;

    val = ADC1BUF0;

#if 0
    sprintf(adcDbgBuff, "picAdcConvert(%u) returns %u\r\n",
	    chan, val);
    serPutString(CFG_SERPORT, adcDbgBuff);
#endif

    return(val);
}

