Post Go back to editing

Trouble getting good electrochemical impedance results with the AD5941

Category: Hardware
Product Number: AD5941

Hello, 

I'm having trouble getting good electrochemical results when testing with a custom AD5941 board. I was using an adapted version of the Impedance example code (removed the sequencer for faster readouts). For some reason, the magnitudes of my impedances are low compared to a benchtop potentiostat. I have experimented with different HSTIAs to prevent saturation and still arrive at the same result. I have tried different concentrations and noticed similar trends. Though the AD5941 demonstrates the correct responses at various concentrations (i.e. higher or lower impedance values at different concentrations), it still is low compared to the benchtop. I've attached pictures here for reference.

Note that the outlier at the 0.1 Hz range is just due to noise and I consider only from 100 kHz to 0.15 Hz. I'm more concerned about the general magnitude differences than that outlier. 

I've also tried the same setup and code on a dummy RC network and demonstrated good and comparable results (barring the noise at the 0.1 Hz frequency point). I've attached a picture here as well: 

Therefore, the issue primarily is with redox active compounds and using it in an actual electrochemistry setting. These measurements were run at the default 1V1 setting with no bias, and I verified that the voltages between the RE and WE were 0.0V. Asides from the HSTIA, what other options do I have for getting better results? Do you have any speculations as to why I'm getting incomparable results? I've tried many RC networks and they give comparable results, so I'm struggling to find out where the issue actually lies. 

  • Hi  ,

    I will contact the product owner and get back to you.

    Regards,
    Jo

  • Hi, is there an update on this? I think the issue might be related to the previous question I had on the matter: 

    Troubleshooting Bias voltages on AD5941 - Q&A - Precision ADCs - EngineerZone (analog.com)

    Because the CE voltage is not stable at 1.1V unless it's tied to RE. Which is why it works for an RC cell because CE and RE are tied together. Can someone confirm what the proper configuration for a 3-electrode-cell should be? 

  • Hi ,

    Do you know what's the impedance between your CE and RE pins? The AD5940 will try to maintain your waveform amplitude between RE and SE but it might be having difficulty in doing that if the impedance between CE and RE is too big. 

    regards,

    Mark

  • Hi Mark, 

    Thanks for the response! With everything turned on and configured for an impedance measurement, I'm getting something on the order of Megaohms (30 MegaOhms since my DMM can't record it consistently). When no power is supplied, the resistance is about 1.5 MegaOhms. Do you have any advice for lowering this impedance? I can put a resistor in parallel between RE and CE to lower it, but unsure as to how low it should be. Do I just tie them together, and how would that look for a 3-electrode cell?

  • I was able to get decent results with a 2Meg Ohm resistor connected between RE and CE, but the measurements, particularly at the lower frequency ranges could be better. I wanted to ask if there was a specific value for this impedance or if I should consider impedances across any of the pins like SE0 and RE0, or SE0 and CE0. 

    Thanks!

  • Hi,

    May I know if this adapted version of Impedance example you are using is from Github? Does adapted mean only removing the sequences?

    Have you run the default  3-wire impedance code in Github and checked the results.

    For the default impedance example in Github, proper configuration for a 3-electrode-cell is as below:

  • Hi Akila, 

    Yes, the adpated version of Impedance is from GitHub. I've verified it with the 3-wire impedance code in GitHub and actually got worse results with the same HSTIA settings (despite playing around with the ODR to see if wakeup time was the issue). I've attached some of the code that I'm using here (it's not letting me paste all of it into the code block). I'm happy to further discuss this matter, and I'd appreciate any feedback as to what configuration I'm missing or I could add to get better results at the lower frequency ranges (i.e. < 1 Hz). The code below along with the resistor across RE and CE can now give results like the image below but I'd like to do better for the lower frequency ranges. Or at least, reasonable areas to troubleshoot like Mark's recommendation to check the impedances for RE and CE or explanations as to what could potentially cause this issue, since it's only prominent for low frequencies

    Any advice would be really appreciated! Thank you for all your help!

    This is the configuration stage (prior to running an EIS cycle). Apologies for pasting like this but the function was too long for the code block:

    void TestLibImp::AD5940_TDD(float startFreq, float endFreq, uint32_t numPoints, float biasVolt, float zeroVolt, float rcalVal, calHSTIA *gainArr, int gainArrSize, int extGain, int dacGain) {

      // SETUP Cfgs
      CLKCfg_Type clk_cfg;
      AGPIOCfg_Type gpio_cfg;
      ClksCalInfo_Type clks_cal;
      LPAmpCfg_Type LpAmpCfg;
     
      // DFT / ADC / WG / HSLoop Cfgs
      AFERefCfg_Type aferef_cfg;
      HSLoopCfg_Type HsLoopCfg;
      DSPCfg_Type dsp_cfg;

      float sysClkFreq = 16000000.0; // 16 MHz
      float adcClkFreq = 16000000.0; // 16 MHz
      float sineVpp = 200.0; // 200 mV
      _rcalVal = rcalVal;

      /* Configuring the Gain Array */
      _gainArrSize = gainArrSize;
      printf("Gain array size: %d\n", _gainArrSize);
      for(uint32_t i = 0; i < _gainArrSize; i++)
      {
        _gainArr[i] = gainArr[i];
        // _arrHSTIA[i] = arrHSTIA[i];
      }

      /* Use hardware reset */
      AD5940_HWReset();
      AD5940_Initialize();

      /* Platform configuration */
      /* Step1. Configure clock - NEED THIS */
      clk_cfg.ADCClkDiv = ADCCLKDIV_1; // Clock source divider - ADC
      clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC; // Enables internal high frequency 16/32 MHz clock as source
      clk_cfg.SysClkDiv = SYSCLKDIV_1; // Clock source divider - System
      clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC; // Enables internal high frequency 16/32 MHz clock as source
      clk_cfg.HfOSC32MHzMode = bFALSE; // Sets it to 16 MHz
      clk_cfg.HFOSCEn = bTRUE; // Enables the internal 16 / 32 MHz source
      clk_cfg.HFXTALEn = bFALSE; // Disables any need for external clocks
      clk_cfg.LFOSCEn = bTRUE; // Enables 32 kHz clock for timing / wakeups
      AD5940_CLKCfg(&clk_cfg); // Configures the clock
      Serial.println("Clock setup successfully.");

      /* Step3. Interrupt controller */
      AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);   /* Enable all interrupt in INTC1, so we can check INTC flags */
      AD5940_INTCClrFlag(AFEINTSRC_ALLINT); // Clears all INT flags

      /* Set INT0 source to be DFT READY */
      AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DFTRDY, bTRUE);
      AD5940_INTCClrFlag(AFEINTSRC_ALLINT); // clears all flags
      Serial.println("INTs setup successfully.");

      /* Step4: Reconfigure GPIO */
      gpio_cfg.FuncSet = GP0_INT;

      gpio_cfg.InputEnSet = 0; // Disables any GPIOs as inputs
      gpio_cfg.OutputEnSet = AGPIO_Pin0; // Enables GPIOs as outputs

      gpio_cfg.OutVal = 0; // Value for the output
      gpio_cfg.PullEnSet = 0; // Disables any GPIO pull-ups / Pull-downs

      AD5940_AGPIOCfg(&gpio_cfg); // Configures the GPIOs
      Serial.println("GPIOs setup successfully.");

      /* CONFIGURING FOR DFT */
      // AFE Configuration
      AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);  /* Initializing to disabled state */

      // Enabling high power bandgap since we're using High Power DAC
      // Enables operation at higher frequencies
      aferef_cfg.HpBandgapEn = bTRUE;

      aferef_cfg.Hp1V1BuffEn = bTRUE; // Enables 1v1 buffer
      aferef_cfg.Hp1V8BuffEn = bTRUE; // Enables 1v8 buffer

      /* Not going to discharge capacitors - haven't seen this ever used */
      aferef_cfg.Disc1V1Cap = bFALSE;
      aferef_cfg.Disc1V8Cap = bFALSE;

      /* Disabling buffers and current limits*/
      aferef_cfg.Hp1V8ThemBuff = bFALSE;
      aferef_cfg.Hp1V8Ilimit = bFALSE;

      /* Disabling low power buffers */
      aferef_cfg.Lp1V1BuffEn = bFALSE;
      aferef_cfg.Lp1V8BuffEn = bFALSE;

      /* LP reference control - turn off if no bias */
      if((biasVolt == 0.0f) && (zeroVolt == 0.0f))
      {
        aferef_cfg.LpBandgapEn = bFALSE;
        aferef_cfg.LpRefBufEn = bFALSE;
        printf("No bias today!\n");
      }
      else
      {
        aferef_cfg.LpBandgapEn = bTRUE;
        aferef_cfg.LpRefBufEn = bTRUE;
        printf("We have bias!\n");
      }

      /* Doesn't enable boosting buffer current */
      aferef_cfg.LpRefBoostEn = bFALSE;
      AD5940_REFCfgS(&aferef_cfg);  // Configures the AFE
      Serial.println("AFE setup successfully.");
     
      /* Disconnect SE0 from LPTIA - double check this too */
      LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
      LpAmpCfg.LpPaPwrEn = bFALSE; //bTRUE
      LpAmpCfg.LpTiaPwrEn = bFALSE; //bTRUE
      LpAmpCfg.LpTiaRf = LPTIARF_1M;
      LpAmpCfg.LpTiaRload = LPTIARLOAD_100R;
      LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN; /* Disconnect Rtia to avoid RC filter discharge */
      LpAmpCfg.LpTiaSW = LPTIASW(7)|LPTIASW(8)|LPTIASW(12)|LPTIASW(13);
      AD5940_LPAMPCfgS(&LpAmpCfg);
      Serial.println("SE0 disconnected from LPTIA.");
     
      // Configuring High Speed Loop (high power loop)
      /* Vpp * BufGain * DacGain */
      // HsLoopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_0P25;
      // HsLoopCfg.HsDacCfg.HsDacGain = HSDACGAIN_0P2;

      _extGain = extGain;
      _dacGain = dacGain;

      HsLoopCfg.HsDacCfg.ExcitBufGain = _extGain;
      HsLoopCfg.HsDacCfg.HsDacGain = _dacGain;

      /* For low power / frequency measurements use 0x1B, o.w. 0x07 */
      HsLoopCfg.HsDacCfg.HsDacUpdateRate = 0x1B;
      HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE;

      /* Assuming no bias - default to 1V1 bias */
      if((biasVolt == 0.0f) && (zeroVolt == 0.0f))
      {
        HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
        printf("HSTIA bias set to 1.1V.\n");
      }
      else
      {
        HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0;
        printf("HSTIA bias set to Vzero.\n");
      }

      /* Sets feedback capacitor on HSTIA */
      HsLoopCfg.HsTiaCfg.HstiaCtia = 31; /* 31pF + 2pF */

      /* No load and RTIA on the D Switch*/
      HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
      HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;

      /* Assuming low frequency measurement */
      HsLoopCfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_40K;

      HsLoopCfg.SWMatCfg.Dswitch = SWD_CE0;       // Connects WG to CE0
      HsLoopCfg.SWMatCfg.Pswitch = SWP_RE0;       // Connects positive input to RE0
      HsLoopCfg.SWMatCfg.Nswitch = SWN_SE0;       // Connects negative input to SE0
      HsLoopCfg.SWMatCfg.Tswitch = SWT_SE0LOAD|SWT_TRTIA;   // Connects SEO to HSTIA via SE0Load

      _currentFreq = startFreq;
      HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
      HsLoopCfg.WgCfg.GainCalEn = bTRUE;          // Gain calibration
      HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;        // Offset calibration
      printf("Current Freq: %f\n", _currentFreq);
      HsLoopCfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(_currentFreq, sysClkFreq);
      HsLoopCfg.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)((sineVpp/800.0f)*2047 + 0.5f);
      HsLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
      HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
      AD5940_HSLoopCfgS(&HsLoopCfg);
      Serial.println("HS Loop configured successfully");
     
      /* Configuring Sweep Functionality */
      _sweepCfg.SweepEn = bTRUE;
      _sweepCfg.SweepLog = bTRUE;
      _sweepCfg.SweepIndex = 0;
      _sweepCfg.SweepStart = startFreq;
      _sweepCfg.SweepStop = endFreq;

      _startFreq = startFreq;
      _endFreq = endFreq;
     
      // Defaulting to a logarithmic sweep. Works both upwards and downwards
      if(startFreq > endFreq) _sweepCfg.SweepPoints = (uint32_t)(1.5 + (log10(startFreq) - log10(endFreq)) * (numPoints)) - 1;
      else _sweepCfg.SweepPoints = (uint32_t)(1.5 + (log10(endFreq) - log10(startFreq)) * (numPoints)) - 1;
      printf("Number of points: %d\n", _sweepCfg.SweepPoints);
      Serial.println("Sweep configured successfully.");

       /* Configuring LPDAC if necessary */
      if((biasVolt != 0.0f) || (zeroVolt != 0.0f))
      {
        LPDACCfg_Type lpdac_cfg;
       
        lpdac_cfg.LpdacSel = LPDAC0;
        lpdac_cfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Use Vbias to tune BiasVolt. */
        lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT;  /* Vbias-Vzero = BiasVolt */

        // Uses 2v5 as a reference, can set to AVDD
        lpdac_cfg.LpDacRef = LPDACREF_2P5;
        lpdac_cfg.LpDacSrc = LPDACSRC_MMR;      /* Use MMR data, we use LPDAC to generate bias voltage for LPTIA - the Vzero */
        lpdac_cfg.PowerEn = bTRUE;              /* Power up LPDAC */
        /*
          Default bias case - centered around Vzero = 1.1V
          This works decently well. Error seems to increase as you go higher in bias.
         */
        if(zeroVolt == 0.0f)
        {
          // Edge cases
          if(biasVolt<-1100.0f) biasVolt = -1100.0f + DAC12BITVOLT_1LSB;
          if(biasVolt> 1100.0f) biasVolt = 1100.0f - DAC12BITVOLT_1LSB;
         
          /* Bit conversion from voltage */
          // Converts the bias voltage to a data bit - uses the 1100 to offset it with Vzero
          lpdac_cfg.DacData6Bit = 0x40 >> 1;            /* Set Vzero to middle scale - sets Vzero to 1.1V */
          lpdac_cfg.DacData12Bit = (uint16_t)((biasVolt + 1100.0f)/DAC12BITVOLT_1LSB);
        }
        else
        {
          /*
            Working decently well now.
          */
          lpdac_cfg.DacData6Bit = (uint32_t)((zeroVolt-200)/DAC6BITVOLT_1LSB);
          lpdac_cfg.DacData12Bit = (int32_t)((biasVolt)/DAC12BITVOLT_1LSB) + (lpdac_cfg.DacData6Bit * 64);
          if(lpdac_cfg.DacData12Bit < lpdac_cfg.DacData6Bit * 64) lpdac_cfg.DacData12Bit--; // compensation as per datasheet
        }
        lpdac_cfg.DataRst = bFALSE;      /* Do not reset data register */
        // Allows for measuring of Vbias and Vzero voltages and connects them to LTIA, LPPA, and HSTIA
        lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN|LPDACSW_VZERO2HSTIA;
        AD5940_LPDACCfgS(&lpdac_cfg);
        Serial.println("LPDAC configured successfully.");
      }

      // /* Sets the input of the ADC to the output of the HSTIA */
      dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
      dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;

      /* Programmable gain array for the ADC */
      dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1;
      // dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_2;
     
      /* Disables digital comparator functionality */
      memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
     
      /* Is this actually being used? */
      dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; // Impedance example uses 16
      dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ;  /* Tell filter block clock rate of ADC*/

      dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_1P6MHZ;  /* Tell filter block clock rate of ADC*/
      dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_22; // Oversampling ratio for SINC2
      dsp_cfg.ADCFilterCfg.ADCSinc3Osr = ADCSINC3OSR_2; // Oversampling ratio for SINC3
      /* Using Recommended OSR of 4 for SINC3 */
      // dsp_cfg.ADCFilterCfg.ADCSinc3Osr = ADCSINC3OSR_4; // Oversampling ratio for SINC3

      dsp_cfg.ADCFilterCfg.BpNotch = bTRUE; // Bypasses Notch filter
      dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; // Doesn't bypass SINC3
      dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE; // Enables SINC2 filter

      dsp_cfg.DftCfg.DftNum = DFTNUM_16384; // Max number of DFT points
      dsp_cfg.DftCfg.DftSrc = DFTSRC_SINC3; // Sets DFT source to SINC3
      dsp_cfg.DftCfg.HanWinEn = bTRUE;  // Enables HANNING WINDOW - recommended to always be on
     
      /* Disables STAT block */
      memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg));
     
      AD5940_DSPCfgS(&dsp_cfg); // Sets the DFT
      Serial.println("DSP configured successfully.");

      /* Calculating Clock Cycles to wait given DFT settings */
      clks_cal.DataType = DATATYPE_DFT;
      clks_cal.DftSrc = DFTSRC_SINC3; // Source of DFT
      clks_cal.DataCount = 1L<<(DFTNUM_16384+2); /* 2^(DFTNUMBER+2) */
      clks_cal.ADCSinc2Osr = ADCSINC2OSR_22;
      clks_cal.ADCSinc3Osr = ADCSINC3OSR_2;
      clks_cal.ADCAvgNum = ADCAVGNUM_16;
      clks_cal.RatioSys2AdcClk = sysClkFreq / adcClkFreq; // Same ADC / SYSTEM CLCK FREQ
      AD5940_ClksCalculate(&clks_cal, &_waitClcks);

      /* Clears any interrupts just in case */
      AD5940_ClrMCUIntFlag();
      AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);

      /* Do I need to include AFECTRL_HPREFPWR? It's the only one not here. */

       // Added bias option conditionally
      if((biasVolt == 0.0f) && (zeroVolt == 0.0f))
      {
        AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
                      AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
                      AFECTRL_SINC2NOTCH, bTRUE);
        Serial.println("No bias applied.");
      }
      else
      {
        /*
          Also powers the DC offset buffers that's used with LPDAC (Vbias)
          Buffers need to be powered up here but aren't turned off in measurement like
          the rest. This is to ensure the LPDAC and bias stays on the entire time.
        */
        AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
                      AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
                      AFECTRL_SINC2NOTCH|AFECTRL_DCBUFPWR, bTRUE);
        Serial.println("Bias is applied.");
      }

      // AD5940_SleepKeyCtrlS(SLPKEY_LOCK); // Disables Sleep Mode

      Serial.println("Everything turned on.");
      printf("Number of points to sweep: %d\n", _sweepCfg.SweepPoints);
      printf("Bias: %f, Zero: %f\n", biasVolt, zeroVolt);
    }

    This is how I measure the DFT

     

    void TestLibImp::AD5940_DFTMeasure(void) {
    
      SWMatrixCfg_Type sw_cfg;
      impStruct eis;
    
      /* Real / Imaginary components */
      int32_t realRcal, imageRcal; 
      int32_t realRz, imageRz; 
    
      /* Magnitude / phase */
      float magRcal, phaseRcal; 
      float magRz, phaseRz;
      float calcMag, calcPhase; // phase in rads 
      // float rcalVal = 9930; // known Rcal - measured with DMM
    
      // Serial.print("Recommended clock cycles: ");
      // Serial.println(_waitClcks);
    
      AD5940_Delay10us(_waitClcks * (1/SYSCLCK));
    
      /* Measuring RCAL */
      sw_cfg.Dswitch = SWD_RCAL0;
      sw_cfg.Pswitch = SWP_RCAL0;
      sw_cfg.Nswitch = SWN_RCAL1;
      sw_cfg.Tswitch = SWT_RCAL1|SWT_TRTIA;
      AD5940_SWMatrixCfgS(&sw_cfg);
    	
    	AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
                    AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
                    AFECTRL_SINC2NOTCH, bTRUE);
    
      AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE);  /* Enable Waveform generator */
      // delay(500); 
      settlingDelay(_currentFreq);
      
      AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE);  /* Start ADC convert and DFT */
      settlingDelay(_currentFreq);
    
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
    
      /* Polling and retrieving data from the DFT */
      pollDFT(&realRcal, &imageRcal);
    
      // AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      // AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
    
      //wait for first data ready
      AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE);  /* Stop ADC convert and DFT */
    
      sw_cfg.Dswitch = SWD_CE0;
      sw_cfg.Pswitch = SWP_RE0;
      sw_cfg.Nswitch = SWN_SE0;
      sw_cfg.Tswitch = SWT_TRTIA|SWT_SE0LOAD;
      AD5940_SWMatrixCfgS(&sw_cfg);
      // Serial.println("Switched to SE0.");
    
      AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE);  /* Enable Waveform generator */
      // delay(500);
      settlingDelay(_currentFreq);
    
      AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE);  /* Start ADC convert and DFT */
      settlingDelay(_currentFreq);
      // delay(500);
    
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
    
      /* Polling and retrieving data from the DFT */
      pollDFT(&realRz, &imageRz);
    
      // AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
      // AD5940_Delay10us((_waitClcks / 2) * (1/SYSCLCK));
    
      AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE);  /* Stop ADC convert and DFT */
      AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
                    AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
                    AFECTRL_SINC2NOTCH, bFALSE);
    
      // Serial.println("Measurement sequence finished.");
    
      getMagPhase(realRcal, imageRcal, &magRcal, &phaseRcal);
      getMagPhase(realRz, imageRz, &magRz, &phaseRz);
    
      /* Finding the actual magnitude and phase */
      eis.magnitude = (magRcal / magRz) * _rcalVal; 
      eis.phaseRad = phaseRcal - phaseRz;
      eis.real = eis.magnitude * cos(eis.phaseRad);
      eis.imag = eis.magnitude * sin(eis.phaseRad) * -1; 
      eis.phaseDeg = eis.phaseRad * 180 / MATH_PI; 
      eis.freq = _currentFreq;
    
      /* Printing Values */
      printf("%d,", _sweepCfg.SweepIndex);
      printf("%.2f,", _currentFreq);
      printf("%.3f,", magRcal);
      printf("%.3f,", magRz);
      printf("%f,", eis.magnitude);
      printf("%.4f,", eis.real);
      printf("%.4f,", eis.imag);
      printf("%.4f\n", eis.phaseRad);
    
      eisArr[_sweepCfg.SweepIndex + (_currentCycle * _sweepCfg.SweepPoints)] = eis; 
      // printf("Array Index: %d\n",_sweepCfg.SweepIndex + (_currentCycle * _sweepCfg.SweepPoints));
    
      /* Updating Frequency */
      logSweep(&_sweepCfg, &_currentFreq);
    }
    
    void TestLibImp::getDFT(int32_t* pReal, int32_t* pImage) 
    { 
      *pReal = AD5940_ReadAfeResult(AFERESULT_DFTREAL);
      *pReal &= 0x3ffff;
      /* Data is 18bit in two's complement, bit17 is the sign bit */
      if(*pReal&(1<<17)) *pReal |= 0xfffc0000;     
    
      delay(200); 
    
      *pImage = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
      *pImage &= 0x3ffff;
      /* Data is 18bit in two's complement, bit17 is the sign bit */
      if(*pImage&(1<<17)) *pImage |= 0xfffc0000; 
    
      // printf("Real: %d, Image: %d\n", *pReal, *pImage);
    }

    And this is how I run a sweep:

    void TestLibImp::runSweep(uint32_t numCycles, uint32_t delaySecs) 
    {
      _numCycles = numCycles; 
      _currentCycle = 0; 
      /*
        Need to not run the program if ArraySize < total points 
        TO DO: ADD A CHECK HERE  
      */
      printf("Total points to run: %d\n", (_numCycles + 1) * _sweepCfg.SweepPoints); // since 0 based indexing, add 1
      printf("Set array size: %d\n", ARRAY_SIZE);
      printf("Calibration resistor value: %f\n", _rcalVal);
    
      // LED to show start of spectroscopy 
      // digitalWrite(LED1, HIGH); 
    
      for(uint32_t i = 0; i <= numCycles; i++) {
        /* 
          Wakeup AFE by read register, read 10 times at most.
          Do this because AD594x goes to sleep after each cycle. 
        */
        AD5940_SleepKeyCtrlS(SLPKEY_LOCK); // Disables Sleep Mode 
        if(delaySecs)
        {
          printf("Delaying for %d seconds\n", delaySecs);
          delay(delaySecs * 1000); // Delaying for equilibration 
        } 
        // Timer for cycle time
        unsigned long timeStart = millis();
    
        if(i > 0){
          if(AD5940_WakeUp(10) > 10) Serial.println("Wakeup failed!");       
           resetSweep(&_sweepCfg, &_currentFreq);
           delay(300); // empirical settling delay
           _currentCycle++;
        }
        
        /* Calibrates based on frequency */
        // Should calibrate when AFE is active
        // checkFreq(_currentFreq);
        configureFrequency(_currentFreq);
        delay(10); // switching delay
    
        printf("Cycle %d\n", i);
        printf("Index, Frequency (Hz), DFT Cal, DFT Mag, Rz (Ohms), Rreal, Rimag, Rphase (rads)\n");
        
        while(_sweepCfg.SweepEn == bTRUE)
        {
          AD5940_DFTMeasure();
          // AD5940_DFTMeasureEIS();
          AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
                  AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
                  AFECTRL_SINC2NOTCH, bTRUE);
          delay(200);
        }
    
        unsigned long timeEnd = millis(); 
        printf("Time spent running Cycle %d (seconds): %lu\n", i, (timeEnd-timeStart)/1000);
      }
      
      /* Shutdown to conserve power. This turns off the LP-Loop and resets the AFE. */
      AD5940_ShutDownS();
      Serial.println("All cycles finished.");
      Serial.println("AD594x shutting down.");
    
      /* LEDs to show end of cycle */
      // digitalWrite(LED1, LOW);
      // digitalWrite(LED2, HIGH);
    }
    

    For calibrating based on frequency, I use a method similar to the AppImpCheckFreq function (but made the if-statements variable to make it easier to change things).

  • Hi,

    For measurement at low frequencies (near 1Hz), ODR must be set to very small value. It may take around 1 minute for measurement of 1 sample.

    For adapting to frequency, AppImpCheckFreq() must be called after measurement of each sample or inside   /* Updating Frequency */ section in your code.

    Are you changing HSRTIA value w.r.t. frequency inside AppImpCheckFreq()?

    input current Imax = 0.9/RTIAmin   (for PGA gain = 1 or 1.5)

    If the expected input current at low frequencies is higher than Imax, then RTIA must be reduced further than minimum internal RTIA value (RTIAmin ).  This can be done by using external RTIA of value <200 Ohms.

    Kindly refer to "External RTIA Selection" in datasheet for details on using external feedback resistor with HSTIA.

  • Hi Akila, 

    Yes, I have been updating frequency and HSRTIA w.r.t frequency. I've attached the codes below for your reference.

    I have a few questions based on this discussion so far: 

    You mention an Imax, but my issue is with low frequencies where the impedance is high. Is there an Imin?

    Can you please explain why the ODR must be set to a very small value? I understand that long measurements at low frequencies are a limitation of the AD5941, but I'd like to understand why that is the case.

    Furthermore, you mention it may take around 1 minute for the measurement of 1 sample. Does this refer to letting the generated signal wait for a minute before starting the conversion for a DFT?

    Generalizing that to frequencies < 1 Hz, how many periods do you recommend waiting for?

    I notice that there's also a delay once the DFT begins - does this need to be 1 minute as well? Or is this delay only to give time before reading the FIFO? If I'm not using the FIFO, do I need to delay after the DFT conversion starts if I'm polling the register anyways?

    As always, thank you for all your help!

    Here is the code for log sweep

    void TestLibImp::logSweep(SoftSweepCfg_Type *pSweepCfg, float *pNextFreq) 
    {
      float frequency; 
    
      // if you reach last point, go back to 0
      // if(++pSweepCfg->SweepIndex == pSweepCfg->SweepPoints) pSweepCfg->SweepIndex = 0;
      // if you reach last point, end the cycle
      if(++pSweepCfg->SweepIndex == pSweepCfg->SweepPoints) pSweepCfg -> SweepEn = bFALSE;
      else {
        frequency = pSweepCfg->SweepStart * pow(10, (pSweepCfg->SweepIndex * log10(pSweepCfg->SweepStop/pSweepCfg->SweepStart)/(pSweepCfg->SweepPoints-1)));
        *pNextFreq = frequency;
    
        /* Calibrating based on frequency */
        AD5940_WGFreqCtrlS(frequency, SYSCLCK);
        // checkFreq(frequency);
        configureFrequency(frequency);
      }
    }

    And here is the configure frequency code: 

    AD5940Err TestLibImp::setHSTIA(float freq)
    {
      HSDACCfg_Type hsdac_cfg;
      FreqParams_Type freq_params;
      ClksCalInfo_Type clks_cal;
      ADCFilterCfg_Type filter_cfg;
      DFTCfg_Type dft_cfg;
      float adcClck;

      AD5940Err exitStatus = AD5940ERR_ERROR;

      hsdac_cfg.ExcitBufGain = _extGain;
      hsdac_cfg.HsDacGain = _dacGain;

      // hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_0P25;
      // hsdac_cfg.HsDacGain = HSDACGAIN_0P2;

      // hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_2;
      // hsdac_cfg.HsDacGain = HSDACGAIN_1;

      /* Getting optimal freq parameters */
      freq_params = AD5940_GetFreqParameters(freq);
     
      for(uint32_t i = 0; i < _gainArrSize; i++)
      {
        if(freq <= _gainArr[i].freq)
        {
          if(freq >= 80000) // High Power Mode Case
          {
            hsdac_cfg.HsDacUpdateRate = 0x07;
            AD5940_HSDacCfgS(&hsdac_cfg);
            AD5940_HSRTIACfgS(_gainArr[i].rTIA);
          __AD5940_SetDExRTIA(0, HSTIADERTIA_OPEN, HSTIADERLOAD_0R);
           
            // AD5940_HPModeEn(bTRUE);
            // printf("Setting HSTIA to: %d for %.2f Hz\n", _gainArr[i].rTIA, freq);

            filter_cfg.ADCRate = ADCRATE_1P6MHZ;
            adcClck = 32e6;
            AD5940_HPModeEn(bTRUE);
            exitStatus = AD5940ERR_OK;
            break;

            // return AD5940ERR_OK;
          }
          else
          {
            hsdac_cfg.HsDacUpdateRate = 0x1B;
            AD5940_HSDacCfgS(&hsdac_cfg);
            AD5940_HSRTIACfgS(_gainArr[i].rTIA);
          __AD5940_SetDExRTIA(0, HSTIADERTIA_OPEN, HSTIADERLOAD_0R);

            // AD5940_HPModeEn(bFALSE);
            // printf("Setting HSTIA to: %d for %.2f Hz\n", _gainArr[i].rTIA, freq);

            filter_cfg.ADCRate = ADCRATE_800KHZ;
            adcClck = 16e6;
            AD5940_HPModeEn(bFALSE); // Low Power Mode
            exitStatus = AD5940ERR_OK;
            break;

            // return AD5940ERR_OK;
          }
        }
      }
      // Serial.println("No longer in for loop!");
      filter_cfg.ADCAvgNum = ADCAVGNUM_16;  // Not using this so it doesn't matter
      filter_cfg.ADCSinc2Osr = freq_params.ADCSinc2Osr;
      filter_cfg.ADCSinc3Osr = freq_params.ADCSinc3Osr;
      filter_cfg.BpSinc3 = bFALSE;
      filter_cfg.BpNotch = bTRUE; // Not using onboard 60 Hz Notch Filter
      filter_cfg.Sinc2NotchEnable = bTRUE;
     
      dft_cfg.DftNum = freq_params.DftNum;
      dft_cfg.DftSrc = freq_params.DftSrc;
      dft_cfg.HanWinEn = bTRUE;
      // Serial.println("Filter and DFT configured.");

      AD5940_ADCFilterCfgS(&filter_cfg);
      AD5940_DFTCfgS(&dft_cfg);

      /* Calculating clock cycles - usually used for FIFO but I use it here
      as an additional wait time calculator. Potentially redundant, but keeping it for now.*/
      clks_cal.DataType = DATATYPE_DFT;
      clks_cal.DftSrc = freq_params.DftSrc;
      clks_cal.DataCount = 1L<<(freq_params.DftNum+2); /* 2^(DFTNUMBER+2) */
      clks_cal.ADCSinc2Osr = freq_params.ADCSinc2Osr;
      clks_cal.ADCSinc3Osr = freq_params.ADCSinc3Osr;
      clks_cal.ADCAvgNum = 0;
      clks_cal.RatioSys2AdcClk = SYSCLCK/adcClck;
      AD5940_ClksCalculate(&clks_cal, &_waitClcks);
      // Serial.println("Clocks calculated.");
      // If we can't calibrate based on configuration, return an error.
     
      return exitStatus;
    }

    void TestLibImp::configureFrequency(float freq)
    {
      AD5940Err check = setHSTIA(freq);
      // configureDFT(freq);

      if(check != AD5940ERR_OK) Serial.println("Unable to configure.");
      // else Serial.println("Configured successfully.");
    }
  • Hi,

    Imin = 50pA

    If DFT number chosen is 8192, then DFT block requires 8192 ADC samples before DFT conversion. Hence, if excitation frequency is 1Hz, ODR value must be less than (1/8192).

    AD5940_ClksCalculate() function defined in AD5940.c is used to calculate the number of Waitclks needed to generate required number of data from filter output.

    ODR must be less than (1/Waitclks).

    Polling the registers take negligible time. No separate delay is needed to be set for that.