Post Go back to editing

how to interface AD5940 with ESP32

I am looking on how to port the code to ESP32 in order to interface the AD5940 sensor via SPI. Appreciate if anyone is able to provide an example code for it or the port file for ESP32. 



.
[edited by: Grex4 at 4:58 AM (GMT -5) on 7 Dec 2021]
Parents Reply Children
  • Hi,

    Thanks for your reply. However, I managed to compile in Arduino IDE and here is the following code. 

    /*!
     *****************************************************************************
      @file:    AD590_Arduino_Main_CV.ino
      @author:  D. Bill (adapted from Analog Devices example code)
    
      -----------------------------------------------------------------------------
      @license: https://github.com/analogdevicesinc/ad5940lib/blob/master/LICENSE (accessed on December 3, 2020)
    
      Copyright (c) 2019 Analog Devices, Inc.  All rights reserved.
    
      Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
        - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
        - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
        - Modified versions of the software must be conspicuously marked as such.
        - This software is licensed solely and exclusively for use with processors/products manufactured by or for Analog Devices, Inc.
        - This software may not be combined or merged with other code in any manner that would cause the software to become subject to terms and conditions which differ from those listed here.
        - Neither the name of Analog Devices, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
        - The use of this software may or may not infringe the patent rights of one or more patent holders.  This license does not release you from the requirement that you obtain separate licenses from these patent holders to use this software.
    
      THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, TITLE, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANALOG DEVICES, INC. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, DAMAGES ARISING OUT OF CLAIMS OF INTELLECTUAL PROPERTY RIGHTS INFRINGEMENT; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
      2019-01-10-7CBSD SLA
    
      This file is a MODIFIED VERSION of the source code provided from Analog Devices!
    
      -----------------------------------------------------------------------------
      @brief:   The Main file for running CV/LSV on AD5940
      - performs cyclic or linear sweep voltammetry and sends data (voltage, current) to Computer (USB) for real-time plot with corresponding python application
      - content of this file is basically the AD5940Main.c file of the AD5940_Ramp example project
      - content from main.c (init of serial communication and mcu resource) placed in setup()
      - setup() inits the AD5941 AFE and sets the ramp parameters to values configurable with AD5940RampStructInit()
      - loop() does not do anything
      - use the makro definitions to determine operation variants
      - data output options: USB
      - Cyclic Voltammetry (CV): AppRampCfg.bRampOneDir = bFALSE
      - Linear Sweep Voltammetry (LSV): AppRampCfg.bRampOneDir = bTRUE
      - Potentiometry() method in RampTest.c file can be used to measure Open Circuit Potential (OCP)
      - CV/LSV only starts after opening a serial port on computer to communicate with MCU
      -----------------------------------------------------------------------------
      @todo:
      - low power DAC offset calibration (remove sensor bias from calibration routine)
      - ADC PGA gain calibration does not work properly
      - make DAC code re-writes during runtime more time efficient (prevent floating point arithmetic in AppRAMPSeqDACCtrlGen() and RampDacRegUpdate())
    *****************************************************************************/
    extern "C" {
    #include "ad5940.h"
    #include "math.h"
    #include "ElectrodermalActivity.h"
    }
    
    #include <SPI.h>
    //#include "ad5940.h"
    //#include "math.h"
    //#include "ElectrodermalActivity.h"
    //#include <LibPrintf.h> //allows to use c-style print() statement instead of Arduino style Serial.print()
    //#include "ESP32Port.cpp"
    
    #define PLOT_DATA /* use this macro to output data via serial port for python plotting */
    //#define DEBUG /* use this macro to output debug info*/
    
    /**
       User could configure following parameters
    **/
    #define APPBUFF_SIZE 512 // change this to 512;
    
    uint32_t AppBuff[APPBUFF_SIZE]; //buffer to fetch AD5940 samples
    float LFOSCFreq;                /* Measured LFOSC frequency */
    uint32_t ResistorForBaseline = 0;
    
    ////////////
    //Pin makros depending on target board, can be extended with other boards (check if enough flash memory is available)
    #define SPI_CS_AD5940_Pin 15  // esp32 GPIO 15
    #define AD5940_ResetPin 0    // ESP32 GPIO 0
    #define AD5940_IntPin 2       // ESP32 GPIO 2
    
    //declarations/definitions
    volatile static uint32_t ucInterrupted = 0;       /* Flag to indicate interrupt occurred */
    void Ext_Int0_Handler(void);
    
    void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, unsigned long length)
    {
      //set SPI settings for the following transaction
      //speedMaximum: 12MHz found to be max for Adafruit Feather M0, AD5940 rated for max 16MHz clock frequency
      //dataOrder: MSB first
      //dataMode: SCLK idles low/ data clocked on SCLK falling edge --> mode 0
      SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));
    
      for (int i = 0; i < length; i++)
      {
        *pRecvBuff++ = SPI.transfer(*pSendBuffer++);  //do a transfer
      }
    
      SPI.endTransaction(); //transaction over
    }
    
    void AD5940_CsClr(void)
    {
      digitalWrite(SPI_CS_AD5940_Pin, LOW);
    }
    
    void AD5940_CsSet(void)
    {
      digitalWrite(SPI_CS_AD5940_Pin, HIGH);
    }
    
    void AD5940_RstSet(void)
    {
      digitalWrite(AD5940_ResetPin, HIGH);
    }
    
    void AD5940_RstClr(void)
    {
      digitalWrite(AD5940_ResetPin, LOW);
    }
    
    void AD5940_Delay10us(uint32_t time)
    {
      //Warning: micros() only has 4us (for 16MHz boards) or 8us (for 8MHz boards) resolution - use a timer instead?
      unsigned long time_last = micros();
      while (micros() - time_last < time * 10) // subtraction handles the roll over of micros()
      {
        //wait
      }
    }
    
    uint32_t AD5940_GetMCUIntFlag(void)
    {
      return ucInterrupted;
    }
    
    uint32_t AD5940_ClrMCUIntFlag(void)
    {
      ucInterrupted = 0;
      return 1;
    }
    
    /* Functions that used to initialize MCU platform */
    
    uint32_t AD5940_MCUResourceInit(void *pCfg)
    {
      /* Step1, initialize SPI peripheral and its GPIOs for CS/RST */
      //start the SPI library (setup SCK, MOSI, and MISO pins)
      SPI.begin();
      //initalize SPI chip select pin
      pinMode(SPI_CS_AD5940_Pin, OUTPUT);
      //initalize Reset pin
      pinMode(AD5940_ResetPin, OUTPUT);
    
      /* Step2: initialize GPIO interrupt that connects to AD5940's interrupt output pin(Gp0, Gp3, Gp4, Gp6 or Gp7 ) */
      //init AD5940 interrupt pin
      pinMode(AD5940_IntPin, INPUT_PULLUP);
      //attach ISR for falling edge
      attachInterrupt(digitalPinToInterrupt(AD5940_IntPin), Ext_Int0_Handler, FALLING);
    
      //chip select high to de-select AD5940 initially
      AD5940_CsSet();
      AD5940_RstSet();
      return 0;
    }
    
    /* MCU related external line interrupt service routine */
    //The interrupt handler handles the interrupt to the MCU
    //when the AD5940 INTC pin generates an interrupt to alert the MCU that data is ready
    void Ext_Int0_Handler()
    {
      ucInterrupted = 1;
      /* This example just set the flag and deal with interrupt in AD5940Main function. It's your choice to choose how to process interrupt. */
    }
    
    /**
       @brief data ouput function
       @param pData: the buffer stored data (voltage of a step and corresponding cell current) for this application. The data from FIFO has been pre-processed.
       @param DataCount: The available data count in buffer pData.
       @return return 0.
    */
    /* print EDA result to uart */
    AD5940Err EDAShowResult(void *pData, uint32_t DataCount)
    {
      float RtiaMag;
      /*Process data*/
      fImpCar_Type *pImp = (fImpCar_Type*)pData;
      AppEDACtrl(EDACTRL_GETRTIAMAG, &RtiaMag);
    
      /*Process data*/
      for (int i = 0; i < DataCount; i++)
      {
        float mag, phase, sie;
        fImpCar_Type res;
        res = pImp[i];
        res.Real += ResistorForBaseline;    /* Show the real result of impedance under test(between F+/S+) */
        mag = AD5940_ComplexMag(&res);
        sie = 1 / AD5940_ComplexMag(&res);
        phase = AD5940_ComplexPhase(&res) * 180 / MATH_PI;
        /*//printf("Rtia:%.2f,(Real,Image):(%.2f,%.2f)Ohm---Mag:%.2fOhm,Phase:%.2f`\n",RtiaMag, res.Real, res.Image, mag, phase);*/
        printf("(R,I):(%.2f,%.2f)Ohm, Mag:%.2fOhm, Sie:%.6fS\n", res.Real, res.Image, mag, sie); //time?
        Serial.print("(R,I):"); Serial.print(res.Real); Serial.print(","); Serial.print(res.Image);
        Serial.print("; Mag: "); Serial.print(mag); Serial.print("Ohm");
        Serial.print(", Sie: "); Serial.print(sie); Serial.println("S");
        /* Serial.print("(R,I):(%.2f,%.2f)Ohm",res.Real, res.Image);  //time?
          Serial.print(", Mag:%.2fOhm" ,mag);
          Serial.println(", Sie:%.6fS" ,sie);*/
        //    Serial.println("(R,I):(%.2f,%.2f)Ohm, Mag:%.2fOhm, Sie:%.6fS\n",res.Real, res.Image, mag, sie);  //time?
      }
      return 0;
    }
    
    /*
       @brief The general configuration to AD5940 like FIFO/Sequencer/Clock.
       @note This function will firstly reset AD5940 using reset pin.
       @return return 0.
    */
    static int32_t AD5940PlatformCfg(void)
    {
      CLKCfg_Type clk_cfg;
      FIFOCfg_Type fifo_cfg;
      SEQCfg_Type seq_cfg;
      AGPIOCfg_Type gpio_cfg;
      LFOSCMeasure_Type LfoscMeasure;
    
      /* Use hardware reset */
      AD5940_HWReset();
      /* Platform configuration */
      AD5940_Initialize();
    
      /* Step1. Configure clock */
      clk_cfg.ADCClkDiv = ADCCLKDIV_1;
      clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
      clk_cfg.SysClkDiv = SYSCLKDIV_1;
      clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
      clk_cfg.HfOSC32MHzMode = bFALSE;
      clk_cfg.HFOSCEn = bTRUE;
      clk_cfg.HFXTALEn = bFALSE;
      clk_cfg.LFOSCEn = bTRUE;
      AD5940_CLKCfg(&clk_cfg);
      /* Step2. Configure FIFO and Sequencer*/
      fifo_cfg.FIFOEn = bFALSE;
      fifo_cfg.FIFOMode = FIFOMODE_FIFO;
      fifo_cfg.FIFOSize = FIFOSIZE_4KB;                       /* 4kB for FIFO, The reset 2kB for sequencer */
      fifo_cfg.FIFOSrc = FIFOSRC_DFT;
      fifo_cfg.FIFOThresh = 4;//AppBIACfg.FifoThresh;        /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
      AD5940_FIFOCfg(&fifo_cfg);                             /* Disable to reset FIFO. */
      fifo_cfg.FIFOEn = bTRUE;
      AD5940_FIFOCfg(&fifo_cfg);                             /* Enable FIFO here */
      /* 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);
    
      /* Step3. Interrupt controller */
      AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);           /* Enable all interrupt in Interrupt Controller 1, so we can check INTC flags */
      AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);   /* Interrupt Controller 0 will control GP0 to generate interrupt to MCU */
      AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
      /* Step4: Reconfigure GPIO */
      gpio_cfg.FuncSet = GP6_SYNC | GP5_SYNC | GP4_SYNC | GP2_EXTCLK | GP1_SYNC | GP0_INT;
      gpio_cfg.InputEnSet = AGPIO_Pin2;
      gpio_cfg.OutputEnSet = AGPIO_Pin0 | AGPIO_Pin1 | AGPIO_Pin4 | AGPIO_Pin5 | AGPIO_Pin6;
      gpio_cfg.OutVal = 0;
      gpio_cfg.PullEnSet = 0;
      AD5940_AGPIOCfg(&gpio_cfg);
    
      AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);  /* Enable AFE to enter sleep mode. */
    
      /* Measure LFOSC frequency */
      LfoscMeasure.CalDuration = 1000.0;  /* 1000ms used for calibration. */
      LfoscMeasure.CalSeqAddr = 0;
      LfoscMeasure.SystemClkFreq = 16000000.0f; /* 16MHz in this firmware. */
      AD5940_LFOSCMeasure(&LfoscMeasure, &LFOSCFreq);
      //printf("Freq:%f\n", LFOSCFreq);
      //  Serial.println("Freq:%f\n", LFOSCFreq);
      Serial.print("Freq: "); Serial.println(LFOSCFreq);
    
      return 0;
    }
    
    /**
       @brief The interface for user to change application parameters. All parameters belong to the AppRAMPCfg (see RampTest.c file)
       @return return 0.
    */
    void AD5940EDAStructInit(void)
    {
      AppEDACfg_Type *pCfg;
    
      AppEDAGetCfg(&pCfg);
      pCfg->MaxSeqLen = 512;
    
      pCfg->LfoscClkFreq = 32000;             /* Don't do LFOSC calibration now. We assume the default LFOSC is trimmed. */
      pCfg->RtiaAutoScaleEnable = bTRUE;      /* We manually select resistor value. */
      pCfg->LptiaRtiaSel = LPTIARTIA_120K;
      pCfg->SinAmplitude = 1100 * 3 / 4;      /* Set excitation voltage to 0.75 times of full range. */
      pCfg->SinFreq = 100.0f;
      pCfg->SampleFreq = 400.0f;              /* Do not change sample frequency unless you know how it works. */
      pCfg->EDAODR = 4.0f;                    /* ODR decides how frequently to start the engine to measure impedance. */
      pCfg->FifoThresh = 4;                   /* The minimum threshold value is 4, and should always be 4*N, where N is 1,2,3... */
      pCfg->bParaChanged = bTRUE;
    }
    
    void AD5940_Main(void)
    {
      uint32_t temp;
      fImpCar_Type EDABase =
      {
        .Real = 24299.84f,
        .Image = -110778.71f,
      };
    
      AD5940PlatformCfg();
    
      AD5940EDAStructInit(); /* Configure your parameters in this function */
    
      AppEDAInit(AppBuff, APPBUFF_SIZE);    /* Initialize BIA application. Provide a buffer, which is used to store sequencer commands */
      AppEDACtrl(APPCTRL_START, 0);         /* Control BIA measurement to start. Second parameter has no meaning with this command. */
      AppEDACtrl(EDACTRL_SETBASE, &EDABase);
      ResistorForBaseline = 20000;          /* Above result is obtained using 20kOhm resistor on BioElec Rev C board. */
      while (1)
      {
        /* Check if interrupt flag which will be set when interrupt occurred. */
        if (AD5940_GetMCUIntFlag())
        {
          AD5940_ClrMCUIntFlag(); /* Clear this flag */
          temp = APPBUFF_SIZE;
          AppEDAISR(AppBuff, &temp); /* Deal with it and provide a buffer to store data we got */
          EDAShowResult(AppBuff, temp); /* Show the results to UART */
          //get_average_imp(AppBuff, temp);
        }
      }
    }
    
    //***************************SETUP***************************
    void setup()
    {
      Serial.begin(115200);
    
      //wait until COM port is opened (e.g. by python)
      while (!Serial)
        ;
    
      pinMode(5, INPUT_PULLUP); //allow AD5940 to set the LED on this pin ;change this to somehing else, D05;
    
    
      //init GPIOs (SPI, AD5940 Reset and Interrupt)
      AD5940_MCUResourceInit(0);
    
      //configure AFE
      AD5940PlatformCfg();
      //init application with pre-defined parameters
      AD5940EDAStructInit();
    
      //run EDA once
      AD5940_Main();
    }
    
    //***************************LOOP***************************
    void loop()
    {
      //do nothing forever
    }

    However, the received readings from the serial port is stated below.

    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:1
    load:0x3fff0018,len:4
    load:0x3fff001c,len:928
    ho 0 tail 12 room 4
    load:0x40078000,len:10080
    load:0x40080400,len:5856
    entry 0x400806a8
    Freq: inf
    Freq: inf

    I am wondering why is the frequency stating inf or do I need to change the frequency from 16Mhz to 32Mhz since it is compatible with the Analog MCU at 16Mhz?

    Your help is appreciated.