/***************************************************************************//**
 *   @file   AD7190.c
 *   @brief  Implementation of AD719x Driver.
 *   @author Kévin (Kevin.marchand@intelligence-electronique.com)
********************************************************************************
 * Copyright 2014(c) PLCD, Inc.
 *
 * 
 * Codage des données envoyées par l'AD7195  :
 *  - Unipolar : Code = (2^N × AIN × Gain)/VREF
 *  - Bipolar : Code = 2^(N?1) × [(AIN × Gain/VREF) + 1]
 *
 * Revision 1.1 30/09/2014 Kévin MARCHAND
 * - Gestion du mode "continuous read" sous interruption
 *
********************************************************************************
 *  @version: 1.1
*******************************************************************************/

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/
#include "AD7190.h"     // AD7190 definitions.
#include "HardwareProfile.h"
#include "PPS.h"

/***************************************************************************//**
 * @brief Writes data into a register.
 *
 * @param registerAddress - Address of the register.
 * @param registerValue - Data value to write.
 * @param bytesNumber - Number of bytes to be written.
 * @param modifyCS - Allows Chip Select to be modified.
 *
 * @return none.
*******************************************************************************/
void AD7190_SetRegisterValue(unsigned char registerAddress,
                             unsigned long registerValue,
                             unsigned char bytesNumber,
                             unsigned char modifyCS)
{
    unsigned char writeCommand[5] = {0, 0, 0, 0, 0};
    unsigned char* dataPointer    = (unsigned char*)&registerValue;
    unsigned char bytesNr         = bytesNumber;
    
    writeCommand[0] = AD7190_COMM_WRITE | AD7190_COMM_ADDR(registerAddress);
    while(bytesNr > 0)
    {
        writeCommand[bytesNr] = *dataPointer;
        dataPointer ++;
        bytesNr --;
    }
    SPI_Write(AD7190_SLAVE_ID * modifyCS, writeCommand, bytesNumber + 1);
    
}

/***************************************************************************//**
 * @brief Reads the value of a register.
 *
 * @param registerAddress - Address of the register.
 * @param bytesNumber - Number of bytes that will be read.
 * @param modifyCS    - Allows Chip Select to be modified.
 *
 * @return buffer - Value of the register.
*******************************************************************************/
unsigned long AD7190_GetRegisterValue(unsigned char registerAddress,
                                      unsigned char bytesNumber,
                                      unsigned char modifyCS)
{
    unsigned char registerWord[5] = {0, 0, 0, 0, 0}; 
    unsigned long buffer          = 0x0;
    unsigned char i               = 0;
    
    registerWord[0] = AD7190_COMM_READ | AD7190_COMM_ADDR(registerAddress);
    SPI_Read(AD7190_SLAVE_ID * modifyCS, registerWord, bytesNumber + 1);
    for(i = 1; i < bytesNumber + 1; i++) 
    {
        buffer = (buffer << 8) + registerWord[i];
    }
    
    return buffer;
}

/***************************************************************************//**
 * @brief Checks if the AD7190 part is present.
 *
 * @return status - Indicates if the part is present or not.
*******************************************************************************/
unsigned char AD7190_Init(void)
{
    unsigned char status = 1;
    unsigned char regVal = 0;
    
    AD7190_Reset();
    /* Allow at least 500 us before accessing any of the on-chip registers. */
    DelayMs(1);
    regVal = AD7190_GetRegisterValue(AD7190_REG_ID, 1, 1);
    if( (regVal & AD7190_ID_MASK) != ID_AD7195)
    {
        status = 0;
    }
    return status ;
}

/***************************************************************************//**
 * @brief Resets the device.
 *
 * @return none.
*******************************************************************************/
void AD7190_Reset(void)
{
    unsigned char registerWord[7];
    
    registerWord[0] = 0x01;
    registerWord[1] = 0xFF;
    registerWord[2] = 0xFF;
    registerWord[3] = 0xFF;
    registerWord[4] = 0xFF;
    registerWord[5] = 0xFF;
    registerWord[6] = 0xFF;
    SPI_Write(AD7190_SLAVE_ID, registerWord, 7);

}

