Porting of examples on EVAL-CN0428-EBZ to AD5940 with ad5940lib


I would like to use the AD5940 chip with a custom design in order to provide pH measures on water. To achieve this, I ported the ad5940lib  to our MCU to communicate with the chip through SPI and I successfully run the examples on the ad5940-examples repository.

To evaluate the capability of the AD5940 chip to perform pH measures, I previously tested the EVAL-CN0428-EBZ board with the "Water Quality" example and I obtained satisfactory results.

Now, I would like to port this example to our platform with the AD5940 chip (preferably using the ad5940lib) but I found some difficulties in this process, in particular:

  1. The EVAL-CN0428-EBZ uses the ADuCM355 instead of the AD5940 and the API used to configure the chip is completely different respect to the ad5940lib;
  2. The repository aducm355-examples is still empty, so I can make no comparison with the configuration of the ADuCM355 using the ad5940lib;
  3. I can't see any useful example in the ad5940-examples repository to perform pH measures;

Is there any possibility to get the pH measure example ported to the ad5940lib? If not, do you suggest to implement the application using the ad5940lib anyway or do you think there is a simpler approach to get the example work?

Thank you.

  • 0
    •  Analog Employees 
    on Jan 23, 2020 4:57 PM


    I might be able to give you a partial answer here and someone else might be able to add if they have additional experience. I was a designer on the CN0428. I haven't ported the CN0428 to AD5940, but if all you need is the pH measurement piece of it, I think it should translate pretty easily just by matching the ADC configuration. I think the best starting point is probably ad5940_ADCMeanFIFO.

    To break the CN0428 measurement routine down into steps:

    1. The pH electrode is buffered by LTC6078 and then routed to an analog input

      - See the CN0428 schematic for protection resistance and analog low-pass filter

    2. The ADC converts the voltage at the analog input

      - The relevant function in the CN0428 source code is called SnsMeasure() in the M355WqEcTests.c file, and you can probably match the adc configuration from there.

      - The key is that the ADC samples at 800kSPS, the SINC3 OSR is 4, and the SINC 2 OSR is 1333, which is an overall 150 SPS. Then 15 consecutive samples are averaged to get 10 SPS. This number was chosen because it gets you an integer division of 50 and 60Hz for a little extra power-line rejection (in addition to the notch being enabled). If you wanted to do more averaging with the stats block like in the 5940 example project, it couldn't hurt. But you may still want to test doing some averages in firmware to get to an integer division of 50 and 60Hz like the CN0428.

    3. The adc code is converted to pH in firmware, and temperature compensation and calibration are applied

      - In the CN0428 source code, this is the Wq_CalculatePH() function in the M355WqCmd.c file. This would be something you could copy/modify for your microcontroller.

    Sorry if this isn't the ideal answer. But I hope it helps.


  • Hi Scott,

    Thank you very much for the quick feedback.

    I will give a try following your hits!



  • Hi Scott,

    I ported the SnsMeasure() function on the CN0428 example to our MCU using the ad5940lib but I'm still not able to get the right values from the pH probe (in the CN0428 demo I get an ADC code which is over 30000 while in the AD5940 I get values around 2000).

    In particular, I observed the following problems:

    1. At the moment we are using the AD5940 on the EVAL-AD5940ELCZ board but, comparing its scheme with the CN0428 one, I have found something strange. In the ADC MUX configuration, I used the AFE4 pin as the positive input but for the negative input the EVAL-AD5940ELCZ scheme suggests to use the CE0 pin which perhaps is not usable as the negative input. To overcome this issue I removed R32 and C12 from the board and I connected together the CE0_AUX and VZERO signals. I made this connection based on what I saw on the CN0428 scheme, is it ok or I made something wrong?
    2. Looking at the source code for the CN0428 I saw that there are many configuration steps which are performed in the SnsInit() function but that you didn't mention in your answer. Can you please explain what are those steps for? Are they needed to get the right pH measure?

    The function which I'm actually using for the configuration and the read of the sensor is the following:

    void AD5940_Main(void) {
        uint32_t tempreg;
        float_t adc_v;
        ADCBaseCfg_Type adc_base;
        ADCFilterCfg_Type adc_filter;
        StatCfg_Type stat_cfg;
        FIFOCfg_Type fifo_cfg;
        ADCPGACal_Type cal_cfg;
        /* Use hardware reset */
        /* Firstly call this function after reset to initialize AFE registers. */
        /* Calibration */
        cal_cfg.ADCPga = ADCPGA_1;
        cal_cfg.ADCSinc2Osr = ADCSINC2OSR_1333;
        cal_cfg.ADCSinc3Osr = ADCSINC3OSR_4;
        cal_cfg.AdcClkFreq = 16000000; //
        cal_cfg.PGACalType = PGACALTYPE_OFFSETGAIN;
        cal_cfg.SysClkFreq = 16000000; //16MHz
        cal_cfg.TimeOut10us = 2000;
        cal_cfg.VRef1p11 = 1.11;
        cal_cfg.VRef1p82 = 1.835;
        if (AD5940_ADCPGACal(&cal_cfg) == AD5940ERR_OK) {
            printf("ADC Calibration OK\n");
        } else {
            printf("ADC Calibration ERROR\n");
        /* Configure AFE power mode and bandwidth */
        AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_50KHZ);
        /* Initialize ADC basic function */
        adc_base.ADCMuxP = ADCMUXP_VAFE4;
        adc_base.ADCMuxN = ADCMUXN_VZERO0;
        adc_base.ADCPga = ADCPGA_1;
        /* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH-->StatisticBlock */
        adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
        adc_filter.ADCSinc2Osr = ADCSINC2OSR_1333;
        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 = bFALSE;         /* 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 = bFALSE;
        adc_filter.WGClkEnable = bFALSE;
        /* Chop enable */
        tempreg = AD5940_ReadReg(REG_AFE_ADCBUFCON) & 0xFFFFFFF0;
        tempreg |= 0x00000004;
        AD5940_WriteReg(REG_AFE_ADCBUFCON, tempreg);
        /* Power-up ADC */
       * Statistic block receive data from SINC2+Notch block. Note the diagram in datasheet page 51 PrM. 
       * The SINC3 can be bypassed optionally. SINC2 cannot be bypassed.
       * */
        stat_cfg.StatDev = STATDEV_1; /* Not used. */
        stat_cfg.StatEnable = bTRUE;
        stat_cfg.StatSample = STATSAMPLE_16; /* Sample 16 points and calculate mean. */
        fifo_cfg.FIFOEn = bTRUE;
        fifo_cfg.FIFOMode = FIFOMODE_FIFO;
        fifo_cfg.FIFOSize = FIFOSIZE_4KB;
        fifo_cfg.FIFOSrc = FIFOSRC_MEAN;
        fifo_cfg.FIFOThresh = 2;
        /* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
        AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);   /* Enable FIFO threshold interrupt. */
        AD5940_ClrMCUIntFlag(); /* Clear the MCU interrupt flag which will be set in ISR. */
        while (1) {
            uint32_t FifoCnt;
            if (AD5940_GetMCUIntFlag()) {
                AD5940_ClrMCUIntFlag(); /* Clear this flag */
                if (AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE) {
                    FifoCnt = AD5940_FIFOGetCnt();
                    AD5940_FIFORd((uint32_t *)ADCBuff, FifoCnt);
                    printf("Get %d data, ADC Code[0]:%d\n", FifoCnt, ADCBuff[0] & 0xffff);



  • +1
    •  Analog Employees 
    on Jan 28, 2020 2:38 PM in reply to PaoloS

    Hi Paolo,

    I discussed this the AD5940 apps engineer. He let me know a couple things.

    1. Regarding the difference in the adc codes, when using the mean feature in the statistics block, 32867 is subtracted in hardware. So when using the mean function, the appropriate conversion of the ADC code to voltage is:

      voltage = AD5940_ADCCode2Volt(ADCCode +32768, ADCPGA_1P5, 1.82);

    2. The intention for the EVAL-AD5940ELCZ BNC connections was to use the potentiostat to set CE0 to 1.1V (CN0428 uses VZERO for this), then connect VCE0 to the positive mux of the ADC, and connect AFE4 to N mux on the ADC. This gives you the differential voltage across the probe, then you just have to flip the sign.

    Good catch on the SnsInit() function. I forgot to mention it because most of the CN0428 SnsInit() function is not relevant to just pH measurement, however that is the place where the VZERO voltage was set to 1.1V and connected to the VZERO0 pin so the 1.1V could be used externally as a bias for the pH probe reference. If you're using CE0 as suggested above, your configuration would be a little bit different.


  • Hi Scott,

    Thanks for your support.

    After working on other measurements I decided to come back to the pH one to check its correctness using the default pins defined in the EVAL-AD5940ELCZ board. I followed your instruction to configure the LPDAC and the ADC in order to use AFE4 and CE0 as sense and reference pin respectively. As you mentioned, the ADC multiplexer has to be configured with the positive and negative input flipped and then the sign of the voltage should be inverted after the conversion but in this phase, I found some problems.

    As fas as I understood, the sign of the ADC input voltage is given by the operation ADC_CODE - 32768 but when the statistics block is used to get the mean value of multiple ADC readings this operation is already performed by the statistics block itself (as stated in the comments in the examples). The problem arises from the fact that the statistics block treats the ADC_CODE as a 16-bit unsigned integer, so if the ADC_CODE is less than 32768 the subtraction will cause an overflow in the unsigned representation.

    For example, if the actual ADC_CODE is 31000, the statistics block would give a value of 63768 ((31000 - 32768) + 65535 +1) which makes no sense and it cannot be directly converted to the input voltage. In my code, I fixed this issue by subtracting USHRT_MAX + 1 when the ADC_CODE value is greater than 32768 and I added back 32768 to get the right conversion from the AD5940_ADCCode2Volt() function. Where USHRT_MAX is a macro defined in the "limits.h" header of the standard C library which represents the maximum number representable in an unsigned short integer.

    I had to spend a while to find the cause of this issue because neither in the examples nor in the AD5940 datasheet is explained the behaviour of the statistics block when a negative input voltage is applied to the ADC. I would suggest fixing the statistics block output or explain this behaviour in the documentation.