Post Go back to editing

EVAL-AD5940BIOZ ECG SensorPal and C code example difference

I am using your AD5940-Bioz dev kit to measure ECG (simulator). When I use SensorPal it works acceptable, but when I use your C code example, it does not work at all. 

I wanted to ask what do I need to change in the example (AD5940_ECG) for it to work the same way as it does in SensorPal?

Parents Reply Children
  • Hi,

    Are you telling about the x-axis?

    In SensorPal, x-axis is time :

    whereas in C-code used with IDE, the X-axis is measured sample index:

    Also, 

    For the configuration below in C-Code:

    adc_filter.BpNotch = bTRUE; 
    adc_filter.BpSinc3 = bTRUE; 
    adc_filter.Sinc2NotchEnable = bTRUE;

    clks_cal.DataType = DATATYPE_SINC3;

    AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); /*instead of SEQ_WAIT(WaitClks)*/

    the following clearer output is obtained:

    With AD5940_SEQGenInsert(SEQ_WAIT(WaitClks), the below output is obtained:

  • No, I am not talking about the x axis. I do understand sampling and all. Those ripples in both C code pictures are 50 Hz, the peaks in the first one are like 17 Hz, which is so random. In a few days I will try to use:

    AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); /*instead of SEQ_WAIT(WaitClks)*/

    But I have tried other options (filters on/off, output channels) and none of them worked. I have tried using different sampling rate (250 instead of 500), different serial communication speed, nothing helped. 

  • I've tried using AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2)); but then I would not get any readings at all (no wrong data, just nothing printing out).

    I had somewhat of an advancement, though. When I disconnect an ethernet cable from the laptop (power supply, monitor etc. is always disconnected), I do get somewhat of a signal with the C code, but when the ethernet cable is connected, C code gives those "signals" that I've posted above. SensorPal on the other, gives me pretty much the same - normal signal in both cases.

    Does SensorPal do some additional filtering? Even if that's the case, I feel like there is still some setup configuration that I am missing, since the noise seems to overlap with the ECG spectrum. 

  • Hi,

    Did you check with the above filter settings that I have mentioned?

  • None of the proposed solutions helped. In the end I found out that power supply and overall environment noise was making all the problem. It did not, however, explain how the GUI still managed to record a good quality ECG in the noisy environment, while the C example could not.

  • Hello. Has there been any development in this? I am getting exact same noisy plots when running ECG example, while SensorPal shows a perfect cardiogram. I've tried the changes posted here, the plots change, but are still unusable. So I'd like to know how SensorPal produces such perfect plots.

  • Hi,

    With the code below, I get the below ECG output:

    /*!
     *****************************************************************************
     @file:    Electrocardiograph.c
     @author:  Neo Xu
     @brief:   ECG Measurement.
     -----------------------------------------------------------------------------
    
    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"
    #include <stdio.h>
    #include "string.h"
    #include "math.h"
    #include "Electrocardiograph.h"
    
    /* 
      Application configuration structure. Specified by user from template.
      The variables are usable in this whole application.
      It includes basic configuration for sequencer generator and application related parameters
    */
    AppECGCfg_Type AppECGCfg = 
    {
      .bParaChanged = bFALSE,
      .bBioElecBoard = bTRUE,
      .SeqStartAddr = 0,
      .MaxSeqLen = 512,
    
      .SeqStartAddrCal = 0,
      .MaxSeqLenCal = 512,
    
      .ECGODR = 1000.0,           /* 1000.0 Hz*/
      .NumOfData = -1,
      .FifoThresh = 100,
    
      .LfoscClkFreq = 32000.0,
      .SysClkFreq = 16000000.0,
      .AdcClkFreq = 16000000.0,
      .PwrMod = AFEPWR_LP,
    
      .AdcPgaGain = ADCPGA_1,
      .ADCSinc3Osr = ADCSINC3OSR_2,
      .ADCSinc2Osr = ADCSINC2OSR_22,
    
      .ECGInited = bFALSE,
      .StopRequired = bFALSE,
      .FifoDataCount = 0,
    };
    
    /**
       This function is provided for upper controllers that want to change 
       application parameters specially for user defined parameters.
    */
    AD5940Err AppECGGetCfg(void *pCfg)
    {
      if(pCfg){
        *(AppECGCfg_Type**)pCfg = &AppECGCfg;
        return AD5940ERR_OK;
      }
      return AD5940ERR_PARA;
    }
    
    int32_t AppECGCtrl(int32_t Command, void *pPara)
    {
      
      switch (Command)
      {
        case APPCTRL_START:
        {
          WUPTCfg_Type wupt_cfg;
    
          if(AD5940_WakeUp(10) > 10)  /* Wakeup AFE by read register, read 10 times at most */
            return AD5940ERR_WAKEUP;  /* Wakeup Failed */
          if(AppECGCfg.ECGInited == bFALSE)
            return AD5940ERR_APPERROR;
          /* Start it */
          wupt_cfg.WuptEn = bTRUE;
          wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
          wupt_cfg.WuptOrder[0] = SEQID_0;
          wupt_cfg.SeqxSleepTime[SEQID_0] = 4-1;
          wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppECGCfg.LfoscClkFreq/AppECGCfg.ECGODR)-4-1;
          AD5940_WUPTCfg(&wupt_cfg);
          
          AppECGCfg.FifoDataCount = 0;  /* restart */
          break;
        }
        case APPCTRL_STOPNOW:
        {
          if(AD5940_WakeUp(10) > 10)  /* Wakeup AFE by read register, read 10 times at most */
            return AD5940ERR_WAKEUP;  /* Wakeup Failed */
          /* Start Wupt right now */
          AD5940_WUPTCtrl(bFALSE);
          /* There is chance this operation will fail because sequencer could put AFE back 
            to hibernate mode just after waking up. Use STOPSYNC is better. */
          AD5940_WUPTCtrl(bFALSE);
          break;
        }
        case APPCTRL_STOPSYNC:
        {
          AppECGCfg.StopRequired = bTRUE;
          break;
        }
        case APPCTRL_SHUTDOWN:
        {
          AppECGCtrl(APPCTRL_STOPNOW, 0);  /* Stop the measurement if it's running. */
          /* Turn off LPloop related blocks which are not controlled automatically by hibernate operation */
          AFERefCfg_Type aferef_cfg;
          LPLoopCfg_Type lp_loop;
          memset(&aferef_cfg, 0, sizeof(aferef_cfg));
          AD5940_REFCfgS(&aferef_cfg);
          memset(&lp_loop, 0, sizeof(lp_loop));
          AD5940_LPLoopCfgS(&lp_loop);
          AD5940_EnterSleepS();  /* Enter Hibernate */
        }
        break;
        default: break;
      }
      return AD5940ERR_OK;
    }
    
    /* Application initialization */
    static AD5940Err AppECGSeqCfgGen(void)
    {
      AD5940Err error = AD5940ERR_OK;
      const uint32_t *pSeqCmd;
      uint32_t SeqLen;
      AFERefCfg_Type aferef_cfg;
      ADCBaseCfg_Type adc_base;
      ADCFilterCfg_Type adc_filter;
      SWMatrixCfg_Type sw_matrix;
    
      /* Start sequence generator here */
      AD5940_SEQGenCtrl(bTRUE);
      
      AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);  /* Init all to disable state */
    
      aferef_cfg.HpBandgapEn = bTRUE;
      aferef_cfg.Hp1V1BuffEn = bTRUE;
      aferef_cfg.Hp1V8BuffEn = bTRUE;       /* The High speed buffers are automatically turned off during hibernate */
      aferef_cfg.Disc1V1Cap = bFALSE;
      aferef_cfg.Disc1V8Cap = bFALSE;
      aferef_cfg.Hp1V8ThemBuff = bFALSE;
      aferef_cfg.Hp1V8Ilimit = bFALSE;
      aferef_cfg.Lp1V1BuffEn = bFALSE;
      aferef_cfg.Lp1V8BuffEn = bFALSE;
      /* LP reference control - turn off them to save power*/
      aferef_cfg.LpBandgapEn = bFALSE;
      aferef_cfg.LpRefBufEn = bFALSE;
      aferef_cfg.LpRefBoostEn = bFALSE;
      AD5940_REFCfgS(&aferef_cfg);	
    
      /* Initialize ADC basic function */
      adc_base.ADCMuxP = ADCMUXP_AIN6;
      adc_base.ADCMuxN = ADCMUXN_VSET1P1;
      adc_base.ADCPga = AppECGCfg.AdcPgaGain;
      AD5940_ADCBaseCfgS(&adc_base);
      
      /* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
      adc_filter.ADCSinc3Osr = AppECGCfg.ADCSinc3Osr;
      adc_filter.ADCSinc2Osr = AppECGCfg.ADCSinc2Osr;
      adc_filter.ADCAvgNum = ADCAVGNUM_2;         /* Don't care about it. Average function is only used for DFT */
      adc_filter.ADCRate = 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.Sinc3ClkEnable = bTRUE;          /* Enable SINC3 clock.  */
    //  adc_filter.Sinc2NotchClkEnable = bTRUE;     
      adc_filter.Sinc2NotchEnable = bTRUE;        /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
    //  adc_filter.DFTClkEnable = bTRUE;
    //  adc_filter.WGClkEnable = bTRUE;  
      AD5940_ADCFilterCfgS(&adc_filter);
    
      sw_matrix.Dswitch = SWD_OPEN;
      /* Performing a three wire ECG measurement */
      sw_matrix.Pswitch = SWP_RE1|SWP_DE0;
      sw_matrix.Nswitch = SWN_AIN2|SWN_SE0;
      sw_matrix.Tswitch = SWT_AIN0|SWT_AFE3LOAD;
      AD5940_SWMatrixCfgS(&sw_matrix);
    
      AD5940_AFECtrlS(AFECTRL_HPREFPWR, bTRUE); /* Enable reference. It's automatically turned off during hibernate */
    
      /* Enable all of them. They are automatically turned off during hibernate mode to save power */
      AD5940_SEQGpioCtrlS(/*AGPIO_Pin6|*/AGPIO_Pin5|AGPIO_Pin1); /* GP6 to indicate sequencer is running. GP5 to disable AD8233. GP1 to enable AD8233 RLD function. */
    
        /* Sequence end. */
      AD5940_SEQGenInsert(SEQ_STOP()); /* Add one extra command to disable sequencer for initialization sequence because we only want it to run one time. */
    
      /* Stop here */
      error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
      AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
      if(error == AD5940ERR_OK)
      {
        AppECGCfg.InitSeqInfo.SeqId = SEQID_1;
        AppECGCfg.InitSeqInfo.SeqRamAddr = AppECGCfg.SeqStartAddr;
        AppECGCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
        AppECGCfg.InitSeqInfo.SeqLen = SeqLen;
        /* Write command to SRAM */
        AD5940_SEQCmdWrite(AppECGCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
      }
      else
        return error; /* Error */
      return AD5940ERR_OK;
    }
    
    static AD5940Err AppECGSeqMeasureGen(void)
    {
      AD5940Err error = AD5940ERR_OK;
      const uint32_t *pSeqCmd;
      uint32_t SeqLen;
      
      uint32_t WaitClks;
      ClksCalInfo_Type clks_cal;
    
      clks_cal.DataType = DATATYPE_SINC3;
      clks_cal.DataCount = 1;             /* Sample one data when wakeup */
      clks_cal.ADCSinc2Osr = AppECGCfg.ADCSinc2Osr;
      clks_cal.ADCSinc3Osr = AppECGCfg.ADCSinc3Osr;
      clks_cal.ADCAvgNum = 0;
      clks_cal.RatioSys2AdcClk = AppECGCfg.SysClkFreq/AppECGCfg.AdcClkFreq;
      AD5940_ClksCalculate(&clks_cal, &WaitClks);
      //printf("Wait clocks:%d\n", WaitClks);
      AD5940_SEQGenCtrl(bTRUE);
      AD5940_SEQGpioCtrlS(AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1);//GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
      AD5940_SEQGenInsert(SEQ_WAIT(16*200));  /* Time for reference settling.*/
      AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
      AD5940_SEQGenInsert(SEQ_WAIT(16*50));
      AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);  /* Start ADC convert */
      AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
      //wait for first data ready
      AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_ADCPWR, bFALSE);  /* Stop ADC convert and DFT */
      AD5940_SEQGpioCtrlS(/*AGPIO_Pin6|*/AGPIO_Pin5|AGPIO_Pin1); /* GP6 to indicate Sequencer is running. GP5 to enable AD8233. GP1 to enable AD8233 RLD function. */
      AD5940_EnterSleepS();/* Goto hibernate */
      /* Sequence end. */
      error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
      AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
    
      if(error == AD5940ERR_OK)
      {
        AppECGCfg.MeasureSeqInfo.SeqId = SEQID_0;
        AppECGCfg.MeasureSeqInfo.SeqRamAddr = AppECGCfg.InitSeqInfo.SeqRamAddr + AppECGCfg.InitSeqInfo.SeqLen ;
        AppECGCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
        AppECGCfg.MeasureSeqInfo.SeqLen = SeqLen;
        /* Write command to SRAM. The buffer 'pSeqCmd' will be used to generate next sequence  */
        AD5940_SEQCmdWrite(AppECGCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
      }
      else
        return error; /* Error */
      return AD5940ERR_OK;
    }
    
    /* This function provide application initialize. It can also enable Wupt that will automatically trigger sequence. Or it can configure  */
    int32_t AppECGInit(uint32_t *pBuffer, uint32_t BufferSize)
    {
      AD5940Err error = AD5940ERR_OK;  
    
      SEQCfg_Type seq_cfg;
      if(AD5940_WakeUp(10) > 10)  /* Wakeup AFE by read register, read 10 times at most */
        return AD5940ERR_WAKEUP;  /* Wakeup Failed */
        /* Configure sequencer and stop it */
      seq_cfg.SeqMemSize = SEQMEMSIZE_2KB;
      seq_cfg.SeqBreakEn = bFALSE;
      seq_cfg.SeqIgnoreEn = bFALSE;
      seq_cfg.SeqCntCRCClr = bTRUE;
      seq_cfg.SeqEnable = bFALSE;
      seq_cfg.SeqWrTimer = 0;
      AD5940_SEQCfg(&seq_cfg);
      
      /* Reconfigure FIFO */
      AD5940_FIFOCtrlS(FIFOSRC_SINC3, bFALSE);									/* Disable FIFO firstly */
      AD5940_FIFOThrshSet(AppECGCfg.FifoThresh);
      AD5940_FIFOCtrlS(FIFOSRC_SINC3, bTRUE);
      
      AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
    
      /* Start sequence generator */
      /* Initialize sequencer generator */
      if((AppECGCfg.ECGInited == bFALSE)||\
           (AppECGCfg.bParaChanged == bTRUE))
      {
        if(pBuffer == 0)  return AD5940ERR_PARA;
        if(BufferSize == 0) return AD5940ERR_PARA;   
        AD5940_SEQGenInit(pBuffer, BufferSize);
    
        /* Generate initialize sequence */
        error = AppECGSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
        if(error != AD5940ERR_OK) return error;
    
        /* Generate measurement sequence */
        error = AppECGSeqMeasureGen();
        if(error != AD5940ERR_OK) return error;
    
        AppECGCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
      }
    
      /* Initialization sequencer  */
      AppECGCfg.InitSeqInfo.WriteSRAM = bFALSE;
      AD5940_SEQInfoCfg(&AppECGCfg.InitSeqInfo);
      AD5940_SEQCtrlS(bTRUE);  /* Enable sequencer */
      AD5940_SEQMmrTrig(AppECGCfg.InitSeqInfo.SeqId);
      while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
      
      /* Measurement sequence  */
      AppECGCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
      AD5940_SEQInfoCfg(&AppECGCfg.MeasureSeqInfo);
    
      AD5940_SEQCtrlS(bTRUE);  /* Enable sequencer, and wait for trigger. It's disabled in initialization sequence */
      AD5940_ClrMCUIntFlag();   /* Clear interrupt flag generated before */
    
      AD5940_AFEPwrBW(AppECGCfg.PwrMod, AFEBW_250KHZ);
    
      AppECGCfg.ECGInited = bTRUE;  /* ECG application has been initialized. */
      return AD5940ERR_OK;
    }
    
    /* Modify registers when AFE wakeup */
    int32_t AppECGRegModify(int32_t * const pData, uint32_t *pDataCount)
    {
      if(AppECGCfg.NumOfData > 0)
      {
        AppECGCfg.FifoDataCount += *pDataCount/4;
        if(AppECGCfg.FifoDataCount >= AppECGCfg.NumOfData)
        {
          AD5940_WUPTCtrl(bFALSE);
          return AD5940ERR_OK;
        }
      }
      if(AppECGCfg.StopRequired == bTRUE)
      {
        AD5940_WUPTCtrl(bFALSE);
        return AD5940ERR_OK;
      }
      return AD5940ERR_OK;
    }
    
    /* Depending on the data type, do appropriate data pre-process before return back to controller */
    static int32_t AppECGDataProcess(int32_t * const pData, uint32_t *pDataCount)
    {
      uint32_t DataCount = *pDataCount;
      
      *pDataCount = 0;
    
      /* Get ADC result */
      for(uint32_t i=0; i<DataCount; i++)
      {
        pData[i] &= 0xffff; /* @todo option to check ECC */
      }
      *pDataCount = DataCount; 
    
      return 0;
    }
    
    /**
    
    */
    AD5940Err AppECGISR(void *pBuff, uint32_t *pCount)
    {
      uint32_t BuffCount;
      uint32_t FifoCnt;
      BuffCount = *pCount;
      
      if(AD5940_WakeUp(10) > 10)  /* Wakeup AFE by read register, read 10 times at most */
        return AD5940ERR_WAKEUP;  /* Wakeup Failed */
      AD5940_SleepKeyCtrlS(SLPKEY_LOCK);     /* We are operating registers, so we don't allow AFE enter sleep mode which is done in our sequencer */
      *pCount = 0;
      if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
      {
        /* Now there should be 4 data in FIFO */
        FifoCnt = AD5940_FIFOGetCnt();
        
        if(FifoCnt > BuffCount)
        {
          ///@todo buffer is limited.
        }
        AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
        AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
        AppECGRegModify(pBuff, &FifoCnt);   /* If there is need to do AFE re-configure, do it here when AFE is in active state */
        AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);    /* Allow AFE to enter sleep mode. AFE will stay at active mode until sequencer trigger sleep */
        /* AD5940_EnterSleepS(); // We cannot manually put AFE to hibernate because it's possible sequencer is running to take measurements */
        /* Process data */ 
        AppECGDataProcess(pBuff,&FifoCnt); 
        *pCount = FifoCnt;
        return AD5940ERR_OK;
      }
      
      return AD5940ERR_OK;
    }