/***************************************************************************//**
 * @brief Set device to idle or power-down.
 *
 * @param pwrMode - Selects idle mode or power-down mode.
 *                  Example: 0 - power-down
 *                           1 - idle
 *
 * @return none.
*******************************************************************************/
void AD7190_SetPower(unsigned char pwrMode)
{
     unsigned long oldPwrMode = 0x0;
     unsigned long newPwrMode = 0x0; 
 
     oldPwrMode = AD7190_GetRegisterValue(AD7190_REG_MODE, 3, 1);
     oldPwrMode &= ~(AD7190_MODE_SEL(0x7));
     newPwrMode = oldPwrMode | AD7190_MODE_SEL((pwrMode * (AD7190_MODE_IDLE)) | (!pwrMode * (AD7190_MODE_PWRDN)));
     AD7190_SetRegisterValue(AD7190_REG_MODE, newPwrMode, 3, 1);
}

/***************************************************************************//**
 * @brief Waits for RDY pin to go low.
 *
 * @return none.
*******************************************************************************/
void AD7190_WaitRdyGoLow(void)
{
    unsigned long timeOutCnt = 0x0FFFFF;

    while(AD7190_RDY_STATE && timeOutCnt--)
        Nop();
}

/***************************************************************************//**
 * @brief Selects the channel to be enabled.
 *
 * @param channel - Selects a channel.
 *  
 * @return none.
*******************************************************************************/
void AD7190_ChannelSelect(unsigned short channel)
{
    unsigned long oldRegValue = 0x0;
    unsigned long newRegValue = 0x0;   
     
    oldRegValue = AD7190_GetRegisterValue(AD7190_REG_CONF, 3, 1);
    oldRegValue &= ~(AD7190_CONF_CHAN(0xFF));
    newRegValue = oldRegValue | AD7190_CONF_CHAN(1 << channel);   
    AD7190_SetRegisterValue(AD7190_REG_CONF, newRegValue, 3, 1);
}

/***************************************************************************//**
 * @brief Performs the given calibration to the specified channel.
 *
 * @param mode - Calibration type.
 * @param channel - Channel to be calibrated.
 *
 * @return none.
*******************************************************************************/
void AD7190_Calibrate(unsigned char mode, unsigned char channel)
{
    unsigned long oldRegValue = 0x0;
    unsigned long newRegValue = 0x0;
    
    AD7190_ChannelSelect(channel);
    oldRegValue = AD7190_GetRegisterValue(AD7190_REG_MODE, 3, 1);
    oldRegValue &= ~AD7190_MODE_SEL(0x7);
    newRegValue = oldRegValue | AD7190_MODE_SEL(mode);
    AD_CS_IO = 0;
    AD7190_SetRegisterValue(AD7190_REG_MODE, newRegValue, 3, 0); // CS is not modified.
    AD7190_WaitRdyGoLow();
    AD_CS_IO = 1;
}

/***************************************************************************//**
 * @brief Selects the polarity of the conversion and the ADC input range.
 *
 * @param polarity - Polarity select bit. 
                     Example: 0 - bipolar operation is selected.
                              1 - unipolar operation is selected.
* @param range - Gain select bits. These bits are written by the user to select 
                 the ADC input range.     
 *
 * @return none.
*******************************************************************************/
void AD7190_RangeSetup(unsigned char polarity, unsigned char range)
{
    unsigned long oldRegValue = 0x0;
    unsigned long newRegValue = 0x0;
    
    oldRegValue = AD7190_GetRegisterValue(AD7190_REG_CONF,3, 1);
    oldRegValue &= ~(AD7190_CONF_UNIPOLAR | AD7190_CONF_GAIN(0x7));
    newRegValue = oldRegValue | (polarity * AD7190_CONF_UNIPOLAR) | AD7190_CONF_GAIN(range);
    AD7190_SetRegisterValue(AD7190_REG_CONF, newRegValue, 3, 1);
}

