From 42871ed8374c87479043c50e89ac70fd5109708c Mon Sep 17 00:00:00 2001 From: pszsh Date: Tue, 27 Jan 2026 21:53:25 -0800 Subject: [PATCH] I decided to include this because it has has bug fixes that aren't in the official driver. also the bug hadn't been fully squashed; i stomped on it. --- .gitignore | 2 +- Impedance.c | 103 +- ad5940.c | 4422 +++++++++++++++++++++++++++++++++++++++ host/src/MainWindow.cpp | 4 +- host/src/main.cpp | 2 +- 5 files changed, 4492 insertions(+), 41 deletions(-) create mode 100644 ad5940.c diff --git a/.gitignore b/.gitignore index c352adf..88918f4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ build *.swp *.cmake requirements.txt -ad5940.* +ad5940 pico_sdk_import.cmake build* *.png diff --git a/Impedance.c b/Impedance.c index 5d3d7bd..9027280 100644 --- a/Impedance.c +++ b/Impedance.c @@ -5,9 +5,9 @@ #include "math.h" #include "Impedance.h" -/* Default LPDAC resolution(2.5V internal reference). */ -#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV -#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV +/* Default LPDAC resolution (2.5V internal reference) */ +#define DAC12BITVOLT_1LSB (2200.0f/4095) // mV +#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) // mV AppIMPCfg_Type AppIMPCfg = { @@ -18,14 +18,14 @@ AppIMPCfg_Type AppIMPCfg = .SeqStartAddrCal = 0, .MaxSeqLenCal = 0, - .ImpODR = 20.0, /* 20.0 Hz = 50ms period */ - .NumOfData = -1, - .RealDataCount = -1, /* Default to no filtering */ + .ImpODR = 20.0, /* Output Data Rate: 20.0 Hz */ + .NumOfData = -1, /* -1 for infinite/continuous measurement */ + .RealDataCount = -1, .SysClkFreq = 16000000.0, - .WuptClkFreq = 32000.0, + .WuptClkFreq = 32000.0, /* Low Frequency Oscillator (LFO) typically 32kHz */ .AdcClkFreq = 16000000.0, - .RcalVal = 10000.0, - .RtiaVal = 200.0, + .RcalVal = 10000.0, /* Calibration Resistor Value (Ohms) */ + .RtiaVal = 200.0, /* TIA Gain Resistor Value (Ohms) */ .DswitchSel = SWD_CE0, .PswitchSel = SWP_CE0, @@ -39,14 +39,14 @@ AppIMPCfg_Type AppIMPCfg = .ExcitBufGain = EXCITBUFGAIN_0P25, .HsDacGain = HSDACGAIN_0P2, .HsDacUpdateRate = 7, - .DacVoltPP = 600.0, - .BiasVolt = -0.0f, + .DacVoltPP = 600.0, /* Excitation Amplitude (mV peak-to-peak) */ + .BiasVolt = -0.0f, /* DC Bias Voltage */ - .SinFreq = 1000.0, + .SinFreq = 1000.0, /* Fixed Frequency (Hz) */ - .DftNum = DFTNUM_16384, + .DftNum = DFTNUM_16384, /* DFT Point Count */ .DftSrc = DFTSRC_SINC3, - .HanWinEn = bTRUE, + .HanWinEn = bTRUE, /* Hanning Window for spectral leakage reduction */ .AdcPgaGain = ADCPGA_1P5, .ADCSinc3Osr = ADCSINC3OSR_2, @@ -61,7 +61,7 @@ AppIMPCfg_Type AppIMPCfg = .SweepCfg.SweepLog = bFALSE, .SweepCfg.SweepIndex = 0, - .FifoThresh = 6, /* 3 measurements * 2 (Real/Imag) = 6 words */ + .FifoThresh = 6, /* Threshold: 3 measurements * 2 (Real/Imag) = 6 words */ .IMPInited = bFALSE, .StopRequired = bFALSE, }; @@ -70,18 +70,21 @@ AppIMPCfg_Type AppIMPCfg = /* Helper Functions */ /* ----------------------------------------------------------------------- */ +// Forward declaration to allow usage in AppIMPCtrl +AD5940Err AppIMPCheckFreq(float freq); + void AppIMPCleanup(void) { // Ensure chip is awake before sending commands if(AD5940_WakeUp(10) > 10) return; - // 1. Stop Sequencer and Wakeup Timer + // Stop Sequencer and Wakeup Timer AD5940_WUPTCtrl(bFALSE); AD5940_SEQCtrlS(bFALSE); - // 2. Stop any active conversions/WG, but KEEP Reference/LDOs ON + // Stop active conversions and Waveform Generator, keep Reference/LDOs on AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE); - // 3. Reset FIFO + // Reset FIFO configuration FIFOCfg_Type fifo_cfg; fifo_cfg.FIFOEn = bFALSE; AD5940_FIFOCfg(&fifo_cfg); @@ -93,7 +96,7 @@ void AppIMPCleanup(void) { fifo_cfg.FIFOThresh = 6; AD5940_FIFOCfg(&fifo_cfg); - // 4. Clear Interrupts + // Clear all interrupt flags AD5940_INTCClrFlag(AFEINTSRC_ALLINT); } @@ -104,7 +107,7 @@ void AppIMPCalibrateLFO(void) { cal_cfg.SystemClkFreq = 16000000.0; float freq; - // Use SDK function to measure LFOSC + // Measure LFOSC frequency to calibrate Wakeup Timer if(AD5940_LFOSCMeasure(&cal_cfg, &freq) == AD5940ERR_OK) { AppIMPCfg.WuptClkFreq = freq; } @@ -128,7 +131,7 @@ int32_t AppIMPCtrl(uint32_t Command, void *pPara) { case IMPCTRL_START: { - // Unified Start Logic: Always use Manual Trigger + ENDSEQ Interrupt + // Configure interrupts and trigger the first sequence manually AD5940_WUPTCtrl(bFALSE); AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, bTRUE); @@ -138,7 +141,23 @@ int32_t AppIMPCtrl(uint32_t Command, void *pPara) AppIMPCfg.FifoDataCount = 0; AppIMPCfg.StopRequired = bFALSE; - // Trigger first sequence manually + // Reset Sweep State for subsequent sweeps + if(AppIMPCfg.SweepCfg.SweepEn) + { + AppIMPCfg.SweepCfg.SweepIndex = 0; + AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart; + AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart; + + // Calculate next frequency immediately so the ISR has the correct 'next' value + AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq); + + // Reset Hardware WG Frequency to Start Frequency (it was left at Stop Freq) + AD5940_WGFreqCtrlS(AppIMPCfg.SweepCurrFreq, AppIMPCfg.SysClkFreq); + + // Reset SRAM Wait Times for the Start Frequency + AppIMPCheckFreq(AppIMPCfg.SweepCurrFreq); + } + AD5940_SEQMmrTrig(SEQID_0); break; } @@ -189,6 +208,7 @@ float AppIMPGetCurrFreq(void) return AppIMPCfg.SinFreq; } +/* Generate Initialization Sequence */ static AD5940Err AppIMPSeqCfgGen(void) { AD5940Err error = AD5940ERR_OK; @@ -202,6 +222,7 @@ static AD5940Err AppIMPSeqCfgGen(void) AD5940_SEQGenCtrl(bTRUE); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); + // Configure Reference System (High Power and Low Power Buffers) aferef_cfg.HpBandgapEn = bTRUE; aferef_cfg.Hp1V1BuffEn = bTRUE; aferef_cfg.Hp1V8BuffEn = bTRUE; @@ -216,6 +237,7 @@ static AD5940Err AppIMPSeqCfgGen(void) aferef_cfg.LpRefBoostEn = bFALSE; AD5940_REFCfgS(&aferef_cfg); + // Configure High Speed Loop (DAC and TIA) HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain; HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain; HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate; @@ -228,11 +250,13 @@ static AD5940Err AppIMPSeqCfgGen(void) HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel; HsLoopCfg.HsTiaCfg.ExtRtia = AppIMPCfg.ExtRtia; + // Configure Switch Matrix HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel; HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel; HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel; HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel; + // Configure Waveform Generator (Sine Wave) HsLoopCfg.WgCfg.WgType = WGTYPE_SIN; HsLoopCfg.WgCfg.GainCalEn = bTRUE; HsLoopCfg.WgCfg.OffsetCalEn = bTRUE; @@ -256,6 +280,7 @@ static AD5940Err AppIMPSeqCfgGen(void) HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0; AD5940_HSLoopCfgS(&HsLoopCfg); + // Configure DSP and ADC dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N; dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P; dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain; @@ -276,6 +301,7 @@ static AD5940Err AppIMPSeqCfgGen(void) memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); AD5940_DSPCfgS(&dsp_cfg); + // Enable Power for AFE blocks AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\ AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\ AFECTRL_SINC2NOTCH, bTRUE); @@ -297,6 +323,7 @@ static AD5940Err AppIMPSeqCfgGen(void) return AD5940ERR_OK; } +/* Generate Measurement Sequence */ static AD5940Err AppIMPSeqMeasureGen(void) { AD5940Err error = AD5940ERR_OK; @@ -306,6 +333,7 @@ static AD5940Err AppIMPSeqMeasureGen(void) SWMatrixCfg_Type sw_cfg; ClksCalInfo_Type clks_cal; + // Calculate settling time (WaitClks) based on DFT/Filter settings clks_cal.DataType = DATATYPE_DFT; clks_cal.DftSrc = AppIMPCfg.DftSrc; clks_cal.DataCount = 1L<<(AppIMPCfg.DftNum+2); @@ -320,7 +348,7 @@ static AD5940Err AppIMPSeqMeasureGen(void) AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* ----------------------------------------------------------------------- */ - /* Measurement 1: RCAL (Current) */ + /* Step 1: Measure Current across RCAL (Calibration) */ /* ----------------------------------------------------------------------- */ sw_cfg.Dswitch = SWD_RCAL0; sw_cfg.Pswitch = SWP_RCAL0; @@ -328,9 +356,10 @@ static AD5940Err AppIMPSeqMeasureGen(void) sw_cfg.Tswitch = SWT_RCAL1|SWT_TRTIA; AD5940_SWMatrixCfgS(&sw_cfg); - /* ADC Mux for Current (HSTIA) */ + // ADC Mux for Current (HSTIA) AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N); + // Enable AFE Power and Waveform Generator AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\ AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\ AFECTRL_SINC2NOTCH, bTRUE); @@ -338,6 +367,7 @@ static AD5940Err AppIMPSeqMeasureGen(void) AD5940_SEQGenInsert(SEQ_WAIT(16*2000)); AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); + // Store address to update wait times later during sweep AD5940_SEQGenFetchSeq(NULL, &AppIMPCfg.SeqWaitAddr[0]); AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); @@ -348,7 +378,7 @@ static AD5940Err AppIMPSeqMeasureGen(void) AD5940_AFECtrlS(AFECTRL_WG, bFALSE); /* ----------------------------------------------------------------------- */ - /* Measurement 2: Sensor Current (I) */ + /* Step 2: Measure Sensor Current (I) */ /* ----------------------------------------------------------------------- */ sw_cfg.Dswitch = AppIMPCfg.DswitchSel; sw_cfg.Pswitch = AppIMPCfg.PswitchSel; @@ -356,7 +386,7 @@ static AD5940Err AppIMPSeqMeasureGen(void) sw_cfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel; AD5940_SWMatrixCfgS(&sw_cfg); - /* ADC Mux for Current (HSTIA) */ + // ADC Mux for Current (HSTIA) AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N); AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE); @@ -367,17 +397,16 @@ static AD5940Err AppIMPSeqMeasureGen(void) AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); - // Stop ADC/DFT first, wait 10us, then stop WG AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bFALSE); AD5940_SEQGenInsert(SEQ_WAIT(16*10)); AD5940_AFECtrlS(AFECTRL_WG, bFALSE); /* ----------------------------------------------------------------------- */ - /* Measurement 3: Sensor Voltage (V) */ + /* Step 3: Measure Sensor Voltage (V) */ /* ----------------------------------------------------------------------- */ - /* Switches remain same (Force path active) */ + // Switches remain same (Force path active) - /* ADC Mux for Voltage (AIN2/AIN3) */ + // ADC Mux for Voltage (AIN2/AIN3) AD5940_ADCMuxCfgS(ADCMUXP_AIN2, ADCMUXN_AIN3); AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE); @@ -388,11 +417,12 @@ static AD5940Err AppIMPSeqMeasureGen(void) AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); - // Stop ADC/DFT first, wait 10us, then stop WG and ADC Power + // Stop ADC/DFT, wait 10us, then stop WG and ADC Power AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bFALSE); AD5940_SEQGenInsert(SEQ_WAIT(16*10)); AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); + // Power down AFE blocks AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\ AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\ AFECTRL_SINC2NOTCH, bFALSE); @@ -444,7 +474,7 @@ AD5940Err AppIMPCheckFreq(float freq) AD5940_ADCFilterCfgS(&filter_cfg); AD5940_DFTCfgS(&dft_cfg); - // Calculate new WaitClks for the sequence + // Recalculate settling times (WaitClks) for the new frequency clks_cal.DataType = DATATYPE_DFT; clks_cal.DftSrc = freq_params.DftSrc; clks_cal.DataCount = 1L<<(freq_params.DftNum+2); @@ -482,7 +512,7 @@ int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize) if(AD5940_WakeUp(10) > 10) return AD5940ERR_WAKEUP; - // CRITICAL: Stop WUPT and Sequencer before reconfiguration + // Stop timers and sequencer before reconfiguration AD5940_WUPTCtrl(bFALSE); AD5940_SEQCtrlS(bFALSE); @@ -526,7 +556,7 @@ int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize) AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, bTRUE); - // Safety timeout for Init Sequence + // Wait for initialization sequence to complete int timeout = 100000; while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE) { if(--timeout <= 0) { @@ -614,6 +644,7 @@ int32_t AppIMPDataProcess(int32_t * const pData, uint32_t *pDataCount) MagV = sqrt((float)pDftV->Real*pDftV->Real + (float)pDftV->Image*pDftV->Image); PhaseV = atan2(-pDftV->Image, pDftV->Real); + // Calculate Impedance: Z = (V / I) * Rtia if(MagI > 1e-9) ZMag = (MagV / MagI) * AppIMPCfg.RtiaVal; else @@ -705,9 +736,7 @@ int32_t AppIMPISR(void *pBuff, uint32_t *pCount) AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); AppIMPDataProcess((int32_t*)pBuff, &FifoCnt); - // DUMMY POINT FILTERING - // If we are in sweep mode and have exceeded the user's requested count, - // discard this data packet so the host doesn't see the artifact. + // Discard extra data points if they exceed the requested count if (AppIMPCfg.SweepCfg.SweepEn && AppIMPCfg.RealDataCount > 0) { if (AppIMPCfg.FifoDataCount > AppIMPCfg.RealDataCount) { *pCount = 0; // Discard diff --git a/ad5940.c b/ad5940.c new file mode 100644 index 0000000..79e89dc --- /dev/null +++ b/ad5940.c @@ -0,0 +1,4422 @@ +/** + * @file ad5940.c + * @brief AD5940 library. This file contains all AD5940 library functions. + * @author ADI + * @date March 2019 + * @par Revision History: + * + * Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved. + * + * This software is proprietary to Analog Devices, Inc. and its licensors. + * By using this software you agree to the terms of the associated + * Analog Devices Software License Agreement. +**/ +#include "ad5940.h" + +/*! \mainpage AD5940 Library Introduction + * + * ![AD5940 EVAL Board](https://www.analog.com/-/media/analog/en/evaluation-board-images/images/eval-ad5940elcztop-web.gif?h=500&thn=1&hash=1F38F7CC1002894616F74D316365C0A2631C432B "ADI logo") + * + * # Introduction + * + * The documentation is for AD594x library and examples. + * + * # Manual Structure + * + * @ref AD5940_Library + * - @ref AD5940_Functions + * - @ref TypeDefinitions + * @ref AD5940_Standard_Examples + * @ref AD5940_System_Examples + * + * # How to Use It + * We provide examples that can directly run out of box. + * The files can generally be separated to three parts: + * - AD5940 Library files. ad5940.c and ad5940.h specifically. These two files are shared among all examples. + * - AD5940 System Examples. The system examples mean system level application like measuring impedance. + * - Standard examples. These include basic block level examples like ADC. It shows how to setup and use one specific block. + * + * ## Requirements to run these examples + * ### Hardware + * - Use EVAL_AD5940 or EVAL_AD5941. The default MCU board we used is ADICUP3029. We also provide project for ST NUCLEO board. + * - Or use EVAL_ADuCM355 + * ### Software + * - Pull all the source file from [GitHub](https://github.com/analogdevicesinc/ad5940-examples.git) + * - CMSIS pack that related to specific MCU. This normally is done by IDE you use. + * + * ## Materials + * Please use this library together with following materials. + * - [AD5940 Data Sheet](https://www.analog.com/media/en/technical-documentation/data-sheets/AD5940.pdf) + * - [AD5940 Eval Board](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/EVAL-AD5940.html) + * + */ + +/* Remove below variables after AD594x is released. */ +static BoolFlag bIsS2silicon = bFALSE; + +/* Declare of SPI functions used to read/write registers */ +#ifndef CHIPSEL_M355 +static uint32_t AD5940_SPIReadReg(uint16_t RegAddr); +static void AD5940_SPIWriteReg(uint16_t RegAddr, uint32_t RegData); +#else +static uint32_t AD5940_D2DReadReg(uint16_t RegAddr); +static void AD5940_D2DWriteReg(uint16_t RegAddr, uint32_t RegData); +#endif + +/** + * @addtogroup AD5940_Library + * The library functions, structures and constants. + * @{ + * @defgroup AD5940_Functions + * @{ + * @defgroup Function_Helpers + * @brief The functions with no hardware access. They are helpers. + * @{ + * @defgroup Sequencer_Generator_Functions + * @brief The set of function used to track all register read and write once it's enabled. It can translate register write operation to sequencer commands. + * @{ +*/ + +#define SEQUENCE_GENERATOR /*!< Build sequence generator part in to lib. Comment this line to remove this feature */ + +#ifdef SEQUENCE_GENERATOR +/** + * Structure used to store register information(address and its data) + * */ +typedef struct +{ + uint32_t RegAddr :8; /**< 8bit address is enough for sequencer */ + uint32_t RegValue :24; /**< Reg data is limited to 24bit by sequencer */ +}SEQGenRegInfo_Type; + +/** + * Sequencer generator data base. +*/ +struct +{ + BoolFlag EngineStart; /**< Flag to mark start of the generator */ + uint32_t BufferSize; /**< Total buffer size */ + + uint32_t *pSeqBuff; /**< The buffer for sequence generator(both sequences and RegInfo) */ + uint32_t SeqLen; /**< Generated sequence length till now */ + SEQGenRegInfo_Type *pRegInfo; /**< Pointer to buffer where stores register info */ + uint32_t RegCount; /**< The count of register info available in buffer *pRegInfo. */ + AD5940Err LastError; /**< The last error message. */ +}SeqGenDB; /* Data base of Seq Generator */ + +/** + * @brief Manually input a command to sequencer generator. + * @param CmdWord: The 32-bit width sequencer command word. @ref Sequencer_Helper can be used to generate commands. + * @return None; +*/ +void AD5940_SEQGenInsert(uint32_t CmdWord) +{ + uint32_t temp; + temp = SeqGenDB.RegCount + SeqGenDB.SeqLen; + /* Generate Sequence command */ + if(temp < SeqGenDB.BufferSize) + { + SeqGenDB.pSeqBuff[SeqGenDB.SeqLen] = CmdWord; + SeqGenDB.SeqLen ++; + } + else /* There is no buffer */ + SeqGenDB.LastError = AD5940ERR_BUFF; +} + +/** + * @brief Search data-base to get current register value. + * @param RegAddr: The register address. + * @param pIndex: Pointer to a variable that used to store index of found register-info. + * @return Return AD5940ERR_OK if register found in data-base. Otherwise return AD5940ERR_SEQREG. +*/ +static AD5940Err AD5940_SEQGenSearchReg(uint32_t RegAddr, uint32_t *pIndex) +{ + uint32_t i; + + RegAddr = (RegAddr>>2)&0xff; + for(i=0;i>2)&0xff; + SeqGenDB.pRegInfo[0].RegValue = RegData&0x00ffffff; + SeqGenDB.RegCount ++; + } + else /* There is no more buffer */ + { + SeqGenDB.LastError = AD5940ERR_BUFF; + } +} + +/** + * @brief Get current register value. If we have record in data-base, read it. Otherwise, return the register default value. + * @param RegAddr: The register address. + * @return Return register value. +*/ +static uint32_t AD5940_SEQReadReg(uint16_t RegAddr) +{ + uint32_t RegIndex, RegData; + + if(AD5940_SEQGenSearchReg(RegAddr, &RegIndex) != AD5940ERR_OK) + { + /* There is no record in data-base, read the default value. */ + AD5940_SEQGenGetRegDefault(RegAddr, &RegData); + AD5940_SEQRegInfoInsert(RegAddr, RegData); + } + else + { + /* return the current register value stored in data-base */ + RegData = SeqGenDB.pRegInfo[RegIndex].RegValue; + } + + return RegData; +} + +/** + * @brief Generate a sequencer command to write register. If the register address is out of range, it won't generate a command. + * This function will also update the register-info in data-base to record current register value. + * @param RegAddr: The register address. + * @param RegData: The register value. + * @return Return None. +*/ +static void AD5940_SEQWriteReg(uint16_t RegAddr, uint32_t RegData) +{ + uint32_t RegIndex; + + if(RegAddr > 0x21ff) + { + SeqGenDB.LastError = AD5940ERR_ADDROR; /* address out of range */ + return; + } + + if(AD5940_SEQGenSearchReg(RegAddr, &RegIndex) == AD5940ERR_OK) + { + /* Store register value */ + SeqGenDB.pRegInfo[RegIndex].RegValue = RegData; + /* Generate Sequence command */ + AD5940_SEQGenInsert(SEQ_WR(RegAddr, RegData)); + } + else + { + AD5940_SEQRegInfoInsert(RegAddr, RegData); + /* Generate Sequence command */ + AD5940_SEQGenInsert(SEQ_WR(RegAddr, RegData)); + } +} + +/** + * @brief Initialize sequencer generator with specified buffer. + * The buffer is used to store sequencer generated and record register value changes. + * The command is stored from start address of buffer while register value is stored from end of buffer. + * Buffer[0] : First sequencer command; + * Buffer[1] : Second Sequencer command; + * ... + * Buffer[Last-1]: The second register value record. + * Buffer[Last]: The first register value record. + * @param pBuffer: Pointer to the buffer. + * @param BufferSize: The buffer length. + * @return Return None. +*/ +void AD5940_SEQGenInit(uint32_t *pBuffer, uint32_t BufferSize) +{ + if(BufferSize < 2) return; + SeqGenDB.BufferSize = BufferSize; + SeqGenDB.pSeqBuff = pBuffer; + SeqGenDB.pRegInfo = (SEQGenRegInfo_Type*)pBuffer + BufferSize - 1; /* Point to the last element in buffer */ + SeqGenDB.SeqLen = 0; + + SeqGenDB.RegCount = 0; + SeqGenDB.LastError = AD5940ERR_OK; + SeqGenDB.EngineStart = bFALSE; +} + +/** + * @brief Get sequencer command generated. + * @param ppSeqCmd: Pointer to a variable(pointer) used to store the pointer to generated sequencer command. + * @param pSeqLen: Pointer to a variable that used to store how many commands available in buffer. + * @return Return lasterror. +*/ +AD5940Err AD5940_SEQGenFetchSeq(const uint32_t **ppSeqCmd, uint32_t *pSeqLen) +{ + AD5940Err lasterror; + + if(ppSeqCmd) + *ppSeqCmd = SeqGenDB.pSeqBuff; + if(pSeqLen) + *pSeqLen = SeqGenDB.SeqLen; + + //SeqGenDB.SeqLen = 0; /* Start a new sequence */ + lasterror = SeqGenDB.LastError; + //SeqGenDB.LastError = AD5940ERR_OK; /* Clear error message */ + return lasterror; +} + +/** + * @brief Start or stop the sequencer generator. Once started, the register write will be recorded to sequencer generator. + * Once it's disabled, the register write is written to AD5940 directly by SPI bus. + * @param bFlag: Enable or disable sequencer generator. + * @return Return None. +*/ +void AD5940_SEQGenCtrl(BoolFlag bFlag) +{ + if(bFlag == bFALSE) /* Disable sequence generator */ + { + SeqGenDB.EngineStart = bFALSE; + } + else + { + SeqGenDB.SeqLen = 0; + SeqGenDB.LastError = AD5940ERR_OK; /* Clear error message */ + SeqGenDB.EngineStart = bTRUE; + } +} + +/** + * @brief Calculate the number of cycles in the sequence + * @return Return Number of ACLK Cycles that a generated sequence will take. +*/ +uint32_t AD5940_SEQCycleTime(void) +{ + uint32_t i, Cycles, Cmd; + Cycles = 0; + for(i=0;i> 30) & 0x3; + if (Cmd & 0x2) + { + /* A write command */ + Cycles += 1; + } + else + { + if (Cmd & 0x1) + { + /* Timeout Command */ + Cycles += 1; + } + else + { + /* Wait command */ + Cycles += SeqGenDB.pSeqBuff[i] & 0x3FFFFFFF; + } + } + } + return Cycles; +} +#endif +/** + * @} Sequencer_Generator_Functions +*/ + +/** + * Check if an uint8_t value exist in table. +*/ +static int32_t _is_value_in_table(uint8_t value, const uint8_t *table, uint8_t len, uint8_t *index) +{ + for(int i=0; iADCRate == ADCRATE_800KHZ && pFilterInfo->ADCSinc3Osr == ADCSINC3OSR_2)||\ + (pFilterInfo->ADCRate == ADCRATE_1P6MHZ && pFilterInfo->ADCSinc3Osr != ADCSINC3OSR_2)) + { + //this combination suits for filter: + //SINC3 OSR2, for 800kSPS + //and SINC3 OSR4 and OSR5 for 1.6MSPS, + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_533, ADCSINC2OSR_667,ADCSINC2OSR_800, ADCSINC2OSR_889, ADCSINC2OSR_1333}; + const uint8_t dl_50Hz[] = {15,12,10,9,6}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_50Hz[index]; + return bTRUE; + } + } + else if(pFilterInfo->ADCRate == ADCRATE_1P6MHZ && pFilterInfo->ADCSinc3Osr == ADCSINC3OSR_2) + { + //this combination suits for filter: + //SINC3 OSR2 for 1.6MSPS + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_889, ADCSINC2OSR_1067, ADCSINC2OSR_1333}; + const uint8_t dl_50Hz[] = {18,15,12}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_50Hz[index]; + return bTRUE; + } + } + else if(pFilterInfo->ADCRate == ADCRATE_800KHZ && pFilterInfo->ADCSinc3Osr != ADCSINC3OSR_2) + { + //this combination suits for filter: + //SINC3 OSR4 and OSR5 for 800kSPS, + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_178, ADCSINC2OSR_267, ADCSINC2OSR_533, ADCSINC2OSR_640,\ + ADCSINC2OSR_800, ADCSINC2OSR_1067}; + const uint8_t dl_50Hz[] = {18,12,6,5,4,3}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_50Hz[index]; + return bTRUE; + } + } + *dl = 0; + return bFALSE; +} + +/** + * @brief return if the SINC3/SINC2 combination is available for notch 60Hz filter. + * If it's not availabe, hardware automatically bypass Notch even if it's enabled. + * @param pFilterInfo the filter configuration, need sinc2/sinc3 osr and adc data rate information. + * @return return bTRUE if notch 60Hz filter is available. +*/ +BoolFlag AD5940_Notch60HzAvailable(ADCFilterCfg_Type *pFilterInfo, uint8_t *dl) +{ + if((pFilterInfo->ADCRate == ADCRATE_800KHZ && pFilterInfo->ADCSinc3Osr == ADCSINC3OSR_2)||\ + (pFilterInfo->ADCRate == ADCRATE_1P6MHZ && pFilterInfo->ADCSinc3Osr != ADCSINC3OSR_2)) + { + //this combination suits for filter: + //SINC3 OSR2, for 800kSPS + //and SINC3 OSR4 and OSR5 for 1.6MSPS, + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_667, ADCSINC2OSR_1333}; + const uint8_t dl_60Hz[] = {10,5}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_60Hz[index]; + return bTRUE; + } + } + else if(pFilterInfo->ADCRate == ADCRATE_1P6MHZ && pFilterInfo->ADCSinc3Osr == ADCSINC3OSR_2) + { + //this combination suits for filter: + //SINC3 OSR2 for 1.6MSPS + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_889, ADCSINC2OSR_1333}; + const uint8_t dl_60Hz[] = {15,10}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_60Hz[index]; + return bTRUE; + } + } + else if(pFilterInfo->ADCRate == ADCRATE_800KHZ && pFilterInfo->ADCSinc3Osr != ADCSINC3OSR_2) + { + //this combination suits for filter: + //SINC3 OSR4 and OSR5 for 800kSPS, + const uint8_t available_sinc2_osr[] = {ADCSINC2OSR_178, ADCSINC2OSR_267, ADCSINC2OSR_533, ADCSINC2OSR_667,\ + ADCSINC2OSR_889, ADCSINC2OSR_1333}; + const uint8_t dl_60Hz[] = {15,10,5,4,3,2}; + uint8_t index; + if(_is_value_in_table(pFilterInfo->ADCSinc2Osr, available_sinc2_osr, sizeof(available_sinc2_osr), &index)) + { + *dl = dl_60Hz[index]; + return bTRUE; + } + } + *dl = 0; + return bFALSE; +} + +/** + * @brief Calculate how many clocks are needed in sequencer wait command to generate required number of data from filter output. + * @note When measurement is done, it's recommend to disable blocks like ADCPWR, ADCCNV, SINC2, DFT etc. If blocks remain powered up, + * they may need less clocks to generate required number of output. Use function @ref AD5940_AFECtrlS to control these blocks. + * @param pFilterInfo: Pointer to configuration structure. + * @param pClocks: pointer used to store results. + * @return return none. +*/ +void AD5940_ClksCalculate(ClksCalInfo_Type *pFilterInfo, uint32_t *pClocks) +{ + uint32_t temp = 0; + const uint32_t sinc2osr_table[] = {22,44,89,178,267,533,640,667,800,889,1067,1333,0}; + const uint32_t sinc3osr_table[] = {5,4,2,0}; + + *pClocks = 0; + if(pFilterInfo == NULL) return; + if(pClocks == NULL) return; + if(pFilterInfo->ADCSinc2Osr > ADCSINC2OSR_1333) return; + if(pFilterInfo->ADCSinc3Osr > 2) return; /* 0: OSR5, 1:OSR4, 2:OSR2 */ + if(pFilterInfo->ADCAvgNum > ADCAVGNUM_16) return; /* Average number index:0,1,2,3 */ + switch(pFilterInfo->DataType) + { + case DATATYPE_ADCRAW: + temp = (uint32_t)(20*pFilterInfo->DataCount*pFilterInfo->RatioSys2AdcClk); + break; + case DATATYPE_SINC3: + temp = (uint32_t)(((pFilterInfo->DataCount+2)*sinc3osr_table[pFilterInfo->ADCSinc3Osr]+1)*20*pFilterInfo->RatioSys2AdcClk + 0.5f); + break; + case DATATYPE_SINC2: + temp = (pFilterInfo->DataCount+1)*sinc2osr_table[pFilterInfo->ADCSinc2Osr] + 1; + pFilterInfo->DataType = DATATYPE_SINC3; + pFilterInfo->DataCount = temp; + AD5940_ClksCalculate(pFilterInfo, &temp); + pFilterInfo->DataType = DATATYPE_SINC2; + temp += 15; /* Need extra 15 clocks for FIFO etc. Just to be safe. */ + break; + case DATATYPE_NOTCH: + { + ADCFilterCfg_Type filter; + filter.ADCRate = pFilterInfo->ADCRate; + filter.ADCSinc3Osr = pFilterInfo->ADCSinc3Osr; + filter.ADCSinc2Osr = pFilterInfo->ADCSinc2Osr; + uint8_t dl=0, dl_50, dl_60; + if(AD5940_Notch50HzAvailable(&filter, &dl_50)){ + dl += dl_50 - 1; + } + if(AD5940_Notch60HzAvailable(&filter, &dl_60)){ + dl += dl_60 - 1; + } + pFilterInfo->DataType = DATATYPE_SINC2; + pFilterInfo->DataCount += dl; //DL is the extra data input needed for filter to output first data. + AD5940_ClksCalculate(pFilterInfo,&temp); + //restore the filter info. + pFilterInfo->DataType = DATATYPE_NOTCH; + pFilterInfo->DataCount -= dl; + break; + } + case DATATYPE_DFT: + switch(pFilterInfo->DftSrc) + { + case DFTSRC_ADCRAW: + pFilterInfo->DataType = DATATYPE_ADCRAW; + AD5940_ClksCalculate(pFilterInfo, &temp); + break; + case DFTSRC_SINC3: + pFilterInfo->DataType = DATATYPE_SINC3; + AD5940_ClksCalculate(pFilterInfo, &temp); + break; + case DFTSRC_SINC2NOTCH: + if(pFilterInfo->BpNotch) + pFilterInfo->DataType = DATATYPE_SINC2; + else + pFilterInfo->DataType = DATATYPE_NOTCH; + AD5940_ClksCalculate(pFilterInfo, &temp); + break; + case DFTSRC_AVG: + pFilterInfo->DataType = DATATYPE_SINC3; + pFilterInfo->DataCount *= 1L<<(pFilterInfo->ADCAvgNum+1); /* 0: average2, 1: average4, 2: average8, 3: average16 */ + AD5940_ClksCalculate(pFilterInfo, &temp); + break; + default: + break; + } + pFilterInfo->DataType = DATATYPE_DFT; + temp += 25; /* add margin */ + break; + default: + break; + } + *pClocks = temp; +} + +/** + @brief void AD5940_SweepNext(SoftSweepCfg_Type *pSweepCfg, float *pNextFreq) + For sweep function, calculate next frequency point according to pSweepCfg info. + @return Return next frequency point in Hz. +*/ +void AD5940_SweepNext(SoftSweepCfg_Type *pSweepCfg, float *pNextFreq) +{ + float frequency; + + if(pSweepCfg->SweepLog)/* Log step */ + { + if(pSweepCfg->SweepStartSweepStop) /* Normal */ + { + if(++pSweepCfg->SweepIndex == pSweepCfg->SweepPoints) + pSweepCfg->SweepIndex = 0; + frequency = pSweepCfg->SweepStart*pow(10,pSweepCfg->SweepIndex*log10(pSweepCfg->SweepStop/pSweepCfg->SweepStart)/(pSweepCfg->SweepPoints-1)); + } + else + { + pSweepCfg->SweepIndex --; + if(pSweepCfg->SweepIndex >= pSweepCfg->SweepPoints) + pSweepCfg->SweepIndex = pSweepCfg->SweepPoints-1; + frequency = pSweepCfg->SweepStop*pow(10,pSweepCfg->SweepIndex* + (log10(pSweepCfg->SweepStart/pSweepCfg->SweepStop)/(pSweepCfg->SweepPoints-1))); + } + } + else/* Linear step */ + { + if(pSweepCfg->SweepStartSweepStop) /* Normal */ + { + if(++pSweepCfg->SweepIndex == pSweepCfg->SweepPoints) + pSweepCfg->SweepIndex = 0; + frequency = pSweepCfg->SweepStart + pSweepCfg->SweepIndex*(double)(pSweepCfg->SweepStop-pSweepCfg->SweepStart)/(pSweepCfg->SweepPoints-1); + } + else + { + pSweepCfg->SweepIndex --; + if(pSweepCfg->SweepIndex >= pSweepCfg->SweepPoints) + pSweepCfg->SweepIndex = pSweepCfg->SweepPoints-1; + frequency = pSweepCfg->SweepStop + pSweepCfg->SweepIndex*(double)(pSweepCfg->SweepStart - pSweepCfg->SweepStop)/(pSweepCfg->SweepPoints-1); + } + } + + *pNextFreq = frequency; +} + +/** + @brief Initialize Structure members to zero + @param pStruct: Pointer to the structure. + @param StructSize: The structure size in Byte. + @return Return None. +**/ +void AD5940_StructInit(void *pStruct, uint32_t StructSize) +{ + memset(pStruct, 0, StructSize); +} + +/** + @brief Convert ADC Code to voltage. + @param ADCPga: The ADC PGA used for this result. + @param code: ADC code. + @param VRef1p82: the actual 1.82V reference voltage. + @return Voltage in volt. +**/ +float AD5940_ADCCode2Volt(uint32_t code, uint32_t ADCPga, float VRef1p82) +{ + float kFactor = 1.835/1.82; + float fVolt = 0.0; + float tmp = 0; + tmp = (int32_t)code - 32768; + switch(ADCPga) + { + case ADCPGA_1: + break; + case ADCPGA_1P5: + tmp /= 1.5f; + break; + case ADCPGA_2: + tmp /= 2.0f; + break; + case ADCPGA_4: + tmp /= 4.0f; + break; + case ADCPGA_9: + tmp /= 9.0f; + break; + default:break; + } + fVolt = tmp*VRef1p82/32768*kFactor; + return fVolt; +} + +/** + * @brief Do complex number division. + * @param a: The dividend. + * @param b: The divisor. + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexDivFloat(fImpCar_Type *a, fImpCar_Type *b) +{ + fImpCar_Type res; + float temp; + temp = b->Real*b->Real + b->Image*b->Image; + res.Real = a->Real*b->Real + a->Image*b->Image; + res.Real /= temp; + res.Image = a->Image*b->Real - a->Real*b->Image; + res.Image /= temp; + return res; +} + +/** + * @brief Do complex number multiplication. + * @param a: The multiplicand. + * @param b: The multiplier . + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexMulFloat(fImpCar_Type *a, fImpCar_Type *b) +{ + fImpCar_Type res; + + res.Real = a->Real*b->Real - a->Image*b->Image; + res.Image = a->Image*b->Real + a->Real*b->Image; + + return res; +} +/** + * @brief Do complex number addition. + * @param a: The addend. + * @param b: The addend . + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexAddFloat(fImpCar_Type *a, fImpCar_Type *b) +{ + fImpCar_Type res; + + res.Real = a->Real + b->Real; + res.Image = a->Image + b->Image; + + return res; +} + +/** + * @brief Do complex number subtraction. + * @param a: The minuend. + * @param b: The subtrahend . + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexSubFloat(fImpCar_Type *a, fImpCar_Type *b) +{ + fImpCar_Type res; + + res.Real = a->Real - b->Real; + res.Image = a->Image - b->Image; + + return res; +} + +/** + * @brief Do complex number division. + * @param a: The dividend. + * @param b: The divisor. + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexDivInt(iImpCar_Type *a, iImpCar_Type *b) +{ + fImpCar_Type res; + float temp; + temp = (float)b->Real*b->Real + (float)b->Image*b->Image; + res.Real = (float)a->Real*b->Real + (float)a->Image*b->Image; + res.Real /= temp; + res.Image = (float)a->Image*b->Real - (float)a->Real*b->Image; + res.Image /= temp; + return res; +} + +/** + * @brief Do complex number multiplication. + * @param a: The multiplicand. + * @param b: The multiplier . + * @return Return result. +**/ +fImpCar_Type AD5940_ComplexMulInt(iImpCar_Type *a, iImpCar_Type *b) +{ + fImpCar_Type res; + + res.Real = (float)a->Real*b->Real - (float)a->Image*b->Image; + res.Image = (float)a->Image*b->Real + (float)a->Real*b->Image; + + return res; +} + +/** + * @brief Calculate the complex number magnitude. + * @param a: The complex number. + * @return Return magnitude. +**/ +float AD5940_ComplexMag(fImpCar_Type *a) +{ + return sqrt(a->Real*a->Real + a->Image*a->Image); +} + +/** + * @brief Calculate the complex number phase. + * @param a: The complex number. + * @return Return phase. +**/ +float AD5940_ComplexPhase(fImpCar_Type *a) +{ + return atan2(a->Image, a->Real); +} + +/** + * @brief Calculate the optimum filter settings based on signal frequency. + * @param freq: Frequency of signalr. + * @return Return FreqParams. +**/ +FreqParams_Type AD5940_GetFreqParameters(float freq) +{ + const uint32_t dft_table[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384}; + const uint32_t sinc2osr_table[] = {1, 22,44,89,178,267,533,640,667,800,889,1067,1333}; + const uint32_t sinc3osr_table[] = {2, 4, 5}; + float AdcRate = 800000; + uint32_t n1 = 0; // Sample rate after ADC filters + uint32_t n2 = 0; // Sample rate after DFT block + uint32_t iCycle = 0; + FreqParams_Type freq_params; + /* High power mode */ + if(freq >= 20000) + { + freq_params. DftSrc = DFTSRC_SINC3; + freq_params.ADCSinc2Osr = 0; + freq_params.ADCSinc3Osr = 2; + freq_params.DftNum = DFTNUM_8192; + freq_params.NumClks = 0; + freq_params.HighPwrMode = bTRUE; + return freq_params; + } + + if(freq < 0.51) + { + freq_params. DftSrc = DFTSRC_SINC2NOTCH; + freq_params.ADCSinc2Osr = 6; + freq_params.ADCSinc3Osr = 1; + freq_params.DftNum = DFTNUM_8192; + freq_params.NumClks = 0; + freq_params.HighPwrMode = bTRUE; + return freq_params; + } + + /* Start with SINC2 setting */ + for(uint8_t i = 0; i=0x1000)&&(RegAddr<=0x3014))) /* 32bit register */ + *(volatile uint32_t *)(RegAddr+0x400c0000) = RegData; + else /* 16bit register */ + *(volatile uint16_t *)(RegAddr+0x400c0000) = RegData; +} + +static uint32_t AD5940_D2DReadReg(uint16_t RegAddr) +{ + if(((RegAddr>=0x1000)&&(RegAddr<=0x3014))) /* 32bit register */ + return *(volatile uint32_t *)(RegAddr+0x400c0000); + else /* 16bit register */ + return *(volatile uint16_t *)(RegAddr+0x400c0000); +} + +void AD5940_FIFORd(uint32_t *pBuffer, uint32_t uiReadCount) +{ + while(uiReadCount--) + *pBuffer++ = *(volatile uint32_t *)(0x400c206C); +} +#else +/** + * @defgroup SPI_Block + * @brief Functions to communicate with AD5940 registers following AD5940 SPI protocols + * @{ + * + * @defgroup SPI_Block_Functions + * @brief The basic SPI protocols. All functions are basic on AD5940_ReadWriteNBytes which + * provided by user. + * + * ##SPI basic protocol + * All SPI protocol starts with one-byte command word. Following are data(16B or 32B) + * There are four SPI commands available @ref SPI_Block_Const. + * @{ +*/ + +/** + @brief Using SPI to transmit one byte and return the received byte. + @param data: The 8-bit data SPI will transmit. + @return received data. +**/ +static unsigned char AD5940_ReadWrite8B(unsigned char data) +{ + uint8_t tx[1], rx[1]; + tx[0] = data; + AD5940_ReadWriteNBytes(tx,rx,1); + return rx[0]; +} + +/** + @brief Using SPI to transmit two bytes and return the received bytes. + @param data: The 16-bit data SPI will transmit. + @return received data. +**/ +static uint16_t AD5940_ReadWrite16B(uint16_t data) +{ + uint8_t SendBuffer[2]; + uint8_t RecvBuffer[2]; + SendBuffer[0] = data>>8; + SendBuffer[1] = data&0xff; + AD5940_ReadWriteNBytes(SendBuffer,RecvBuffer,2); + return (((uint16_t)RecvBuffer[0])<<8)|RecvBuffer[1]; +} + +/** + * @brief Using SPI to transmit four bytes and return the received bytes. + * @param data: The 32-bit data SPI will transmit. + * @return received data. +**/ +static uint32_t AD5940_ReadWrite32B(uint32_t data) +{ + uint8_t SendBuffer[4]; + uint8_t RecvBuffer[4]; + + SendBuffer[0] = (data>>24)&0xff; + SendBuffer[1] = (data>>16)&0xff; + SendBuffer[2] = (data>> 8)&0xff; + SendBuffer[3] = (data )&0xff; + AD5940_ReadWriteNBytes(SendBuffer,RecvBuffer,4); + return (((uint32_t)RecvBuffer[0])<<24)|(((uint32_t)RecvBuffer[1])<<16)|(((uint32_t)RecvBuffer[2])<<8)|RecvBuffer[3]; +} + +/** + * @brief Write register through SPI. + * @param RegAddr: The register address. + * @param RegData: The register data. + * @return Return None. +**/ +static void AD5940_SPIWriteReg(uint16_t RegAddr, uint32_t RegData) +{ + /* Set register address */ + AD5940_CsClr(); + AD5940_ReadWrite8B(SPICMD_SETADDR); + AD5940_ReadWrite16B(RegAddr); + AD5940_CsSet(); + /* Add delay here to meet the SPI timing. */ + AD5940_CsClr(); + AD5940_ReadWrite8B(SPICMD_WRITEREG); + if(((RegAddr>=0x1000)&&(RegAddr<=0x3014))) + AD5940_ReadWrite32B(RegData); + else + AD5940_ReadWrite16B(RegData); + AD5940_CsSet(); +} + +/** + * @brief Read register through SPI. + * @param RegAddr: The register address. + * @return Return register data. +**/ +static uint32_t AD5940_SPIReadReg(uint16_t RegAddr) +{ + uint32_t Data = 0; + /* Set register address that we want to read */ + AD5940_CsClr(); + AD5940_ReadWrite8B(SPICMD_SETADDR); + AD5940_ReadWrite16B(RegAddr); + AD5940_CsSet(); + /* Read it */ + AD5940_CsClr(); + AD5940_ReadWrite8B(SPICMD_READREG); + AD5940_ReadWrite8B(0); //Dummy read + /* The real data is coming */ + if((RegAddr>=0x1000)&&(RegAddr<=0x3014)) + Data = AD5940_ReadWrite32B(0); + else + Data = AD5940_ReadWrite16B(0); + AD5940_CsSet(); + return Data; +} + +/** + @brief Read specific number of data from FIFO with optimized SPI access. + @param pBuffer: Pointer to a buffer that used to store data read back. + @param uiReadCount: How much data to be read. + @return none. +**/ +void AD5940_FIFORd(uint32_t *pBuffer, uint32_t uiReadCount) +{ + /* Use function AD5940_SPIReadReg to read REG_AFE_DATAFIFORD is also one method. */ + uint32_t i; + + if(uiReadCount < 3) + { + /* This method is more efficient when readcount < 3 */ + uint32_t i; + AD5940_CsClr(); + AD5940_ReadWrite8B(SPICMD_SETADDR); + AD5940_ReadWrite16B(REG_AFE_DATAFIFORD); + AD5940_CsSet(); + for(i=0;iHpBandgapEn == bFALSE) + tempreg |= BITM_AFE_AFECON_HPREFDIS; + AD5940_WriteReg(REG_AFE_AFECON, tempreg); + /* Reference buffer configure */ + tempreg = AD5940_ReadReg(REG_AFE_BUFSENCON); + if(pBufCfg->Hp1V8BuffEn == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P8HPADCEN; + if(pBufCfg->Hp1V1BuffEn == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P1HPADCEN; + if(pBufCfg->Lp1V8BuffEn == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P8LPADCEN; + if(pBufCfg->Lp1V1BuffEn == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P1LPADCEN; + if(pBufCfg->Hp1V8ThemBuff == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P8THERMSTEN; + if(pBufCfg->Hp1V8Ilimit == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P8HPADCILIMITEN; + if(pBufCfg->Disc1V8Cap == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P8HPADCCHGDIS; + if(pBufCfg->Disc1V1Cap == bTRUE) + tempreg |= BITM_AFE_BUFSENCON_V1P1LPADCCHGDIS; + AD5940_WriteReg(REG_AFE_BUFSENCON, tempreg); + + /* LPREFBUFCON */ + tempreg = 0; + if(pBufCfg->LpRefBufEn == bFALSE) + tempreg |= BITM_AFE_LPREFBUFCON_LPBUF2P5DIS; + if(pBufCfg->LpBandgapEn == bFALSE) + tempreg |= BITM_AFE_LPREFBUFCON_LPREFDIS; + if(pBufCfg->LpRefBoostEn == bTRUE) + tempreg |= BITM_AFE_LPREFBUFCON_BOOSTCURRENT; + AD5940_WriteReg(REG_AFE_LPREFBUFCON, tempreg); +} +/** + * @} End of AFE_Control_Functions + * @} End of AFE_Control + * */ + +/** + * @defgroup High_Speed_Loop + * @brief The high speed loop + * @{ + * @defgroup High_Speed_Loop_Functions + * @{ +*/ + +/** + @brief Configure High speed loop(high bandwidth loop or + called excitation loop). This configuration includes HSDAC, HSTIA and Switch matrix. + @param pHsLoopCfg : Pointer to configure structure; + @return return none. +*/ +void AD5940_HSLoopCfgS(HSLoopCfg_Type *pHsLoopCfg) +{ + AD5940_HSDacCfgS(&pHsLoopCfg->HsDacCfg); + AD5940_HSTIACfgS(&pHsLoopCfg->HsTiaCfg); + AD5940_SWMatrixCfgS(&pHsLoopCfg->SWMatCfg); + AD5940_WGCfgS(&pHsLoopCfg->WgCfg); +} + +/** + @brief Initialize switch matrix + @param pSwMatrix: Pointer to configuration structure + @return return none. +*/ +void AD5940_SWMatrixCfgS(SWMatrixCfg_Type *pSwMatrix) +{ + AD5940_WriteReg(REG_AFE_DSWFULLCON, pSwMatrix->Dswitch); + AD5940_WriteReg(REG_AFE_PSWFULLCON, pSwMatrix->Pswitch); + AD5940_WriteReg(REG_AFE_NSWFULLCON, pSwMatrix->Nswitch); + AD5940_WriteReg(REG_AFE_TSWFULLCON, pSwMatrix->Tswitch); + AD5940_WriteReg(REG_AFE_SWCON, BITM_AFE_SWCON_SWSOURCESEL); /* Update switch configuration */ +} + +/** + @brief Initialize HSDAC + @param pHsDacCfg: Pointer to configuration structure + @return return none. +*/ +void AD5940_HSDacCfgS(HSDACCfg_Type *pHsDacCfg) +{ + uint32_t tempreg; + //Check parameters + tempreg = 0; + if(pHsDacCfg->ExcitBufGain == EXCITBUFGAIN_0P25) + tempreg |= BITM_AFE_HSDACCON_INAMPGNMDE; /* Enable attenuator */ + if(pHsDacCfg->HsDacGain == HSDACGAIN_0P2) + tempreg |= BITM_AFE_HSDACCON_ATTENEN; /* Enable attenuator */ + tempreg |= (pHsDacCfg->HsDacUpdateRate&0xff)<= HSTIADERTIA_OPEN) + tempreg = 0x1f << 3; /* bit field HPTIRES03CON[7:3] */ + else if(DeRtia >= HSTIADERTIA_1K) + { + tempreg = (DeRtia - 3 + 11) << 3; + } + else /* DERTIA 50/100/200Ohm */ + { + const uint8_t DeRtiaTable[3][5] = + { +//Rload 0 10 30 50 100 + {0x00, 0x01, 0x02, 0x03, 0x06}, /* RTIA 50Ohm */ + {0x03, 0x04, 0x05, 0x06, 0x07}, /* RTIA 100Ohm */ + {0x07, 0x07, 0x09, 0x09, 0x0a}, /* RTIA 200Ohm */ + }; + if(DeRload < HSTIADERLOAD_OPEN) + tempreg = (uint32_t)(DeRtiaTable[DeRtia][DeRload])<<3; + else + tempreg = (0x1f)<<3; /* Set it to HSTIADERTIA_OPEN. This setting is illegal */ + } + /* deal with HSTIA Rload */ + tempreg |= DeRload; + if(DExPin) //DE1 + AD5940_WriteReg(REG_AFE_DE1RESCON, tempreg); + else //DE0 + AD5940_WriteReg(REG_AFE_DE0RESCON, tempreg); +} + +/** + @brief Initialize High speed TIA amplifier + @param pHsTiaCfg: Pointer to configuration structure + @return return none. +*/ +AD5940Err AD5940_HSTIACfgS(HSTIACfg_Type *pHsTiaCfg) +{ + uint32_t tempreg; + //Check parameters + if(pHsTiaCfg == NULL) return AD5940ERR_NULLP; + /* Available parameter is 1k, 5k,...,160k, short, OPEN */ + if(pHsTiaCfg->HstiaDeRtia < HSTIADERTIA_1K) + return AD5940ERR_PARA; + if(pHsTiaCfg->HstiaDeRtia > HSTIADERTIA_OPEN) + return AD5940ERR_PARA; /* Parameter is invalid */ + + if(pHsTiaCfg->HstiaDeRload > HSTIADERLOAD_OPEN) + return AD5940ERR_PARA; /* Available parameter is OPEN, 0R,..., 100R */ + + tempreg = 0; + tempreg |= pHsTiaCfg->HstiaBias; + AD5940_WriteReg(REG_AFE_HSTIACON, tempreg); + /* HSRTIACON */ + /* Calculate CTIA value */ + tempreg = pHsTiaCfg->HstiaCtia << BITP_AFE_HSRTIACON_CTIACON; + tempreg |= pHsTiaCfg->HstiaRtiaSel; + if(pHsTiaCfg->DiodeClose == bTRUE) + tempreg |= BITM_AFE_HSRTIACON_TIASW6CON; /* Close switch 6 */ + AD5940_WriteReg(REG_AFE_HSRTIACON, tempreg); + /* DExRESCON */ + __AD5940_SetDExRTIA(0, pHsTiaCfg->HstiaDeRtia, pHsTiaCfg->HstiaDeRload); +#ifdef CHIPSEL_M355 + __AD5940_SetDExRTIA(1, pHsTiaCfg->HstiaDe1Rtia, pHsTiaCfg->HstiaDe1Rload); +#endif + + /* Done */ + return AD5940ERR_OK; +} +/** + * @brief Configure HSTIA RTIA resistor and keep other parameters unchanged. + * @param HSTIARtia: The RTIA setting, select it from @ref HSTIARTIA_Const + * @return return none. +*/ +void AD5940_HSRTIACfgS(uint32_t HSTIARtia) +{ + uint32_t tempreg; + tempreg = AD5940_ReadReg(REG_AFE_HSRTIACON); + tempreg &= ~BITM_AFE_HSRTIACON_RTIACON; + HSTIARtia &= BITM_AFE_HSRTIACON_RTIACON; + tempreg |= HSTIARtia<WgType == WGTYPE_SIN) + { + /* Configure Sine wave Generator */ + AD5940_WriteReg(REG_AFE_WGFCW, pWGInit->SinCfg.SinFreqWord); + AD5940_WriteReg(REG_AFE_WGAMPLITUDE, pWGInit->SinCfg.SinAmplitudeWord); + AD5940_WriteReg(REG_AFE_WGOFFSET, pWGInit->SinCfg.SinOffsetWord); + AD5940_WriteReg(REG_AFE_WGPHASE, pWGInit->SinCfg.SinPhaseWord); + } + else if(pWGInit->WgType == WGTYPE_TRAPZ) + { + /* Configure Trapezoid Generator */ + AD5940_WriteReg(REG_AFE_WGDCLEVEL1, pWGInit->TrapzCfg.WGTrapzDCLevel1); + AD5940_WriteReg(REG_AFE_WGDCLEVEL2, pWGInit->TrapzCfg.WGTrapzDCLevel2); + AD5940_WriteReg(REG_AFE_WGDELAY1, pWGInit->TrapzCfg.WGTrapzDelay1); + AD5940_WriteReg(REG_AFE_WGDELAY2, pWGInit->TrapzCfg.WGTrapzDelay2); + AD5940_WriteReg(REG_AFE_WGSLOPE1, pWGInit->TrapzCfg.WGTrapzSlope1); + AD5940_WriteReg(REG_AFE_WGSLOPE2, pWGInit->TrapzCfg.WGTrapzSlope2); + } + else + { + /* Write DAC data. It's only have effect when WgType set to WGTYPE_MMR */ + AD5940_WriteReg(REG_AFE_HSDACDAT, pWGInit->WgCode); + } + tempreg = 0; + + if(pWGInit->GainCalEn == bTRUE) + tempreg |= BITM_AFE_WGCON_DACGAINCAL; + if(pWGInit->OffsetCalEn == bTRUE) + tempreg |= BITM_AFE_WGCON_DACOFFSETCAL; + tempreg |= (pWGInit->WgType) << BITP_AFE_WGCON_TYPESEL; + AD5940_WriteReg(REG_AFE_WGCON, tempreg); +} + +/** + * @brief Write HSDAC code directly when WG configured to MMR type + * @param code: The 12-bit HSDAC code. + * @return return none. +*/ +AD5940Err AD5940_WGDACCodeS(uint32_t code) +{ + code &= 0xfff; + AD5940_WriteReg(REG_AFE_HSDACDAT, code); + return AD5940ERR_OK; +} + +/** + * @brief Update WG SIN wave frequency in Hz. + * @param SinFreqHz: The desired frequency in Hz. + * @param WGClock: The clock for WG. It's same as system clock and the default value is internal 16MHz HSOSC. + * @return return none. +*/ +void AD5940_WGFreqCtrlS(float SinFreqHz, float WGClock) +{ + uint32_t freq_word; + freq_word = AD5940_WGFreqWordCal(SinFreqHz, WGClock); + AD5940_WriteReg(REG_AFE_WGFCW, freq_word); +} + +/** + @brief Calculate sine wave generator frequency word. The maxim frequency is 250kHz-1LSB + @param SinFreqHz : Target frequency in Hz unit. + @param WGClock: Waveform generator clock frequency in Hz unit. The clock is sourced from system clock, default value is 16MHz HFOSC. + @return return none. +*/ +uint32_t AD5940_WGFreqWordCal(float SinFreqHz, float WGClock) +{ + uint32_t temp; + uint32_t __BITWIDTH_WGFCW = 26; + if(bIsS2silicon == bTRUE) + __BITWIDTH_WGFCW = 30; + if(WGClock == 0) return 0; + temp = (uint32_t)(SinFreqHz*(1LL<<__BITWIDTH_WGFCW)/WGClock + 0.5f); + if(temp > ((__BITWIDTH_WGFCW == 26)?0xfffff:0xffffff)) + temp = (__BITWIDTH_WGFCW == 26)?0xfffff:0xffffff; + + return temp; +} + +/** + * @} Waveform_Generator_Functions + * @} High_Speed_Loop_Functions + * @} High_Speed_Loop +*/ + + +/** + * @defgroup Low_Power_Loop + * @brief The low power loop. + * @{ + * @defgroup Low_Power_Loop_Functions + * @{ +*/ + +/** + @brief Configure low power loop include LPDAC LPAmp(PA and TIA) + @param pLpLoopCfg: Pointer to configure structure; + @return return none. +*/ +void AD5940_LPLoopCfgS(LPLoopCfg_Type *pLpLoopCfg) +{ + AD5940_LPDACCfgS(&pLpLoopCfg->LpDacCfg); + AD5940_LPAMPCfgS(&pLpLoopCfg->LpAmpCfg); +} + +/** + @brief Initialize LPDAC + @param pLpDacCfg: Pointer to configuration structure + @return return none. +*/ +void AD5940_LPDACCfgS(LPDACCfg_Type *pLpDacCfg) +{ + uint32_t tempreg; + tempreg = 0; + tempreg = (pLpDacCfg->LpDacSrc)<LpDacVzeroMux)<LpDacVbiasMux)<LpDacRef)<DataRst == bFALSE) + tempreg |= BITM_AFE_LPDACCON0_RSTEN; + if(pLpDacCfg->PowerEn == bFALSE) + tempreg |= BITM_AFE_LPDACCON0_PWDEN; + if(pLpDacCfg->LpdacSel == LPDAC0) + { + AD5940_WriteReg(REG_AFE_LPDACCON0, tempreg); + AD5940_LPDAC0WriteS(pLpDacCfg->DacData12Bit, pLpDacCfg->DacData6Bit); + AD5940_WriteReg(REG_AFE_LPDACSW0, pLpDacCfg->LpDacSW|BITM_AFE_LPDACSW0_LPMODEDIS); /* Overwrite LPDACSW settings. On Si1, this register is not accessible. */ + } + else + { + AD5940_WriteReg(REG_AFE_LPDACCON1, tempreg); + AD5940_LPDAC1WriteS(pLpDacCfg->DacData12Bit, pLpDacCfg->DacData6Bit); + AD5940_WriteReg(REG_AFE_LPDACSW1, pLpDacCfg->LpDacSW|BITM_AFE_LPDACSW0_LPMODEDIS); /* Overwrite LPDACSW settings. On Si1, this register is not accessible. */ + } +} + +/** + @brief Write LPDAC data + @param Data12Bit: 12Bit DAC data + @param Data6Bit: 6Bit DAC data + @return return none. +*/ +void AD5940_LPDACWriteS(uint16_t Data12Bit, uint8_t Data6Bit) +{ + /* Check parameter */ + Data6Bit &= 0x3f; + Data12Bit &= 0xfff; + AD5940_WriteReg(REG_AFE_LPDACDAT0, ((uint32_t)Data6Bit<<12)|Data12Bit); +} + +/** + @brief Write LPDAC0 data + @param Data12Bit: 12Bit DAC data + @param Data6Bit: 6Bit DAC data + @return return none. +*/ +void AD5940_LPDAC0WriteS(uint16_t Data12Bit, uint8_t Data6Bit) +{ + /* Check parameter */ + Data6Bit &= 0x3f; + Data12Bit &= 0xfff; + AD5940_WriteReg(REG_AFE_LPDACDAT0, ((uint32_t)Data6Bit<<12)|Data12Bit); +} + +/** + @brief Write LPDAC1 data + @param Data12Bit: 12Bit DAC data + @param Data6Bit: 6Bit DAC data + @return return none. +*/ +void AD5940_LPDAC1WriteS(uint16_t Data12Bit, uint8_t Data6Bit) +{ + /* Check parameter */ + Data6Bit &= 0x3f; + Data12Bit &= 0xfff; + AD5940_WriteReg(REG_AFE_LPDACDAT1, ((uint32_t)Data6Bit<<12)|Data12Bit); +} + +/** + @brief Initialize LP TIA and PA + @param pLpAmpCfg: Pointer to configuration structure + @return return none. +*/ +void AD5940_LPAMPCfgS(LPAmpCfg_Type *pLpAmpCfg) +{ + //check parameters + uint32_t tempreg; + + tempreg = 0; + if(pLpAmpCfg->LpPaPwrEn == bFALSE) + tempreg |= BITM_AFE_LPTIACON0_PAPDEN; + if(pLpAmpCfg->LpTiaPwrEn == bFALSE) + tempreg |= BITM_AFE_LPTIACON0_TIAPDEN; + if(pLpAmpCfg->LpAmpPwrMod == LPAMPPWR_HALF) + tempreg |= BITM_AFE_LPTIACON0_HALFPWR; + else + { + tempreg |= pLpAmpCfg->LpAmpPwrMod<LpTiaRtia<LpTiaRload<LpTiaRf<LpAmpSel == LPAMP0) + { + AD5940_WriteReg(REG_AFE_LPTIACON0, tempreg); + AD5940_WriteReg(REG_AFE_LPTIASW0, pLpAmpCfg->LpTiaSW); + } + else + { + AD5940_WriteReg(REG_AFE_LPTIACON1, tempreg); + AD5940_WriteReg(REG_AFE_LPTIASW1, pLpAmpCfg->LpTiaSW); + } +} +/** + * @} Low_Power_Loop_Functions + * @} Low_Power_Loop +*/ + + +/** + * @defgroup DSP_Block + * @brief DSP block includes ADC, filters, DFT and statistic functions. + * @{ + * @defgroup DSP_Block_Functions + * @{ + * */ + +/** + @brief Configure low power loop include LPDAC LPAmp(PA and TIA) + @param pDSPCfg: Pointer to configure structure; + @return return none. +*/ +void AD5940_DSPCfgS(DSPCfg_Type *pDSPCfg) +{ + AD5940_ADCBaseCfgS(&pDSPCfg->ADCBaseCfg); + AD5940_ADCFilterCfgS(&pDSPCfg->ADCFilterCfg); + AD5940_ADCDigCompCfgS(&pDSPCfg->ADCDigCompCfg); + AD5940_DFTCfgS(&pDSPCfg->DftCfg); + AD5940_StatisticCfgS(&pDSPCfg->StatCfg); +} + +/** + @brief Read AD5940 generated data like ADC and DFT etc. + @param AfeResultSel: available parameters are @ref AFERESULT_Const + - AFERESULT_SINC3: Read SINC3 filter data result + - AFERESULT_SINC2: Read SINC2+NOTCH filter result, when Notch filter is bypassed, the result is SINC2 + - AFERESULT_STATSVAR: Statistic variance result + @return return data read back. +*/ +uint32_t AD5940_ReadAfeResult(uint32_t AfeResultSel) +{ + uint32_t rd = 0; + //PARA_CHECK((AfeResultSel)); + switch (AfeResultSel) + { + case AFERESULT_SINC3: + rd = AD5940_ReadReg(REG_AFE_ADCDAT); + break; + case AFERESULT_SINC2: + rd = AD5940_ReadReg(REG_AFE_SINC2DAT); + break; + case AFERESULT_TEMPSENSOR: + rd = AD5940_ReadReg(REG_AFE_TEMPSENSDAT); + break; + case AFERESULT_DFTREAL: + rd = AD5940_ReadReg(REG_AFE_DFTREAL); + break; + case AFERESULT_DFTIMAGE: + rd = AD5940_ReadReg(REG_AFE_DFTIMAG); + break; + case AFERESULT_STATSMEAN: + rd = AD5940_ReadReg(REG_AFE_STATSMEAN); + break; + case AFERESULT_STATSVAR: + rd = AD5940_ReadReg(REG_AFE_STATSVAR); + break; + } + + return rd; +} + +/** + * @defgroup ADC_Block_Functions + * @{ +*/ + +/** + @brief Initializes ADC peripheral according to the specified parameters in the pADCInit. + @param pADCInit: Pointer to ADC initialize structure. + @return return none. +*/ +void AD5940_ADCBaseCfgS(ADCBaseCfg_Type *pADCInit) +{ + uint32_t tempreg = 0; + //PARA_CHECK(IS_ADCMUXP(pADCInit->ADCMuxP)); + //PARA_CHECK(IS_ADCMUXN(pADCInit->ADCMuxN)); + PARA_CHECK(IS_ADCPGA(pADCInit->ADCPga)); + PARA_CHECK(IS_ADCAAF(pADCInit->ADCAAF)); + + tempreg = pADCInit->ADCMuxP; + tempreg |= (uint32_t)(pADCInit->ADCMuxN)<OffCancEnable == bTRUE) + // tempreg |= BITM_AFE_ADCCON_GNOFSELPGA; + tempreg |= (uint32_t)(pADCInit->ADCPga)<ADCSinc3Osr)); + PARA_CHECK(IS_ADCSINC2OSR(pFiltCfg->ADCSinc2Osr)); + PARA_CHECK(IS_ADCAVGNUM(pFiltCfg->ADCAvgNum)); + PARA_CHECK(IS_ADCRATE(pFiltCfg->ADCRate)); + + tempreg = AD5940_ReadReg(REG_AFE_ADCFILTERCON); + tempreg &= BITM_AFE_ADCFILTERCON_AVRGEN; /* Keep this bit setting. */ + + tempreg |= pFiltCfg->ADCRate; + if(pFiltCfg->BpNotch == bTRUE) + tempreg |= BITM_AFE_ADCFILTERCON_LPFBYPEN; + if(pFiltCfg->BpSinc3 == bTRUE) + tempreg |= BITM_AFE_ADCFILTERCON_SINC3BYP; + /** + * Average filter is enabled when DFT source is @ref DFTSRC_AVG in function @ref AD5940_DFTCfgS. + * Once average function is enabled, it's automatically set as DFT source, register DFTCON.DFTINSEL is ignored. + */ + //if(pFiltCfg->AverageEnable == bTRUE) + // tempreg |= BITM_AFE_ADCFILTERCON_AVRGEN; + tempreg |= (uint32_t)(pFiltCfg->ADCSinc2Osr)<ADCSinc3Osr)<ADCAvgNum)<Sinc2NotchEnable) + { + AD5940_AFECtrlS(AFECTRL_SINC2NOTCH,bTRUE); + } +} + +/** + @brief Power up or power down ADC block(including ADC PGA and FRONTBUF). + @param State : {bTRUE, bFALSE} + - bTRUE: Power up ADC + - bFALSE: Power down ADC + @return return none. +*/ +void AD5940_ADCPowerCtrlS(BoolFlag State) +{ + uint32_t tempreg; + tempreg = AD5940_ReadReg(REG_AFE_AFECON); + if(State == bTRUE) + { + tempreg |= BITM_AFE_AFECON_ADCEN; + } + else + { + tempreg &= ~BITM_AFE_AFECON_ADCEN; + } + AD5940_WriteReg(REG_AFE_AFECON,tempreg); +} + +/** + @brief Start or stop ADC convert. + @param State : {bTRUE, bFALSE} + - bTRUE: Start ADC convert + - bFALSE: Stop ADC convert + @return return none. +*/ +void AD5940_ADCConvtCtrlS(BoolFlag State) +{ + uint32_t tempreg; + tempreg = AD5940_ReadReg(REG_AFE_AFECON); + if(State == bTRUE) + { + tempreg |= BITM_AFE_AFECON_ADCCONVEN; + } + else + { + tempreg &= ~BITM_AFE_AFECON_ADCCONVEN; + } + AD5940_WriteReg(REG_AFE_AFECON,tempreg); +} + +/** + @brief Configure ADC input MUX + @param ADCMuxP : {ADCMUXP_FLOAT, ADCMUXP_HSTIA_P, ,,, ,ADCMUXP_P_NODE} + - ADCMUXP_FLOAT: float ADC MUX positive input + - ADCMUXP_HSTIA_P: High speed TIA output sense terminal + - ADCMUXP_P_NODE: Excitation loop P node + @param ADCMuxN : {ADCMUXP_FLOAT, ADCMUXP_HSTIA_P, ,,, ,ADCMUXP_P_NODE} + - ADCMUXP_FLOAT: float ADC MUX positive input + - ADCMUXP_HSTIA_P: High speed TIA output sense terminal + - ADCMUXP_P_NODE: Excitation loop P node + + @return return none. +*/ +void AD5940_ADCMuxCfgS(uint32_t ADCMuxP, uint32_t ADCMuxN) +{ + uint32_t tempreg; + //PARA_CHECK(IS_ADCMUXP(ADCMuxP)); + //PARA_CHECK(IS_ADCMUXN(ADCMuxN)); + + tempreg = AD5940_ReadReg(REG_AFE_ADCCON); + tempreg &= ~(BITM_AFE_ADCCON_MUXSELN|BITM_AFE_ADCCON_MUXSELP); + tempreg |= ADCMuxP<ADCMin); + AD5940_WriteReg(REG_AFE_ADCMINSM, pCompCfg->ADCMinHys); + AD5940_WriteReg(REG_AFE_ADCMAX, pCompCfg->ADCMax); + AD5940_WriteReg(REG_AFE_ADCMAXSMEN, pCompCfg->ADCMaxHys); +} +/** @} ADC_Block_Functions */ + +/** + @brief Configure statistic functions + @param pStatCfg: Pointer to configuration structure + @return return none. +*/ +void AD5940_StatisticCfgS(StatCfg_Type *pStatCfg) +{ + uint32_t tempreg; + //check parameters + tempreg = 0; + if(pStatCfg->StatEnable == bTRUE) + tempreg |= BITM_AFE_STATSCON_STATSEN; + tempreg |= (pStatCfg->StatSample) << BITP_AFE_STATSCON_SAMPLENUM; + tempreg |= (pStatCfg->StatDev) << BITP_AFE_STATSCON_STDDEV; + AD5940_WriteReg(REG_AFE_STATSCON, tempreg); +} + +/** + * @brief Set ADC Repeat convert function number. Turn off ADC automatically after Number samples of ADC raw data are ready + * @param Number: Specify after how much ADC raw data need to sample before shutdown ADC + * @return return none. +*/ +void AD5940_ADCRepeatCfgS(uint32_t Number) +{ + //check parameter if(number<255) + AD5940_WriteReg(REG_AFE_REPEATADCCNV, Number<DftSrc == DFTSRC_AVG) + { + reg_adcfilter |= BITM_AFE_ADCFILTERCON_AVRGEN; + AD5940_WriteReg(REG_AFE_ADCFILTERCON, reg_adcfilter); + } + else + { + /* Disable Average function and set correct DFT source */ + reg_adcfilter &= ~BITM_AFE_ADCFILTERCON_AVRGEN; + AD5940_WriteReg(REG_AFE_ADCFILTERCON, reg_adcfilter); + + /* Set new DFT source */ + reg_dftcon |= (pDftCfg->DftSrc) << BITP_AFE_DFTCON_DFTINSEL; + } + /* Set DFT number */ + reg_dftcon |= (pDftCfg->DftNum) << BITP_AFE_DFTCON_DFTNUM; + + if(pDftCfg->HanWinEn == bTRUE) + reg_dftcon |= BITM_AFE_DFTCON_HANNINGEN; + AD5940_WriteReg(REG_AFE_DFTCON, reg_dftcon); +} + +/** + * @} DSP_Block_Functions + * @} DSP_Block +*/ + +/** + * @defgroup Sequencer_FIFO + * @brief Sequencer and FIFO. + * @{ + * @defgroup Sequencer_FIFO_Functions + * @{ +*/ + +/** + @brief Configure AD5940 FIFO + @param pFifoCfg: Pointer to configuration structure. + @return return none. +*/ +void AD5940_FIFOCfg(FIFOCfg_Type *pFifoCfg) +{ + uint32_t tempreg; + //check parameters + AD5940_WriteReg(REG_AFE_FIFOCON, 0); /* Disable FIFO firstly! */ + /* CMDDATACON register. Configure this firstly */ + tempreg = AD5940_ReadReg(REG_AFE_CMDDATACON); + tempreg &= BITM_AFE_CMDDATACON_CMD_MEM_SEL|BITM_AFE_CMDDATACON_CMDMEMMDE; /* Keep sequencer memory settings */ + tempreg |= pFifoCfg->FIFOMode << BITP_AFE_CMDDATACON_DATAMEMMDE; /* Data FIFO mode: stream or FIFO */ + tempreg |= pFifoCfg->FIFOSize << BITP_AFE_CMDDATACON_DATA_MEM_SEL; /* Data FIFO memory size */ + /* The reset memory can be used for sequencer, configure it by function AD5940_SEQCfg() */ + AD5940_WriteReg(REG_AFE_CMDDATACON, tempreg); + + /* FIFO Threshold */ + AD5940_WriteReg(REG_AFE_DATAFIFOTHRES, pFifoCfg->FIFOThresh << BITP_AFE_DATAFIFOTHRES_HIGHTHRES); + /* FIFOCON register. Final step is to enable FIFO */ + tempreg = 0; + if(pFifoCfg->FIFOEn == bTRUE) + tempreg |= BITM_AFE_FIFOCON_DATAFIFOEN; /* Enable FIFO after everything set. */ + tempreg |= pFifoCfg->FIFOSrc << BITP_AFE_FIFOCON_DATAFIFOSRCSEL; + AD5940_WriteReg(REG_AFE_FIFOCON, tempreg); +} + +/** + @brief Read current FIFO configuration. + @param pFifoCfg: Pointer to a buffer that used to store FIFO configuration. + @return return AD5940ERR_OK if succeed. +*/ +AD5940Err AD5940_FIFOGetCfg(FIFOCfg_Type *pFifoCfg) +{ + uint32_t tempreg; + //check parameters + if(pFifoCfg == NULL) return AD5940ERR_NULLP; + /* CMDDATACON register. */ + tempreg = AD5940_ReadReg(REG_AFE_CMDDATACON); + pFifoCfg->FIFOMode = (tempreg&BITM_AFE_CMDDATACON_DATAMEMMDE)>>BITP_AFE_CMDDATACON_DATAMEMMDE; + pFifoCfg->FIFOSize = (tempreg&BITM_AFE_CMDDATACON_DATA_MEM_SEL)>>BITP_AFE_CMDDATACON_DATA_MEM_SEL; + + /* FIFO Threshold */ + tempreg = AD5940_ReadReg(REG_AFE_DATAFIFOTHRES); + pFifoCfg->FIFOThresh = (tempreg&BITM_AFE_DATAFIFOTHRES_HIGHTHRES)>>BITP_AFE_DATAFIFOTHRES_HIGHTHRES; + /* FIFOCON register. */ + tempreg = AD5940_ReadReg(REG_AFE_FIFOCON); + pFifoCfg->FIFOEn = (tempreg&BITM_AFE_FIFOCON_DATAFIFOEN)?bTRUE:bFALSE; + pFifoCfg->FIFOSrc = (tempreg&BITM_AFE_FIFOCON_DATAFIFOSRCSEL)>>BITP_AFE_FIFOCON_DATAFIFOSRCSEL; + + return AD5940ERR_OK; +} + +/** + * @brief Configure AD5940 FIFO Source and enable or disable FIFO. + * @param FifoSrc : available choices are @ref FIFOSRC_Const + * - FIFOSRC_SINC3 SINC3 data + * - FIFOSRC_DFT DFT real and imaginary part + * - FIFOSRC_SINC2NOTCH SINC2+NOTCH block. Notch can be bypassed, so SINC2 data can be feed to FIFO + * - FIFOSRC_VAR Statistic variance output + * - FIFOSRC_MEAN Statistic mean output + * @param FifoEn: enable or disable the FIFO. + * @return return none. +*/ +void AD5940_FIFOCtrlS(uint32_t FifoSrc, BoolFlag FifoEn) +{ + uint32_t tempreg; + + tempreg = 0; + if(FifoEn == bTRUE) + tempreg |= BITM_AFE_FIFOCON_DATAFIFOEN; + tempreg |= FifoSrc << BITP_AFE_FIFOCON_DATAFIFOSRCSEL; + AD5940_WriteReg(REG_AFE_FIFOCON, tempreg); +} + +/** + * @brief Configure AD5940 Data FIFO threshold value + @param FIFOThresh: FIFO threshold value + @return return none. +*/ +void AD5940_FIFOThrshSet(uint32_t FIFOThresh) +{ + /* FIFO Threshold */ + AD5940_WriteReg(REG_AFE_DATAFIFOTHRES, FIFOThresh << BITP_AFE_DATAFIFOTHRES_HIGHTHRES); +} + +/** + * @brief Get Data count in FIFO + * @return return none. +*/ +uint32_t AD5940_FIFOGetCnt(void) +{ + return AD5940_ReadReg(REG_AFE_FIFOCNTSTA) >> BITP_AFE_FIFOCNTSTA_DATAFIFOCNTSTA; +} + + +/* Sequencer */ +/** + * @brief Initialize Sequencer + * @param pSeqCfg: Pointer to configuration structure + @return return none. +*/ +void AD5940_SEQCfg(SEQCfg_Type *pSeqCfg) +{ + /* check parameters */ + uint32_t tempreg, fifocon; + + fifocon = AD5940_ReadReg(REG_AFE_FIFOCON); + AD5940_WriteReg(REG_AFE_FIFOCON, 0); /* Disable FIFO before changing memory configuration */ + /* Configure CMDDATACON register */ + tempreg = AD5940_ReadReg(REG_AFE_CMDDATACON); + tempreg &= ~(BITM_AFE_CMDDATACON_CMDMEMMDE|BITM_AFE_CMDDATACON_CMD_MEM_SEL); /* Clear settings for sequencer memory */ + tempreg |= (1L) << BITP_AFE_CMDDATACON_CMDMEMMDE; /* Sequencer is always in memory mode */ + tempreg |= (pSeqCfg->SeqMemSize) << BITP_AFE_CMDDATACON_CMD_MEM_SEL; + AD5940_WriteReg(REG_AFE_CMDDATACON, tempreg); + + if(pSeqCfg->SeqCntCRCClr) + { + AD5940_WriteReg(REG_AFE_SEQCON, 0); /* Disable sequencer firstly */ + AD5940_WriteReg(REG_AFE_SEQCNT, 0); /* When sequencer is disabled, any write to SEQCNT will clear CNT and CRC register */ + } + tempreg = 0; + if(pSeqCfg->SeqEnable == bTRUE) + tempreg |= BITM_AFE_SEQCON_SEQEN; + tempreg |= (pSeqCfg->SeqWrTimer) << BITP_AFE_SEQCON_SEQWRTMR; + AD5940_WriteReg(REG_AFE_SEQCON, tempreg); + AD5940_WriteReg(REG_AFE_FIFOCON, fifocon); /* restore FIFO configuration */ + + // tempreg = 0; + // if(pSeqCfg->SeqBreakEn) + // tempreg |= 0x01; // add register definition? bitm_afe_ + // if(pSeqCfg->SeqIgnoreEn) + // tempreg |= 0x02; + // AD5940_WriteReg(0x21dc, tempreg); +} +/** + * @brief Read back current sequencer configuration and store it to pSeqCfg + * @param pSeqCfg: Pointer to structure + * @return return AD5940ERR_OK if succeed. +*/ +AD5940Err AD5940_SEQGetCfg(SEQCfg_Type *pSeqCfg) +{ + /* check parameters */ + uint32_t tempreg; + if(pSeqCfg == NULL) + return AD5940ERR_NULLP; + /* Read CMDDATACON register */ + tempreg = AD5940_ReadReg(REG_AFE_CMDDATACON); + pSeqCfg->SeqMemSize = (tempreg&BITM_AFE_CMDDATACON_CMD_MEM_SEL) >> BITP_AFE_CMDDATACON_CMD_MEM_SEL; + pSeqCfg->SeqCntCRCClr = bFALSE; /* Has no meaning */ + /* SEQCON register */ + tempreg = AD5940_ReadReg(REG_AFE_SEQCON); + pSeqCfg->SeqEnable = (tempreg&BITM_AFE_SEQCON_SEQEN)?bTRUE:bFALSE; + pSeqCfg->SeqWrTimer = (tempreg&BITM_AFE_SEQCON_SEQWRTMR) >> BITP_AFE_SEQCON_SEQWRTMR; + return AD5940ERR_OK; +} + +/** + * @brief Enable or Disable sequencer. + * @note Only after valid trigger signal, sequencer can run. + * @return return none. +*/ +void AD5940_SEQCtrlS(BoolFlag SeqEn) +{ + uint32_t tempreg = AD5940_ReadReg(REG_AFE_SEQCON); + if(SeqEn == bTRUE) + tempreg |= BITM_AFE_SEQCON_SEQEN; + else + tempreg &= ~BITM_AFE_SEQCON_SEQEN; + + AD5940_WriteReg(REG_AFE_SEQCON, tempreg); +} + +/** + * @brief Halt sequencer immediately. Use this to debug. In normal application, there is no situation that can use this function. + * @return return none. +*/ +void AD5940_SEQHaltS(void) +{ + AD5940_WriteReg(REG_AFE_SEQCON, BITM_AFE_SEQCON_SEQHALT|BITM_AFE_SEQCON_SEQEN); +} + +/** + * @brief Trigger sequencer by register write. + * @return return none. +**/ +void AD5940_SEQMmrTrig(uint32_t SeqId) +{ + if(SeqId > SEQID_3) + return; + AD5940_WriteReg(REG_AFECON_TRIGSEQ, 1L<SeqId) + { + case SEQID_0: + /* Configure SEQINFO register */ + AD5940_WriteReg(REG_AFE_SEQ0INFO, (pSeq->SeqLen<< 16) | pSeq->SeqRamAddr); + break; + case SEQID_1: + AD5940_WriteReg(REG_AFE_SEQ1INFO, (pSeq->SeqLen<< 16) | pSeq->SeqRamAddr); + break; + case SEQID_2: + AD5940_WriteReg(REG_AFE_SEQ2INFO, (pSeq->SeqLen<< 16) | pSeq->SeqRamAddr); + break; + case SEQID_3: + AD5940_WriteReg(REG_AFE_SEQ3INFO, (pSeq->SeqLen<< 16) | pSeq->SeqRamAddr); + break; + default: + break; + } + if(pSeq->WriteSRAM == bTRUE) + { + AD5940_SEQCmdWrite(pSeq->SeqRamAddr, pSeq->pSeqCmd, pSeq->SeqLen); + } +} + +/** + * @brief Get sequence info: start address and sequence length. + * @param SeqId: Select from {SEQID_0, SEQID_1, SEQID_2, SEQID_3} + - Select which sequence we want to get the information. + @param pSeqInfo: Pointer to sequence info structure. + @return return AD5940ERR_OK when succeed. +*/ +AD5940Err AD5940_SEQInfoGet(uint32_t SeqId, SEQInfo_Type *pSeqInfo) +{ + uint32_t tempreg; + if(pSeqInfo == NULL) return AD5940ERR_NULLP; + switch(SeqId) + { + case SEQID_0: + tempreg = AD5940_ReadReg(REG_AFE_SEQ0INFO); + break; + case SEQID_1: + tempreg = AD5940_ReadReg(REG_AFE_SEQ1INFO); + break; + case SEQID_2: + tempreg = AD5940_ReadReg(REG_AFE_SEQ2INFO); + break; + case SEQID_3: + tempreg = AD5940_ReadReg(REG_AFE_SEQ3INFO); + break; + default: + return AD5940ERR_PARA; + } + pSeqInfo->pSeqCmd = 0; /* We don't know where you store the sequence in MCU SRAM */ + pSeqInfo->SeqId = SeqId; + pSeqInfo->SeqLen = (tempreg>>16)&0x7ff; + pSeqInfo->SeqRamAddr = tempreg&0x7ff; + pSeqInfo->WriteSRAM = bFALSE; /* Don't care */ + + return AD5940ERR_OK; +} + + +/** + @brief Control GPIO with register SYNCEXTDEVICE. Because sequencer have no ability to access register GPIOOUT, + so we use this register for sequencer. + @param Gpio : Select from {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + - The combination of GPIO pins. The selected pins will be set to High. Others will be pulled low. + @return return None. + +**/ +void AD5940_SEQGpioCtrlS(uint32_t Gpio) +{ + AD5940_WriteReg(REG_AFE_SYNCEXTDEVICE, Gpio); +} + +/** + * @brief Read back current count down timer value for Sequencer Timer Out command. + * @return return register value of Sequencer Timer out value. +**/ +uint32_t AD5940_SEQTimeOutRd(void) +{ + return AD5940_ReadReg(REG_AFE_SEQTIMEOUT); +} + +/** + * @brief Configure GPIO to allow it to trigger corresponding sequence(SEQ0/1/2/3). + * @details There are four sequences. We can use GPIO to trigger each sequence. For example, + * GP0 or GP4 can be used to trigger sequence0 and GP3 or GP7 to trigger sequence3. + * There are five mode available to detect pin action: Rising edge, falling edge, both rising and falling + * edge, low level or high level. + * Be careful to use level detection. The trigger signal is always available if the pin level is matched. + * Once the sequence is done, it will immediately run again if the pin level is still matched. + * @return return AD5940ERR_OK if succeed. +**/ +AD5940Err AD5940_SEQGpioTrigCfg(SeqGpioTrig_Cfg *pSeqGpioTrigCfg) +{ + uint32_t reg_ei0con, reg_ei1con; + uint32_t pin_count, pin_mask; + uint32_t mode, en; + if(pSeqGpioTrigCfg == NULL) + return AD5940ERR_NULLP; + reg_ei0con = AD5940_ReadReg(REG_ALLON_EI0CON); + reg_ei1con = AD5940_ReadReg(REG_ALLON_EI1CON); + + pin_count = 0; /* Start from pin0 */ + pin_mask = 0x01; /* start from pin0, mask 0x01 */ + pSeqGpioTrigCfg->SeqPinTrigMode &= 0x07; /* 3bit width */ + + mode = pSeqGpioTrigCfg->SeqPinTrigMode; + en = pSeqGpioTrigCfg->bEnable?1:0; + for(;;) + { + uint32_t bit_position; + if(pSeqGpioTrigCfg->PinSel&pin_mask) + { + if(pin_count < 4) /* EI0CON register */ + { + bit_position = pin_count*4; + reg_ei0con &= ~(0xfL<SeqxWakeupTime[0] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ0WUPH, (pWuptCfg->SeqxWakeupTime[0] & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ0SLEEPL, (pWuptCfg->SeqxSleepTime[0] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ0SLEEPH, (pWuptCfg->SeqxSleepTime[0] & 0xF0000)>>16); + + AD5940_WriteReg(REG_WUPTMR_SEQ1WUPL, (pWuptCfg->SeqxWakeupTime[1] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ1WUPH, (pWuptCfg->SeqxWakeupTime[1] & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ1SLEEPL, (pWuptCfg->SeqxSleepTime[1] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ1SLEEPH, (pWuptCfg->SeqxSleepTime[1] & 0xF0000)>>16); + + AD5940_WriteReg(REG_WUPTMR_SEQ2WUPL, (pWuptCfg->SeqxWakeupTime[2] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ2WUPH, (pWuptCfg->SeqxWakeupTime[2] & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ2SLEEPL, (pWuptCfg->SeqxSleepTime[2] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ2SLEEPH, (pWuptCfg->SeqxSleepTime[2] & 0xF0000)>>16); + + AD5940_WriteReg(REG_WUPTMR_SEQ3WUPL, (pWuptCfg->SeqxWakeupTime[3] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ3WUPH, (pWuptCfg->SeqxWakeupTime[3] & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ3SLEEPL, (pWuptCfg->SeqxSleepTime[3] & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ3SLEEPH, (pWuptCfg->SeqxSleepTime[3] & 0xF0000)>>16); + + /* TMRCON register */ + //if(pWuptCfg->WakeupEn == bTRUE) /* enable use Wupt to wakeup AFE */ + /* We always allow Wupt to wakeup AFE automatically. */ + AD5940_WriteReg(REG_ALLON_TMRCON, BITM_ALLON_TMRCON_TMRINTEN); + /* Wupt order */ + tempreg = 0; + tempreg |= (pWuptCfg->WuptOrder[0]&0x03) << BITP_WUPTMR_SEQORDER_SEQA; /* position A */ + tempreg |= (pWuptCfg->WuptOrder[1]&0x03) << BITP_WUPTMR_SEQORDER_SEQB; /* position B */ + tempreg |= (pWuptCfg->WuptOrder[2]&0x03) << BITP_WUPTMR_SEQORDER_SEQC; /* position C */ + tempreg |= (pWuptCfg->WuptOrder[3]&0x03) << BITP_WUPTMR_SEQORDER_SEQD; /* position D */ + tempreg |= (pWuptCfg->WuptOrder[4]&0x03) << BITP_WUPTMR_SEQORDER_SEQE; /* position E */ + tempreg |= (pWuptCfg->WuptOrder[5]&0x03) << BITP_WUPTMR_SEQORDER_SEQF; /* position F */ + tempreg |= (pWuptCfg->WuptOrder[6]&0x03) << BITP_WUPTMR_SEQORDER_SEQG; /* position G */ + tempreg |= (pWuptCfg->WuptOrder[7]&0x03) << BITP_WUPTMR_SEQORDER_SEQH; /* position H */ + AD5940_WriteReg(REG_WUPTMR_SEQORDER, tempreg); + + tempreg = 0; + if(pWuptCfg->WuptEn == bTRUE) + tempreg |= BITM_WUPTMR_CON_EN; + /* We always allow Wupt to trigger sequencer */ + tempreg |= pWuptCfg->WuptEndSeq << BITP_WUPTMR_CON_ENDSEQ; + //tempreg |= 1L<<4; + AD5940_WriteReg(REG_WUPTMR_CON, tempreg); +} + +/** + * @brief Enable or disable wakeup timer + * @param Enable : {bTRUE, bFALSE} + * - bTRUE: enable wakeup timer + * - bFALSE: Disable wakeup timer + * @return return none. +*/ +void AD5940_WUPTCtrl(BoolFlag Enable) +{ + uint16_t tempreg; + tempreg = AD5940_ReadReg(REG_WUPTMR_CON); + tempreg &= ~BITM_WUPTMR_CON_EN; + + if(Enable == bTRUE) + tempreg |= BITM_WUPTMR_CON_EN; + + AD5940_WriteReg(REG_WUPTMR_CON, tempreg); +} + +/** + * @brief Configure WakeupTimer. + * @param SeqId: Select from SEQID_0/1/2/3. The wakeup timer will load corresponding value from four sets of registers. + * @param SleepTime: After how much time, AFE will try to enter hibernate. We disabled this feature in AD59840_Initialize. After this timer expired, nothing will happen. + * @param WakeupTime: After how much time, AFE will wakeup and trigger corresponding sequencer. + * @note By SleepTime and WakeupTime, the sequencer is triggered periodically and period is (SleepTime+WakeupTime) + * @return return none. +*/ +AD5940Err AD5940_WUPTTime(uint32_t SeqId, uint32_t SleepTime, uint32_t WakeupTime) +{ + switch (SeqId) + { + case SEQID_0: + { + AD5940_WriteReg(REG_WUPTMR_SEQ0WUPL, (WakeupTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ0WUPH, (WakeupTime & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ0SLEEPL, (SleepTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ0SLEEPH, (SleepTime & 0xF0000)>>16); + break; + } + case SEQID_1: + { + AD5940_WriteReg(REG_WUPTMR_SEQ1WUPL, (WakeupTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ1WUPH, (WakeupTime & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ1SLEEPL, (SleepTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ1SLEEPH, (SleepTime & 0xF0000)>>16); + break; + } + case SEQID_2: + { + AD5940_WriteReg(REG_WUPTMR_SEQ2WUPL, (WakeupTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ2WUPH, (WakeupTime & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ2SLEEPL, (SleepTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ2SLEEPH, (SleepTime & 0xF0000)>>16); + break; + } + case SEQID_3: + { + AD5940_WriteReg(REG_WUPTMR_SEQ3WUPL, (WakeupTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ3WUPH, (WakeupTime & 0xF0000)>>16); + AD5940_WriteReg(REG_WUPTMR_SEQ3SLEEPL, (SleepTime & 0xFFFF)); + AD5940_WriteReg(REG_WUPTMR_SEQ3SLEEPH, (SleepTime & 0xF0000)>>16); + break; + } + default: + return AD5940ERR_PARA; + } + return AD5940ERR_OK; +} + +/** + * @} end-of Sequencer_FIFO_Functions + * @} end-of Sequencer_FIFO +*/ + +/** + * @defgroup MISC_Block + * @brief Other functions not included in above blocks. Clock, GPIO, INTC etc. + * @{ + * @defgroup MISC_Block_Functions + * @{ +*/ + +/** + * @brief Configure AD5940 clock + * @param pClkCfg: Pointer to configuration structure. + * @return return none. +*/ +void AD5940_CLKCfg(CLKCfg_Type *pClkCfg) +{ + uint32_t tempreg, reg_osccon; + + reg_osccon = AD5940_ReadReg(REG_ALLON_OSCCON); + /* Enable clocks */ + if(pClkCfg->HFXTALEn == bTRUE) + { + reg_osccon |= BITM_ALLON_OSCCON_HFXTALEN; + AD5940_WriteReg(REG_ALLON_OSCKEY,KEY_OSCCON); /* Write Key */ + AD5940_WriteReg(REG_ALLON_OSCCON, reg_osccon); /* Enable HFXTAL */ + while((AD5940_ReadReg(REG_ALLON_OSCCON)&BITM_ALLON_OSCCON_HFXTALOK) == 0); /* Wait for clock ready */ + } + + if(pClkCfg->HFOSCEn == bTRUE) + { + reg_osccon |= BITM_ALLON_OSCCON_HFOSCEN; + AD5940_WriteReg(REG_ALLON_OSCKEY,KEY_OSCCON); /* Write Key */ + AD5940_WriteReg(REG_ALLON_OSCCON, reg_osccon); /* Enable HFOSC */ + while((AD5940_ReadReg(REG_ALLON_OSCCON)&BITM_ALLON_OSCCON_HFOSCOK) == 0); /* Wait for clock ready */ + /* Configure HFOSC mode if it's enabled. */ + if(pClkCfg->HfOSC32MHzMode == bTRUE) + AD5940_HFOSC32MHzCtrl(bTRUE); + else + AD5940_HFOSC32MHzCtrl(bFALSE); + } + + if(pClkCfg->LFOSCEn == bTRUE) + { + reg_osccon |= BITM_ALLON_OSCCON_LFOSCEN; + AD5940_WriteReg(REG_ALLON_OSCKEY,KEY_OSCCON); /* Write Key */ + AD5940_WriteReg(REG_ALLON_OSCCON, reg_osccon); /* Enable LFOSC */ + while((AD5940_ReadReg(REG_ALLON_OSCCON)&BITM_ALLON_OSCCON_LFOSCOK) == 0); /* Wait for clock ready */ + } + + /* Switch clocks */ + /* step1. Set clock divider */ + tempreg = pClkCfg->SysClkDiv&0x3f; + tempreg |= (pClkCfg->SysClkDiv&0x3f) << BITP_AFECON_CLKCON0_SYSCLKDIV; + tempreg |= (pClkCfg->ADCClkDiv&0xf) << BITP_AFECON_CLKCON0_ADCCLKDIV; + AD5940_WriteReg(REG_AFECON_CLKCON0, tempreg); + AD5940_Delay10us(10); + /* Step2. set clock source */ + tempreg = pClkCfg->SysClkSrc; + tempreg |= pClkCfg->ADCCLkSrc << BITP_AFECON_CLKSEL_ADCCLKSEL; + AD5940_WriteReg(REG_AFECON_CLKSEL, tempreg); + + /* Disable clocks */ + if(pClkCfg->HFXTALEn == bFALSE) + reg_osccon &= ~BITM_ALLON_OSCCON_HFXTALEN; + if(pClkCfg->HFOSCEn == bFALSE) + reg_osccon &= ~BITM_ALLON_OSCCON_HFOSCEN; + if(pClkCfg->LFOSCEn == bFALSE) + reg_osccon &= ~BITM_ALLON_OSCCON_LFOSCEN; + AD5940_WriteReg(REG_ALLON_OSCKEY, KEY_OSCCON); /* Write Key */ + AD5940_WriteReg(REG_ALLON_OSCCON, reg_osccon); +} + +/** + * @brief Configure Internal HFOSC to output 32MHz or 16MHz. + * @param Mode32MHz : {bTRUE, bFALSE} + * - bTRUE: HFOSC 32MHz mode. + * - bFALSE: HFOSC 16MHz mode. + * @return return none. +*/ +void AD5940_HFOSC32MHzCtrl(BoolFlag Mode32MHz) +{ + uint32_t RdCLKEN1; + uint32_t RdHPOSCCON; + + uint32_t bit8,bit9; + + RdCLKEN1 = AD5940_ReadReg(REG_AFECON_CLKEN1); + bit8 = (RdCLKEN1>>9)&0x01; + bit9 = (RdCLKEN1>>8)&0x01; /* Fix bug in silicon, bit8 and bit9 is swapped when read back. */ + RdCLKEN1 = RdCLKEN1&0xff; + RdCLKEN1 |= (bit8<<8)|(bit9<<9); + AD5940_WriteReg(REG_AFECON_CLKEN1,RdCLKEN1|BITM_AFECON_CLKEN1_ACLKDIS); /* Disable ACLK during clock changing */ + + RdHPOSCCON = AD5940_ReadReg(REG_AFE_HPOSCCON); + if(Mode32MHz == bTRUE) + { + AD5940_WriteReg(REG_AFE_HPOSCCON,RdHPOSCCON&(~BITM_AFE_HPOSCCON_CLK32MHZEN)); /* Enable 32MHz output(bit definition-0: 32MHz, 1: 16MHz) */ + while((AD5940_ReadReg(REG_ALLON_OSCCON)&BITM_ALLON_OSCCON_HFOSCOK) == 0); /* Wait for clock ready */ + } + else + { + AD5940_WriteReg(REG_AFE_HPOSCCON,RdHPOSCCON|BITM_AFE_HPOSCCON_CLK32MHZEN); /* Enable 16MHz output(bit definition-0: 32MHz, 1: 16MHz) */ + while((AD5940_ReadReg(REG_ALLON_OSCCON)&BITM_ALLON_OSCCON_HFOSCOK) == 0); /* Wait for clock ready */ + } + + AD5940_WriteReg(REG_AFECON_CLKEN1,RdCLKEN1&(~BITM_AFECON_CLKEN1_ACLKDIS)); /* Enable ACLK */ +} +/** + * @brief Enable high power mode for high frequency EIS + * @param Mode32MHz : {bTRUE, bFALSE} + * - bTRUE: HFOSC 32MHz mode. + * - bFALSE: HFOSC 16MHz mode. + * @return return none. +*/ +void AD5940_HPModeEn(BoolFlag Enable) +{ + CLKCfg_Type clk_cfg; + uint32_t temp_reg = 0; + + /* Check what the system clock is */ + temp_reg = AD5940_ReadReg(REG_AFECON_CLKSEL); + clk_cfg.ADCCLkSrc = (temp_reg>>2)&0x3; + clk_cfg.SysClkSrc = temp_reg & 0x3; + if(Enable == bTRUE) + { + clk_cfg.SysClkDiv = SYSCLKDIV_2; + clk_cfg.HfOSC32MHzMode = bTRUE; + AD5940_AFEPwrBW(AFEPWR_HP, AFEBW_250KHZ); + } + else + { + clk_cfg.SysClkDiv = SYSCLKDIV_1; + clk_cfg.HfOSC32MHzMode = bFALSE; + AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_100KHZ); + } + clk_cfg.ADCClkDiv = ADCCLKDIV_1; + clk_cfg.HFOSCEn = (temp_reg & 0x3) == 0x1? bFALSE : bTRUE;; + clk_cfg.HFXTALEn = (temp_reg & 0x3) == 0x1? bTRUE : bFALSE; + clk_cfg.LFOSCEn = bTRUE; + AD5940_CLKCfg(&clk_cfg); +} + +/** + * @defgroup Interrupt_Controller_Functions + * @{ +*/ +/* AFE Interrupt Controller */ +/** + * @brief Enable or Disable selected interrupt source(s) + * @param AfeIntcSel : {AFEINTC_0, AFEINTC_1} + * - AFEINTC_0: Configure Interrupt Controller 0 + * - AFEINTC_1: Configure Interrupt Controller 1 + * @param AFEIntSrc: select from @ref AFEINTC_SRC_Const + * - AFEINTSRC_ADCRDY : Bit0 ADC Result Ready Status + * - AFEINTSRC_DFTRDY : Bit1 DFT Result Ready Status + * - AFEINTSRC_SUPPLYFILTRDY : Bit2 Low Pass Filter Result Status + * - AFEINTSRC_TEMPRDY : Bit3, Temp Sensor Result Ready + * - AFEINTSRC_ADCMINERR : Bit4, ADC Minimum Value + * - AFEINTSRC_ADCMAXERR : Bit5, ADC Maximum Value + * - AFEINTSRC_ADCDIFFERR : Bit6, ADC Delta Ready + * - AFEINTSRC_MEANRDY : Bit7, Mean Result Ready + * - AFEINTSRC_VARRDY : Bit8, Variance Result Ready + * - AFEINTSRC_DLYCMDDONE : Bit9, User controlled interrupt by writing AFEGENINTSTA. Provides an Early Indication for the End of the Test _Block. + * - AFEINTSRC_HWSETUPDONE : Bit10, User controlled interrupt by writing AFEGENINTSTA. Indicates the MMR Setup for the Measurement Step Finished + * - AFEINTSRC_BRKSEQ : Bit11, User controlled interrupt by writing AFEGENINTSTA. + * - AFEINTSRC_CUSTOMINS : Bit12, User controlled interrupt by writing AFEGENINTSTA. General Purpose Custom Interrupt. + * - AFEINTSRC_BOOTLDDONE : Bit13, OTP Boot Loading Done + * - AFEINTSRC_WAKEUP : Bit14, AFE Woken up + * - AFEINTSRC_ENDSEQ : Bit15, End of Sequence Interrupt. + * - AFEINTSRC_SEQTIMEOUT : Bit16, Sequencer Timeout Command Finished. + * - AFEINTSRC_SEQTIMEOUTERR : Bit17, Sequencer Timeout Command Error. + * - AFEINTSRC_CMDFIFOFULL : Bit18, Command FIFO Full Interrupt. + * - AFEINTSRC_CMDFIFOEMPTY : Bit19, Command FIFO Empty + * - AFEINTSRC_CMDFIFOTHRESH: Bit20, Command FIFO Threshold Interrupt. + * - AFEINTSRC_CMDFIFOOF : Bit21, Command FIFO Overflow Interrupt. + * - AFEINTSRC_CMDFIFOUF : Bit22, Command FIFO Underflow Interrupt. + * - AFEINTSRC_DATAFIFOFULL : Bit23, Data FIFO Full Interrupt. + * - AFEINTSRC_DATAFIFOEMPTY: Bit24, Data FIFO Empty + * - AFEINTSRC_DATAFIFOTHRESH: Bit25, Data FIFO Threshold Interrupt. + * - AFEINTSRC_DATAFIFOOF : Bit26, Data FIFO Overflow Interrupt. + * - AFEINTSRC_DATAFIFOUF : Bit27, Data FIFO Underflow Interrupt. + * - AFEINTSRC_WDTIRQ : Bit28, WDT Timeout Interrupt. + * - AFEINTSRC_CRC_OUTLIER : Bit29, CRC interrupt for M355, Outliers Int for AD5940 + * - AFEINTSRC_GPT0INT_SLPWUT: Bit30, General Purpose Timer0 IRQ for M355. Sleep or Wakeup Timer timeout for AD5940 + * - AFEINTSRC_GPT1INT_TRYBRK: Bit31, General Purpose Timer1 IRQ for M355. Tried to Break IRQ for AD5940 + * - AFE_INTC_ALLINT : All interrupts + * @param State : {bTRUE, bFALSE} + * - bTRUE: Enable these interrupt source(s) + * - bFALSE: Disable interrupt source(s) + * @return return none. +*/ +void AD5940_INTCCfg(uint32_t AfeIntcSel, uint32_t AFEIntSrc, BoolFlag State) +{ + uint32_t tempreg; + uint32_t regaddr = REG_INTC_INTCSEL0; + + if(AfeIntcSel == AFEINTC_1) + regaddr = REG_INTC_INTCSEL1; + + tempreg = AD5940_ReadReg(regaddr); + if(State == bTRUE) + tempreg |= AFEIntSrc; /* Enable this interrupt */ + else + tempreg &= ~(AFEIntSrc); /* Disable this interrupt */ + AD5940_WriteReg(regaddr,tempreg); +} + +/** + * @brief Check if current interrupt configuration. + * @param AfeIntcSel : {AFEINTC_0, AFEINTC_1} + * - AFEINTC_0: Configure Interrupt Controller 0 + * - AFEINTC_1: Configure Interrupt Controller 1 +*/ +uint32_t AD5940_INTCGetCfg(uint32_t AfeIntcSel) +{ + uint32_t tempreg; + if(AfeIntcSel == AFEINTC_0) + tempreg = AD5940_ReadReg(REG_INTC_INTCSEL0); + else + tempreg = AD5940_ReadReg(REG_INTC_INTCSEL1); + return tempreg; +} + +/** + * @brief Clear selected interrupt(s) flag(INTC0Flag and INTC1Flag are both cleared). + * @param AfeIntSrcSel: Select from @ref AFEINTC_SRC_Const + * @return return none. +**/ +void AD5940_INTCClrFlag(uint32_t AfeIntSrcSel) +{ + AD5940_WriteReg(REG_INTC_INTCCLR,AfeIntSrcSel); +} + +/** + * @brief Test if selected interrupt source(s) is(are) bTRUE. + * @param AfeIntcSel : {AFEINTC_0, AFEINTC_1} + * - AFEINTC_0: Read Interrupt Controller 0 flag + * - AFEINTC_1: Read Interrupt Controller 1 flag + * @param AfeIntSrcSel: Select from @ref AFEINTC_SRC_Const + * @return If selected interrupt source(s) are all cleared, return bFALSE. Otherwise return bTRUE. +**/ +BoolFlag AD5940_INTCTestFlag(uint32_t AfeIntcSel, uint32_t AfeIntSrcSel) +{ + uint32_t tempreg; + uint32_t regaddr = (AfeIntcSel == AFEINTC_0)? REG_INTC_INTCFLAG0: REG_INTC_INTCFLAG1; + + tempreg = AD5940_ReadReg(regaddr); + if(tempreg & AfeIntSrcSel) + return bTRUE; + else + return bFALSE; +} + +/** + * @brief return register value of REG_INTC_INTCFLAGx + * @param AfeIntcSel : {AFEINTC_0, AFEINTC_1} + * - AFEINTC_0: Read Interrupt Controller 0 flag + * - AFEINTC_1: Read Interrupt Controller 1 flag + * @return register value of REG_INTC_INTCFLAGx. +**/ +uint32_t AD5940_INTCGetFlag(uint32_t AfeIntcSel) +{ + uint32_t tempreg; + uint32_t regaddr = (AfeIntcSel == AFEINTC_0)? REG_INTC_INTCFLAG0: REG_INTC_INTCFLAG1; + + tempreg = AD5940_ReadReg(regaddr); + return tempreg; +} + +/** + * @} Interrupt_Controller_Functions +*/ + +/** + * @defgroup GPIO_Block_Functions + * @{ +*/ + +/** + * @brief Initialize AFE GPIO + * @param pAgpioCfg: Pointer to configuration structure + * @return return none. +*/ +void AD5940_AGPIOCfg(AGPIOCfg_Type *pAgpioCfg) +{ + AD5940_AGPIOFuncCfg(pAgpioCfg->FuncSet); + AD5940_AGPIOOen(pAgpioCfg->OutputEnSet); + AD5940_AGPIOIen(pAgpioCfg->InputEnSet); + AD5940_AGPIOPen(pAgpioCfg->PullEnSet); + AD5940_WriteReg(REG_AGPIO_GP0OUT, pAgpioCfg->OutVal); +} + +/** + * @brief Configure the function of GP0 to GP7. + * @param uiCfgSet :{GP0_INT,GP0_TRIG,GP0_SYNC,GP0_GPIO| + * GP1_GPIO,GP1_TRIG,GP1_SYNC,GP1_SLEEP| + * GP2_PORB,GP2_TRIG,GP2_SYNC,GP2_EXTCLK| + * GP3_GPIO,GP3_TRIG,GP3_SYNC,GP3_INT0|\ + * GP4_GPIO,GP4_TRIG,GP4_SYNC,GP4_INT1| + * GP5_GPIO,GP5_TRIG,GP5_SYNC,GP5_EXTCLK| + * GP6_GPIO,GP6_TRIG,GP6_SYNC,GP6_INT0| + * GP7_GPIO,GP7_TRIG,GP7_SYNC,GP7_INT} + * @return return none. +**/ +void AD5940_AGPIOFuncCfg(uint32_t uiCfgSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0CON,uiCfgSet); +} + +/** + * @brief Enable GPIO output mode on selected pins. Disable output on non-selected pins. + * @param uiPinSet :Select from {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOOen(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0OEN,uiPinSet); +} + +/** + * @brief Enable input on selected pins while disable others. + * @param uiPinSet: Select from {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOIen(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0IEN,uiPinSet); +} + +/** + * @brief Read the GPIO status. + * @return return GP0IN register which is the GPIO status. +**/ +uint32_t AD5940_AGPIOIn(void) +{ + return AD5940_ReadReg(REG_AGPIO_GP0IN); +} + +/** + * @brief Enable pull-up or down on selected pins while disable other pins. + * @param uiPinSet: Select from: {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOPen(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0PE,uiPinSet); +} + +/** + * @brief Put selected GPIOs to high level. + * @param uiPinSet: Select from: {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOSet(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0SET,uiPinSet); +} + +/** + * @brief Put selected GPIOs to low level. + * @param uiPinSet: Select from: {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOClr(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0CLR,uiPinSet); +} + +/** + * @brief Toggle selected GPIOs. + * @param uiPinSet: Select from: {AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin3|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6|AGPIO_Pin7} + * @return return none +**/ +void AD5940_AGPIOToggle(uint32_t uiPinSet) +{ + AD5940_WriteReg(REG_AGPIO_GP0TGL,uiPinSet); +} + +/** + * @} GPIO_Block_Functions +*/ + +/** + * @defgroup LPMode_Block_Functions + * @{ +*/ +/** + * @brief Enter or leave LPMODE. + * @details Once enter this mode, some registers are collected together to a new register so we can + * Control most blocks with in one register. The so called LPMODE has nothing to do with AD5940 power. + * @return return AD5940ERR_OK +**/ +AD5940Err AD5940_LPModeEnS(BoolFlag LPModeEn) +{ + if(LPModeEn == bTRUE) + AD5940_WriteReg(REG_AFE_LPMODEKEY, KEY_LPMODEKEY); /* Enter LP mode by right key. */ + else + AD5940_WriteReg(REG_AFE_LPMODEKEY, 0); /* Write wrong key to exit LP mode */ + return AD5940ERR_OK; +} + +/** + * @brief Select system clock source for LPMODE. + * @note Only in LP Mode, this operation takes effect. Enter LPMODE by function @ref AD5940_LPModeEnS. + * @param LPModeClk: Select from @ref LPMODECLK_Const + * - LPMODECLK_LFOSC: Select LFOSC 32kHz for system clock + * - LPMODECLK_HFOSC: Select HFOSC 16MHz/32MHz for system clock + * @return none. +*/ +void AD5940_LPModeClkS(uint32_t LPModeClk) +{ + AD5940_WriteReg(REG_AFE_LPMODECLKSEL, LPModeClk); +} + +/** + * @} LPMode_Block_Functions +*/ + +/** + * @brief Enter sleep mode key to unlock it or enter incorrect key to lock it. \ + * Once key is unlocked, it will always be effect until manually lock it + * @param SlpKey : {SLPKEY_UNLOCK, SLPKEY_LOCK} + - SLPKEY_UNLOCK Unlock Key so we can enter sleep(or called hibernate) mode. + - SLPKEY_LOCK Lock key so AD5940 is prohibited to enter sleep mode. + @return return none. +*/ +void AD5940_SleepKeyCtrlS(uint32_t SlpKey) +{ + AD5940_WriteReg(REG_AFE_SEQSLPLOCK, SlpKey); +} + +/** + * @brief Put AFE to hibernate. + * @details This will only take effect when SLP_KEY has been unlocked. Use function @ref AD5940_SleepKeyCtrlS to enter correct key. + * @return return none. +*/ +void AD5940_EnterSleepS(void) +{ + AD5940_WriteReg(REG_AFE_SEQTRGSLP, 0); + AD5940_WriteReg(REG_AFE_SEQTRGSLP, 1); +} + +/** + * @brief Turn off LP-Loop and put AFE to hibernate mode; + * @details By function @ref AD5940_EnterSleepS, we can put most blocks to hibernate mode except LP block. + * This function will shut down LP block and then enter sleep mode. + * @return return none. +*/ +void AD5940_ShutDownS(void) +{ + /* Turn off LPloop related blocks which are not controlled automatically by hibernate operation */ + AFERefCfg_Type aferef_cfg; + LPLoopCfg_Type lp_loop; + /* Turn off LP-loop manually because it's not affected by sleep/hibernate mode */ + AD5940_StructInit(&aferef_cfg, sizeof(aferef_cfg)); + AD5940_StructInit(&lp_loop, sizeof(lp_loop)); + AD5940_REFCfgS(&aferef_cfg); + AD5940_LPLoopCfgS(&lp_loop); + AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Unlock the key */ + AD5940_EnterSleepS(); /* Enter Hibernate */ +} + +/** + * @brief Try to wakeup AD5940 by read register. + * @details Any SPI operation can wakeup AD5940. AD5940_Initialize must be called to enable this function. + * @param TryCount Specify how many times we will read register. Zero or negative number means always waiting here. + * @return How many times register is read. If returned value is bigger than TryCount, it means wakeup failed. +*/ +uint32_t AD5940_WakeUp(int32_t TryCount) +{ + uint32_t count = 0; + while(1) + { + count++; + if(AD5940_ReadReg(REG_AFECON_ADIID) == AD5940_ADIID) + break; /* Succeed */ + if(TryCount<=0) + continue; /* Always try to wakeup AFE */ + + if(count > TryCount) + break; /* Failed */ + } + return count; +} + +/** + * @brief Read ADIID register, the value for current version is @ref AD5940_ADIID + * @return return none. +*/ +uint32_t AD5940_GetADIID(void) +{ + return AD5940_ReadReg(REG_AFECON_ADIID); +} + +/** + * @brief Read CHIPID register, the value for current version is 0x5501. + * @return return none. +*/ +uint32_t AD5940_GetChipID(void) +{ + return AD5940_ReadReg(REG_AFECON_CHIPID); +} +/** + * @brief Reset AD5940 by register. + * @note AD5940 must be in active state so we can access registers. + * If AD5940 system clock is too low, we consider to use hardware reset, or + * we need to make sure register write is successfully. + * @return return none. +*/ +AD5940Err AD5940_SoftRst(void) +{ + AD5940_WriteReg(REG_AFECON_SWRSTCON, AD5940_SWRST); + AD5940_Delay10us(20); /* AD5940 need some time to exit reset status. 200us looks good. */ + /* We can check RSTSTA register to make sure software reset happened. */ + return AD5940ERR_OK; +} + +/** + * @brief Reset AD5940 with RESET pin. + * @note This will call function AD5940_RstClr which locates in file XXXPort.C + * @return return none. +*/ +void AD5940_HWReset(void) +{ +#ifndef CHIPSEL_M355 + AD5940_RstClr(); + AD5940_Delay10us(200); /* Delay some time */ + AD5940_RstSet(); + AD5940_Delay10us(500); /* AD5940 need some time to exit reset status. 200us looks good. */ +#else + //There is no method to reset AFE only for M355. +#endif +} + +/** + * @} MISC_Block_Functions + * @} MISC_Block +*/ + +/** + * @defgroup Calibration_Block + * @brief The non-factory calibration routines. + * @{ + * @defgroup Calibration_Functions + * @{ + * + * + */ +/** + * @brief Turn on High power 1.8V/1.1V reference and 2.5V LP reference. + * @return return none. +*/ +static void __AD5940_ReferenceON(void) +{ + AFERefCfg_Type ref_cfg; + /* Turn ON ADC/DAC and LPDAC reference */ + ref_cfg.Hp1V1BuffEn = bTRUE; + ref_cfg.Hp1V8BuffEn = bTRUE; + ref_cfg.HpBandgapEn = bTRUE; + ref_cfg.HSDACRefEn = bTRUE; + ref_cfg.LpBandgapEn = bTRUE; + ref_cfg.LpRefBufEn = bTRUE; + + ref_cfg.Disc1V1Cap = bFALSE; + ref_cfg.Disc1V8Cap = bFALSE; + ref_cfg.Hp1V8Ilimit = bFALSE; + ref_cfg.Hp1V8ThemBuff = bFALSE; + ref_cfg.Lp1V1BuffEn = bFALSE; + ref_cfg.Lp1V8BuffEn = bFALSE; + ref_cfg.LpRefBoostEn = bFALSE; + AD5940_REFCfgS(&ref_cfg); +} + +/** + * @brief Turn on ADC to sample one SINC2 data. + * @return return ADCCode. +*/ +static uint32_t __AD5940_TakeMeasurement(int32_t *time_out) +{ + uint32_t ADCCode = 0; + AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_SINC2NOTCH, bTRUE);/* Start conversion */ + do + { + AD5940_Delay10us(1); /* Delay 10us */ + if(AD5940_INTCTestFlag(AFEINTC_1,AFEINTSRC_SINC2RDY)) + { + ADCCode = AD5940_ReadAfeResult(AFERESULT_SINC2); + break; + } + if(*time_out != -1) + (*time_out)--; + }while(*time_out != 0); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_SINC2NOTCH, bFALSE);/* Stop conversion */ + return ADCCode; +} + +/** + @brief Calibrate ADC PGA + @param pADCPGACal: PGA calibration parameters include filter setup and PGA gain. + @return AD5940ERR_OK. +**/ +AD5940Err AD5940_ADCPGACal(ADCPGACal_Type *pADCPGACal) +{ + const float kFactor = 1.835f/1.82f; + ADCBaseCfg_Type adc_base; + + int32_t time_out; + uint32_t INTCCfg; + int32_t ADCCode; + BoolFlag bADCClk32MHzMode; + + uint32_t regaddr_gain, regaddr_offset; + + if(pADCPGACal == NULL) return AD5940ERR_NULLP; + if(pADCPGACal->ADCPga > ADCPGA_9) return AD5940ERR_PARA; /* Parameter Error */ + + if(pADCPGACal->AdcClkFreq > (32000000*0.8)) + bADCClk32MHzMode = bTRUE; + + /** + * Determine Gain calibration method according to different gain value... + * and calibration register + * */ + static const struct _cal_registers + { + uint16_t gain_reg; + uint16_t offset_reg; + }cal_registers[] = { + {REG_AFE_ADCGAINGN1,REG_AFE_ADCOFFSETGN1}, + {REG_AFE_ADCGAINGN1P5,REG_AFE_ADCOFFSETGN1P5}, + {REG_AFE_ADCGAINGN2,REG_AFE_ADCOFFSETGN2}, + {REG_AFE_ADCGAINGN4,REG_AFE_ADCOFFSETGN4}, + {REG_AFE_ADCGAINGN9,REG_AFE_ADCOFFSETGN9}, + }; + regaddr_gain = cal_registers[pADCPGACal->ADCPga].gain_reg; + regaddr_offset = cal_registers[pADCPGACal->ADCPga].offset_reg; + + /* Do initialization */ + __AD5940_ReferenceON(); + ADCFilterCfg_Type adc_filter; + /* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH. Use SIN2 data for calibration-->Lower noise */ + adc_filter.ADCSinc3Osr = pADCPGACal->ADCSinc3Osr; + adc_filter.ADCSinc2Osr = pADCPGACal->ADCSinc2Osr; /* 800KSPS/4/1333 = 150SPS */ + adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */ + adc_filter.ADCRate = bADCClk32MHzMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */ + adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */ + adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */ + adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */ + AD5940_ADCFilterCfgS(&adc_filter); + /* Turn ON reference and ADC power, and DAC reference. We use DAC 1.8V reference to calibrate ADC because of the ADC reference bug. */ + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable all */ + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_HPREFPWR|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|AFECTRL_SINC2NOTCH, bTRUE); + AD5940_Delay10us(25); /* Wait 250us for reference power up */ + /* INTC configure and open calibration lock */ + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, KEY_CALDATLOCK); /* Unlock KEY */ + + /* Do offset calibration. */ + if(pADCPGACal->PGACalType != PGACALTYPE_GAIN){ /* Need offset calibration */ + int32_t ExpectedCode = 0x8000; /* Ideal ADC output */ + AD5940_WriteReg(regaddr_offset, 0); /* Reset offset register */ + + adc_base.ADCMuxP = ADCMUXP_VSET1P1; + adc_base.ADCMuxN = ADCMUXN_VSET1P1; /* Short input with common voltage set to 1.11v */ + adc_base.ADCPga = pADCPGACal->ADCPga; /* Set correct Gain value. */ + AD5940_ADCBaseCfgS(&adc_base); + AD5940_Delay10us(5); /* Wait for sometime */ + ADCCode = 0; + for(int i=0; i<8; i++) + { /* ADC offset calibration register has resolution of 0.25LSB. take full use of it. */ + time_out = pADCPGACal->TimeOut10us; /* Reset time out counter */ + ADCCode += __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) goto ADCPGACALERROR_TIMEOUT; /* Time out error. */ + } + /* Calculate and write the result to registers before gain calibration */ + ADCCode = (ExpectedCode<<3) - ADCCode; /* We will shift back 1bit below */ + /** + * AD5940 use formular Output = gain*(input + offset) for calibration. + * So, the measured results should be divided by gain to get value for offset register. + */ + uint32_t gain = AD5940_ReadReg(regaddr_gain); + ADCCode = (ADCCode*0x4000)/gain; + ADCCode = ((ADCCode+1)>>1)&0x7fff; /* Round 0.5 */ + AD5940_WriteReg(regaddr_offset, ADCCode); + } + + /* Do gain calibration */ + if(pADCPGACal->PGACalType != PGACALTYPE_OFFSET) /* Need gain calibration */ + { + int32_t ExpectedGainCode; + static const float ideal_pga_gain[]={1,1.5,2,4,9}; + AD5940_WriteReg(regaddr_gain, 0x4000); /* Reset gain register */ + if(pADCPGACal->ADCPga <= ADCPGA_2) + { + //gain1,1.5,2 could use reference directly + adc_base.ADCMuxP = ADCMUXP_VREF1P8DAC; + adc_base.ADCMuxN = ADCMUXN_VSET1P1; + ExpectedGainCode = (int32_t)((pADCPGACal->VRef1p82 - pADCPGACal->VRef1p11)*ideal_pga_gain[pADCPGACal->ADCPga]/\ + pADCPGACal->VRef1p82*32768/kFactor)\ + + 0x8000; + } + else + { + //gain4,9 use DAC generated voltage + adc_base.ADCMuxP = ADCMUXP_P_NODE; + adc_base.ADCMuxN = ADCMUXN_N_NODE; + /* Setup HSLOOP to generate voltage for GAIN4/9 calibration. */ + AD5940_AFECtrlS(AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR|AFECTRL_HSTIAPWR|AFECTRL_WG, bTRUE); + HSLoopCfg_Type hsloop_cfg; + hsloop_cfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2; + hsloop_cfg.HsDacCfg.HsDacGain = HSDACGAIN_1; + hsloop_cfg.HsDacCfg.HsDacUpdateRate = 7; + hsloop_cfg.HsTiaCfg.DiodeClose = bFALSE; + hsloop_cfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1; + hsloop_cfg.HsTiaCfg.HstiaCtia = 31; + hsloop_cfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDe1Rload = HSTIADERLOAD_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDe1Rtia = HSTIADERTIA_OPEN; + hsloop_cfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_200; + hsloop_cfg.SWMatCfg.Dswitch = SWD_OPEN; + hsloop_cfg.SWMatCfg.Pswitch = SWP_PL; + hsloop_cfg.SWMatCfg.Nswitch = SWN_NL; + hsloop_cfg.SWMatCfg.Tswitch = SWT_TRTIA; + hsloop_cfg.WgCfg.GainCalEn = bTRUE; + hsloop_cfg.WgCfg.OffsetCalEn = bTRUE; + hsloop_cfg.WgCfg.WgType = WGTYPE_MMR; + uint32_t HSDACCode; + if(pADCPGACal->ADCPga == ADCPGA_4) + HSDACCode = 0x800 + 0x300; /* 0x300--> 0x300/0x1000*0.8*BUFFERGAIN2 = 0.3V. */ + else if(pADCPGACal->ADCPga == ADCPGA_9) + HSDACCode = 0x800 + 0x155; /* 0x155--> 0x155/0x1000*0.8*BUFFERGAIN2 = 0.133V. */ + hsloop_cfg.WgCfg.WgCode = HSDACCode; + AD5940_HSLoopCfgS(&hsloop_cfg); + + //measure expected code + adc_base.ADCPga = ADCPGA_1P5; + AD5940_ADCBaseCfgS(&adc_base); + AD5940_Delay10us(5); + time_out = pADCPGACal->TimeOut10us; /* Reset time out counter */ + ExpectedGainCode = 0x8000 + (int32_t)((__AD5940_TakeMeasurement(&time_out) - 0x8000)/1.5f\ + *ideal_pga_gain[pADCPGACal->ADCPga]); + if(time_out == 0) goto ADCPGACALERROR_TIMEOUT; + } + adc_base.ADCPga = pADCPGACal->ADCPga; /* Set to gain under calibration */ + AD5940_ADCBaseCfgS(&adc_base); + AD5940_Delay10us(5); + time_out = pADCPGACal->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); + if(time_out == 0) goto ADCPGACALERROR_TIMEOUT; + /* Calculate and write the result to registers */ + ADCCode = (ExpectedGainCode - 0x8000)*0x4000/(ADCCode-0x8000); + ADCCode &= 0x7fff; + AD5940_WriteReg(regaddr_gain, ADCCode); + } + + /* Restore INTC1 SINC2 configure */ + if(INTCCfg&AFEINTSRC_SINC2RDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bFALSE); /* Disable SINC2 Interrupt */ + + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + /* Done */ + return AD5940ERR_OK; + +ADCPGACALERROR_TIMEOUT: + AD5940_ADCConvtCtrlS(bFALSE); /* Stop conversion */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + return AD5940ERR_TIMEOUT; +} + +/** + * @brief Calibrate LPTIA offset + * @param pLPTIAOffsetCal Pointer to LPTIA offset calibration settings. + * @return AD5940ERR_OK. +**/ +AD5940Err AD5940_LPTIAOffsetCal(LPTIAOffsetCal_Type *pLPTIAOffsetCal) +{ + AD5940Err error = AD5940ERR_OK; + LPLoopCfg_Type lploop_cfg; + ADCBaseCfg_Type adc_base; + ADCFilterCfg_Type adc_filter; + + int32_t time_out; + uint32_t INTCCfg; + int32_t ADCCode; + BoolFlag bADCClk32MHzMode; + + if(pLPTIAOffsetCal == NULL) return AD5940ERR_NULLP; + if(pLPTIAOffsetCal->AdcClkFreq > (32000000*0.8)) + bADCClk32MHzMode = bTRUE; + + /* Step0: Do initialization */ + /* Turn on AD5940 references in case it's disabled. */ + __AD5940_ReferenceON(); + lploop_cfg.LpAmpCfg.LpAmpSel = pLPTIAOffsetCal->LpAmpSel; + lploop_cfg.LpAmpCfg.LpAmpPwrMod = pLPTIAOffsetCal->LpAmpPwrMod; /* Power mode will affect amp offset. */ + lploop_cfg.LpAmpCfg.LpPaPwrEn = bTRUE; + lploop_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE; + lploop_cfg.LpAmpCfg.LpTiaRf = LPTIARF_OPEN; + lploop_cfg.LpAmpCfg.LpTiaRload = LPTIARLOAD_100R; + lploop_cfg.LpAmpCfg.LpTiaRtia = pLPTIAOffsetCal->LpTiaRtia; + lploop_cfg.LpAmpCfg.LpTiaSW = pLPTIAOffsetCal->LpTiaSW; /* Disconnect capacitors so it settles quickly */ + lploop_cfg.LpDacCfg.LpdacSel = (pLPTIAOffsetCal->LpAmpSel == LPAMP0)?LPDAC0:LPDAC1; + lploop_cfg.LpDacCfg.DacData12Bit = pLPTIAOffsetCal->DacData12Bit; + lploop_cfg.LpDacCfg.DacData6Bit = pLPTIAOffsetCal->DacData6Bit; + lploop_cfg.LpDacCfg.DataRst = bFALSE; + lploop_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5; + lploop_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR; + lploop_cfg.LpDacCfg.LpDacVzeroMux = pLPTIAOffsetCal->LpDacVzeroMux; + lploop_cfg.LpDacCfg.LpDacSW = LPDACSW_VZERO2LPTIA; + lploop_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; + lploop_cfg.LpDacCfg.PowerEn = bTRUE; + AD5940_LPLoopCfgS(&lploop_cfg); + + /* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH. Use SIN2 data for calibration-->Lower noise */ + adc_filter.ADCSinc3Osr = pLPTIAOffsetCal->ADCSinc3Osr; + adc_filter.ADCSinc2Osr = pLPTIAOffsetCal->ADCSinc2Osr; /* 800KSPS/4/1333 = 150SPS */ + adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */ + adc_filter.ADCRate = bADCClk32MHzMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */ + adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */ + adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */ + adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */ + AD5940_ADCFilterCfgS(&adc_filter); + /* Initialize ADC MUx and PGA */ + if(pLPTIAOffsetCal->LpAmpSel == LPAMP0) + { + adc_base.ADCMuxP = ADCMUXP_LPTIA0_P; + adc_base.ADCMuxN = ADCMUXN_LPTIA0_N; + } + else + { + adc_base.ADCMuxP = ADCMUXP_LPTIA1_P; + adc_base.ADCMuxN = ADCMUXN_LPTIA1_N; + } + adc_base.ADCPga = pLPTIAOffsetCal->ADCPga; /* Set correct Gain value. */ + AD5940_ADCBaseCfgS(&adc_base); + /* Turn ON ADC and its reference. And SINC2. */ + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable all firstly, we only enable things we use */ + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_HPREFPWR|AFECTRL_SINC2NOTCH, bTRUE); + AD5940_Delay10us(25); /* Wait 250us for reference power up */ + /* INTC configure and open calibration lock */ + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, KEY_CALDATLOCK); /* Unlock KEY */ + + /* Do offset calibration. */ + { + int32_t ExpectedCode = 0x8000; /* Ideal ADC output */ + AD5940_WriteReg(REG_AFE_ADCOFFSETLPTIA0, 0); /* Reset offset register */ + + if(pLPTIAOffsetCal->SettleTime10us > 0) + AD5940_Delay10us(pLPTIAOffsetCal->SettleTime10us); /* Delay 10us */ + time_out = pLPTIAOffsetCal->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPTIAOFFSETCALERROR; + } /* Time out error. */ + /* Calculate and write the result to registers before gain calibration */ + ADCCode = ((ExpectedCode - ADCCode)<<3); /* We will shift back 1bit below */ + ADCCode = ((ADCCode+1)>>1); /* Round 0.5 */ + if((ADCCode > 0x3fff) || + (ADCCode < -0x4000)) /* The register used for offset calibration is limited to -0x4000 to 0x3fff */ + { + error = AD5940ERR_CALOR; + goto LPTIAOFFSETCALERROR; + } + ADCCode &= 0x7fff; + if(pLPTIAOffsetCal->LpAmpSel == LPAMP0) + AD5940_WriteReg(REG_AFE_ADCOFFSETLPTIA0, ADCCode); + else + AD5940_WriteReg(REG_AFE_ADCOFFSETLPTIA1, ADCCode); + } + /* Restore INTC1 SINC2 configure */ + if(INTCCfg&AFEINTSRC_SINC2RDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bFALSE); /* Disable SINC2 Interrupt */ + + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + /* Done */ + return AD5940ERR_OK; + +LPTIAOFFSETCALERROR: + AD5940_ADCConvtCtrlS(bFALSE); /* Stop conversion */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + return error; +} + +/** + * @brief Calibrate HSTIA offset-ongoing. + * @param pHSTIAOffsetCal: pointer to configuration. + * @return AD5940ERR_OK. +**/ +AD5940Err AD5940_HSTIAOffsetCal(LPTIAOffsetCal_Type *pHSTIAOffsetCal) +{ + return AD5940ERR_OK; +} + +/** + * @brief Measure HSTIA internal RTIA impedance. + * @param pCalCfg: pointer to calibration structure. + * @param pResult: Pointer to a variable that used to store result. + * If bPolarResult in structure is set, then use type fImpPol_Type otherwise use fImpCar_Type. + * @return AD5940ERR_OK if succeed. +**/ +AD5940Err AD5940_HSRtiaCal(HSRTIACal_Type *pCalCfg, void *pResult) +{ + /***** CALIBRATION METHOD ****** + 1) Measure the complex voltage V_Rcal across the calibration DUT (Rcal). + 2) Measure the complex voltage V_Rtia across Rtia [HSTIA_P (output) - HSTIA_N]. + 3) Note Rtia carries the same current as Rcal; I_Rtia = I_exc = I_Rcal + 4) Implement the equation: Rtia = V_Rtia / I_Rtia + --> Rtia = (V_Rtia / V_Rcal) * Rcal + *******************************/ + + AFERefCfg_Type aferef_cfg; + HSLoopCfg_Type hs_loop; + DSPCfg_Type dsp_cfg; + uint32_t INTCCfg; + + BoolFlag bADCClk32MHzMode = bFALSE; + uint32_t ExcitBuffGain = EXCITBUFGAIN_2; + uint32_t HsDacGain = HSDACGAIN_1; + + float ExcitVolt; /* Excitation voltage, unit is mV */ + uint32_t RtiaVal; + uint32_t const HpRtiaTable[]={200,1000,5000,10000,20000,40000,80000,160000,0}; + uint32_t const HSTIADERLOADTable[]={0,10,30,50,100,999999999999}; + uint32_t const HSTIADERTIATable[] = {50,100,200,1000,5000,10000,20000,40000,80000,160000,0,999999999999999}; + uint32_t WgAmpWord; + + iImpCar_Type DftRcalVolt, DftRtiaVolt; + + if(pCalCfg == NULL) return AD5940ERR_NULLP; + if(pCalCfg->fRcal == 0) + return AD5940ERR_PARA; + //if(pCalCfg->HsTiaCfg.HstiaRtiaSel > HSTIARTIA_160K) + // return AD5940ERR_PARA; + //if(pCalCfg->HsTiaCfg.HstiaRtiaSel == HSTIARTIA_OPEN) + // return AD5940ERR_PARA; /* Do not support calibrating DE0-RTIA */ + if(pResult == NULL) + return AD5940ERR_NULLP; + + if(pCalCfg->AdcClkFreq > (32000000*0.8)) + bADCClk32MHzMode = bTRUE; + + /* Calculate the excitation voltage we should use based on RCAL/Rtia */ + if(pCalCfg->HsTiaCfg.HstiaRtiaSel == HSTIARTIA_OPEN) + { + if(pCalCfg->HsTiaCfg.HstiaDeRtia == HSTIADERTIA_TODE) + { + RtiaVal = pCalCfg->HsTiaCfg.ExtRtia; + } + else + { + RtiaVal = pCalCfg->HsTiaCfg.ExtRtia + HSTIADERLOADTable[pCalCfg->HsTiaCfg.HstiaDeRload] + HSTIADERTIATable[pCalCfg->HsTiaCfg.HstiaDeRtia]; + } + } + else + RtiaVal = HpRtiaTable[pCalCfg->HsTiaCfg.HstiaRtiaSel]; + /* + DAC output voltage calculation + Note: RCAL value should be similar to RTIA so the accuracy is best. + HSTIA output voltage should be limited to 0.2V to AVDD-0.2V, with 1.1V bias. We use 80% of this range for safe. + Because the bias voltage is fixed to 1.1V, so for AC signal maximum amplitude is 1.1V-0.2V = 0.9Vp. That's 1.8Vpp. + Formula is: ExcitVolt(in mVpp) = (1800mVpp*80% / RTIA) * RCAL + ADC input range is +-1.5V which is enough for calibration. + + */ + ExcitVolt = 1800*0.8*pCalCfg->fRcal/RtiaVal; + + if(ExcitVolt <= 800*0.05) /* Voltage is so small that we can enable the attenuator of DAC(1/5) and Excitation buffer(1/4). 800mVpp is the DAC output voltage */ + { + ExcitBuffGain = EXCITBUFGAIN_0P25; + HsDacGain = HSDACGAIN_0P2; + /* Excitation buffer voltage full range is 800mVpp*0.05 = 40mVpp */ + WgAmpWord = ((uint32_t)(ExcitVolt/40*2047*2)+1)>>1; /* Assign value with rounding (0.5 LSB error) */ + } + else if(ExcitVolt <= 800*0.25) /* Enable Excitation buffer attenuator */ + { + ExcitBuffGain = EXCITBUFGAIN_0P25; + HsDacGain = HSDACGAIN_1; + /* Excitation buffer voltage full range is 800mVpp*0.25 = 200mVpp */ + WgAmpWord = ((uint32_t)(ExcitVolt/200*2047*2)+1)>>1; /* Assign value with rounding (0.5 LSB error) */ + } + else if(ExcitVolt <= 800*0.4) /* Enable DAC attenuator */ + { + ExcitBuffGain = EXCITBUFGAIN_2; + HsDacGain = HSDACGAIN_0P2; + /* Excitation buffer voltage full range is 800mVpp*0.4 = 320mV */ + WgAmpWord = ((uint32_t)(ExcitVolt/320*2047*2)+1)>>1; /* Assign value with rounding (0.5 LSB error) */ + } + else /* No attenuator is needed. This is the best condition which means RTIA is close to RCAL */ + { + ExcitBuffGain = EXCITBUFGAIN_2; + HsDacGain = HSDACGAIN_1; + /* Excitation buffer voltage full range is 800mVpp*2=1600mVpp */ + WgAmpWord = ((uint32_t)(ExcitVolt/1600*2047*2)+1)>>1; /* Assign value with rounding (0.5 LSB error) */ + } + + if(WgAmpWord > 0x7ff) + WgAmpWord = 0x7ff; + + /*INTC configuration */ + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Init all to disable state */ + /* Configure reference system */ + aferef_cfg.HpBandgapEn = bTRUE; + aferef_cfg.Hp1V1BuffEn = bTRUE; + aferef_cfg.Hp1V8BuffEn = bTRUE; + aferef_cfg.Disc1V1Cap = bFALSE; + aferef_cfg.Disc1V8Cap = bFALSE; + aferef_cfg.Hp1V8ThemBuff = bFALSE; + aferef_cfg.Hp1V8Ilimit = bFALSE; + aferef_cfg.Lp1V1BuffEn = bFALSE; + aferef_cfg.Lp1V8BuffEn = bFALSE; + aferef_cfg.LpBandgapEn = bFALSE; + aferef_cfg.LpRefBufEn = bFALSE; + aferef_cfg.LpRefBoostEn = bFALSE; + AD5940_REFCfgS(&aferef_cfg); + /* Configure HP Loop */ + hs_loop.HsDacCfg.ExcitBufGain = ExcitBuffGain; + hs_loop.HsDacCfg.HsDacGain = HsDacGain; + hs_loop.HsDacCfg.HsDacUpdateRate = 7; /* Set it to highest update rate */ + memcpy(&hs_loop.HsTiaCfg, &pCalCfg->HsTiaCfg, sizeof(pCalCfg->HsTiaCfg)); + hs_loop.SWMatCfg.Dswitch = SWD_RCAL0; + hs_loop.SWMatCfg.Pswitch = SWP_RCAL0; + hs_loop.SWMatCfg.Nswitch = SWN_RCAL1; + hs_loop.SWMatCfg.Tswitch = SWT_RCAL1|SWT_TRTIA|SWT_AIN1; + hs_loop.WgCfg.WgType = WGTYPE_SIN; + hs_loop.WgCfg.GainCalEn = bTRUE; + hs_loop.WgCfg.OffsetCalEn = bTRUE; + hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(pCalCfg->fFreq, pCalCfg->SysClkFreq); + hs_loop.WgCfg.SinCfg.SinAmplitudeWord = WgAmpWord; + hs_loop.WgCfg.SinCfg.SinOffsetWord = 0; + hs_loop.WgCfg.SinCfg.SinPhaseWord = 0; + AD5940_HSLoopCfgS(&hs_loop); + /* Configure DSP */ + dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_N_NODE; + dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_P_NODE; + dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1P5; + AD5940_StructInit(&dsp_cfg.ADCDigCompCfg, sizeof(dsp_cfg.ADCDigCompCfg)); + dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */ + dsp_cfg.ADCFilterCfg.ADCRate = bADCClk32MHzMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; + dsp_cfg.ADCFilterCfg.ADCSinc2Osr = pCalCfg->ADCSinc2Osr; + dsp_cfg.ADCFilterCfg.ADCSinc3Osr = pCalCfg->ADCSinc3Osr; + dsp_cfg.ADCFilterCfg.BpNotch = bTRUE; + dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; + dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE; + + memcpy(&dsp_cfg.DftCfg, &pCalCfg->DftCfg, sizeof(pCalCfg->DftCfg)); + memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); + AD5940_DSPCfgS(&dsp_cfg); + + /* Enable all of them. They are automatically turned off during hibernate mode to save power */ + AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\ + /*AFECTRL_WG|*/AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\ + AFECTRL_SINC2NOTCH, bTRUE); + + /***** MEASURE VOLTAGE ACROSS RCAL *****/ + AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */ + //wait for sometime. + AD5940_Delay10us(25); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */ + /* Wait until DFT ready */ + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */ + AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); + + DftRcalVolt.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL); + DftRcalVolt.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE); + + /***** MEASURE VOLTAGE ACROSS RTIA *****/ + AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N); + AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */ + //wait for sometime. + AD5940_Delay10us(25); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */ + /* Wait until DFT ready */ + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */ + AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); + + DftRtiaVolt.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL); + DftRtiaVolt.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE); + + if(DftRcalVolt.Real&(1L<<17)) + DftRcalVolt.Real |= 0xfffc0000; + if(DftRcalVolt.Image&(1L<<17)) + DftRcalVolt.Image |= 0xfffc0000; + if(DftRtiaVolt.Real&(1L<<17)) + DftRtiaVolt.Real |= 0xfffc0000; + if(DftRtiaVolt.Image&(1L<<17)) + DftRtiaVolt.Image |= 0xfffc0000; + /* + ADC MUX is set to HSTIA_P and HSTIA_N. + While the current flow through RCAL and then into RTIA, the current direction should be from HSTIA_N to HSTIA_P if we + measure the voltage across RCAL by MUXSELP_P_NODE and MUXSELN_N_NODE. + So here, we add a negative sign to results + */ + DftRtiaVolt.Image = -DftRtiaVolt.Image; + DftRtiaVolt.Real = -DftRtiaVolt.Real; /* Current is measured by MUX HSTIA_P-HSTIA_N. It should be */ + /* + The impedance engine inside of AD594x give us Real part and Imaginary part of DFT. Due to technology used, the Imaginary + part in register is the opposite number. So we add a negative sign on the Imaginary part of results. + */ + DftRtiaVolt.Image = -DftRtiaVolt.Image; + DftRcalVolt.Image = -DftRcalVolt.Image; + + + /***** Implement RTIA = (V_Rtia / V_Rcal) * Rcal ******/ + fImpCar_Type temp; + temp = AD5940_ComplexDivInt(&DftRtiaVolt, &DftRcalVolt); + temp.Real *= pCalCfg->fRcal; + temp.Image *= pCalCfg->fRcal; + if(pCalCfg->bPolarResult == bFALSE) + { + *(fImpCar_Type*)pResult = temp; + } + else + { + ((fImpPol_Type*)pResult)->Magnitude = AD5940_ComplexMag(&temp); + ((fImpPol_Type*)pResult)->Phase = AD5940_ComplexPhase(&temp); + } + + /* Restore INTC1 DFT configure */ + if(INTCCfg&AFEINTSRC_DFTRDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bFALSE); /* Disable DFT Interrupt */ + + return AD5940ERR_OK; +} + +/** + * @brief Measure LPTIA internal RTIA impedance with HSTIA. This is the recommended method for LPTIA RTIA calibration. + * @param pCalCfg: pointer to calibration structure. + * @param pResult: Pointer to a variable that used to store result. + * If bPolarResult in structure is set, then use type fImpPol_Type otherwise use fImpCar_Type. + * @return AD5940ERR_OK if succeed. +**/ +AD5940Err AD5940_LPRtiaCal(LPRTIACal_Type *pCalCfg, void *pResult) +{ + HSLoopCfg_Type hs_loop; + LPLoopCfg_Type lp_loop; + DSPCfg_Type dsp_cfg; + ADCBaseCfg_Type *pADCBaseCfg; + SWMatrixCfg_Type *pSWCfg; + uint32_t INTCCfg, reg_afecon; + BoolFlag bADCClk32MHzMode = bFALSE; + BoolFlag bDCMode = bFALSE; /* Indicate if frequency is 0, which means we calibrate at DC. */ + + float ExcitVolt; /* Excitation voltage, unit is mV */ + uint32_t RtiaVal; + /* RTIA value table when RLOAD set to 100Ohm */ + uint32_t const LpRtiaTable[]={0,110,1000,2000,3000,4000,6000,8000,10000,12000,16000,20000,24000,30000,32000,40000,48000,64000,85000,96000,100000,120000,128000,160000,196000,256000,512000}; + float const ADCPGAGainTable[] = {1, 1.5, 2, 4, 9}; + uint32_t WgAmpWord; + + uint32_t ADCPgaGainRtia, ADCPgaGainRcal; + float GainRatio; + + iImpCar_Type DftRcal, DftRtia; + + if(pCalCfg == NULL) return AD5940ERR_NULLP; /* Parameters illegal */ + + if(pCalCfg->fRcal == 0) + return AD5940ERR_PARA; + if(pCalCfg->LpTiaRtia > LPTIARTIA_512K) + return AD5940ERR_PARA; + if(pCalCfg->LpTiaRtia == LPTIARTIA_OPEN) + return AD5940ERR_PARA; /* Not supported now. By setting RTIA to open and set corresponding switches can calibrate external RTIA */ + if(pResult == NULL) + return AD5940ERR_NULLP; + + if(pCalCfg->AdcClkFreq > (32000000*0.8)) + bADCClk32MHzMode = bTRUE; /* Clock frequency is high. */ + if(pCalCfg->fFreq == 0.0f) /* Frequency is zero means we calibrate RTIA at DC. */ + bDCMode = bTRUE; + /* Init two pointers */ + pSWCfg = &hs_loop.SWMatCfg; + pADCBaseCfg = &dsp_cfg.ADCBaseCfg; + /* Calculate the excitation voltage we should use based on RCAL/Rtia */ + RtiaVal = LpRtiaTable[pCalCfg->LpTiaRtia]; + /* + * DAC output voltage calculation + * Note: RCAL value should be similar to RTIA so the accuracy is best. + * LPTIA output voltage should be limited to 0.3V to AVDD-0.4V, with 1.3V bias. We use 80% of this range for safe. + * That's 2.0Vpp*80%@2.7V AVDD + * Formula is: ExcitVolt(in mVpp) = (2000mVpp*80% / RTIA) * RCAL + * ADC input range is +-1.5V which is enough for calibration. + * Limitations: + * Note: HSTIA output range is AVDD-0.4V to AGND+0.2V + * HSTIA input common voltage range is 0.3V to AVDD-0.7V; + * When AVDD is 2.7V, the input range is 0.3V to 2.0V; + * If we set Vbias to 1.3V, then maximum AC signal is 0.7Vp*2 = 1.4Vpp. + * Maximum AC signal is further limited by HSTIA RTIA=200Ohm, when RCAL is 200Ohm(for ADuCM355). The maximum output of HSTIA is limited to 2.3V. + * Maximum Vzero voltage is 1.9V when Rcal is 200Ohm and Switch On resistance is 50Ohm*2. Vzero_max = 1.3V + (2.3V-1.3V)/(200+200+50*2)*300. + * Maximum AC signal is (1.9-1.3)*2 = 1.2Vpp(for ADuCM355, RCAl=200Ohm). + */ + /** @cond */ + #define MAXVOLT_P2P 1400 /* Maximum peak to peak voltage 1200mV for ADuCM355. */ + /* Maximum peak2peak voltage for AD5940 10kOhm RCAL is 1400mV */ + #define __MAXVOLT_AMP_CODE (MAXVOLT_P2P*2047L/2200) + /** @endcond */ + ExcitVolt = 2000*0.8*pCalCfg->fRcal/RtiaVal; + WgAmpWord = ((uint32_t)(ExcitVolt/2200*2047*2)+1)>>1; /* Assign value with rounding (0.5 LSB error) */ + if(WgAmpWord > __MAXVOLT_AMP_CODE) + WgAmpWord = __MAXVOLT_AMP_CODE; + /** + * Determine the best ADC PGA gain for both RCAL and RTIA voltage measurement. + */ + { + float RtiaVolt, RcalVolt, temp; + ExcitVolt = WgAmpWord*2000.0f/2047; /* 2000mVpp -->ExcitVolt in Peak to Peak unit */ + RtiaVolt = ExcitVolt/(pCalCfg->fRcal + 100)*RtiaVal; + RcalVolt = RtiaVolt/RtiaVal*pCalCfg->fRcal; + /* The input range of ADC is 1.5Vp, we calculate how much gain we need */ + temp = 3000.0f/RcalVolt; + if(temp >= 9.0f) ADCPgaGainRcal = ADCPGA_9; + else if(temp >= 4.0f) ADCPgaGainRcal = ADCPGA_4; + else if(temp >= 2.0f) ADCPgaGainRcal = ADCPGA_2; + else if(temp >= 1.5f) ADCPgaGainRcal = ADCPGA_1P5; + else ADCPgaGainRcal = ADCPGA_1; + temp = 3000.0f/RtiaVolt; + if(temp >= 9.0f) ADCPgaGainRtia = ADCPGA_9; + else if(temp >= 4.0f) ADCPgaGainRtia = ADCPGA_4; + else if(temp >= 2.0f) ADCPgaGainRtia = ADCPGA_2; + else if(temp >= 1.5f) ADCPgaGainRtia = ADCPGA_1P5; + else ADCPgaGainRtia = ADCPGA_1; + GainRatio = ADCPGAGainTable[ADCPgaGainRtia]/ADCPGAGainTable[ADCPgaGainRcal]; + } + reg_afecon = AD5940_ReadReg(REG_AFE_AFECON); + /* INTC configuration */ + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY|AFEINTSRC_SINC2RDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + AD5940_INTCClrFlag(AFEINTSRC_ALLINT); + + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Init all to disable state */ + /* Configure reference system */ + __AD5940_ReferenceON(); + /* Configure DSP */ + AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg)); + dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */ + dsp_cfg.ADCFilterCfg.ADCRate = bADCClk32MHzMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; + dsp_cfg.ADCFilterCfg.ADCSinc2Osr = pCalCfg->ADCSinc2Osr; + dsp_cfg.ADCFilterCfg.ADCSinc3Osr = pCalCfg->ADCSinc3Osr; + dsp_cfg.ADCFilterCfg.BpNotch = bTRUE; + dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; + dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE; + memcpy(&dsp_cfg.DftCfg, &pCalCfg->DftCfg, sizeof(pCalCfg->DftCfg)); + AD5940_DSPCfgS(&dsp_cfg); + /* Configure LP Loop */ + AD5940_StructInit(&lp_loop, sizeof(lp_loop)); + /* Configure LP Amplifies(LPPA and LPTIA). We won't use LP-PA */ + lp_loop.LpDacCfg.LpdacSel = (pCalCfg->LpAmpSel == LPAMP0)?LPDAC0:LPDAC1; + lp_loop.LpDacCfg.DacData12Bit = 0x800; /* Controlled by WG */ + lp_loop.LpDacCfg.DacData6Bit = 32; /* middle scale value */ + lp_loop.LpDacCfg.DataRst =bFALSE; /* Do not keep DATA registers at reset status */ + lp_loop.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VZERO2HSTIA; + lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5; /* Select internal 2.5V reference */ + lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_WG; /* The LPDAC data comes from WG not MMR in this case */ + lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_6BIT; /* Connect Vbias signal to 6Bit LPDAC output */ + lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_12BIT; /* Connect Vzero signal to 12bit LPDAC output */ + lp_loop.LpDacCfg.PowerEn = bTRUE; /* Power up LPDAC */ + + lp_loop.LpAmpCfg.LpAmpSel = pCalCfg->LpAmpSel; + lp_loop.LpAmpCfg.LpAmpPwrMod = pCalCfg->LpAmpPwrMod; /* Set low power amplifiers to normal power mode */ + lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE; /* Enable LP PA(potential-stat amplifier) power */ + lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE; /* Enable LPTIA*/ + lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_100R; + lp_loop.LpAmpCfg.LpTiaRtia = pCalCfg->LpTiaRtia; + lp_loop.LpAmpCfg.LpTiaRf = LPTIARF_OPEN; + lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(6)|LPTIASW(8)|(pCalCfg->bWithCtia==bTRUE?LPTIASW(5)/*|LPTIASW(9)*/:0); + AD5940_LPLoopCfgS(&lp_loop); + /* Configure HS Loop */ + AD5940_StructInit(&hs_loop, sizeof(hs_loop)); + /* Take care of HSTIA, we need to disconnect internal RTIA because it connects to Tswitch directly. */ + hs_loop.HsTiaCfg.DiodeClose = bFALSE; + hs_loop.HsTiaCfg.HstiaBias = (pCalCfg->LpAmpSel == LPAMP0)?HSTIABIAS_VZERO0:HSTIABIAS_VZERO1; + hs_loop.HsTiaCfg.HstiaCtia = 31; + hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN; + hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN; + hs_loop.HsTiaCfg.HstiaDe1Rload = HSTIADERLOAD_OPEN; + hs_loop.HsTiaCfg.HstiaDe1Rtia = HSTIADERTIA_OPEN; + hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_200; + /* Configure HSDAC */ + hs_loop.HsDacCfg.ExcitBufGain = 0; + hs_loop.HsDacCfg.HsDacGain = 0; /* Don't care */ + hs_loop.HsDacCfg.HsDacUpdateRate = 255; /* Lowest for LPDAC */ + + hs_loop.SWMatCfg.Dswitch = SWD_RCAL0|((pCalCfg->LpAmpSel == LPAMP0)?SWD_SE0:SWD_SE1); + hs_loop.SWMatCfg.Pswitch = SWP_RCAL0; + hs_loop.SWMatCfg.Nswitch = SWN_RCAL1; + hs_loop.SWMatCfg.Tswitch = SWT_TRTIA|SWT_RCAL1; + if(bDCMode) + { + int32_t time_out = -1; /* Always wait. */ + int32_t offset_rcal, offset_rtia; + /* Configure WG */ + hs_loop.WgCfg.WgType = WGTYPE_MMR; + hs_loop.WgCfg.WgCode = WgAmpWord; /* Amplitude word is exactly the maximum DC voltage we could use */ + hs_loop.WgCfg.GainCalEn = bFALSE; /* We don't have calibration value for LPDAC, so we don't use it. */ + hs_loop.WgCfg.OffsetCalEn = bFALSE; + AD5940_HSLoopCfgS(&hs_loop); + AD5940_WGDACCodeS(WgAmpWord + 0x800); + AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Apply voltage to loop and turn on ADC */ + /* Do offset measurement */ + pSWCfg->Dswitch = SWD_RCAL0;//|SWD_SE0; /* Disconnect SE0 for now to measure the offset voltage. */ + pSWCfg->Pswitch = SWP_RCAL0; + pSWCfg->Nswitch = SWN_RCAL1; + pSWCfg->Tswitch = SWT_TRTIA|SWT_RCAL1; + AD5940_SWMatrixCfgS(pSWCfg); + AD5940_Delay10us(1000); /* Wait some time here. */ + /* Measure RCAL channel voltage offset */ + pADCBaseCfg->ADCMuxN = ADCMUXN_N_NODE; + pADCBaseCfg->ADCMuxP = ADCMUXP_P_NODE; + pADCBaseCfg->ADCPga = ADCPgaGainRcal; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_Delay10us(50); /* Wait some time here. */ + offset_rcal = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + /* Measure RTIA channel voltage offset */ + if(pCalCfg->LpAmpSel == LPAMP0) + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA0_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA0_P; + }else + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA1_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA1_P; + } + pADCBaseCfg->ADCPga = ADCPgaGainRtia; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_Delay10us(50); /* Wait some time here. */ + offset_rtia = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + /* Connect LPTIA loop, let current flow to RTIA. */ + pSWCfg->Dswitch = SWD_RCAL0|((pCalCfg->LpAmpSel == LPAMP0)?SWD_SE0:SWD_SE1); + pSWCfg->Pswitch = SWP_RCAL0; + pSWCfg->Nswitch = SWN_RCAL1; + pSWCfg->Tswitch = SWT_TRTIA|SWT_RCAL1; + AD5940_SWMatrixCfgS(pSWCfg); + AD5940_Delay10us(1000); /* Wait some time here. */ + /* Measure RCAL */ + pADCBaseCfg = &dsp_cfg.ADCBaseCfg; + pADCBaseCfg->ADCMuxN = ADCMUXN_N_NODE; + pADCBaseCfg->ADCMuxP = ADCMUXP_P_NODE; + pADCBaseCfg->ADCPga = ADCPgaGainRcal; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_Delay10us(50); /* Wait some time here. */ + DftRcal.Real = (int32_t)__AD5940_TakeMeasurement(&time_out)- offset_rcal; + DftRcal.Image = 0; + /* Measure RTIA */ + if(pCalCfg->LpAmpSel == LPAMP0) + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA0_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA0_P; + }else + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA1_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA1_P; + } + pADCBaseCfg->ADCPga = ADCPgaGainRtia; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_Delay10us(50); /* Wait some time here. */ + DftRtia.Real = (int32_t)__AD5940_TakeMeasurement(&time_out)- offset_rtia; + DftRtia.Image = 0; + } + else + { + hs_loop.WgCfg.SinCfg.SinAmplitudeWord = WgAmpWord; + hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(pCalCfg->fFreq, pCalCfg->SysClkFreq); + hs_loop.WgCfg.SinCfg.SinOffsetWord = 0; + hs_loop.WgCfg.SinCfg.SinPhaseWord = 0; + hs_loop.WgCfg.WgCode = 0; + hs_loop.WgCfg.WgType = WGTYPE_SIN; + hs_loop.WgCfg.GainCalEn = bFALSE; /* disable it */ + hs_loop.WgCfg.OffsetCalEn = bFALSE; + AD5940_HSLoopCfgS(&hs_loop); + AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); + + AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR, bTRUE); + AD5940_Delay10us(100); /* Wait for loop stable. */ + pADCBaseCfg = &dsp_cfg.ADCBaseCfg; + /* DFT on RCAL */ + pADCBaseCfg->ADCMuxN = ADCMUXN_N_NODE; + pADCBaseCfg->ADCMuxP = ADCMUXP_P_NODE; + pADCBaseCfg->ADCPga = ADCPgaGainRcal; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE); + AD5940_Delay10us(25); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); + /* Wait until DFT ready */ + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */ + AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); + DftRcal.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL); + DftRcal.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE); + /* DFT on RTIA */ + if(pCalCfg->LpAmpSel == LPAMP0) + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA0_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA0_P; + }else + { + pADCBaseCfg->ADCMuxN = ADCMUXN_LPTIA1_N; + pADCBaseCfg->ADCMuxP = ADCMUXP_LPTIA1_P; + } + pADCBaseCfg->ADCPga = ADCPgaGainRtia; + AD5940_ADCBaseCfgS(pADCBaseCfg); + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE); + AD5940_Delay10us(25); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); + /* Wait until DFT ready */ + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE); + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */ + AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); + DftRtia.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL); + DftRtia.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE); + if(DftRcal.Real&(1L<<17)) + DftRcal.Real |= 0xfffc0000; + if(DftRcal.Image&(1L<<17)) + DftRcal.Image |= 0xfffc0000; + if(DftRtia.Real&(1L<<17)) + DftRtia.Real |= 0xfffc0000; + if(DftRtia.Image&(1L<<17)) + DftRtia.Image |= 0xfffc0000; + } + /* + The impedance engine inside of AD594x give us Real part and Imaginary part of DFT. Due to technology used, the Imaginary + part in register is the opposite number. So we add a negative sign on the Imaginary part of results. + */ + DftRtia.Image = -DftRtia.Image; + DftRcal.Image = -DftRcal.Image; + + fImpCar_Type res; + /* RTIA = (DftRtia.Real, DftRtia.Image)/(DftRcal.Real, DftRcal.Image)*fRcal */ + res = AD5940_ComplexDivInt(&DftRtia, &DftRcal); + res.Real *= pCalCfg->fRcal/GainRatio; + res.Image *= pCalCfg->fRcal/GainRatio; + if(pCalCfg->bPolarResult == bFALSE) + { + ((fImpCar_Type*)pResult)->Real = res.Real; + ((fImpCar_Type*)pResult)->Image = res.Image; + } + else + { + ((fImpPol_Type*)pResult)->Magnitude = AD5940_ComplexMag(&res); + ((fImpPol_Type*)pResult)->Phase = AD5940_ComplexPhase(&res); + } + + /* Restore INTC1 DFT configure */ + if(INTCCfg&AFEINTSRC_DFTRDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bFALSE); /* Disable DFT Interrupt */ + if(INTCCfg&AFEINTSRC_SINC2RDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bFALSE); /* Disable SINC2 Interrupt */ + AD5940_WriteReg(REG_AFE_AFECON, reg_afecon); /* Restore AFECON register */ + /* Open all switches in switch-matrix */ + hs_loop.SWMatCfg.Dswitch = SWD_OPEN; + hs_loop.SWMatCfg.Pswitch = SWP_OPEN; + hs_loop.SWMatCfg.Nswitch = SWN_OPEN; + hs_loop.SWMatCfg.Tswitch = SWT_OPEN; + AD5940_SWMatrixCfgS(&hs_loop.SWMatCfg); + + return AD5940ERR_OK; +} + + +/** + * @brief calibrate HSDAC output voltage using ADC. + * @note It acutally calibrates voltage output of excitation buffer. + * @param pCalCfg: pointer to configuration structure + * @return return AD5940ERR_OK if succeeded. +*/ +AD5940Err AD5940_HSDACCal(HSDACCal_Type *pCalCfg) +{ + ADCBaseCfg_Type adc_base; + ADCFilterCfg_Type adc_filter; + HSLoopCfg_Type hsloop_cfg; + LPLoopCfg_Type lploop_cfg; + + /* LSB_Numerator and LSB_Denometer are used to calculate + the codes to write to calibration registers depending on + which calibration register is used + There are LSB_Numerator ADC LSBs in + LSB_Denominator DAC Calibration LSBs*/ + int32_t LSB_Numerator; + int32_t LEB_Denominator; + int32_t time_out; + int32_t ADCCode; + uint32_t HSDACCode = 0x800; /* Mid scale DAC */ + + uint32_t regaddr_offset; + uint32_t ADCPGA_Sel; + BoolFlag bHPMode; + + if(pCalCfg == NULL) return AD5940ERR_NULLP; + if(pCalCfg->ExcitBufGain > 1) return AD5940ERR_PARA; + if(pCalCfg->HsDacGain > 1) return AD5940ERR_PARA; + + bHPMode = pCalCfg->AfePwrMode == AFEPWR_HP?bTRUE:bFALSE; + + switch(pCalCfg->ExcitBufGain) + { + case EXCITBUFGAIN_2: + regaddr_offset = bHPMode?REG_AFE_DACOFFSETHP:REG_AFE_DACOFFSET; + if(pCalCfg->HsDacGain == HSDACGAIN_0P2) + { + LSB_Numerator = 40; + LEB_Denominator = 14; + ADCPGA_Sel = ADCPGA_4; + } + else + { + LSB_Numerator = 7; + LEB_Denominator = 2; + ADCPGA_Sel = ADCPGA_1; + } + break; + case EXCITBUFGAIN_0P25: + regaddr_offset = bHPMode?REG_AFE_DACOFFSETATTENHP:REG_AFE_DACOFFSETATTEN; + if(pCalCfg->HsDacGain == HSDACGAIN_0P2) + { + LSB_Numerator = 5; + LEB_Denominator = 14; + } + else + { + LSB_Numerator = 25; + LEB_Denominator = 14; + } + ADCPGA_Sel = ADCPGA_4; + break; + default: + return AD5940ERR_PARA; + } + + /* Turn On References*/ + __AD5940_ReferenceON(); + /* Step0.0 Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH. Use SIN2 data for calibration-->Lower noise */ + adc_filter.ADCSinc3Osr = pCalCfg->ADCSinc3Osr; + adc_filter.ADCSinc2Osr = pCalCfg->ADCSinc2Osr; /* 800KSPS/4/1333 = 150SPS */ + adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */ + adc_filter.ADCRate = bHPMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */ + adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */ + adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */ + adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */ + AD5940_ADCFilterCfgS(&adc_filter); + /* Step0.1 Initialize ADC basic function */ + adc_base.ADCMuxP = ADCMUXP_P_NODE; + adc_base.ADCMuxN = ADCMUXN_N_NODE; + adc_base.ADCPga = ADCPGA_Sel; + AD5940_ADCBaseCfgS(&adc_base); + + /* Step0.2 Configure LPDAC to connect VZERO to HSTIA */ + lploop_cfg.LpDacCfg.LpdacSel = LPDAC0; + lploop_cfg.LpDacCfg.DacData12Bit = 0x7C0; + lploop_cfg.LpDacCfg.DacData6Bit = 0x1F; + lploop_cfg.LpDacCfg.DataRst = bFALSE; + lploop_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5; + lploop_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR; + lploop_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; + lploop_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; + lploop_cfg.LpDacCfg.PowerEn = bTRUE; + lploop_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2HSTIA; + AD5940_LPLoopCfgS(&lploop_cfg); + + /* Step0.3 Configure HSLOOP */ + hsloop_cfg.HsDacCfg.ExcitBufGain = pCalCfg->ExcitBufGain; + hsloop_cfg.HsDacCfg.HsDacGain = pCalCfg->HsDacGain; + hsloop_cfg.HsDacCfg.HsDacUpdateRate = bHPMode?0x7:0x1B; + hsloop_cfg.HsTiaCfg.DiodeClose = bFALSE; + hsloop_cfg.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0; + hsloop_cfg.HsTiaCfg.HstiaCtia = 8; + hsloop_cfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDe1Rload = HSTIADERLOAD_OPEN; + hsloop_cfg.HsTiaCfg.HstiaDe1Rtia = HSTIADERTIA_OPEN; + hsloop_cfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_200; + hsloop_cfg.SWMatCfg.Dswitch = SWD_RCAL0; + hsloop_cfg.SWMatCfg.Pswitch = SWP_RCAL0; + hsloop_cfg.SWMatCfg.Nswitch = SWN_RCAL1; + hsloop_cfg.SWMatCfg.Tswitch = SWT_TRTIA|SWT_RCAL1; + hsloop_cfg.WgCfg.GainCalEn = bTRUE; + hsloop_cfg.WgCfg.OffsetCalEn = bTRUE; + hsloop_cfg.WgCfg.WgType = WGTYPE_MMR; + hsloop_cfg.WgCfg.WgCode = HSDACCode; + AD5940_HSLoopCfgS(&hsloop_cfg); + /* Step0.4 Turn ON reference and ADC power, and DAC power and DAC reference. We use DAC 1.8V reference to calibrate ADC. */ + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable all */ + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_HPREFPWR|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|AFECTRL_SINC2NOTCH|\ + AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR|AFECTRL_HSTIAPWR|AFECTRL_WG, bTRUE); + AD5940_Delay10us(25); /* Wait 250us for reference power up */ + /* Step0.5 INTC configure and open calibration lock */ + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, KEY_CALDATLOCK); /* Unlock KEY */ + /* Reset Offset register before calibration */ + AD5940_WriteReg(regaddr_offset, 0); + /* Update HSDACDAT after resetting calibration register */ + AD5940_WriteReg(REG_AFE_HSDACDAT, 0x800); + /* Step1: Do offset calibration. */ + { + int32_t ExpectedCode = 0x8000; /* Ideal ADC output */ + AD5940_Delay10us(10); + time_out = 1000; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); +#ifdef ADI_DEBUG + ADI_Print("Voltage before cal: %f \n", AD5940_ADCCode2Volt(ADCCode, ADCPGA_Sel, 1.82)); +#endif + + if(time_out == 0) goto DACCALERROR_TIMEOUT; /* Time out error. */ + ADCCode = ADCCode - ExpectedCode; + ADCCode = (((ADCCode)*LEB_Denominator)/LSB_Numerator); + if(ADCCode>0) + ADCCode = 0xFFF - ADCCode; + else + ADCCode = -ADCCode; + AD5940_WriteReg(regaddr_offset, ADCCode); + AD5940_Delay10us(10); + AD5940_WriteReg(REG_AFE_HSDACDAT, 0x800); + AD5940_Delay10us(10); +#ifdef ADI_DEBUG + ADCCode = __AD5940_TakeMeasurement(&time_out); + ADI_Print("Voltage after cal: %f \n", AD5940_ADCCode2Volt(ADCCode, ADCPGA_Sel, 1.82)); +#endif + } + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + return AD5940ERR_OK; +DACCALERROR_TIMEOUT: + AD5940_ADCConvtCtrlS(bFALSE); /* Stop conversion */ + AD5940_WriteReg(REG_AFE_CALDATLOCK, 0); /* Lock KEY */ + return AD5940ERR_TIMEOUT; +} + +/** + * @brief Use ADC to measure LPDAC offset and gain factor. + * @note Assume ADC is accurate enough or accurate than LPDAC at least. + * @param pCalCfg: pointer to structure. + * @param pResult: the pointer to save calibration result. + * @return AD5940ERR_OK if succeed. + *LPDACCal() function is added in ad5940.c only to suggest an optional LPDAC calibration sequence. + *It is not verified by ADI software team and user may use it at own risk. + +**/ +AD5940Err AD5940_LPDACCal(LPDACCal_Type *pCalCfg, LPDACPara_Type *pResult) +{ + AD5940Err error = AD5940ERR_OK; + LPDACCfg_Type LpDacCfg; + ADCBaseCfg_Type adc_base; + ADCFilterCfg_Type adc_filter; + + int32_t time_out; + uint32_t INTCCfg; + int32_t ADCCode, ADCCodeVref1p1; + BoolFlag bADCClk32MHzMode; + + if(pCalCfg == NULL) return AD5940ERR_NULLP; + if(pResult == NULL) return AD5940ERR_NULLP; + if(pCalCfg->AdcClkFreq > (32000000*0.8)) + bADCClk32MHzMode = bTRUE; + + /* Step0: Do initialization */ + /* Turn on AD5940 references in case it's disabled. */ + __AD5940_ReferenceON(); + LpDacCfg.LpdacSel = pCalCfg->LpdacSel; + LpDacCfg.DacData12Bit = 0; + LpDacCfg.DacData6Bit = 0; + LpDacCfg.DataRst = bFALSE; + LpDacCfg.LpDacRef = LPDACREF_2P5; + LpDacCfg.LpDacSrc = LPDACSRC_MMR; + LpDacCfg.LpDacSW = LPDACSW_VBIAS2PIN|LPDACSW_VZERO2PIN; + LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; + LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; + LpDacCfg.PowerEn = bTRUE; + AD5940_LPDACCfgS(&LpDacCfg); + + /* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH. Use SIN2 data for calibration-->Lower noise */ + adc_filter.ADCSinc3Osr = pCalCfg->ADCSinc3Osr; + adc_filter.ADCSinc2Osr = pCalCfg->ADCSinc2Osr; /* 800KSPS/4/1333 = 150SPS */ + adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */ + adc_filter.ADCRate = bADCClk32MHzMode?ADCRATE_1P6MHZ:ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */ + adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */ + adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */ + adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */ + AD5940_ADCFilterCfgS(&adc_filter); + /* Initialize ADC MUx and PGA */ + adc_base.ADCMuxP = ADCMUXP_AGND; + adc_base.ADCMuxN = ADCMUXN_VSET1P1; + adc_base.ADCPga = ADCPGA_1; + AD5940_ADCBaseCfgS(&adc_base); + /* Turn ON ADC and its reference. And SINC2. */ + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable all firstly, we only enable things we use */ + AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_HPREFPWR|AFECTRL_SINC2NOTCH, bTRUE); + AD5940_Delay10us(25); /* Wait 250us for reference power up */ + /* INTC configure and open calibration lock */ + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); /* Enable SINC2 Interrupt in INTC1 */ + /* Step1: Measure internal 1.1V reference. */ + { + //AD5940_ADCMuxCfgS(ADCMUXP_AGND, ADCMUXN_VSET1P1); + time_out = pCalCfg->TimeOut10us; /* Reset time out counter */ + ADCCodeVref1p1 = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPDACCALERROR; + } /* Time out error. */ + /* Equation1: ADCCodeVref1p1 = AGND - Vref1p1 */ + } + /* Step2: Do offset measurement. */ + { + /* Equation2': ADCCode = Vbias0/1 - Vref1p1 */ + AD5940_LPDACWriteS(0,0); /* Set LPDAC output voltage to 0.2V(zero code) */ + if(pCalCfg->SettleTime10us > 0) + AD5940_Delay10us(pCalCfg->SettleTime10us); /* Delay nx10us */ + if(pCalCfg->LpdacSel == LPDAC0) + AD5940_ADCMuxCfgS(ADCMUXP_VBIAS0, ADCMUXN_VREF1P1); /* Vbias0 is routed to 12BIT LPDAC */ + else + AD5940_ADCMuxCfgS(ADCMUXP_VBIAS1, ADCMUXN_VREF1P1); /* Vbias1 is routed to 12BIT LPDAC */ + + AD5940_Delay10us(5); /* Delay 50us */ + time_out = pCalCfg->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPDACCALERROR; + } /* Time out error. */ + /* Calculate the offset voltage using Equation2 - Equation1 */ + ADCCode -= ADCCodeVref1p1; /* Get the code of Vbias0-AGND. Then calculate the offset voltage in mV. */ + pResult->bC2V_DAC12B = ADCCode*pCalCfg->ADCRefVolt*1e3f/32768*1.835f/1.82f; /*mV unit*/ + /* Measure 6BIT DAC output(Vzero0/1) */ + if(pCalCfg->LpdacSel == LPDAC0) + AD5940_ADCMuxCfgS(ADCMUXP_VZERO0, ADCMUXN_VREF1P1); /* Vbias0 is routed to 12BIT LPDAC */ + else + AD5940_ADCMuxCfgS(ADCMUXP_VZERO1, ADCMUXN_VREF1P1); /* Vbias1 is routed to 12BIT LPDAC */ + AD5940_Delay10us(5); /* Delay 50us */ + time_out = pCalCfg->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPDACCALERROR; + } /* Time out error. */ + /* Calculate the offset voltage */ + ADCCode -= ADCCodeVref1p1; /* Get the code of Vbias0-AGND. Then calculate the offset voltage in mV. */ + pResult->bC2V_DAC6B = ADCCode*pCalCfg->ADCRefVolt*1e3f/32768*1.835f/1.82f; /*mV unit*/ + } + /* Step3: Do gain measurement */ + { + /* Equation2: ADCCode = Vbias0 - Vref1p1 */ + AD5940_LPDACWriteS(0xfff,0x3f); /* Set LPDAC output voltage to 2.4V(zero code) */ + if(pCalCfg->SettleTime10us > 0) + AD5940_Delay10us(pCalCfg->SettleTime10us); /* Delay nx10us */ + if(pCalCfg->LpdacSel == LPDAC0) + AD5940_ADCMuxCfgS(ADCMUXP_VBIAS0, ADCMUXN_VREF1P1); /* Vbias0 is routed to 12BIT LPDAC */ + else + AD5940_ADCMuxCfgS(ADCMUXP_VBIAS1, ADCMUXN_VREF1P1); /* Vbias1 is routed to 12BIT LPDAC */ + AD5940_Delay10us(5); /* Delay 50us */ + time_out = pCalCfg->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPDACCALERROR; + } /* Time out error. */ + /* Calculate the offset voltage */ + ADCCode -= ADCCodeVref1p1; /* Get the code of Vbias0-AGND. Then calculate the gain factor 'k'. */ + pResult->kC2V_DAC12B = (ADCCode*pCalCfg->ADCRefVolt*1e3f/32768*1.835f/1.82f - pResult->bC2V_DAC12B)/0xfff;/*mV unit*/ + /* Measure 6BIT DAC output(Vzero0) */ + if(pCalCfg->LpdacSel == LPDAC0) + AD5940_ADCMuxCfgS(ADCMUXP_VZERO0, ADCMUXN_VREF1P1); /* Vbias0 is routed to 12BIT LPDAC */ + else + AD5940_ADCMuxCfgS(ADCMUXP_VZERO1, ADCMUXN_VREF1P1); /* Vbias1 is routed to 12BIT LPDAC */ + AD5940_Delay10us(5); /* Delay 50us */ + time_out = pCalCfg->TimeOut10us; /* Reset time out counter */ + ADCCode = __AD5940_TakeMeasurement(&time_out); /* Turn on ADC to get one valid data and then turn off ADC. */ + if(time_out == 0) + { + error = AD5940ERR_TIMEOUT; + goto LPDACCALERROR; + } /* Time out error. */ + /* Calculate the offset voltage */ + ADCCode -= ADCCodeVref1p1; /* Get the code of Vbias0-AGND. Then calculate the offset voltage in mV. */ + pResult->kC2V_DAC6B = (ADCCode*pCalCfg->ADCRefVolt*1e3f/32768*1.835f/1.82f - pResult->bC2V_DAC6B)/0x3f;/*mV unit*/ + } + /* Step4: calculate the parameters for voltage to code calculation. */ + pResult->kV2C_DAC12B = 1/pResult->kC2V_DAC12B; + pResult->bV2C_DAC12B = -pResult->bC2V_DAC12B/pResult->kC2V_DAC12B; + pResult->kV2C_DAC6B = 1/pResult->kC2V_DAC6B; + pResult->bV2C_DAC6B = -pResult->bC2V_DAC6B/pResult->kC2V_DAC6B; + /* Restore INTC1 SINC2 configure */ + if(INTCCfg&AFEINTSRC_SINC2RDY); + else + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bFALSE); /* Disable SINC2 Interrupt */ + /* Done */ + return AD5940ERR_OK; + +LPDACCALERROR: + AD5940_ADCConvtCtrlS(bFALSE); /* Stop conversion */ + return error; +} + +/** + * @brief Use system clock to measure LFOSC frequency. + * @note Set system clock to external crystal to get a better measurement accuracy. + * This function use 3 sequences and the start address is specified by parameter. + * @param pCfg: pointer to structure. + * @param pFreq: Pointer to a variable that used to store frequency in Hz. + * @return AD5940ERR_OK if succeed. +**/ +AD5940Err AD5940_LFOSCMeasure(LFOSCMeasure_Type *pCfg, float *pFreq) /* Measure current LFOSC frequency. */ +{ + /** + * @code + * Sleep wakeup timer running... + * -SLP----WKP----SLP----WKP----SLP----WKP + * --|-----|-------------|-------------|------------Trigger sequencer when Wakeup Timer over. + * --------|SEQA---------|SEQB----------------------Execute SeqA then SeqB + * ---------|InitT--------|StopT--------------------SeqA start timer and SeqB trigger interrupt so MCU read back current count + * ------------------------|INT--------------------- + * -----------------------------------------|Read---We read SEQTIMEOUT register here + * ---------|-----TimerCount----------------|------- + * ---------|--------------|---TimerCount2--|-------We change SeqB to reset timer so we measure how much time needed for MCU to read back SEQTIMEOUT register(TimerCount2) + * @endcode + * **/ + uint32_t TimerCount, TimerCount2; + SEQCfg_Type seq_cfg, seq_cfg_backup; + SEQInfo_Type seqinfo; + WUPTCfg_Type wupt_cfg; + uint32_t INTCCfg; + uint32_t WuptPeriod; + + static const uint32_t SeqA[]= + { + SEQ_TOUT(0x3fffffff), /* Set time-out timer. It will always run until disable Sequencer by SPI interface. */ + }; + static const uint32_t SeqB[]= + { + /** + * Interrupt flag AFEINTSRC_ENDSEQ will be set after this command. So We can inform MCU to read back + * current timer value. MCU will need some additional time to read back time count. + * So we use SeqB to measure how much time needed for MCU to read back + * */ + SEQ_STOP(), + }; + static const uint32_t SeqBB[]= + { + SEQ_TOUT(0x3fffffff), /* Re-Set time-out timer, so we can measure the time needed for MCU to read out Timer Count register. */ + SEQ_STOP(), /* Interrupt flag AFEINTSRC_ENDSEQ will be set here */ + }; + + if(pCfg == NULL) return AD5940ERR_NULLP; + if(pFreq == NULL) return AD5940ERR_NULLP; + if(pCfg->CalDuration < 1.0f) + return AD5940ERR_PARA; + AD5940_SEQGetCfg(&seq_cfg_backup); + INTCCfg = AD5940_INTCGetCfg(AFEINTC_1); + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, bTRUE); + AD5940_INTCClrFlag(AFEINTSRC_ALLINT); + + seq_cfg.SeqMemSize = SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer */ + seq_cfg.SeqBreakEn = bFALSE; + seq_cfg.SeqIgnoreEn = bFALSE; + seq_cfg.SeqCntCRCClr = bFALSE; + seq_cfg.SeqEnable = bTRUE; + seq_cfg.SeqWrTimer = 0; + AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */ + + seqinfo.pSeqCmd = SeqA; + seqinfo.SeqId = SEQID_0; + seqinfo.SeqLen = SEQ_LEN(SeqA); + seqinfo.SeqRamAddr = pCfg->CalSeqAddr; + seqinfo.WriteSRAM = bTRUE; + AD5940_SEQInfoCfg(&seqinfo); + seqinfo.SeqId = SEQID_1; + seqinfo.SeqRamAddr = pCfg->CalSeqAddr + SEQ_LEN(SeqA) ; + seqinfo.SeqLen = SEQ_LEN(SeqB); + seqinfo.pSeqCmd = SeqB; + AD5940_SEQInfoCfg(&seqinfo); /* Configure sequence0 and sequence1 with command SeqA and SeqB */ + + wupt_cfg.WuptEn = bFALSE; + wupt_cfg.WuptOrder[0] = SEQID_0; + wupt_cfg.WuptOrder[1] = SEQID_1; + wupt_cfg.WuptEndSeq = WUPTENDSEQ_B; + wupt_cfg.SeqxWakeupTime[0] = 4; /* Don't care. >4 is acceptable */ + wupt_cfg.SeqxSleepTime[0] = (uint32_t)((pCfg->CalDuration)*32 + 0.5f) - 1 - 4; + wupt_cfg.SeqxWakeupTime[1] = 4-1; + wupt_cfg.SeqxSleepTime[1] = 0xffffffff; /* Don't care */ + WuptPeriod = (wupt_cfg.SeqxSleepTime[0]+1) + (wupt_cfg.SeqxWakeupTime[1]+1); + AD5940_WUPTCfg(&wupt_cfg); + + AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ); + AD5940_WUPTCtrl(bTRUE); + + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE); + TimerCount = AD5940_SEQTimeOutRd(); + + AD5940_WUPTCtrl(bFALSE); + AD5940_WUPTTime(SEQID_0, 4, 4); /* Set it to minimum value because we don't care about sequence0 now. We only want to measure how much time MCU will need to read register */ + seqinfo.SeqId = SEQID_1; + seqinfo.SeqRamAddr = pCfg->CalSeqAddr + SEQ_LEN(SeqA) ; + seqinfo.SeqLen = SEQ_LEN(SeqBB); + seqinfo.pSeqCmd = SeqBB; + seqinfo.WriteSRAM = bTRUE; + AD5940_SEQInfoCfg(&seqinfo); + AD5940_SEQCtrlS(bTRUE); /* Enable Sequencer again */ + + AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ); + AD5940_WUPTCtrl(bTRUE); + while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE); + TimerCount2 = AD5940_SEQTimeOutRd(); + AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_ENDSEQ); + + AD5940_WUPTCtrl(bFALSE); + AD5940_SEQCfg(&seq_cfg_backup); /* restore sequencer configuration */ + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, (INTCCfg&AFEINTSRC_ENDSEQ)?bTRUE:bFALSE); /* Restore interrupt configuration */ + AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ); + //printf("Time duration:%d ", (TimerCount2 - TimerCount)); + *pFreq = pCfg->SystemClkFreq*WuptPeriod/(TimerCount2 - TimerCount); + return AD5940ERR_OK; +} + +/** + * @} Calibration + * @} Calibration_Block +*/ + +/** + * @} AD5940_Functions + * @} AD5940_Library +*/ \ No newline at end of file diff --git a/host/src/MainWindow.cpp b/host/src/MainWindow.cpp index bf07a89..83f89c8 100644 --- a/host/src/MainWindow.cpp +++ b/host/src/MainWindow.cpp @@ -82,7 +82,6 @@ void MainWindow::setupUi() { nyquistGraph = new GraphWidget(this); nyquistGraph->configureNyquistPlot(); - // Raw Data is now Default (Index 0) tabWidget->addTab(rawGraph, "Raw Data"); tabWidget->addTab(nyquistGraph, "Nyquist Plot"); @@ -329,6 +328,7 @@ void MainWindow::parseData(const QString &data) { } } + void MainWindow::computeHilbert() { int n = sweepReals.size(); if (n == 0) return; @@ -360,7 +360,7 @@ void MainWindow::computeHilbert() { // 4. IFFT ifft(signal); - // 5. Extract Imaginary Part (Hilbert Transform) + // 5. Extract Imaginary Part (Hilbert Transform) to represent the analytical. QVector hilbertImag; for (int i = 0; i < n; i++) { hilbertImag.append(signal[i].imag()); diff --git a/host/src/main.cpp b/host/src/main.cpp index 9273b2c..48df579 100644 --- a/host/src/main.cpp +++ b/host/src/main.cpp @@ -1,4 +1,4 @@ -// File: aluf/src/main.cpp +// File: host/src/main.cpp #include #include "MainWindow.h"