/***************************************************************************//**
 * @brief Returns the result of a single conversion.
 *
 * @return regData - Result of a single analog-to-digital conversion.
*******************************************************************************/
unsigned long AD7190_SingleConversion(void)
{
    unsigned long command = 0x0;
     unsigned long test = 0x0;
    unsigned long regData = 0x0;

    command = AD7190_MODE_SEL(AD7190_MODE_SINGLE) | AD7190_MODE_CLKSRC(AD7190_CLK_INT) | AD7190_MODE_RATE(0x060);
    AD_CS_IO = 0;
    AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3, 0); // CS is not modified.
    AD7190_WaitRdyGoLow();
    regData = AD7190_GetRegisterValue(AD7190_REG_DATA, 3, 0);
    AD_CS_IO = 1;
    return regData;
}

/***************************************************************************//**
 * @brief Returns the average of several conversion results.
 * @param sampleNumber - Number of sample for average.
 * @return samplesAverage - The average of the conversion results.
*******************************************************************************/
unsigned long AD7190_ContinuousReadAvg(unsigned char sampleNumber)
{
    unsigned long samplesAverage = 0x0;
    unsigned char count = 0x0;
    unsigned long command = 0x0;
    
    command = AD7190_MODE_SEL(AD7190_MODE_CONT) | AD7190_MODE_CLKSRC(AD7190_CLK_INT) | AD7190_MODE_RATE(0x002);//AD7190_MODE_RATE(0x020) | AD7190_MODE_REJ60 ;
    AD_CS_IO = 0;
    AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3, 0); // CS is not modified.
    for(count = 0;count < sampleNumber;count ++)
    {
        AD7190_WaitRdyGoLow();
        samplesAverage += AD7190_GetRegisterValue(AD7190_REG_DATA, 3, 0); // CS is not modified.
    }
    AD_CS_IO = 1;
    samplesAverage = samplesAverage / sampleNumber;
    
    return samplesAverage ;
}

/***************************************************************************//**
 * @brief Read data from temperature sensor and converts it to Celsius degrees.
 *
 * @return temperature - Celsius degrees.
*******************************************************************************/
unsigned long AD7190_TemperatureRead(void)
{
    unsigned char temperature = 0x0;
    unsigned long dataReg = 0x0;
    
    AD7190_RangeSetup(0, AD7190_CONF_GAIN_1);
    AD7190_ChannelSelect(AD7190_CH_TEMP_SENSOR);
    dataReg = AD7190_SingleConversion();
    dataReg -= 0x800000;
    dataReg /= 2815;   // Kelvin Temperature
    dataReg -= 273;    //Celsius Temperature
    temperature = (unsigned long) dataReg;
    
    return temperature;
}

/***************************************************************************//**
 * @brief Active le mode de lecture continue de l'AD7195.
 * @details Initialise la lecture sous interruption.
 * When chop is disabled and continuous conversion mode is selected :
 * 
 * Output Data Rate = (MCLK/1024)/FS
 * - MCLK = 4.92 MHz (see AD7190_MODE_CLKSRC)
 * - <u>FS = <b>filterSelect</b> = Filter Word</u>
 *
 * Effective Resolution (Peak-to-Peak Resolution) vs. Gain and Output Data Rate
 * for SINC4 CHOP DISABLED (voir documentation de l'AD7195 pour plus de détails):
 * <table><tr><td class="current fill">Filter Word (Decimal)</td><td class="fill">Data Rate (Hz)</td><td class="fill">Gain of 1</td></tr><tr><td class="fill">1023</td><td class="">4.7</td><td class="">24 (22.6)</td></tr><tr><td class="fill">640</td><td class="">7.5</td><td class="">24 (22.1)</td></tr><tr><td class="fill">480</td><td class="">10</td><td class="">24 (21.7)</td></tr><tr><td class="fill">96</td><td class="">50</td><td class="">23.3 (20.7)</td></tr><tr><td class="fill">80</td><td class="">60</td><td class="">23.1 (20.4)</td></tr><tr><td class="fill">32</td><td class="">150</td><td class="">22.7 (20.2)</td></tr><tr><td class="fill">16</td><td class="">300</td><td class="">22.3 (19.8)</td></tr><tr><td class="fill">5</td><td class="">960</td><td class="">21.7 (18.9)</td></tr><tr><td class="fill">2</td><td class="">2400</td><td class="">20.9 (18.3)</td></tr><tr><td class="fill">1</td><td class="">4800</td><td class="">19.4 (16.8)</td></tr></table>
 * @internal
 * ????????????????????????????
 * ?Filter Word     ?Output Data ?Gain of 1  ?
 * ?(Decimal)       ?Rate (Hz)   ?           ?
 * ????????????????????????????
 * ?1023            ?4.7         ?24 (22.6)  ?
 * ????????????????????????????
 * ?640             ?7.5         ?24 (22.1)  ?
 * ????????????????????????????
 * ?480             ?10          ?24 (21.7)  ?
 * ????????????????????????????
 * ?96              ?50          ?23.3 (20.7)?
 * ????????????????????????????
 * ?80              ?60          ?23.1 (20.4)?
 * ????????????????????????????
 * ?32              ?150         ?22.7 (20.2)?
 * ????????????????????????????
 * ?16              ?300         ?22.3 (19.8)?
 * ????????????????????????????
 * ?5               ?960         ?21.7 (18.9)?
 * ????????????????????????????
 * ?2               ?2400        ?20.9 (18.3)?
 * ????????????????????????????
 * ?1               ?4800        ?19.4 (16.8)?
 * ????????????????????????????
 * @endinternal
 * @param filterSelect - The 10 bits of data programmed into these bits 
 * determine the filter cut-off frequency, the position of the first notch 
 * of the filter, and the output data rate.
 * @return None.
*******************************************************************************/
void AD7190_StartContinuousRead(unsigned int filterSelect)
{
    unsigned long command = 0x0;
    // filterSelect doit être sur 10 bits
    if(filterSelect>1023)
        filterSelect = 1023;
    AD_CS_IO = 0;
    command = AD7190_MODE_SEL(AD7190_MODE_CONT) | AD7190_MODE_CLKSRC(AD7190_CLK_INT) | AD7190_MODE_RATE(filterSelect);// | AD7190_MODE_REJ60 ;
    AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3, 0); // CS is not modified.
    AD7190_WaitRdyGoLow();

    command = AD7190_COMM_READ | AD7190_COMM_CREAD | AD7190_COMM_ADDR(AD7190_REG_DATA);
    SPI_Write(0, command , 1);

    // Configuratin de l'interruption INT3 sur front descendant
    INTCON2bits.INT3EP = 1;
    IEC3bits.INT3IE = 1;    
}

/***************************************************************************//**
 * @brief Stops the interrupt-driven sampling process and disables the AD7190's
 *        continuous read mode.
 *
 * @return None.
*******************************************************************************/
void AD7190_StopContinuousRead()
{
    unsigned long command = 0x0;
    IEC3bits.INT3IE = 0;
    AD7190_WaitRdyGoLow();
    //command = AD7190_COMM_READ;
    //AD7190_SetRegisterValue(AD7190_REG_COMM, command, 1, 0); // CS is not modified.
    command = AD7190_COMM_READ | AD7190_COMM_ADDR(AD7190_REG_DATA);
    SPI_Write(0, command , 1);
    AD_CS_IO = 1;
    //this->_spi.write(COMM_READ | COMM_ADDR(REG_DATA));
}

/***************************************************************************//**
 * @brief Print registers values on UART2 for debuging.
 *
 * @return none.
*******************************************************************************/
void AD7190_PrintRegisters(void)
{
     unsigned long regVal = 0x0;
     char strRes[11];

     regVal = AD7190_GetRegisterValue(AD7190_REG_STAT, 1, 1);
     sprintf(strRes,"\r\n%02lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_MODE, 3, 1);
     sprintf(strRes,"%06lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_CONF, 3, 1);
     sprintf(strRes,"%06lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_DATA, 3, 1);
     sprintf(strRes,"%06lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_ID, 1, 1);
     sprintf(strRes,"%02lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_GPOCON, 1, 1);
     sprintf(strRes,"%02lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_OFFSET, 3, 1);
     sprintf(strRes,"%06lx\r\n",regVal);
     putsUART2(strRes);
     regVal = AD7190_GetRegisterValue(AD7190_REG_FULLSCALE, 3, 1);
     sprintf(strRes,"%06lx\r\n",regVal);
     putsUART2(strRes);

}