oh sweet bejusus finally a little morsel of progress

This commit is contained in:
pszsh 2026-01-23 02:38:30 -08:00
parent 9eff1ecd36
commit 6e1d6cc970
23 changed files with 8655 additions and 752 deletions

2
.gitignore vendored
View File

@ -6,7 +6,7 @@ build
*.swp *.swp
*.cmake *.cmake
requirements.txt requirements.txt
ad5940* ad5940.c
pico_sdk_import.cmake pico_sdk_import.cmake
build* build*
*.png *.png

View File

@ -1,25 +1,23 @@
# File: CMakeLists.txt # File: CMakeLists.txt
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake) include(pico_sdk_import.cmake)
project(EIS C CXX ASM) project(EIS C CXX ASM)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
pico_sdk_init() pico_sdk_init()
# Add ad5940.c to the build # Changed main.cpp to main.c
add_executable(EIS add_executable(EIS
main.c main.c
ad5940.c ad5940.c
) )
# Define CHIPSEL_594X for the library
target_compile_definitions(EIS PRIVATE CHIPSEL_594X) target_compile_definitions(EIS PRIVATE CHIPSEL_594X)
# REQUIRED: Tell CMake where to find headers
target_include_directories(EIS PRIVATE ${CMAKE_CURRENT_LIST_DIR}) target_include_directories(EIS PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(EIS target_link_libraries(EIS

4950
ad5940.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
/*!
*****************************************************************************
@file: AD5940_ADCMeanFIFO.c
@author: $Author: nxu2 $
@brief: Use FIFO to read statistic block mean result.
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
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.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_MEAN_FIFO_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
uint32_t ADCBuff[256];
void AD5940_Main(void)
{
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
StatCfg_Type stat_cfg;
FIFOCfg_Type fifo_cfg;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
adc_base.ADCMuxP = ADCMUXP_AVDD_2;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_1;
AD5940_ADCBaseCfgS(&adc_base);
/* 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 = 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);
/**
* 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_128; /* Sample 128 points and calculate mean. */
AD5940_StatisticCfgS(&stat_cfg);
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_MEAN;
fifo_cfg.FIFOThresh = 2;
AD5940_FIFOCfg(&fifo_cfg);
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE); /* Enable FIFO threshold interrupt. */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_ClrMCUIntFlag(); /* Clear the MCU interrupt flag which will be set in ISR. */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);
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);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
printf("Get %d data, ADC Code[0]:%d\n",FifoCnt, ADCBuff[0]&0xffff);
/*!!!!!NOTE!!!!!*/
/* The mean result already removed 32768. So to calculate the voltage, assume mean result is n, use below equation.
Voltage = n/32768*Vref
*/
}
}
}
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,205 @@
/*!
*****************************************************************************
@file: AD5940_ADCNotchTest.c
@author: Neo Xu
@brief: Notch filter test.
-----------------------------------------------------------------------------
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.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_Notch_Test_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
#ifdef ADI_DEBUG
#undef ADI_DEBUG
#endif
void ad5940_sequencer_init(uint8_t adc_rate, uint8_t sinc3osr, uint8_t sinc2osr){
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_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_SINC2NOTCH;
fifo_cfg.FIFOThresh = 1;
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);
uint32_t WaitClks;
uint8_t dl_60, dl_50, dl=0;
ADCFilterCfg_Type filter;
filter.ADCSinc3Osr = sinc3osr;
filter.ADCSinc2Osr = sinc2osr;
filter.ADCRate = adc_rate;
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};
printf("SINC3:OSR%d, SINC2:OSR%d, ", sinc3osr_table[sinc3osr], sinc2osr_table[sinc2osr]);
if(AD5940_Notch50HzAvailable(&filter, &dl_50)){
printf("NOTCH50: DL:%d, %dHz ", dl_50, (uint32_t)((adc_rate==ADCRATE_1P6MHZ?1.6e6:800000.0)/sinc3osr_table[sinc3osr]/sinc2osr_table[sinc2osr]/dl_50 + .5));
dl += dl_50-1;
}
if(AD5940_Notch60HzAvailable(&filter, &dl_60)){
printf("NOTCH60: DL:%d, %dHz ", dl_60, (uint32_t)((adc_rate==ADCRATE_1P6MHZ?1.6e6:800000.0)/sinc3osr_table[sinc3osr]/sinc2osr_table[sinc2osr]/dl_60 + .5));
dl += dl_60-1;
}
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_NOTCH;
clks_cal.ADCRate = adc_rate;
clks_cal.DataCount = 1; /* Sample one data when wakeup */
clks_cal.ADCSinc2Osr = sinc2osr;
clks_cal.ADCSinc3Osr = sinc3osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = adc_rate==ADCRATE_1P6MHZ?.5:1;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
static uint32_t buff[128];
AD5940_SEQGenInit(buff, 128);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin1);
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_SINC2NOTCH, bTRUE); /* Start ADC convert */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
//wait for first data ready
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bFALSE); /* Stop ADC convert and DFT */
AD5940_SEQGpioCtrlS(0);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
SEQInfo_Type seqinfo;
AD5940_SEQGenFetchSeq(&seqinfo.pSeqCmd, &seqinfo.SeqLen);
seqinfo.SeqId = SEQID_0;
seqinfo.SeqRamAddr = 0;
seqinfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo);
AGPIOCfg_Type gpio_cfg;
gpio_cfg.FuncSet = GP6_SYNC|GP5_SYNC|GP2_TRIG|GP1_SYNC|GP0_INT;
gpio_cfg.InputEnSet = AGPIO_Pin2;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin5|AGPIO_Pin6;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = AGPIO_Pin2;
AD5940_AGPIOCfg(&gpio_cfg);
}
uint32_t ad5940_notch_test(uint8_t adc_rate, uint8_t sinc3osr, uint8_t sinc2osr){
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
CLKCfg_Type clk_cfg;
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = adc_rate==ADCRATE_1P6MHZ?SYSCLKDIV_2:SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = adc_rate==ADCRATE_1P6MHZ?bTRUE:bFALSE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
adc_base.ADCMuxP = ADCMUXP_VREF1P8DAC;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_1P5;
AD5940_ADCBaseCfgS(&adc_base);
AD5940_AFECtrlS(AFECTRL_DACREFPWR|AFECTRL_HSDACPWR, bTRUE);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
adc_filter.ADCSinc3Osr = sinc3osr;
adc_filter.ADCSinc2Osr = sinc2osr;
adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */
adc_filter.ADCRate = adc_rate; /* 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.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
AD5940_ADCFilterCfgS(&adc_filter);
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
ad5940_sequencer_init(adc_rate, sinc3osr, sinc2osr);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_SEQCtrlS(bTRUE);
AD5940_ClrMCUIntFlag();
AD5940_SEQMmrTrig(SEQID_0);
while(1)
{
{int32_t i = 1000000;while(i--);}
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag();
uint32_t fifo_count = AD5940_FIFOGetCnt();
if(fifo_count == 1){
int32_t rd;
AD5940_FIFORd((uint32_t*)&rd, 1);
rd &= 0xffff;
float volt = AD5940_ADCCode2Volt(rd, ADCPGA_1P5, 1.82)+1.11;
volt -= 1.82;
if(volt < 0) volt = -volt;
if(volt > 0.0005){
printf("FAILED:CODE:rd:%d\n", rd);
return 1;
}
printf("PASS\n");
return 0;
}
else{
printf("FAILED:%d\n", fifo_count);
return 1;
}
}
}
}
void AD5940_Main(void)
{
uint32_t failed = 0;
uint8_t sinc3=0, sinc2=0;
uint8_t adc_rate=0;
for(;adc_rate<=1;adc_rate++){
sinc3=0;
for(;sinc3<=ADCSINC3OSR_2;sinc3++){
sinc2=0;
for(;sinc2<=ADCSINC2OSR_1333;sinc2++){
failed |= ad5940_notch_test(adc_rate, sinc3, sinc2);
}
}
}
printf("Test Done with status: %s\n",failed?"FAILED":"SUCCEED");
while(1);
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,110 @@
/*!
*****************************************************************************
@file: AD5940_ADCPolling.c
@author: $Author: nxu2 $
@brief: ADC Polling mode example
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
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.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_Polling_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
#define ADCPGA_GAIN_SEL ADCPGA_1P5
static void AD5940_PGA_Calibration(void){
AD5940Err err;
ADCPGACal_Type pgacal;
pgacal.AdcClkFreq = 16e6;
pgacal.ADCSinc2Osr = ADCSINC2OSR_178;
pgacal.ADCSinc3Osr = ADCSINC3OSR_4;
pgacal.SysClkFreq = 16e6;
pgacal.TimeOut10us = 1000;
pgacal.VRef1p11 = 1.11f;
pgacal.VRef1p82 = 1.82f;
pgacal.PGACalType = PGACALTYPE_OFFSETGAIN;
pgacal.ADCPga = ADCPGA_GAIN_SEL;
err = AD5940_ADCPGACal(&pgacal);
if(err != AD5940ERR_OK){
printf("AD5940 PGA calibration failed.");
}
}
void AD5940_Main(void)
{
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
AD5940_PGA_Calibration();
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
AD5940_AFECtrlS(AFECTRL_DACREFPWR|AFECTRL_HSDACPWR, bTRUE); //We are going to measure DAC 1.82V reference.
adc_base.ADCMuxP = ADCMUXP_VREF1P8DAC;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_GAIN_SEL;
AD5940_ADCBaseCfgS(&adc_base);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
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 = 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);
//AD5940_ADCMuxCfgS(ADCMUXP_AIN2, ADCMUXN_VSET1P1); /* Optionally, you can change ADC MUX with this function */
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
//AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
//AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);
AD5940_ADCPowerCtrlS(bTRUE);
AD5940_ADCConvtCtrlS(bTRUE);
while(1)
{
uint32_t rd;
if(AD5940_INTCTestFlag(AFEINTC_1,AFEINTSRC_SINC2RDY))
{
static uint32_t count;
AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY);
rd = AD5940_ReadAfeResult(AFERESULT_SINC2);
count ++;
/* ADC Sample rate is 800kSPS. SINC3 OSR is 4, SINC2 OSR is 1333. So the final output data rate is 800kSPS/4/1333 = 150.0375Hz */
if(count == 150) /* Print data @1Hz */
{
count = 0;
float diff_volt = AD5940_ADCCode2Volt(rd, ADCPGA_GAIN_SEL, 1.82);
printf("ADC Code:%d, diff-volt: %.4f, volt:%.4f\n",rd, diff_volt, diff_volt+1.11);
}
}
}
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,151 @@
/*!
*****************************************************************************
@file: AD5940Main.c
@author: Neo Xu
@brief: Electrochemical impedance spectroscopy based on example AD5940_Impedance
This project is optomized for 3-lead electrochemical sensors that typically have
an impedance <200ohm. For optimum performance RCAL should be close to
impedance of the sensor.
-----------------------------------------------------------------------------
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 "Impedance.h"
#define APPBUFF_SIZE 512
uint32_t AppBuff[APPBUFF_SIZE];
int32_t ImpedanceShowResult(uint32_t *pData, uint32_t DataCount)
{
float freq;
fImpPol_Type *pImp = (fImpPol_Type*)pData;
AppIMPCtrl(IMPCTRL_GETFREQ, &freq);
printf("Freq:%.2f ", freq);
/*Process data*/
for(int i=0;i<DataCount;i++)
{
printf("RzMag: %f Ohm , RzPhase: %f \n",pImp[i].Magnitude,pImp[i].Phase*180/MATH_PI);
}
return 0;
}
static int32_t AD5940PlatformCfg(void)
{
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
/* Platform configuration */
/* 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 = 6;
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
/* 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);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP0_INT|GP1_SLEEP|GP2_SYNC;
gpio_cfg.InputEnSet = 0;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
return 0;
}
void AD5940ImpedanceStructInit(void)
{
AppIMPCfg_Type *pImpedanceCfg;
AppIMPGetCfg(&pImpedanceCfg);
/* Step1: configure initialization sequence Info */
pImpedanceCfg->SeqStartAddr = 0;
pImpedanceCfg->MaxSeqLen = 512; /* @todo add checker in function */
pImpedanceCfg->RcalVal = 200.0;
pImpedanceCfg->FifoThresh = 6;
pImpedanceCfg->SinFreq = 1000.0;
/* Configure Excitation Waveform
*
* Output waveform = DacVoltPP * ExcitBufGain * HsDacGain
*
* = 300 * 0.25 * 0.2 = 15mV pk-pk
*
*/
pImpedanceCfg->DacVoltPP = 300; /* Maximum value is 600mV*/
pImpedanceCfg->ExcitBufGain = EXCITBUFGAIN_0P25;
pImpedanceCfg->HsDacGain = HSDACGAIN_0P2;
/* Set switch matrix to onboard(EVAL-AD5940ELECZ) gas sensor. */
pImpedanceCfg->DswitchSel = SWD_CE0;
pImpedanceCfg->PswitchSel = SWP_RE0;
pImpedanceCfg->NswitchSel = SWN_SE0LOAD;
pImpedanceCfg->TswitchSel = SWT_SE0LOAD;
/* The dummy sensor is as low as 5kOhm. We need to make sure RTIA is small enough that HSTIA won't be saturated. */
pImpedanceCfg->HstiaRtiaSel = HSTIARTIA_200;
pImpedanceCfg->BiasVolt = 0.0;
/* Configure the sweep function. */
pImpedanceCfg->SweepCfg.SweepEn = bFALSE;
pImpedanceCfg->SweepCfg.SweepStart = 100.0f; /* Start from 1kHz */
pImpedanceCfg->SweepCfg.SweepStop = 100e3f; /* Stop at 100kHz */
pImpedanceCfg->SweepCfg.SweepPoints = 101; /* Points is 101 */
pImpedanceCfg->SweepCfg.SweepLog = bTRUE;
/* Configure Power Mode. Use HP mode if frequency is higher than 80kHz. */
pImpedanceCfg->PwrMod = AFEPWR_LP;
/* Configure filters if necessary */
pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_4; /* Sample rate is 800kSPS/2 = 400kSPS */
pImpedanceCfg->DftNum = DFTNUM_16384;
pImpedanceCfg->DftSrc = DFTSRC_SINC3;
}
void AD5940_Main(void)
{
uint32_t temp;
AD5940PlatformCfg();
AD5940ImpedanceStructInit();
AppIMPInit(AppBuff, APPBUFF_SIZE); /* Initialize IMP application. Provide a buffer, which is used to store sequencer commands */
AppIMPCtrl(IMPCTRL_START, 0); /* Control IMP measurement to start. Second parameter has no meaning with this command. */
while(1)
{
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag();
temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
}
}
}

View File

@ -0,0 +1,678 @@
/*!
*****************************************************************************
@file: Impedance.c
@author: Neo Xu
@brief: Electrochemical impedance spectroscopy based on example AD5940_Impedance
-----------------------------------------------------------------------------
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 "Impedance.h"
/* Default LPDAC resolution(2.5V internal reference). */
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/*
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
*/
AppIMPCfg_Type AppIMPCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ImpODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.DswitchSel = SWD_CE0,
.PswitchSel = SWP_CE0,
.NswitchSel = SWN_AIN1,
.TswitchSel = SWT_AIN1,
.PwrMod = AFEPWR_LP,
.LptiaRtiaSel = LPTIARTIA_4K, /* COnfigure RTIA */
.LpTiaRf = LPTIARF_1M, /* Configure LPF resistor */
.LpTiaRl = LPTIARLOAD_100R,
.HstiaRtiaSel = HSTIARTIA_1K,
.ExcitBufGain = EXCITBUFGAIN_0P25,
.HsDacGain = HSDACGAIN_0P2,
.HsDacUpdateRate = 0x1B,
.DacVoltPP = 300.0,
.BiasVolt = -0.0f,
.SinFreq = 100000.0, /* 1000Hz */
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.AdcPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.ADCAvgNum = ADCAVGNUM_16,
.SweepCfg.SweepEn = bTRUE,
.SweepCfg.SweepStart = 1000,
.SweepCfg.SweepStop = 100000.0,
.SweepCfg.SweepPoints = 101,
.SweepCfg.SweepLog = bFALSE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4,
.IMPInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
int32_t AppIMPGetCfg(void *pCfg)
{
if(pCfg)
{
*(AppIMPCfg_Type**)pCfg = &AppIMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
int32_t AppIMPCtrl(uint32_t Command, void *pPara)
{
switch (Command)
{
case IMPCTRL_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(AppIMPCfg.IMPInited == 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;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppIMPCfg.WuptClkFreq/AppIMPCfg.ImpODR)-4;
AD5940_WUPTCfg(&wupt_cfg);
AppIMPCfg.FifoDataCount = 0; /* restart */
break;
}
case IMPCTRL_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);
AD5940_WUPTCtrl(bFALSE);
break;
}
case IMPCTRL_STOPSYNC:
{
AppIMPCfg.StopRequired = bTRUE;
break;
}
case IMPCTRL_GETFREQ:
{
if(pPara == 0)
return AD5940ERR_PARA;
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppIMPCfg.FreqofData;
else
*(float*)pPara = AppIMPCfg.SinFreq;
}
break;
case IMPCTRL_SHUTDOWN:
{
AppIMPCtrl(IMPCTRL_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 lploop_cfg;
memset(&aferef_cfg, 0, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg);
memset(&lploop_cfg, 0, sizeof(lploop_cfg));
AD5940_LPLoopCfgS(&lploop_cfg);
AD5940_EnterSleepS(); /* Enter Hibernate */
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/* generated code snnipet */
float AppIMPGetCurrFreq(void)
{
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
return AppIMPCfg.FreqofData;
else
return AppIMPCfg.SinFreq;
}
/* Application initialization */
static AD5940Err AppIMPSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HsLoopCfg;
LPLoopCfg_Type lploop_cfg;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* 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;
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 = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
lploop_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lploop_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lploop_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
lploop_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lploop_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5;
lploop_cfg.LpDacCfg.DataRst = bFALSE;
lploop_cfg.LpDacCfg.PowerEn = bTRUE;
lploop_cfg.LpDacCfg.DacData6Bit = (uint32_t)((AppIMPCfg.Vzero-200)/DAC6BITVOLT_1LSB);
lploop_cfg.LpDacCfg.DacData12Bit =(int32_t)((AppIMPCfg.BiasVolt)/DAC12BITVOLT_1LSB) + lploop_cfg.LpDacCfg.DacData6Bit*64;
if(lploop_cfg.LpDacCfg.DacData12Bit>lploop_cfg.LpDacCfg.DacData6Bit*64)
lploop_cfg.LpDacCfg.DacData12Bit--;
lploop_cfg.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lploop_cfg.LpAmpCfg.LpPaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaRf = AppIMPCfg.LpTiaRf;
lploop_cfg.LpAmpCfg.LpTiaRload = AppIMPCfg.LpTiaRl;
lploop_cfg.LpAmpCfg.LpTiaRtia = AppIMPCfg.LptiaRtiaSel;
lploop_cfg.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)|LPTIASW(12)|LPTIASW(13);
AD5940_LPLoopCfgS(&lploop_cfg);
HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain;
HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain;
HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate;
HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HsLoopCfg.HsTiaCfg.HstiaCtia = 31; /* 31pF + 2pF */
HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel;
HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel;
HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel;
HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
HsLoopCfg.WgCfg.GainCalEn = bTRUE;
HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
{
AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
sin_freq = AppIMPCfg.SweepCurrFreq;
}
else
{
sin_freq = AppIMPCfg.SinFreq;
AppIMPCfg.FreqofData = sin_freq;
}
HsLoopCfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppIMPCfg.SysClkFreq);
HsLoopCfg.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppIMPCfg.DacVoltPP/800.0f*2047 + 0.5f);
HsLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&HsLoopCfg);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = AppIMPCfg.ADCAvgNum;
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppIMPCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppIMPCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
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 */
if(AppIMPCfg.BiasVolt == 0.0f)
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
else
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH|AFECTRL_DCBUFPWR, bTRUE);
/* 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)
{
AppIMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppIMPCfg.InitSeqInfo.SeqRamAddr = AppIMPCfg.SeqStartAddr;
AppIMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppIMPSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
LPAmpCfg_Type LpAmpCfg;
/* Calculate number of clocks to get data to FIFO */
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppIMPCfg.DftSrc;
clks_cal.DataCount = 1L<<(AppIMPCfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = AppIMPCfg.ADCAvgNum;
clks_cal.RatioSys2AdcClk = AppIMPCfg.SysClkFreq/AppIMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Start Sequence Generator */
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin2); /* Set GPIO1, clear others that under control */
AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* @todo wait 250us? */
/* Disconnect SE0 from LPTIA*/
LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
LpAmpCfg.LpPaPwrEn = bTRUE;
LpAmpCfg.LpTiaPwrEn = bTRUE;
LpAmpCfg.LpTiaRf = AppIMPCfg.LpTiaRf;
LpAmpCfg.LpTiaRload = AppIMPCfg.LpTiaRl;
LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN; /* Disconnect Rtia to avoid RC filter discharge */
LpAmpCfg.LpTiaSW = LPTIASW(7)|LPTIASW(8)|LPTIASW(12)|LPTIASW(13);
AD5940_LPAMPCfgS(&LpAmpCfg);
/* Sensor + Rload Measurement */
sw_cfg.Dswitch = AppIMPCfg.DswitchSel;
sw_cfg.Pswitch = AppIMPCfg.PswitchSel;
sw_cfg.Nswitch = AppIMPCfg.NswitchSel;
sw_cfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE); /* Enable Waveform generator */
//delay for signal settling DFT_WAIT
AD5940_SEQGenInsert(SEQ_WAIT(16*10));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
//wait for first data ready
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH|AFECTRL_DFT|AFECTRL_ADCCNV, bFALSE);
/* RLOAD Measurement */
sw_cfg.Dswitch = SWD_SE0;
sw_cfg.Pswitch = SWP_SE0;
sw_cfg.Nswitch = SWN_SE0LOAD;
sw_cfg.Tswitch = SWT_SE0LOAD|SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*10)); //delay for signal settling DFT_WAIT
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH|AFECTRL_ADCCNV, bFALSE);
/* RCAL Measurement */
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);
/* Reconnect LP loop */
LpAmpCfg.LpTiaRtia = AppIMPCfg.LptiaRtiaSel; /* Disconnect Rtia to avoid RC filter discharge */
LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)|LPTIASW(12)|LPTIASW(13);
AD5940_LPAMPCfgS(&LpAmpCfg);
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*10)); //delay for signal settling DFT_WAIT
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT/*|AFECTRL_SINC2NOTCH*/, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
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);
AD5940_SEQGpioCtrlS(0); /* Clr GPIO1 */
sw_cfg.Dswitch = SWD_OPEN;
sw_cfg.Pswitch = SWP_OPEN;
sw_cfg.Nswitch = SWN_OPEN;
sw_cfg.Tswitch = SWT_OPEN;
AD5940_SWMatrixCfgS(&sw_cfg);
//AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AppIMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppIMPCfg.MeasureSeqInfo.SeqRamAddr = AppIMPCfg.InitSeqInfo.SeqRamAddr + AppIMPCfg.InitSeqInfo.SeqLen ;
AppIMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.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 AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_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; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bTRUE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
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 = AppIMPCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppIMPCfg.IMPInited == bFALSE)||\
(AppIMPCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppIMPSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppIMPSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppIMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppIMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppIMPCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
/* Measurement sequence */
AppIMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppIMPCfg.PwrMod, AFEBW_250KHZ);
AD5940_WriteReg(REG_AFE_LPTIASW0, 0x3180);
AppIMPCfg.IMPInited = bTRUE; /* IMP application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
int32_t AppIMPRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppIMPCfg.NumOfData > 0)
{
AppIMPCfg.FifoDataCount += *pDataCount/4;
if(AppIMPCfg.FifoDataCount >= AppIMPCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppIMPCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if(AppIMPCfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
/* Check frequency and update FIlter settings */
AD5940_WGFreqCtrlS(AppIMPCfg.SweepNextFreq, AppIMPCfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
int32_t AppIMPDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount/6;
fImpPol_Type * const pOut = (fImpPol_Type*)pData;
iImpCar_Type * pSrcData = (iImpCar_Type*)pData;
*pDataCount = 0;
DataCount = (DataCount/6)*6;/* We expect Rz+Rload, Rload and RCAL data, . One DFT result has two data in FIFO, real part and imaginary part. */
/* Convert DFT result to int32_t type */
for(uint32_t i=0; i<DataCount; i++)
{
pData[i] &= 0x3ffff;
if(pData[i]&(1L<<17)) /* Bit17 is sign bit */
{
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
}
}
for(uint32_t i=0; i<ImpResCount; i++)
{
if(1)
{
fImpCar_Type DftRcal, DftRzRload, DftRload, temp1, temp2, res;
fImpCar_Type DftConst1 = {1.0f, 0};
/*
The sign of DFT Image result is added '-1' by hardware. Fix it below.
*/
DftRzRload.Real = pSrcData->Real;
DftRzRload.Image = -pSrcData->Image;
pSrcData++;
DftRload.Real = pSrcData->Real;
DftRload.Image = -pSrcData->Image;
pSrcData++;
DftRcal.Real = pSrcData->Real;
DftRcal.Image = -pSrcData->Image;
pSrcData++;
/**
Rz = RloadRz - Rload
RloadRz = DftRcal/DftRzRload*RCAL;
Rload = DftRcal/DftRload*RCAL;
Rz = RloadRz - Rload =
(1/DftRzRload - 1/DftRload)*DftRcal*RCAL;
where RCAL is the RCAL resistor value in Ohm.
*/
//temp1 = 1/DftRzRload;
//temp2 = 1/DftRload;
temp1 = AD5940_ComplexDivFloat(&DftConst1, &DftRzRload);
temp2 = AD5940_ComplexDivFloat(&DftConst1, &DftRload);
res = AD5940_ComplexSubFloat(&temp1, &temp2);
res = AD5940_ComplexMulFloat(&res, &DftRcal);
pOut[i].Magnitude = AD5940_ComplexMag(&res)*AppIMPCfg.RcalVal;
pOut[i].Phase = AD5940_ComplexPhase(&res);
}
else
{
iImpCar_Type *pDftRcal, *pDftRzRload, *pDftRload;
pDftRzRload = pSrcData++;
pDftRload = pSrcData++;
pDftRcal = pSrcData++;
float RzRloadMag, RzRloadPhase;
float RloadMag, RloadPhase;
float RzMag,RzPhase;
float RcalMag, RcalPhase;
float RzReal, RzImage;
RzReal = pDftRload->Real - pDftRzRload->Real;
RzImage = pDftRload->Image - pDftRzRload->Image;
RzRloadMag = sqrt((float)pDftRzRload->Real*pDftRzRload->Real+(float)pDftRzRload->Image*pDftRzRload->Image);
RzRloadPhase = atan2(-pDftRzRload->Image,pDftRzRload->Real);
RcalMag = sqrt((float)pDftRcal->Real*pDftRcal->Real+(float)pDftRcal->Image*pDftRcal->Image);
RcalPhase = atan2(-pDftRcal->Image,pDftRcal->Real);
RzMag = sqrt((float)RzReal*RzReal+(float)RzImage*RzImage);
RzPhase = atan2(-RzImage,RzReal);
RloadMag = sqrt((float)pDftRload->Real*pDftRload->Real+(float)pDftRload->Image*pDftRload->Image);
RloadPhase = atan2(-pDftRload->Image,pDftRload->Real);
RzMag = (AppIMPCfg.RcalVal*RcalMag*RzMag)/(RzRloadMag*RloadMag);
RzPhase = -(RcalPhase + RzPhase - RloadPhase - RzRloadPhase);
// RzPhase = (RcalPhase + RzPhase - RloadPhase - RzRloadPhase);
pOut[i].Magnitude = RzMag;
pOut[i].Phase = RzPhase;
}
}
*pDataCount = ImpResCount;
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
/* Calculate next frequency point */
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
{
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepNextFreq;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
}
return 0;
}
/**
*/
int32_t AppIMPISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
*pCount = 0;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Prohibit AFE to enter sleep mode. */
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt()/6)*6;
if(FifoCnt > BuffCount)
{
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppIMPRegModify(pBuff, &FifoCnt); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
//AD5940_EnterSleepS(); /* Manually put AFE back to hibernate mode. This operation only takes effect when register value is ACTIVE previously */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
/* Process data */
AppIMPDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}

View File

@ -0,0 +1,91 @@
/*!
*****************************************************************************
@file: Impedance.h
@author: Neo Xu
@brief: Electrochemical impedance spectroscopy based on example AD5940_Impedance
-----------------------------------------------------------------------------
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.
*****************************************************************************/
#ifndef _IMPEDANCESEQUENCES_H_
#define _IMPEDANCESEQUENCES_H_
#include "AD5940.H"
#include <stdio.h>
#include "string.h"
#include "math.h"
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppBIAInit */
uint32_t SeqStartAddr; /* Initialaztion sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLen; /* Limit the maximum sequence. */
uint32_t SeqStartAddrCal; /* Measurement sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLenCal;
/* Application related parameters */
float ImpODR; /* */
int32_t NumOfData; /* By default it's '-1'. If you want the engine stops after get NumofData, then set the value here. Otherwise, set it to '-1' which means never stop. */
float WuptClkFreq; /* The clock frequency of Wakeup Timer in Hz. Typically it's 32kHz. Leave it here in case we calibrate clock in software method */
float SysClkFreq; /* The real frequency of system clock */
float AdcClkFreq; /* The real frequency of ADC clock */
float RcalVal; /* Rcal value in Ohm */
/* Switch Configuration */
uint32_t DswitchSel;
uint32_t PswitchSel;
uint32_t NswitchSel;
uint32_t TswitchSel;
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
uint32_t HstiaRtiaSel; /* Use internal RTIA, select from RTIA_INT_200, RTIA_INT_1K, RTIA_INT_5K, RTIA_INT_10K, RTIA_INT_20K, RTIA_INT_40K, RTIA_INT_80K, RTIA_INT_160K */
uint32_t ExcitBufGain; /* Select from EXCTBUFGAIN_2, EXCTBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
uint32_t HsDacUpdateRate;
float DacVoltPP; /* DAC output voltage in mV peak to peak. Maximum value is 600mVpp. Peak to peak voltage */
float BiasVolt; /* The excitation signal is DC+AC. This parameter decides the DC value in mV unit. 0.0mV means no DC bias.*/
float SinFreq; /* Frequency of excitation signal */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
uint32_t AdcPgaGain; /* PGA Gain select from GNPGA_1, GNPGA_1_5, GNPGA_2, GNPGA_4, GNPGA_9 !!! We must ensure signal is in range of +-1.5V which is limited by ADC input stage */
uint8_t ADCSinc3Osr;
uint8_t ADCSinc2Osr;
uint8_t ADCAvgNum;
uint8_t ADC_Rate;
uint32_t LptiaRtiaSel; /* Use internal RTIA, select from RTIA_INT_200, RTIA_INT_1K, RTIA_INT_5K, RTIA_INT_10K, RTIA_INT_20K, RTIA_INT_40K, RTIA_INT_80K, RTIA_INT_160K */
uint32_t LpTiaRf; /* Rfilter select */
uint32_t LpTiaRl; /* SE0 Rload select */
float Vzero; /* Voltage on SE0 pin and Vzero, optimumly 1100mV*/
float Vbias; /* Voltage on CE0 and PA */
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
/* Private variables for internal usage */
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
float FreqofData; /* The frequency of latest data sampled */
BoolFlag IMPInited; /* If the program run firstly, generated sequence commands */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
BoolFlag StopRequired; /* After FIFO is ready, stop the measurement sequence */
uint32_t FifoDataCount; /* Count how many times impedance have been measured */
}AppIMPCfg_Type;
#define IMPCTRL_START 0
#define IMPCTRL_STOPNOW 1
#define IMPCTRL_STOPSYNC 2
#define IMPCTRL_GETFREQ 3 /* Get Current frequency of returned data from ISR */
#define IMPCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize);
int32_t AppIMPGetCfg(void *pCfg);
int32_t AppIMPISR(void *pBuff, uint32_t *pCount);
int32_t AppIMPCtrl(uint32_t Command, void *pPara);
#endif

View File

@ -0,0 +1,95 @@
/*!
*****************************************************************************
@file: AD5940_HSDACCal.c
@author: $Author: nxu2 $
@brief: HSDAC Offset Calibration Demo calibration demo.
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
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"
/* The HSDAC has a number of different gain settings shown in table below.
The HSDAC needs to be calibrated seperately for each gain setting. HSDAC has two powe
modes. There are seperate offset registers for both, DACOFFSET and DACOFFSETHP. The
HSDAC needs to be calibrated for each mode.
HSDACCON[12] | HSDACCON[0] | Output Range |
0 | 0 | +-607mV |
1 | 0 | +-75mV |
1 | 1 | +-15.14mV |
0 | 1 | +-121.2mV |
*/
void AD5940_Main(void){
HSDACCal_Type hsdac_cal;
ADCPGACal_Type adcpga_cal;
CLKCfg_Type clk_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = bTRUE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
printf("ADC Offset Cal\n");
adcpga_cal.AdcClkFreq=16000000;
adcpga_cal.ADCPga = ADCPGA_1;
adcpga_cal.ADCSinc2Osr = ADCSINC2OSR_1333;
adcpga_cal.ADCSinc3Osr = ADCSINC3OSR_4;
adcpga_cal.PGACalType = PGACALTYPE_OFFSET;
adcpga_cal.TimeOut10us = 1000;
adcpga_cal.VRef1p11 = 1.11;
adcpga_cal.VRef1p82 = 1.82;
AD5940_ADCPGACal(&adcpga_cal);
printf("\n 607mV Range Cal\n");
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_2; /**< Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
hsdac_cal.HsDacGain = HSDACGAIN_1; /**< Select from HSDACGAIN_1, HSDACGAIN_0P2 */
hsdac_cal.AfePwrMode = AFEPWR_LP;
hsdac_cal.ADCSinc2Osr = ADCSINC2OSR_1333;
hsdac_cal.ADCSinc3Osr = ADCSINC3OSR_4;
AD5940_HSDACCal(&hsdac_cal);
printf("\nADC GN 4 Offset Cal\n");
adcpga_cal.ADCPga = ADCPGA_4;
AD5940_ADCPGACal(&adcpga_cal);
printf("\n 125mV Range Cal\n");
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_2; /**< Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
hsdac_cal.HsDacGain = HSDACGAIN_0P2; /**< Select from HSDACGAIN_1, HSDACGAIN_0P2 */
AD5940_HSDACCal(&hsdac_cal);
printf("\n 75mV Range Cal\n");
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_0P25; /**< Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
hsdac_cal.HsDacGain = HSDACGAIN_1; /**< Select from HSDACGAIN_1, HSDACGAIN_0P2 */
AD5940_HSDACCal(&hsdac_cal);
printf("\n 15mV Range Cal\n");
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_0P25; /**< Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
hsdac_cal.HsDacGain = HSDACGAIN_0P2; /**< Select from HSDACGAIN_1, HSDACGAIN_0P2 */
AD5940_HSDACCal(&hsdac_cal);
printf("HSDAC Cal Complete!\n");
}

View File

@ -0,0 +1,142 @@
/*!
*****************************************************************************
@file: AD5940Main.c
@author: Neo Xu
@brief: Standard 4-wire or 2-wire impedance measurement example.
-----------------------------------------------------------------------------
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 "Impedance.h"
/**
User could configure following parameters
**/
#define APPBUFF_SIZE 512
uint32_t AppBuff[APPBUFF_SIZE];
int32_t ImpedanceShowResult(uint32_t *pData, uint32_t DataCount)
{
float freq;
fImpPol_Type *pImp = (fImpPol_Type*)pData;
AppIMPCtrl(IMPCTRL_GETFREQ, &freq);
printf("Freq:%.2f ", freq);
/*Process data*/
for(int i=0;i<DataCount;i++)
{
printf("RzMag: %f Ohm , RzPhase: %f \n",pImp[i].Magnitude,pImp[i].Phase*180/MATH_PI);
}
return 0;
}
static int32_t AD5940PlatformCfg(void)
{
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
/* Platform configuration */
/* 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;//AppIMPCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
/* 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);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP0_INT|GP1_SLEEP|GP2_SYNC;
gpio_cfg.InputEnSet = 0;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
return 0;
}
void AD5940ImpedanceStructInit(void)
{
AppIMPCfg_Type *pImpedanceCfg;
AppIMPGetCfg(&pImpedanceCfg);
/* Step1: configure initialization sequence Info */
pImpedanceCfg->SeqStartAddr = 0;
pImpedanceCfg->MaxSeqLen = 512; /* @todo add checker in function */
pImpedanceCfg->RcalVal = 10000.0;
pImpedanceCfg->SinFreq = 60000.0;
pImpedanceCfg->FifoThresh = 4;
/* Set switch matrix to onboard(EVAL-AD5940ELECZ) dummy sensor. */
/* Note the RCAL0 resistor is 10kOhm. */
pImpedanceCfg->DswitchSel = SWD_CE0;
pImpedanceCfg->PswitchSel = SWP_RE0;
pImpedanceCfg->NswitchSel = SWN_SE0;
pImpedanceCfg->TswitchSel = SWT_SE0LOAD;
/* The dummy sensor is as low as 5kOhm. We need to make sure RTIA is small enough that HSTIA won't be saturated. */
pImpedanceCfg->HstiaRtiaSel = HSTIARTIA_5K;
/* Configure the sweep function. */
pImpedanceCfg->SweepCfg.SweepEn = bTRUE;
pImpedanceCfg->SweepCfg.SweepStart = 1.0f; /* Start from 1kHz */
pImpedanceCfg->SweepCfg.SweepStop = 200e3f; /* Stop at 100kHz */
pImpedanceCfg->SweepCfg.SweepPoints = 101; /* Points is 101 */
pImpedanceCfg->SweepCfg.SweepLog = bTRUE;
/* Configure Power Mode. Use HP mode if frequency is higher than 80kHz. */
pImpedanceCfg->PwrMod = AFEPWR_LP;
/* Configure filters if necessary */
pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_2; /* Sample rate is 800kSPS/2 = 400kSPS */
pImpedanceCfg->DftNum = DFTNUM_16384;
pImpedanceCfg->DftSrc = DFTSRC_SINC3;
}
void AD5940_Main(void)
{
uint32_t temp;
AD5940PlatformCfg();
AD5940ImpedanceStructInit();
AppIMPInit(AppBuff, APPBUFF_SIZE); /* Initialize IMP application. Provide a buffer, which is used to store sequencer commands */
AppIMPCtrl(IMPCTRL_START, 0); /* Control IMP measurement to start. Second parameter has no meaning with this command. */
while(1)
{
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag();
temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
}
}
}

View File

@ -0,0 +1,740 @@
/*!
*****************************************************************************
@file: Impedance.c
@author: Neo Xu
@brief: standard 4-wire or 2-wire impedance measurement sequences.
-----------------------------------------------------------------------------
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 "Impedance.h"
/* Default LPDAC resolution(2.5V internal reference). */
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/*
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
*/
AppIMPCfg_Type AppIMPCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ImpODR = 0.0001, /* 20.0 Hz*/
.NumOfData = -1,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.DswitchSel = SWD_CE0,
.PswitchSel = SWP_CE0,
.NswitchSel = SWN_AIN1,
.TswitchSel = SWT_AIN1,
.PwrMod = AFEPWR_HP,
.HstiaRtiaSel = HSTIARTIA_5K,
.ExtRtia = 0, //set only when HstiaRtiaSel = HSTIARTIA_OPEN
.ExcitBufGain = EXCITBUFGAIN_0P25,//EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_0P2,//HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.0,
.BiasVolt = -0.0f,
.SinFreq = 100000.0, /* 1000Hz */
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.AdcPgaGain = ADCPGA_4,//ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.ADCAvgNum = ADCAVGNUM_16,
.SweepCfg.SweepEn = bTRUE,
.SweepCfg.SweepStart = 1000,
.SweepCfg.SweepStop = 100000.0,
.SweepCfg.SweepPoints = 101,
.SweepCfg.SweepLog = bFALSE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4,
.IMPInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
int32_t AppIMPGetCfg(void *pCfg)
{
if(pCfg)
{
*(AppIMPCfg_Type**)pCfg = &AppIMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
int32_t AppIMPCtrl(uint32_t Command, void *pPara)
{
switch (Command)
{
case IMPCTRL_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(AppIMPCfg.IMPInited == 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;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppIMPCfg.WuptClkFreq/AppIMPCfg.ImpODR)-4;
AD5940_WUPTCfg(&wupt_cfg);
AppIMPCfg.FifoDataCount = 0; /* restart */
break;
}
case IMPCTRL_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 IMPCTRL_STOPSYNC:
{
AppIMPCfg.StopRequired = bTRUE;
break;
}
case IMPCTRL_GETFREQ:
{
if(pPara == 0)
return AD5940ERR_PARA;
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppIMPCfg.FreqofData;
else
*(float*)pPara = AppIMPCfg.SinFreq;
}
break;
case IMPCTRL_SHUTDOWN:
{
AppIMPCtrl(IMPCTRL_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;
}
/* generated code snnipet */
float AppIMPGetCurrFreq(void)
{
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
return AppIMPCfg.FreqofData;
else
return AppIMPCfg.SinFreq;
}
/* Application initialization */
static AD5940Err AppIMPSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HsLoopCfg;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* 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;
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*/
if(AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
{
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
}
else
{
aferef_cfg.LpBandgapEn = bFALSE;
aferef_cfg.LpRefBufEn = bFALSE;
}
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain;
HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain;
HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate;
HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
if(AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0;
else
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HsLoopCfg.HsTiaCfg.HstiaCtia = 31; /* 31pF + 2pF */
HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel;
HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel;
HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel;
HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
HsLoopCfg.WgCfg.GainCalEn = bTRUE;
HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
{
AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
sin_freq = AppIMPCfg.SweepCurrFreq;
}
else
{
sin_freq = AppIMPCfg.SinFreq;
AppIMPCfg.FreqofData = sin_freq;
}
HsLoopCfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppIMPCfg.SysClkFreq);
HsLoopCfg.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppIMPCfg.DacVoltPP/800.0f*2047 + 0.5f);
HsLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&HsLoopCfg);
if(AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
{
LPDACCfg_Type lpdac_cfg;
lpdac_cfg.LpdacSel = LPDAC0;
lpdac_cfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Use Vbias to tuning BiasVolt. */
lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Vbias-Vzero = BiasVolt */
lpdac_cfg.DacData6Bit = 0x40>>1; /* Set Vzero to middle scale. */
if(AppIMPCfg.BiasVolt<-1100.0f) AppIMPCfg.BiasVolt = -1100.0f + DAC12BITVOLT_1LSB;
if(AppIMPCfg.BiasVolt> 1100.0f) AppIMPCfg.BiasVolt = 1100.0f - DAC12BITVOLT_1LSB;
lpdac_cfg.DacData12Bit = (uint32_t)((AppIMPCfg.BiasVolt + 1100.0f)/DAC12BITVOLT_1LSB);
lpdac_cfg.DataRst = bFALSE; /* Do not reset data register */
lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN|LPDACSW_VZERO2HSTIA;
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 */
AD5940_LPDACCfgS(&lpdac_cfg);
}
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = AppIMPCfg.ADCAvgNum;
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppIMPCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppIMPCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
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 */
if(AppIMPCfg.BiasVolt == 0.0f)
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
else
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH|AFECTRL_DCBUFPWR, bTRUE);
/* 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)
{
AppIMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppIMPCfg.InitSeqInfo.SeqRamAddr = AppIMPCfg.SeqStartAddr;
AppIMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppIMPSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppIMPCfg.DftSrc;
clks_cal.DataCount = 1L<<(AppIMPCfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = AppIMPCfg.ADCAvgNum;
clks_cal.RatioSys2AdcClk = AppIMPCfg.SysClkFreq/AppIMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin2); /* Set GPIO1, clear others that under control */
AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* @todo wait 250us? */
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 for signal settling DFT_WAIT
AD5940_SEQGenInsert(SEQ_WAIT(16*10));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenFetchSeq(NULL, &AppIMPCfg.SeqWaitAddr[0]); /* Record the start address of the next command. */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
//wait for first data ready
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE); /* Stop ADC convert and DFT */
/* Configure matrix for external Rz */
sw_cfg.Dswitch = AppIMPCfg.DswitchSel;
sw_cfg.Pswitch = AppIMPCfg.PswitchSel;
sw_cfg.Nswitch = AppIMPCfg.NswitchSel;
sw_cfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE); /* Enable Waveform generator */
AD5940_SEQGenInsert(SEQ_WAIT(16*10)); //delay for signal settling DFT_WAIT
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenFetchSeq(NULL, &AppIMPCfg.SeqWaitAddr[1]); /* Record the start address of next command */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
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);
AD5940_SEQGpioCtrlS(0); /* Clr GPIO1 */
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AppIMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppIMPCfg.MeasureSeqInfo.SeqRamAddr = AppIMPCfg.InitSeqInfo.SeqRamAddr + AppIMPCfg.InitSeqInfo.SeqLen ;
AppIMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/* Depending on frequency of Sin wave set optimum filter settings */
AD5940Err AppIMPCheckFreq(float freq)
{
ADCFilterCfg_Type filter_cfg;
DFTCfg_Type dft_cfg;
HSDACCfg_Type hsdac_cfg;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
FreqParams_Type freq_params;
uint32_t SeqCmdBuff[32];
uint32_t SRAMAddr = 0;;
/* Step 1: Check Frequency */
freq_params = AD5940_GetFreqParameters(freq);
if(freq < 0.51)
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain =EXCITBUFGAIN_2;// AppIMPCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = HSDACGAIN_1;//AppIMPCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_HSRTIACfgS(HSTIARTIA_40K); //set as per load current range
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_800KHZ;
AppIMPCfg.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}
else if(freq < 5 )
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain =EXCITBUFGAIN_2;// AppIMPCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = HSDACGAIN_1;//AppIMPCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_HSRTIACfgS(HSTIARTIA_40K); //set as per load current range
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_800KHZ;
AppIMPCfg.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}else if(freq < 450)
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain =AppIMPCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = AppIMPCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_HSRTIACfgS(HSTIARTIA_5K); //set as per load current range
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_800KHZ;
AppIMPCfg.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}
else if(freq<80000)
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain =AppIMPCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = AppIMPCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_HSRTIACfgS(HSTIARTIA_5K); //set as per load current range
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_800KHZ;
AppIMPCfg.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}
/* High power mode */
if(freq >= 80000)
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain =AppIMPCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = AppIMPCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x07;
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_HSRTIACfgS(HSTIARTIA_5K); //set as per load current range
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_1P6MHZ;
AppIMPCfg.AdcClkFreq = 32e6;
/* Change clock to 32MHz oscillator */
AD5940_HPModeEn(bTRUE);
}
/* Step 2: Adjust ADCFILTERCON and DFTCON to set optimumn SINC3, SINC2 and DFTNUM settings */
filter_cfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
filter_cfg.ADCSinc2Osr = freq_params.ADCSinc2Osr;
filter_cfg.ADCSinc3Osr = freq_params.ADCSinc3Osr;
filter_cfg.BpSinc3 = bFALSE;
filter_cfg.BpNotch = bTRUE;
filter_cfg.Sinc2NotchEnable = bTRUE;
dft_cfg.DftNum = freq_params.DftNum;
dft_cfg.DftSrc = freq_params.DftSrc;
dft_cfg.HanWinEn = AppIMPCfg.HanWinEn;
AD5940_ADCFilterCfgS(&filter_cfg);
AD5940_DFTCfgS(&dft_cfg);
/* Step 3: Calculate clocks needed to get result to FIFO and update sequencer wait command */
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 = AppIMPCfg.SysClkFreq/AppIMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
SRAMAddr = AppIMPCfg.MeasureSeqInfo.SeqRamAddr + AppIMPCfg.SeqWaitAddr[0];
SeqCmdBuff[0] =SEQ_WAIT(WaitClks/2);
SeqCmdBuff[1] =SEQ_WAIT(WaitClks/2);
AD5940_SEQCmdWrite(SRAMAddr, SeqCmdBuff, 2);
SRAMAddr = AppIMPCfg.MeasureSeqInfo.SeqRamAddr + AppIMPCfg.SeqWaitAddr[1];
SeqCmdBuff[0] =SEQ_WAIT(WaitClks/2);
SeqCmdBuff[1] =SEQ_WAIT(WaitClks/2);
AD5940_SEQCmdWrite(SRAMAddr, SeqCmdBuff, 2);
return AD5940ERR_OK;
}
/* This function provide application initialize. It can also enable Wupt that will automatically trigger sequence. Or it can configure */
int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_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; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bTRUE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
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 = AppIMPCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppIMPCfg.IMPInited == bFALSE)||\
(AppIMPCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppIMPSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppIMPSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppIMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppIMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppIMPCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Measurement sequence */
AppIMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.MeasureSeqInfo);
AppIMPCheckFreq(AppIMPCfg.FreqofData);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
//AD5940_AFEPwrBW(AppIMPCfg.PwrMod, AFEBW_250KHZ);
AppIMPCfg.IMPInited = bTRUE; /* IMP application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
int32_t AppIMPRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppIMPCfg.NumOfData > 0)
{
AppIMPCfg.FifoDataCount += *pDataCount/4;
if(AppIMPCfg.FifoDataCount >= AppIMPCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppIMPCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if(AppIMPCfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppIMPCfg.SweepNextFreq, AppIMPCfg.SysClkFreq);
AppIMPCheckFreq(AppIMPCfg.SweepNextFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
int32_t AppIMPDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount/4;
fImpPol_Type * const pOut = (fImpPol_Type*)pData;
iImpCar_Type * pSrcData = (iImpCar_Type*)pData;
*pDataCount = 0;
DataCount = (DataCount/4)*4;/* We expect RCAL data together with Rz data. One DFT result has two data in FIFO, real part and imaginary part. */
/* Convert DFT result to int32_t type */
for(uint32_t i=0; i<DataCount; i++)
{
pData[i] &= 0x3ffff; /* @todo option to check ECC */
if(pData[i]&(1L<<17)) /* Bit17 is sign bit */
{
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
}
}
for(uint32_t i=0; i<ImpResCount; i++)
{
iImpCar_Type *pDftRcal, *pDftRz;
pDftRcal = pSrcData++;
pDftRz = pSrcData++;
float RzMag,RzPhase;
float RcalMag, RcalPhase;
RcalMag = sqrt((float)pDftRcal->Real*pDftRcal->Real+(float)pDftRcal->Image*pDftRcal->Image);
RcalPhase = atan2(-pDftRcal->Image,pDftRcal->Real);
RzMag = sqrt((float)pDftRz->Real*pDftRz->Real+(float)pDftRz->Image*pDftRz->Image);
RzPhase = atan2(-pDftRz->Image,pDftRz->Real);
RzMag = RcalMag/RzMag*AppIMPCfg.RcalVal;
RzPhase = RcalPhase - RzPhase;
//printf("V:%d,%d,I:%d,%d ",pDftRcal->Real,pDftRcal->Image, pDftRz->Real, pDftRz->Image);
pOut[i].Magnitude = RzMag;
pOut[i].Phase = RzPhase;
}
*pDataCount = ImpResCount;
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
/* Calculate next frequency point */
if(AppIMPCfg.SweepCfg.SweepEn == bTRUE)
{
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepNextFreq;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
}
return 0;
}
/**
*/
int32_t AppIMPISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
*pCount = 0;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Prohibit AFE to enter sleep mode. */
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt()/4)*4;
if(FifoCnt > BuffCount)
{
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppIMPRegModify(pBuff, &FifoCnt); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
//AD5940_EnterSleepS(); /* Manually put AFE back to hibernate mode. This operation only takes effect when register value is ACTIVE previously */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
/* Process data */
AppIMPDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}

View File

@ -0,0 +1,85 @@
/*!
*****************************************************************************
@file: Impedance.h
@author: Neo XU
@brief: 4-wire/2-wire impedance measurement header file.
-----------------------------------------------------------------------------
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.
*****************************************************************************/
#ifndef _IMPEDANCESEQUENCES_H_
#define _IMPEDANCESEQUENCES_H_
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppBIAInit */
uint32_t SeqStartAddr; /* Initialaztion sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLen; /* Limit the maximum sequence. */
uint32_t SeqStartAddrCal; /* Measurement sequence start address in SRAM of AD5940 */
uint32_t SeqWaitAddr[2];
uint32_t MaxSeqLenCal;
/* Application related parameters */
float ImpODR; /* */
int32_t NumOfData; /* By default it's '-1'. If you want the engine stops after get NumofData, then set the value here. Otherwise, set it to '-1' which means never stop. */
float WuptClkFreq; /* The clock frequency of Wakeup Timer in Hz. Typically it's 32kHz. Leave it here in case we calibrate clock in software method */
float SysClkFreq; /* The real frequency of system clock */
float AdcClkFreq; /* The real frequency of ADC clock */
float RcalVal; /* Rcal value in Ohm */
/* Switch Configuration */
uint32_t DswitchSel;
uint32_t PswitchSel;
uint32_t NswitchSel;
uint32_t TswitchSel;
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
uint32_t HstiaRtiaSel; /* Use internal RTIA, select from RTIA_INT_200, RTIA_INT_1K, RTIA_INT_5K, RTIA_INT_10K, RTIA_INT_20K, RTIA_INT_40K, RTIA_INT_80K, RTIA_INT_160K */
uint32_t ExcitBufGain; /* Select from EXCTBUFGAIN_2, EXCTBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
uint32_t HsDacUpdateRate;
float DacVoltPP; /* DAC output voltage in mV peak to peak. Maximum value is 800mVpp. Peak to peak voltage */
float BiasVolt; /* The excitation signal is DC+AC. This parameter decides the DC value in mV unit. 0.0mV means no DC bias.*/
float SinFreq; /* Frequency of excitation signal */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
uint32_t AdcPgaGain; /* PGA Gain select from GNPGA_1, GNPGA_1_5, GNPGA_2, GNPGA_4, GNPGA_9 !!! We must ensure signal is in range of +-1.5V which is limited by ADC input stage */
uint8_t ADCSinc3Osr;
uint8_t ADCSinc2Osr;
uint8_t ADCAvgNum;
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
/* Private variables for internal usage */
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
float FreqofData; /* The frequency of latest data sampled */
BoolFlag IMPInited; /* If the program run firstly, generated sequence commands */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
BoolFlag StopRequired; /* After FIFO is ready, stop the measurement sequence */
uint32_t FifoDataCount; /* Count how many times impedance have been measured */
}AppIMPCfg_Type;
#define IMPCTRL_START 0
#define IMPCTRL_STOPNOW 1
#define IMPCTRL_STOPSYNC 2
#define IMPCTRL_GETFREQ 3 /* Get Current frequency of returned data from ISR */
#define IMPCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize);
int32_t AppIMPGetCfg(void *pCfg);
int32_t AppIMPISR(void *pBuff, uint32_t *pCount);
int32_t AppIMPCtrl(uint32_t Command, void *pPara);
#endif

View File

@ -0,0 +1,320 @@
/*!
*****************************************************************************
@file: AD5940_Sequencer.c
@author: Neo Xu
@brief: Basic usage of sequencer.
-----------------------------------------------------------------------------
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.
*****************************************************************************/
/**
* Sequencer is used to control the AFE automatically. It can execute commands that
* is pre-loaded to SRAM. There are 6kB SRAM available while you can choose to use
* 2kB or 4kB of it and use reset of SRAM for data FIFO.
* There are 3 commands available. We mainly use only two commands:
* - Write register
* - Wait
* We control the AFE by registers, so with sequencer, we can do almost everything.
*
* Once sequencer is enabled, it starts to wait valid trigger signal. Sequencer can
* manage 4sequences at same time. You can choose which sequence you want to trigger.
* To make the AFE can manage measurement automatically, there are three method to
* trigger sequence.
* - MMR. You can trigger any sequence by register write. Or call function @ref AD5940_SEQMmrTrig
* - GPIO. You can trigger any sequence by GPIO. To use this, you must firstly set
* GPIO function to GPx_TRIG. Where x is the GPIO number. GPIO0 is used to trigger
* Sequence0 and GPIO3 is used to trigger Sequence3. Check the macro definition to
* Check the details (or below table).
* |GPIO|WhichSequence|
* |GP0|SEQUENCE0|
* |GP1|SEQUENCE1|
* |GP2|SEQUENCE2|
* |GP3|SEQUENCE3|
* |GP4|SEQUENCE0|
* |GP5|SEQUENCE1|
* |GP6|SEQUENCE2|
* |GP7|SEQUENCE3|
* - WakeupTimer. Wakeuptimer can automatically wakeup AFE from hibernate state and trigger selected
* sequence in register SEQORDER. This register defines the order of sequence that
* Wakeuptimer will trigger. There are 8 slots in this register. You can fill in any
* of the four sequences. Also, you can choose not to use all these 8 slots, just simply
* specify the end slot. We call the 8 slots are A/B/C/D/E/F/G/H. For example you can
* choose the end slot as C. So wakeup timer will trigger the sequence in below order:
* A->B->C->A->B->C->A->B->C->... until you stop Wakeuptimer.
* If you fill in slot A with sequence0, B with Sequence3, C with sequence1, the sequence
* will be executed in the order defined above(A-B-C-A-B-C...)
* SEQ0->SEQ3->SEQ1->SEQ0->SEQ3->SEQ1->...
* For each sequence, there is a sleep timer and a wakeup timer. The timer will automatically
* load corresponding value.
* The structure @ref WUPTCfg_Type can be used to initialize all above settings.
*
* In this example, we use both three kinds of trigger source.
* We firstly use Wakeup Timer to trigger sequence 0/1/2. The sequence is used to write registers and
* generate a custom-interrupt. We detect the interrupt to identify which sequence is running.
* Finally, we use GPIO to trigger sequence3.
*
* When there is conflict between trigger signals, for example, GPIO triggered one sequence that is running,
* current strategy is ignore this trigger.
* Use @reg SEQCfg_Type to configure sequencer.
*
* @note: connect GP2 and GP1 together. This demo show how to use GPIO to trigger sequencer. GP2 is the trigger input.
* We use GP1 to generate the trigger signal, while in real case, it should be the MCU's GPIO.
*/
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
int32_t SeqISR(void);
BoolFlag bSeqEnd = bFALSE;
static const uint32_t Seq0Commands[]=
{
SEQ_WR(REG_AFE_SWCON, 0x0000),
SEQ_INT0(), /* generate custom-interrupt 0. We can generate any custom interrupt(SEQ_INT0/1/2/3()) by sequencer. */
};
static const uint32_t Seq1Commands[]=
{
SEQ_WR(REG_AFE_SWCON, 0x1111),
SEQ_INT1(), /* generate custom-interrupt 0 */
SEQ_STOP(), /* Disable sequencer */
};
static const uint32_t Seq2Commands[]=
{
SEQ_WR(REG_AFE_SWCON, 0x2222),
SEQ_INT2(), /* generate custom-interrupt 1 */
};
static const uint32_t Seq3Commands[]=
{
SEQ_WR(REG_AFE_SWCON, 0x3333),
SEQ_INT3(), /* generate custom-interrupt 1 */
};
static int32_t AD5940PlatformCfg(void)
{
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize(); /* Call this right after AFE reset */
/* Platform configuration */
/* 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;//AppIMPCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
/* 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);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_ENDSEQ|AFEINTSRC_CUSTOMINT0|AFEINTSRC_CUSTOMINT1|AFEINTSRC_CUSTOMINT2|AFEINTSRC_CUSTOMINT3, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
/* GP0: the interrupt output.
GP1: normal GPIO
GP2: used as trigger to sequence2. If valid trigger signal detected, sequencer will try to run sequence2.
GP3: not used.
GP4: controlled by sequencer.
Others: not used. The default function is mode0.
*/
gpio_cfg.FuncSet = GP0_INT|GP1_GPIO|GP2_TRIG|GP4_SYNC;
gpio_cfg.InputEnSet = AGPIO_Pin2;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin2|AGPIO_Pin4;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
return 0;
}
#define SEQ0ADDR 0
#define SEQ1ADDR 16
#define SEQ2ADDR 32
#define SEQ3ADDR 48
void AD5940_Main(void)
{
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
WUPTCfg_Type wupt_cfg;
SEQInfo_Type seqinfo0, seqinfo1, seqinfo2, seqinfo3;
SeqGpioTrig_Cfg seqgpiotrig_cfg;
AD5940PlatformCfg();
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bTRUE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bTRUE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_SINC3;
fifo_cfg.FIFOThresh = 4;
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
seqinfo0.pSeqCmd = Seq0Commands;
seqinfo0.SeqId = SEQID_0;
seqinfo0.SeqLen = SEQ_LEN(Seq0Commands);
seqinfo0.SeqRamAddr = SEQ0ADDR;
seqinfo0.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo0); /* Configure sequence0 info and write commands to SRAM */
seqinfo1.pSeqCmd = Seq1Commands;
seqinfo1.SeqId = SEQID_1;
seqinfo1.SeqLen = SEQ_LEN(Seq1Commands);
seqinfo1.SeqRamAddr = SEQ1ADDR;
seqinfo1.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo1);
seqinfo2.pSeqCmd = Seq2Commands;
seqinfo2.SeqId = SEQID_2;
seqinfo2.SeqLen = SEQ_LEN(Seq2Commands);
seqinfo2.SeqRamAddr = SEQ2ADDR;
seqinfo2.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo2);
seqinfo3.pSeqCmd = Seq3Commands;
seqinfo3.SeqId = SEQID_3;
seqinfo3.SeqLen = SEQ_LEN(Seq3Commands);
seqinfo3.SeqRamAddr = SEQ3ADDR;
seqinfo3.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo3);
/* Configure wakeup timer */
wupt_cfg.WuptEn = bFALSE; /* Don't start it right now. */
wupt_cfg.WuptEndSeq = WUPTENDSEQ_C; /* A->B->C->A->B-C */
wupt_cfg.WuptOrder[0] = SEQID_0; /* Put SEQ0 to slotA */
wupt_cfg.WuptOrder[1] = SEQID_3; /* Put SEQ3 to slotB */
wupt_cfg.WuptOrder[2] = SEQID_1; /* Put SEQ1 to slotC */
/* There is no need to init slot DEFGH, that's WuptOrder[3] to WuptOrder[7], becaue we don't use it. EndofSeq is C.*/
wupt_cfg.SeqxSleepTime[SEQID_0] = 10;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(32000.0f*500/1000.0f) - 10 - 2; /* 500ms after, wakeup and trigger seq0 */
wupt_cfg.SeqxSleepTime[SEQID_3] = 10;
wupt_cfg.SeqxWakeupTime[SEQID_3] = (uint32_t)(32000.0f*1000/1000.0f)- 10 -2; /* 1000ms after, trigger seq2 */
wupt_cfg.SeqxSleepTime[SEQID_1] = 10;
wupt_cfg.SeqxWakeupTime[SEQID_1] = (uint32_t)(32000.0f*2000/1000.0f)- 10 -2; /* 2000ms after, trigger seq2 */
AD5940_WUPTCfg(&wupt_cfg);
printf("Test0: trigger sequencer by wakeup timer.\n");
AD5940_WUPTCtrl(bTRUE); /* Enable wakeup timer. */
while(1)
{
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag();
SeqISR();
if(bSeqEnd)
break;
}
}
AD5940_WUPTCtrl(bFALSE); /* Wakeup timer is still running and triggering. Trigger is not accepted because sequencer
is disabled in last sequence(SEQ1) command. */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer again, because we disabled it in seq3 last command. */
/* Test MMR trigger */
printf("\nTest1: trigger sequence2 manually by register write.\n");
AD5940_SEQMmrTrig(SEQID_2); /* Trigger sequence2 manually. */
/* Wait until CUSTMINT2 is set. We generate this interrupt in SEQ2 */
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_CUSTOMINT2) == bFALSE); /* Test INTC1, we enabled all interrupts in INTC1. */
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT2);
printf("sequence2 has been executed\n");
printf("SWCON:0x%08x\n", AD5940_ReadReg(REG_AFE_SWCON));
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Toggle GPIO to trigger sequencer2 */
printf("\nTest2: trigger sequence2 manually by GPIO\n");
printf("Please connect GP2 and GP1 together. We will set GP2 function to TRIG.\n"
"GP1 is set to GPIO function and is in output state. We use GP1 to toggle GP2.\n");
AD5940_Delay10us(100*1000*2);
printf("Toggle GPIO now\n");
/* Allow GP2 falling edge to trigger sequence2 */
seqgpiotrig_cfg.bEnable = bTRUE;
seqgpiotrig_cfg.PinSel = AGPIO_Pin2;
seqgpiotrig_cfg.SeqPinTrigMode = SEQPINTRIGMODE_FALLING;
AD5940_SEQGpioTrigCfg(&seqgpiotrig_cfg);
/* GP2 is connected to GP1 by user.
We generate falling edge on GP1(gpio, output) to control GP2(trigger, input).
*/
AD5940_AGPIOSet(AGPIO_Pin1);
AD5940_AGPIOClr(AGPIO_Pin1);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_CUSTOMINT2) == bFALSE); /* Test INTC1, we enabled all interrupts in INTC1. */
printf("Trigger received and sequence2 has been executed\n\n");
printf("Sequencer test done!\n");
while(1);
}
int32_t SeqISR(void)
{
uint32_t IntFlag, temp;
IntFlag = AD5940_INTCGetFlag(AFEINTC_0);
if(IntFlag & AFEINTSRC_CUSTOMINT0)
{
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT0);
printf("Custom INT0!\n");
temp = AD5940_ReadReg(REG_AFE_SWCON);
printf("SWCON:0x%08x\n", temp);
}
if(IntFlag & AFEINTSRC_CUSTOMINT1)
{
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT1);
printf("Custom INT1!\n");
temp = AD5940_ReadReg(REG_AFE_SWCON);
printf("SWCON:0x%08x\n", temp);
}
if(IntFlag & AFEINTSRC_CUSTOMINT2)
{
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT2);
printf("Custom INT2!\n");
temp = AD5940_ReadReg(REG_AFE_SWCON);
printf("SWCON:0x%08x\n", temp);
}
if(IntFlag & AFEINTSRC_CUSTOMINT3)
{
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT3);
printf("Custom INT3!\n");
temp = AD5940_ReadReg(REG_AFE_SWCON);
printf("SWCON:0x%08x\n", temp);
}
if(IntFlag & AFEINTSRC_ENDSEQ) /* This interrupt is generated when Sequencer is disabled. */
{
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
printf("End of Sequence\n");
bSeqEnd = bTRUE;
}
return AD5940ERR_OK;
}

View File

@ -0,0 +1,190 @@
/**
* @file NUCLEOF411Port.c
* @brief ST NUCLEOF411 board port file.
* @version V0.2.0
* @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"
#include "stdio.h"
#include "stm32f4xx_hal.h"
/* Definition for STM32 SPI clock resources */
#define AD5940SPI SPI1
#define AD5940_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE()
#define AD5940_SCK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define AD5940_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define AD5940_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define AD5940_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define AD5940_RST_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define AD5940_GP0INT_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define AD5940SPI_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET()
#define AD5940SPI_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET()
/* Definition for AD5940 Pins */
#define AD5940_SCK_PIN GPIO_PIN_5
#define AD5940_SCK_GPIO_PORT GPIOA
#define AD5940_SCK_AF GPIO_AF5_SPI1
#define AD5940_MISO_PIN GPIO_PIN_6
#define AD5940_MISO_GPIO_PORT GPIOA
#define AD5940_MISO_AF GPIO_AF5_SPI1
#define AD5940_MOSI_PIN GPIO_PIN_7
#define AD5940_MOSI_GPIO_PORT GPIOA
#define AD5940_MOSI_AF GPIO_AF5_SPI1
#define AD5940_CS_PIN GPIO_PIN_6
#define AD5940_CS_GPIO_PORT GPIOB
#define AD5940_RST_PIN GPIO_PIN_0 //A3
#define AD5940_RST_GPIO_PORT GPIOB
#define AD5940_GP0INT_PIN GPIO_PIN_10 //A3
#define AD5940_GP0INT_GPIO_PORT GPIOA
#define AD5940_GP0INT_IRQn EXTI15_10_IRQn
SPI_HandleTypeDef SpiHandle;
#define SYSTICK_MAXCOUNT ((1L<<24)-1) /* we use Systick to complete function Delay10uS(). This value only applies to NUCLEOF411 board. */
#define SYSTICK_CLKFREQ 100000000L /* Systick clock frequency in Hz. This only appies to NUCLEOF411 board */
volatile static uint8_t ucInterrupted = 0; /* Flag to indicate interrupt occurred */
/**
@brief Using SPI to transmit N bytes and return the received bytes. This function targets to
provide a more efficent way to transmit/receive data.
@param pSendBuffer :{0 - 0xFFFFFFFF}
- Pointer to the data to be sent.
@param pRecvBuff :{0 - 0xFFFFFFFF}
- Pointer to the buffer used to store received data.
@param length :{0 - 0xFFFFFFFF}
- Data length in SendBuffer.
@return None.
**/
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,unsigned char *pRecvBuff,unsigned long length)
{
HAL_SPI_TransmitReceive(&SpiHandle, pSendBuffer, pRecvBuff, length, (uint32_t)-1);
}
void AD5940_CsClr(void)
{
HAL_GPIO_WritePin(AD5940_CS_GPIO_PORT, AD5940_CS_PIN, GPIO_PIN_RESET);
}
void AD5940_CsSet(void)
{
HAL_GPIO_WritePin(AD5940_CS_GPIO_PORT, AD5940_CS_PIN, GPIO_PIN_SET);
}
void AD5940_RstSet(void)
{
HAL_GPIO_WritePin(AD5940_RST_GPIO_PORT, AD5940_RST_PIN, GPIO_PIN_SET);
}
void AD5940_RstClr(void)
{
HAL_GPIO_WritePin(AD5940_RST_GPIO_PORT, AD5940_RST_PIN, GPIO_PIN_RESET);
}
void AD5940_Delay10us(uint32_t time)
{
time/=100;
if(time == 0) time =1;
HAL_Delay(time);
}
uint32_t AD5940_GetMCUIntFlag(void)
{
return ucInterrupted;
}
uint32_t AD5940_ClrMCUIntFlag(void)
{
ucInterrupted = 0;
return 1;
}
uint32_t AD5940_MCUResourceInit(void *pCfg)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* Step1, initialize SPI peripheral and its GPIOs for CS/RST */
AD5940_SCK_GPIO_CLK_ENABLE();
AD5940_MISO_GPIO_CLK_ENABLE();
AD5940_MOSI_GPIO_CLK_ENABLE();
AD5940_CS_GPIO_CLK_ENABLE();
AD5940_RST_GPIO_CLK_ENABLE();
/* Enable SPI clock */
AD5940_CLK_ENABLE();
GPIO_InitStruct.Pin = AD5940_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = AD5940_SCK_AF;
HAL_GPIO_Init(AD5940_SCK_GPIO_PORT, &GPIO_InitStruct);
/* SPI MISO GPIO pin configuration */
GPIO_InitStruct.Pin = AD5940_MISO_PIN;
GPIO_InitStruct.Alternate = AD5940_MISO_AF;
HAL_GPIO_Init(AD5940_MISO_GPIO_PORT, &GPIO_InitStruct);
/* SPI MOSI GPIO pin configuration */
GPIO_InitStruct.Pin = AD5940_MOSI_PIN;
GPIO_InitStruct.Alternate = AD5940_MOSI_AF;
HAL_GPIO_Init(AD5940_MOSI_GPIO_PORT, &GPIO_InitStruct);
/* SPI CS GPIO pin configuration */
GPIO_InitStruct.Pin = AD5940_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(AD5940_CS_GPIO_PORT, &GPIO_InitStruct);
/* SPI RST GPIO pin configuration */
GPIO_InitStruct.Pin = AD5940_RST_PIN;
HAL_GPIO_Init(AD5940_RST_GPIO_PORT, &GPIO_InitStruct);
AD5940_CsSet();
AD5940_RstSet();
/* Set the SPI parameters */
SpiHandle.Instance = AD5940SPI;
SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; //SPI clock should be < AD5940_SystemClock
SpiHandle.Init.Direction = SPI_DIRECTION_2LINES;
SpiHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
SpiHandle.Init.CLKPolarity = SPI_POLARITY_LOW;
SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
SpiHandle.Init.TIMode = SPI_TIMODE_DISABLE;
SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
SpiHandle.Init.CRCPolynomial = 7;
SpiHandle.Init.NSS = SPI_NSS_SOFT;
SpiHandle.Init.Mode = SPI_MODE_MASTER;
HAL_SPI_Init(&SpiHandle);
/* Step 2: Configure external interrupot line */
AD5940_GP0INT_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = AD5940_GP0INT_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = 0;
HAL_GPIO_Init(AD5940_GP0INT_GPIO_PORT, &GPIO_InitStruct);
/* Enable and set EXTI Line0 Interrupt to the lowest priority */
HAL_NVIC_EnableIRQ(AD5940_GP0INT_IRQn);
// HAL_NVIC_SetPriority(AD5940_GP0INT_IRQn, 0, 0);
return 0;
}
/* MCU related external line interrupt service routine */
void EXTI15_10_IRQHandler()
{
ucInterrupted = 1;
__HAL_GPIO_EXTI_CLEAR_IT(AD5940_GP0INT_PIN);
}

143
examples/sequencer/main.c Normal file
View File

@ -0,0 +1,143 @@
/*
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 "stdio.h"
#include "ADuCM3029.h"
#include "AD5940.h"
/* Functions that used to initialize MCU platform */
uint32_t MCUPlatformInit(void *pCfg);
int main(void)
{
void AD5940_Main(void);
MCUPlatformInit(0);
AD5940_MCUResourceInit(0);
printf("Hello AD5940-Build Time:%s\n",__TIME__);
AD5940_Main();
}
/* Below functions are used to initialize MCU Platform */
uint32_t MCUPlatformInit(void *pCfg)
{
int UrtCfg(int iBaud);
/*Stop watch dog timer(ADuCM3029)*/
pADI_WDT0->CTL = 0xC9;
/* Clock Configure */
pADI_CLKG0_OSC->KEY = 0xCB14; // Select HFOSC as system clock.
pADI_CLKG0_OSC->CTL = // Int 32khz LFOSC selected in LFMUX
BITM_CLKG_OSC_CTL_HFOSCEN|BITM_CLKG_OSC_CTL_HFXTALEN;
while((pADI_CLKG0_OSC->CTL&BITM_CLKG_OSC_CTL_HFXTALOK) == 0);
pADI_CLKG0_OSC->KEY = 0xCB14;
pADI_CLKG0_CLK->CTL0 = 0x201; /* Select XTAL as system clock */
pADI_CLKG0_CLK->CTL1 = 0; // ACLK,PCLK,HCLK divided by 1
pADI_CLKG0_CLK->CTL5 = 0x00; // Enable clock to all peripherals - no clock gating
UrtCfg(230400);/*Baud rate: 230400*/
return 1;
}
/**
@brief int UrtCfg(int iBaud, int iBits, int iFormat)
==========Configure the UART.
@param iBaud :{B1200,B2200,B2400,B4800,B9600,B19200,B38400,B57600,B115200,B230400,B430800} \n
Set iBaud to the baudrate required:
Values usually: 1200, 2200 (for HART), 2400, 4800, 9600,
19200, 38400, 57600, 115200, 230400, 430800, or type in baud-rate directly
@note
- Powers up UART if not powered up.
- Standard baudrates are accurate to better than 0.1% plus clock error.\n
- Non standard baudrates are accurate to better than 1% plus clock error.
@warning - If an external clock is used for the system the ullRtClk must be modified with \n
the speed of the clock used.
**/
int UrtCfg(int iBaud)
{
int iBits = 3;//8bits,
int iFormat = 0;//, int iBits, int iFormat
int i1;
int iDiv;
int iRtC;
int iOSR;
int iPllMulValue;
unsigned long long ullRtClk = 16000000; // The root clock speed
/*Setup P0[11:10] as UART pins*/
pADI_GPIO0->CFG = (1<<22)|(1<<20)|(pADI_GPIO0->CFG&(~((3<<22)|(3<<20))));
iDiv = (pADI_CLKG0_CLK->CTL1& BITM_CLKG_CLK_CTL1_PCLKDIVCNT); // Read UART clock as set by CLKCON1[10:8]
iDiv = iDiv>>8;
if (iDiv == 0)
iDiv = 1;
iRtC = (pADI_CLKG0_CLK->CTL0& BITM_CLKG_CLK_CTL0_CLKMUX); // Check what is the root clock
switch (iRtC)
{
case 0: // HFOSC selected
ullRtClk = 26000000;
break;
case 1: // HFXTAL selected
if ((pADI_CLKG0_CLK->CTL0 & 0x200)==0x200) // 26Mhz XTAL used
ullRtClk = 26000000;
else
ullRtClk = 16000000; // Assume 16MHz XTAL
break;
case 2: // SPLL output
iPllMulValue = (pADI_CLKG0_CLK->CTL3 & // Check muliplication factor in PLL settings
BITM_CLKG_CLK_CTL3_SPLLNSEL); // bits[4:0]. Assume div value of 0xD in bits [14:11]
ullRtClk = (iPllMulValue *1000000); // Assume straight multiplication by pADI_CLKG0_CLK->CTL3[4:0]
break;
case 3:
ullRtClk = 26000000; //External clock is assumed to be 26MhZ, if different
break; //clock speed is used, this should be changed
default:
break;
}
// iOSR = (pADI_UART0->COMLCR2 & 0x3);
// iOSR = 2^(2+iOSR);
pADI_UART0->COMLCR2 = 0x3;
iOSR = 32;
//i1 = (ullRtClk/(iOSR*iDiv))/iBaud; // UART baud rate clock source is PCLK divided by OSR
i1 = (ullRtClk/(iOSR*iDiv))/iBaud-1; //for bigger M and N value
pADI_UART0->COMDIV = i1;
pADI_UART0->COMFBR = 0x8800|(((((2048/(iOSR*iDiv))*ullRtClk)/i1)/iBaud)-2048);
pADI_UART0->COMIEN = 0;
pADI_UART0->COMLCR = (iFormat&0x3c)|(iBits&3);
pADI_UART0->COMFCR = (BITM_UART_COMFCR_RFTRIG & 0/*RX_FIFO_1BYTE*/ ) |BITM_UART_COMFCR_FIFOEN;
pADI_UART0->COMFCR |= BITM_UART_COMFCR_RFCLR|BITM_UART_COMFCR_TFCLR; // Clear the UART FIFOs
pADI_UART0->COMFCR &= ~(BITM_UART_COMFCR_RFCLR|BITM_UART_COMFCR_TFCLR); // Disable clearing mechanism
NVIC_EnableIRQ(UART_EVT_IRQn); // Enable UART interrupt source in NVIC
pADI_UART0->COMIEN = BITM_UART_COMIEN_ERBFI|BITM_UART_COMIEN_ELSI; /* Rx Interrupt */
return pADI_UART0->COMLSR;
}
#include "stdio.h"
#ifdef __ICCARM__
int putchar(int c)
#else
int fputc(int c, FILE *f)
#endif
{
pADI_UART0->COMTX = c;
while((pADI_UART0->COMLSR&0x20) == 0);// tx fifo empty
return c;
}

View File

@ -1,5 +1,3 @@
# File: host/CMakeLists.txt
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project(EISConfigurator VERSION 1.0 LANGUAGES CXX C) project(EISConfigurator VERSION 1.0 LANGUAGES CXX C)
@ -83,7 +81,8 @@ set(PROJECT_HEADERS
if(ANDROID) if(ANDROID)
add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS}) add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS})
else() else()
add_executable(EISConfigurator MANUAL_FINALIZATION ${PROJECT_SOURCES} ${PROJECT_HEADERS}) # Removed MANUAL_FINALIZATION keyword as add_executable does not support it
add_executable(EISConfigurator ${PROJECT_SOURCES} ${PROJECT_HEADERS})
endif() endif()
if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE) if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE)

View File

@ -1,89 +1,148 @@
#include "GraphWidget.h" #include "GraphWidget.h"
#include <QVBoxLayout> #include <limits>
#include <cmath>
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) {
{ layout = new QVBoxLayout(this);
QVBoxLayout *layout = new QVBoxLayout(this); plot = new QCustomPlot(this);
// Setup Layout
layout->addWidget(plot);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
plot = new JKQTPlotter(this); // Setup Graphs
layout->addWidget(plot); graph1 = plot->addGraph();
graph2 = plot->addGraph(plot->xAxis, plot->yAxis2);
// Initialize Graphs // Style Graph 1 (Primary - Left Axis)
graphY1 = new JKQTPXYLineGraph(plot); QPen pen1(Qt::blue);
graphY2 = new JKQTPXYLineGraph(plot); pen1.setWidth(2);
graph1->setPen(pen1);
graph1->setLineStyle(QCPGraph::lsLine);
graph1->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 3));
graph1->setName("Primary");
// Styling Y1 (e.g., Blue) // Style Graph 2 (Secondary - Right Axis)
graphY1->setColor(QColor("blue")); QPen pen2(Qt::red);
graphY1->setSymbolColor(QColor("blue")); pen2.setWidth(2);
graphY1->setSymbol(JKQTPCircle); graph2->setPen(pen2);
graphY1->setSymbolSize(7); graph2->setLineStyle(QCPGraph::lsLine);
graphY1->setLineWidth(2); graph2->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, 3));
graph2->setName("Secondary");
// Styling Y2 (e.g., Red) // Enable Right Axis
graphY2->setColor(QColor("red")); plot->yAxis2->setVisible(true);
graphY2->setSymbolColor(QColor("red")); plot->yAxis2->setTickLabels(true);
graphY2->setSymbol(JKQTPTriangle);
graphY2->setSymbolSize(7);
graphY2->setLineWidth(2);
plot->addGraph(graphY1); // Interactions
plot->addGraph(graphY2); plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
// Legend
plot->legend->setVisible(true);
QFont legendFont = font();
legendFont.setPointSize(9);
plot->legend->setFont(legendFont);
plot->legend->setBrush(QBrush(QColor(255, 255, 255, 230)));
// Default Mode
configureBodePlot();
} }
void GraphWidget::setupPlot(QString title, QString xLabel, QString y1Label, QString y2Label) void GraphWidget::configureBodePlot() {
{ clear();
// Basic Settings
plot->getPlotter()->setPlotLabel(title);
plot->getXAxis()->setAxisLabel(xLabel);
// Y-Axis setup // X Axis: Frequency (Log)
plot->getYAxis()->setAxisLabel(y1Label); // Left Axis plot->xAxis->setLabel("Frequency (Hz)");
plot->xAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
plot->xAxis->setTicker(logTicker);
plot->xAxis->setNumberFormat("eb");
// If we want a secondary axis for Y2, JKQTPlotter supports it, // Y Axis 1: Impedance (Log)
// but for simplicity here we might plot on the same if scales are similar, plot->yAxis->setLabel("Impedance (Ohms)");
// or rely on user interpretation. plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
// For Bode (Ohm vs Degree), scales are vastly different. plot->yAxis->setTicker(logTicker);
// Ideally, Y2 should be on the right axis. plot->yAxis->setNumberFormat("eb");
// Assign Y1 to Left Axis // Y Axis 2: Phase (Linear)
graphY1->setTitle(y1Label); plot->yAxis2->setLabel("Phase (Degrees)");
plot->yAxis2->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->yAxis2->setTicker(linTicker);
plot->yAxis2->setNumberFormat("f");
plot->yAxis2->setRange(-180, 180);
// Assign Y2 to Right Axis (if supported by specific JKQT config, otherwise same axis) graph1->setName("Impedance");
// Assuming simple usage for now: graph2->setName("Phase");
graphY2->setTitle(y2Label);
// Enable Legend plot->replot();
plot->getPlotter()->setShowKey(true);
} }
void GraphWidget::setLogAxis(bool logX, bool logY) void GraphWidget::configureRawPlot() {
{ clear();
plot->getXAxis()->setLogAxis(logX);
plot->getYAxis()->setLogAxis(logY); // X Axis: Frequency (Log)
plot->xAxis->setLabel("Frequency (Hz)");
plot->xAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
plot->xAxis->setTicker(logTicker);
// Y Axis 1: Real (Linear)
plot->yAxis->setLabel("Real (Ohms)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
// Y Axis 2: Imaginary (Linear)
plot->yAxis2->setLabel("Imaginary (Ohms)");
plot->yAxis2->setScaleType(QCPAxis::stLinear);
plot->yAxis2->setTicker(linTicker);
plot->yAxis2->setNumberFormat("f");
graph1->setName("Real");
graph2->setName("Imaginary");
plot->replot();
} }
void GraphWidget::addData(double x, double y1, double y2) void GraphWidget::addData(double freq, double val1, double val2) {
{ // 1. Validate X-Axis (Frequency)
xData.append(x); // If Log scale, Frequency must be > 0.
y1Data.append(y1); if (plot->xAxis->scaleType() == QCPAxis::stLogarithmic && freq <= 0) {
y2Data.append(y2); return;
// Update graph data pointers
graphY1->setXColumn(xData);
graphY1->setYColumn(y1Data);
graphY2->setXColumn(xData);
graphY2->setYColumn(y2Data);
// Redraw
plot->redrawPlot();
} }
void GraphWidget::clear() // 2. Validate Y-Axis 1 (Impedance/Real)
{ // If Log scale (Impedance), value must be > 0.
xData.clear(); if (plot->yAxis->scaleType() == QCPAxis::stLogarithmic) {
y1Data.clear(); // If we have a zero/negative impedance in log mode, skip it.
y2Data.clear(); // Clamping to 1e-12 causes massive axis scaling issues (squished plots).
plot->redrawPlot(); if (val1 <= 1e-7) {
val1 = std::numeric_limits<double>::quiet_NaN();
}
}
// 3. Add Data
// QCustomPlot handles NaN by creating a line gap, which is visually correct for missing/bad data.
if (!std::isnan(val1)) {
graph1->addData(freq, val1);
}
// Graph 2 (Phase/Imag) is usually Linear.
graph2->addData(freq, val2);
// 4. Auto-scale
// We remove 'true' (onlyEnlarge). We want the axes to shrink
// if we zoom in or if the data range stabilizes.
graph1->rescaleAxes(false);
graph2->rescaleAxes(false);
plot->replot();
}
void GraphWidget::clear() {
graph1->data()->clear();
graph2->data()->clear();
plot->replot();
} }

View File

@ -1,39 +1,26 @@
#ifndef GRAPHWIDGET_H #pragma once
#define GRAPHWIDGET_H
#include <QWidget> #include <QWidget>
#include <QVector> #include <QVBoxLayout>
#include "jkqtplotter/jkqtplotter.h" #include "qcustomplot.h"
#include "jkqtplotter/graphs/jkqtpigraph.h"
#include "jkqtplotter/graphs/jkqtpimage.h"
#include "jkqtplotter/graphs/jkqtpscatter.h"
#include "jkqtplotter/graphs/jkqtplyines.h"
class GraphWidget : public QWidget class GraphWidget : public QWidget {
{
Q_OBJECT Q_OBJECT
public: public:
explicit GraphWidget(QWidget *parent = nullptr); explicit GraphWidget(QWidget *parent = nullptr);
// Configuration // Data Handling
void setupPlot(QString title, QString xLabel, QString y1Label, QString y2Label); void addData(double freq, double val1, double val2);
void setLogAxis(bool logX, bool logY);
// Data Access
void addData(double x, double y1, double y2);
void clear(); void clear();
// View Configurations
void configureBodePlot();
void configureRawPlot();
private: private:
JKQTPlotter *plot; QVBoxLayout *layout;
QCustomPlot *plot;
// Internal Data Storage QCPGraph *graph1;
QVector<double> xData; QCPGraph *graph2;
QVector<double> y1Data;
QVector<double> y2Data;
// Graph Objects
JKQTPXYLineGraph *graphY1;
JKQTPXYLineGraph *graphY2;
}; };
#endif // GRAPHWIDGET_H

View File

@ -1,228 +1,288 @@
#include "MainWindow.h" #include "MainWindow.h"
#include <QSerialPortInfo>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout>
#include <QToolBar>
#include <QDebug> #include <QDebug>
#include <QMessageBox> #include <QDateTime>
#include <QApplication> #include <QScroller>
#include <QGesture>
#include <QSplitter>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QTimer>
#include <cmath>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), currentSweepIndex(0), isSweeping(false) {
: QMainWindow(parent)
{
serial = new QSerialPort(this); serial = new QSerialPort(this);
connect(serial, &QSerialPort::readyRead, this, &MainWindow::onReadData); connect(serial, &QSerialPort::readyRead, this, &MainWindow::handleSerialData);
connect(serial, &QSerialPort::errorOccurred, this, &MainWindow::onPortError);
setupUi(); setupUi();
// Delayed refresh to allow the window to render before auto-scanning
QTimer::singleShot(1000, this, &MainWindow::refreshPorts);
generateSweepPoints();
// Enable Swipe Gestures for Mobile Tab Switching
grabGesture(Qt::SwipeGesture);
} }
MainWindow::~MainWindow() MainWindow::~MainWindow() {
{ if (serial->isOpen()) {
if (serial->isOpen())
serial->close(); serial->close();
} }
void MainWindow::setupUi()
{
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
// --- Toolbar Area ---
QHBoxLayout *toolLayout = new QHBoxLayout();
portSelector = new QComboBox();
const auto ports = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : ports) {
portSelector->addItem(info.portName());
} }
btnConnect = new QPushButton("Connect"); void MainWindow::setupUi() {
connect(btnConnect, &QPushButton::clicked, [this]() { // Central Layout: Splitter (Graphs Top, Log Bottom)
onPortToggled(serial->isOpen()); QSplitter *splitter = new QSplitter(Qt::Vertical, this);
}); setCentralWidget(splitter);
btnMeasure = new QPushButton("Measure"); // Tab Widget for Graphs
btnMeasure->setEnabled(false); tabWidget = new QTabWidget(this);
connect(btnMeasure, &QPushButton::clicked, this, &MainWindow::onMeasureClicked); finalGraph = new GraphWidget(this);
finalGraph->configureBodePlot();
rawGraph = new GraphWidget(this);
rawGraph->configureRawPlot();
btnCalibrate = new QPushButton("Calibrate"); tabWidget->addTab(finalGraph, "Bode Plot");
btnCalibrate->setEnabled(false);
connect(btnCalibrate, &QPushButton::clicked, this, &MainWindow::onCalibrateClicked);
viewSelector = new QComboBox();
viewSelector->addItem("Tabs (Swipe)");
viewSelector->addItem("Split (Dual)");
connect(viewSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::onViewModeChanged);
toolLayout->addWidget(portSelector);
toolLayout->addWidget(btnConnect);
toolLayout->addWidget(btnMeasure);
toolLayout->addWidget(btnCalibrate);
toolLayout->addWidget(viewSelector); // Add view switcher
mainLayout->addLayout(toolLayout);
// --- Graphs Initialization ---
finalGraph = new GraphWidget();
finalGraph->setupPlot("Bode Plot", "Frequency (Hz)", "Impedance (Ohm)", "Phase (Rad)");
finalGraph->setLogAxis(true, true); // Log-Log for Bode
rawGraph = new GraphWidget();
rawGraph->setupPlot("Raw Data (DFT)", "Frequency (Hz)", "Real", "Imaginary");
rawGraph->setLogAxis(true, false); // Log X, Linear Y (Raw data can be neg)
// --- Layout Containers ---
// 1. Tab Widget (Mobile Style)
tabWidget = new QTabWidget();
tabWidget->addTab(finalGraph, "Final Data");
tabWidget->addTab(rawGraph, "Raw Data"); tabWidget->addTab(rawGraph, "Raw Data");
// 2. Splitter (Desktop Style) // Log Widget
splitter = new QSplitter(Qt::Horizontal); logWidget = new QTextEdit(this);
// Note: We can't add the same widget to two parents. logWidget->setReadOnly(true);
// We will reparent them in onViewModeChanged. logWidget->setFont(QFont("Monospace"));
logWidget->setPlaceholderText("Scanning for 0xCAFE EIS Device...");
QScroller::grabGesture(logWidget->viewport(), QScroller::TouchGesture);
// Default to Tabs (Mobile friendly default) splitter->addWidget(tabWidget);
mainLayout->addWidget(tabWidget); splitter->addWidget(logWidget);
splitter->setStretchFactor(0, 2);
splitter->setStretchFactor(1, 1);
// Status Bar // Toolbar Construction
statusLabel = new QLabel("Ready"); toolbar = addToolBar("Connection");
statusBar()->addWidget(statusLabel); toolbar->setMovable(false);
portSelector = new QComboBox(this);
portSelector->setMinimumWidth(150);
connectBtn = new QPushButton("Connect", this);
QPushButton *refreshBtn = new QPushButton("Refresh", this);
toolbar->addWidget(portSelector);
toolbar->addWidget(connectBtn);
toolbar->addWidget(refreshBtn);
toolbar->addSeparator();
checkIdBtn = new QPushButton("Check ID", this);
calibrateBtn = new QPushButton("Calibrate", this);
sweepBtn = new QPushButton("Sweep", this);
toolbar->addWidget(checkIdBtn);
toolbar->addWidget(calibrateBtn);
toolbar->addWidget(sweepBtn);
toolbar->addSeparator();
// Measurement Control
QLabel *lblFreq = new QLabel(" Freq:", this);
QDoubleSpinBox *spinFreq = new QDoubleSpinBox(this);
spinFreq->setRange(10.0, 200000.0);
spinFreq->setValue(1000.0);
spinFreq->setSuffix(" Hz");
QPushButton *measureBtn = new QPushButton("Measure", this);
toolbar->addWidget(lblFreq);
toolbar->addWidget(spinFreq);
toolbar->addWidget(measureBtn);
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
sweepBtn->setEnabled(false);
// Signal Connections
connect(connectBtn, &QPushButton::clicked, this, &MainWindow::connectToPort);
connect(refreshBtn, &QPushButton::clicked, this, &MainWindow::refreshPorts);
connect(checkIdBtn, &QPushButton::clicked, this, &MainWindow::checkDeviceId);
connect(calibrateBtn, &QPushButton::clicked, this, &MainWindow::runCalibration);
connect(sweepBtn, &QPushButton::clicked, this, &MainWindow::startSweep);
connect(measureBtn, &QPushButton::clicked, [this]() {
if (serial->isOpen()) {
logWidget->append(">> Requesting Measure (m)...");
serial->write("m");
}
});
// Auto-detect Platform for default view
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
viewSelector->setCurrentIndex(0); // Tabs toolbar->setIconSize(QSize(32, 32));
viewSelector->setVisible(false); // Hide selector on mobile, force tabs QFont font = portSelector->font();
#else font.setPointSize(14);
viewSelector->setCurrentIndex(1); // Default to Split on Desktop portSelector->setFont(font);
connectBtn->setFont(font);
refreshBtn->setFont(font);
checkIdBtn->setFont(font);
calibrateBtn->setFont(font);
sweepBtn->setFont(font);
measureBtn->setFont(font);
spinFreq->setFont(font);
#endif #endif
} }
void MainWindow::onViewModeChanged(int index) void MainWindow::refreshPorts() {
{ portSelector->clear();
// 0 = Tabs, 1 = Split const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
// Remove graphs from their current parents portSelector->addItem(info.portName());
finalGraph->setParent(nullptr); // Auto-connect to device with 0xCAFE Vendor ID
rawGraph->setParent(nullptr); if (info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE) {
logWidget->append(">> Found EIS Device (0xCAFE) on " + info.portName());
QVBoxLayout *layout = qobject_cast<QVBoxLayout*>(centralWidget->layout()); portSelector->setCurrentText(info.portName());
if (!serial->isOpen()) {
if (index == 0) { // Tabs connectToPort();
if (splitter->isVisible()) {
layout->removeWidget(splitter);
splitter->hide();
} }
tabWidget->clear();
tabWidget->addTab(finalGraph, "Final Data");
tabWidget->addTab(rawGraph, "Raw Data");
if (!tabWidget->isVisible()) {
layout->addWidget(tabWidget);
tabWidget->show();
}
}
else { // Split
if (tabWidget->isVisible()) {
layout->removeWidget(tabWidget);
tabWidget->hide();
}
splitter->addWidget(finalGraph);
splitter->addWidget(rawGraph);
if (!splitter->isVisible()) {
layout->addWidget(splitter);
splitter->show();
} }
} }
} }
void MainWindow::onPortToggled(bool connected) void MainWindow::connectToPort() {
{ if (serial->isOpen()) {
if (connected) {
serial->close(); serial->close();
btnConnect->setText("Connect"); connectBtn->setText("Connect");
btnMeasure->setEnabled(false); logWidget->append("--- Disconnected ---");
btnCalibrate->setEnabled(false); checkIdBtn->setEnabled(false);
statusLabel->setText("Disconnected"); calibrateBtn->setEnabled(false);
} else { sweepBtn->setEnabled(false);
isSweeping = false;
return;
}
if (portSelector->currentText().isEmpty()) return;
serial->setPortName(portSelector->currentText()); serial->setPortName(portSelector->currentText());
serial->setBaudRate(115200); // Or whatever your Pico uses, standard 115200 usually serial->setBaudRate(500000);
if (serial->open(QIODevice::ReadWrite)) { if (serial->open(QIODevice::ReadWrite)) {
btnConnect->setText("Disconnect"); connectBtn->setText("Disconnect");
btnMeasure->setEnabled(true); logWidget->append("--- Connected and Synchronized ---");
btnCalibrate->setEnabled(true); checkIdBtn->setEnabled(true);
statusLabel->setText("Connected"); calibrateBtn->setEnabled(true);
sweepBtn->setEnabled(true);
// Clear old data on connect
finalGraph->clear();
rawGraph->clear();
} else { } else {
QMessageBox::critical(this, "Error", "Could not open serial port."); logWidget->append(">> Connection Error: " + serial->errorString());
}
} }
} }
void MainWindow::onMeasureClicked() void MainWindow::onPortError(QSerialPort::SerialPortError error) {
{ if (error == QSerialPort::ResourceError) {
logWidget->append(">> Critical Error: Connection Lost.");
serial->close();
connectBtn->setText("Connect");
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
sweepBtn->setEnabled(false);
isSweeping = false;
}
}
void MainWindow::checkDeviceId() {
if (serial->isOpen()) { if (serial->isOpen()) {
logWidget->append(">> Checking ID (v)...");
serial->write("v");
}
}
void MainWindow::runCalibration() {
if (serial->isOpen()) {
logWidget->append(">> Running Calibration (c)...");
serial->write("c");
}
}
void MainWindow::startSweep() {
if (!serial->isOpen()) return;
isSweeping = true;
currentSweepIndex = 0;
finalGraph->clear(); finalGraph->clear();
rawGraph->clear(); rawGraph->clear();
// Trigger a sweep or single measurement logWidget->append(">> Starting Ratio-metric Sweep...");
// Example command logic sendNextSweepPoint();
// For single: serial->write("MEAS 10000\n");
// For sweep logic, the MCU might handle it or we loop here.
// Assuming user triggers 'MEAS 1000' style commands manually or logic exists.
// Let's just send a command if needed, or rely on user knowing what to do.
// For now, let's assume we want to measure 10kHz as a test
serial->write("MEAS 10000\n");
}
} }
void MainWindow::onCalibrateClicked() void MainWindow::sendNextSweepPoint() {
{ if (!isSweeping || !serial->isOpen()) return;
if (serial->isOpen()) { if (currentSweepIndex >= sweepPoints.size()) {
serial->write("CAL\n"); isSweeping = false;
logWidget->append(">> Sweep Complete.");
return;
} }
serial->write("m");
} }
void MainWindow::onReadData() void MainWindow::handleSerialData() {
{
while (serial->canReadLine()) { while (serial->canReadLine()) {
QByteArray line = serial->readLine().trimmed(); QByteArray line = serial->readLine();
processLine(line); QString str = QString::fromUtf8(line).trimmed();
if (str.isEmpty()) continue;
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
logWidget->append(QString("[%1] %2").arg(timestamp, str));
logWidget->moveCursor(QTextCursor::End);
if (str.startsWith("DATA,")) {
parseData(str);
}
} }
} }
void MainWindow::processLine(const QByteArray &line) void MainWindow::parseData(const QString &data) {
{ QStringList parts = data.split(',');
if (line.startsWith("DATA")) { if (parts.size() < 6) return;
// Format: DATA,freq,impedance,phase,raw_real,raw_imag
float freq, imp, phase;
int raw_real, raw_imag;
// sscanf is dangerous with comma delimited strings if not careful, but works for fixed format bool okF, okM, okP, okR, okI;
int count = sscanf(line.constData(), "DATA,%f,%f,%f,%d,%d", &freq, &imp, &phase, &raw_real, &raw_imag); double freq = parts[1].toDouble(&okF);
double mag = parts[2].toDouble(&okM);
double phase= parts[3].toDouble(&okP);
double real = parts[4].toDouble(&okR);
double imag = parts[5].toDouble(&okI);
if (count >= 3) { if (okF && okM && okP) finalGraph->addData(freq, mag, phase);
// We have at least the final data if (okF && okR && okI) rawGraph->addData(freq, real, imag);
finalGraph->addData(freq, imp, phase); // Y1=Imp, Y2=Phase
if (isSweeping) {
currentSweepIndex++;
QTimer::singleShot(50, this, &MainWindow::sendNextSweepPoint);
}
} }
if (count >= 5) { void MainWindow::generateSweepPoints() {
// We have raw data too sweepPoints.clear();
rawGraph->addData(freq, (double)raw_real, (double)raw_imag); double startFreq = 100.0;
double endFreq = 200000.0;
int steps = 50;
double logStart = std::log10(startFreq);
double logEnd = std::log10(endFreq);
double logStep = (logEnd - logStart) / (steps - 1);
for (int i = 0; i < steps; ++i) {
sweepPoints.append(std::pow(10, logStart + i * logStep));
}
} }
statusLabel->setText(QString("Received: %1 Hz").arg(freq)); bool MainWindow::event(QEvent *event) {
} if (event->type() == QEvent::Gesture) {
else if (line.startsWith("LOG")) { QGestureEvent *ge = static_cast<QGestureEvent*>(event);
statusLabel->setText(line); if (QGesture *swipe = ge->gesture(Qt::SwipeGesture)) {
qDebug() << "Device Log:" << line; handleSwipe(static_cast<QSwipeGesture*>(swipe));
return true;
}
}
return QMainWindow::event(event);
}
void MainWindow::handleSwipe(QSwipeGesture *gesture) {
if (gesture->state() == Qt::GestureFinished) {
if (gesture->horizontalDirection() == QSwipeGesture::Left) {
if (tabWidget->currentIndex() < tabWidget->count() - 1)
tabWidget->setCurrentIndex(tabWidget->currentIndex() + 1);
} else if (gesture->horizontalDirection() == QSwipeGesture::Right) {
if (tabWidget->currentIndex() > 0)
tabWidget->setCurrentIndex(tabWidget->currentIndex() - 1);
}
} }
} }

View File

@ -1,53 +1,66 @@
#ifndef MAINWINDOW_H #pragma once
#define MAINWINDOW_H
#include <QMainWindow> #include <QMainWindow>
#include <QSerialPort> #include <QSerialPort>
#include <QTabWidget> #include <QTabWidget>
#include <QSplitter> #include <QTextEdit>
#include <QToolBar>
#include <QComboBox> #include <QComboBox>
#include <QPushButton> #include <QPushButton>
#include <QLabel> #include <QSerialPortInfo>
#include <QMessageBox>
#include <QEvent>
#include <QGestureEvent>
#include <QSwipeGesture>
#include <QTimer>
#include "GraphWidget.h" #include "GraphWidget.h"
class MainWindow : public QMainWindow class MainWindow : public QMainWindow {
{
Q_OBJECT Q_OBJECT
public: public:
MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
protected:
bool event(QEvent *event) override;
private slots: private slots:
void onPortToggled(bool connected); void handleSerialData();
void onReadData(); void connectToPort();
void onMeasureClicked(); void refreshPorts();
void onCalibrateClicked(); void onPortError(QSerialPort::SerialPortError error);
void onViewModeChanged(int index); // New slot for desktop view switching
// Action Slots
void checkDeviceId();
void runCalibration();
void startSweep();
void sendNextSweepPoint();
private: private:
void setupUi(); void setupUi();
void processLine(const QByteArray &line); void parseData(const QString &data);
void handleSwipe(QSwipeGesture *gesture);
void generateSweepPoints();
// Hardware
QSerialPort *serial; QSerialPort *serial;
// UI Elements // Views
QComboBox *portSelector;
QPushButton *btnConnect;
QPushButton *btnMeasure;
QPushButton *btnCalibrate;
QLabel *statusLabel;
// Graphs
GraphWidget *finalGraph; // Bode Plot GraphWidget *finalGraph; // Bode Plot
GraphWidget *rawGraph; // Raw Real/Imag Plot GraphWidget *rawGraph; // Raw Data
QTextEdit *logWidget; // Serial Log
// Layout Containers // Layout
QWidget *centralWidget; QTabWidget *tabWidget;
QTabWidget *tabWidget; // For Mobile/Tab Mode QToolBar *toolbar;
QSplitter *splitter; // For Desktop/Split Mode QComboBox *portSelector;
QComboBox *viewSelector; // For Desktop to toggle modes QPushButton *connectBtn;
QPushButton *checkIdBtn;
QPushButton *calibrateBtn;
QPushButton *sweepBtn;
// Sweep Logic
QList<double> sweepPoints;
int currentSweepIndex;
bool isSweeping;
}; };
#endif // MAINWINDOW_H

560
main.c
View File

@ -1,474 +1,258 @@
// File: EIS/main.c // File: main.c
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h> #include <math.h>
#include "pico/stdlib.h" #include "pico/stdlib.h"
#include "hardware/spi.h" #include "hardware/spi.h"
#include "hardware/gpio.h" #include "hardware/gpio.h"
#include "ad5940.h" #include "ad5940.h"
// --------------------------------------------------------------------------
// Pin Definitions // Pin Definitions
// --------------------------------------------------------------------------
#define PIN_MISO 0 #define PIN_MISO 0
#define PIN_CS 1 #define PIN_CS 1
#define PIN_SCK 2 #define PIN_SCK 2
#define PIN_MOSI 3 #define PIN_MOSI 3
#define PIN_RESET 9 #define PIN_RST 9
#define PIN_AD_INTERRUPT 29 #define PIN_INT 29
#define SPI_PORT spi0 #define RCAL_VALUE 100.0f
#define SPI_BAUDRATE_HIGH 500000
// -------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Switch Matrix Bit Definitions // Platform Interface Implementation (Required by AD5940 Lib)
// -------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#ifndef SWT_SE0
#define SWT_SE0 (1 << 6) // T7
#endif
#ifndef SWT_T9
#define SWT_T9 (1 << 8) // T9
#endif
#ifndef SWT_TR1
#define SWT_TR1 (1 << 11) // TR1
#endif
// DSWFULLCON
#ifndef SWD_AIN2
#define SWD_AIN2 (1 << 2) // D3
#endif
#ifndef SWD_CE0
#define SWD_CE0 (1 << 4) // D5
#endif
// TSWFULLCON
#ifndef SWT_AIN3
#define SWT_AIN3 (1 << 3) // T4
#endif
// --------------------------------------------------------------------------
// AD5940 Library Interface
// --------------------------------------------------------------------------
void AD5940_CsClr(void) { void AD5940_CsClr(void) {
gpio_put(PIN_CS, 0); gpio_put(PIN_CS, 0);
sleep_us(1);
} }
void AD5940_CsSet(void) { void AD5940_CsSet(void) {
sleep_us(1);
gpio_put(PIN_CS, 1); gpio_put(PIN_CS, 1);
sleep_us(1);
} }
void AD5940_RstClr(void) { gpio_put(PIN_RESET, 0); } void AD5940_RstClr(void) {
void AD5940_RstSet(void) { gpio_put(PIN_RESET, 1); } gpio_put(PIN_RST, 0);
void AD5940_Delay10us(uint32_t time) { sleep_us(time * 10); }
uint32_t AD5940_GetMCUIntFlag(void) {
uint32_t flag = AD5940_ReadReg(REG_INTC_INTCFLAG0);
return (flag != 0);
} }
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuffer, unsigned long length) { void AD5940_RstSet(void) {
if (pRecvBuffer == NULL) { gpio_put(PIN_RST, 1);
if (pSendBuffer != NULL) {
spi_write_blocking(SPI_PORT, pSendBuffer, length);
}
} else {
if (pSendBuffer == NULL) {
uint8_t dummy = 0x00;
for (unsigned long i = 0; i < length; i++) {
spi_write_read_blocking(SPI_PORT, &dummy, &pRecvBuffer[i], 1);
}
} else {
spi_write_read_blocking(SPI_PORT, pSendBuffer, pRecvBuffer, length);
}
}
} }
// -------------------------------------------------------------------------- void AD5940_Delay10us(uint32_t time) {
// Helpers sleep_us(time * 10);
// --------------------------------------------------------------------------
void AD5940_PulseReset(void) {
AD5940_RstClr();
sleep_ms(10);
AD5940_RstSet();
sleep_ms(50); // Increased wait for boot stability
} }
// Helper to map Register Macro to Actual Sample Count for calculations void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, unsigned long length) {
uint32_t GetDftSamples(uint32_t dft_num_macro) { spi_write_read_blocking(spi0, pSendBuffer, pRecvBuff, length);
if (dft_num_macro == DFTNUM_256) return 256;
if (dft_num_macro == DFTNUM_512) return 512;
if (dft_num_macro == DFTNUM_1024) return 1024;
if (dft_num_macro == DFTNUM_2048) return 2048;
if (dft_num_macro == DFTNUM_4096) return 4096;
if (dft_num_macro == DFTNUM_8192) return 8192;
if (dft_num_macro == DFTNUM_16384) return 16384;
return 2048; // Safe default
} }
// Structure to hold calculated configuration // ---------------------------------------------------------------------------
typedef struct { // Application Logic
uint32_t DftNum; // ---------------------------------------------------------------------------
uint32_t DftSrc;
uint32_t Sinc3Osr;
uint32_t Sinc2Osr;
float SampleRate; // Estimated
} FilterConfig_Type;
// Calculates optimal DFT and Filter settings based on frequency void setup_pins(void) {
// Optimized for High Speed Loop (HFOSC 16MHz) only // SPI0 at 1MHz
void AD5940_GetFilterCfg(float freq, FilterConfig_Type *pCfg) { spi_init(spi0, 1000000);
// Strategy: gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
// For > 50Hz, use SINC3 (Fast, ~200kHz) gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
// For <= 50Hz, use SINC2 path (Slower, ~9kHz) to capture full waves gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
if (freq >= 50.0f) { // CS
pCfg->DftSrc = DFTSRC_SINC3; gpio_init(PIN_CS);
pCfg->Sinc3Osr = ADCSINC3OSR_4; gpio_set_dir(PIN_CS, GPIO_OUT);
pCfg->Sinc2Osr = ADCSINC2OSR_22; // Don't care for SINC3 src gpio_put(PIN_CS, 1);
pCfg->SampleRate = 200000.0f; // 800k / 4
// Adjust DFT size for speed vs accuracy at high freq // RST
if (freq > 40000.0f) pCfg->DftNum = DFTNUM_2048; gpio_init(PIN_RST);
else if (freq > 1000.0f) pCfg->DftNum = DFTNUM_4096; gpio_set_dir(PIN_RST, GPIO_OUT);
else pCfg->DftNum = DFTNUM_8192; gpio_put(PIN_RST, 1);
}
else {
// Low Frequency High Speed (e.g. 10Hz)
// Use SINC2 filter to slow down data rate
// Rate = 800k / (4 * 22) = 9090 Hz
pCfg->DftSrc = DFTSRC_SINC2NOTCH;
pCfg->Sinc3Osr = ADCSINC3OSR_4;
pCfg->Sinc2Osr = ADCSINC2OSR_22;
pCfg->SampleRate = 9090.0f;
// 8192 samples @ 9kHz = ~0.9 seconds // INT
// Covers 9 cycles of 10Hz. Perfect. gpio_init(PIN_INT);
pCfg->DftNum = DFTNUM_8192; gpio_set_dir(PIN_INT, GPIO_IN);
} gpio_pull_up(PIN_INT);
} }
// -------------------------------------------------------------------------- void configure_afe(void) {
// Measurement Functions HSDACCfg_Type hsdac_cfg;
// -------------------------------------------------------------------------- HSTIACfg_Type hsrtia_cfg;
HSLoopCfg_Type hsloop_cfg;
static uint32_t AppBuff[512]; // Reset AFE control
// Internal Helper for Calibration
static AD5940Err RunCalibration(void)
{
ADCPGACal_Type pga_cal;
pga_cal.AdcClkFreq = 16000000.0;
pga_cal.SysClkFreq = 16000000.0;
pga_cal.ADCPga = ADCPGA_1;
pga_cal.ADCSinc2Osr = ADCSINC2OSR_1333;
pga_cal.ADCSinc3Osr = ADCSINC3OSR_4;
pga_cal.TimeOut10us = 100000;
pga_cal.VRef1p82 = 1.82;
pga_cal.VRef1p11 = 1.11;
pga_cal.PGACalType = PGACALTYPE_OFFSETGAIN;
return AD5940_ADCPGACal(&pga_cal);
}
// Command Handler for "CAL"
static void AppADCPgaCal(void)
{
printf("LOG,Preparing for Calibration...\n");
// Ensure clean state before CAL
AD5940_PulseReset();
AD5940_Initialize();
AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF);
AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF);
AD5940Err err = RunCalibration();
if(err != AD5940ERR_OK) printf("LOG,ADC Cal Failed: %d\n", err);
else printf("LOG,ADC Cal Passed.\n");
// Cleanup: Turn off everything after CAL to leave clean slate
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
}
// High Speed Loop (HSTIA + HSDAC) for ALL Frequencies
void MeasureHighSpeed(float freq)
{
// 1. HARD RESET & Re-Initialize
AD5940_PulseReset();
AD5940_Initialize();
AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF);
AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF);
// 2. Calibrate
// Note: Calibration routines are invasive. They might leave the AFE in weird states.
AD5940Err calErr = RunCalibration();
if(calErr != AD5940ERR_OK) {
printf("LOG,Auto-Calibration Failed: %d\n", calErr);
}
// CRITICAL: Shut down EVERYTHING after calibration.
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
// CRITICAL: Disable Sequencer // Configure High Speed DAC
AD5940_SEQCtrlS(bFALSE); hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_2;
hsdac_cfg.HsDacGain = HSDACGAIN_1;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
// 3. Clock Configuration // Configure High Speed TIA
AD5940_WriteReg(REG_ALLON_OSCKEY, 0xCB14); hsrtia_cfg.HstiaDeRtia = HSTIADERTIA_1K;
AD5940_WriteReg(REG_ALLON_OSCCON, 0x0003); // Enable HFOSC hsrtia_cfg.HstiaCtia = 31; // 31pF
sleep_ms(2); hsrtia_cfg.HstiaRtiaSel = HSTIARTIA_1K;
hsrtia_cfg.HstiaBias = HSTIABIAS_1P1;
hsrtia_cfg.DiodeClose = bFALSE;
hsrtia_cfg.HstiaDeRload = HSTIADERLOAD_OPEN;
AD5940_HSTIACfgS(&hsrtia_cfg);
AD5940_WriteReg(REG_AFE_LPMODEKEY, 0xC59D6); // Configure High Speed Loop
AD5940_WriteReg(REG_AFE_LPMODECLKSEL, 0); // Select HFOSC hsloop_cfg.HsDacCfg = hsdac_cfg;
hsloop_cfg.HsTiaCfg = hsrtia_cfg;
spi_set_baudrate(SPI_PORT, SPI_BAUDRATE_HIGH); hsloop_cfg.SWMatCfg.Dswitch = SWD_OPEN;
hsloop_cfg.SWMatCfg.Pswitch = SWP_OPEN;
hsloop_cfg.SWMatCfg.Nswitch = SWN_OPEN;
hsloop_cfg.SWMatCfg.Tswitch = SWT_OPEN;
// 4. Switch Matrix Cleanup hsloop_cfg.WgCfg.WgType = WGTYPE_SIN;
AD5940_WriteReg(REG_AFE_LPTIASW0, 0x0000); hsloop_cfg.WgCfg.GainCalEn = bFALSE;
AD5940_WriteReg(REG_AFE_LPDACSW0, 0x0000); hsloop_cfg.WgCfg.OffsetCalEn = bFALSE;
hsloop_cfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(1000.0, 16000000.0);
hsloop_cfg.WgCfg.SinCfg.SinAmplitudeWord = 2047;
hsloop_cfg.WgCfg.SinCfg.SinOffsetWord = 0;
hsloop_cfg.WgCfg.SinCfg.SinPhaseWord = 0;
// 5. Configure Loop AD5940_HSLoopCfgS(&hsloop_cfg);
HSLoopCfg_Type hs_loop;
hs_loop.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
hs_loop.HsDacCfg.HsDacGain = HSDACGAIN_1;
hs_loop.HsDacCfg.HsDacUpdateRate = 7;
if (freq > 1000.0f) { // Enable Power
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_1K; AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_HSTIAPWR | AFECTRL_HSDACPWR | AFECTRL_HPREFPWR, bTRUE);
} else {
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_5K; // Enable Interrupts
AD5940_WriteReg(REG_INTC_INTCSEL0, BITM_INTC_INTCSEL0_INTSEL14);
} }
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1; float perform_dft(void) {
hs_loop.HsTiaCfg.HstiaCtia = 16; iImpCar_Type dft_res;
hs_loop.HsTiaCfg.DiodeClose = bFALSE; fImpCar_Type f_res;
AD5940_HSLoopCfgS(&hs_loop); AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
// 6. Switch Matrix (S+/S-) // Wait for interrupt or timeout
AD5940_WriteReg(REG_AFE_SWCON, BITM_AFE_SWCON_SWSOURCESEL); uint32_t timeout = 50000;
AD5940_WriteReg(REG_AFE_DSWFULLCON, SWD_CE0); while(gpio_get(PIN_INT) && timeout--) {
AD5940_WriteReg(REG_AFE_PSWFULLCON, 0x0000); sleep_us(10);
AD5940_WriteReg(REG_AFE_NSWFULLCON, 0x0000); }
AD5940_WriteReg(REG_AFE_TSWFULLCON, SWT_SE0 | SWT_T9);
// 7. Get Dynamic Configuration dft_res.Real = (int32_t)AD5940_ReadReg(REG_AFE_DFTREAL);
FilterConfig_Type cfg; dft_res.Image = (int32_t)AD5940_ReadReg(REG_AFE_DFTIMAG);
AD5940_GetFilterCfg(freq, &cfg);
// 8. ADC Configuration AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bFALSE);
ADCBaseCfg_Type adc_cfg; AD5940_WriteReg(REG_INTC_INTCFLAG0, BITM_INTC_INTCFLAG0_FLAG14);
memset(&adc_cfg, 0, sizeof(adc_cfg));
adc_cfg.ADCMuxP = ADCMUXP_HSTIA_P;
adc_cfg.ADCMuxN = ADCMUXN_HSTIA_N;
adc_cfg.ADCPga = ADCPGA_1;
AD5940_ADCBaseCfgS(&adc_cfg);
// Initialize and Enable Clocks f_res.Real = (float)dft_res.Real;
f_res.Image = (float)dft_res.Image;
return AD5940_ComplexMag(&f_res);
}
void set_sw_matrix(uint32_t lp_sw, uint32_t hs_sw) {
AD5940_WriteReg(REG_AFE_LPTIASW0, lp_sw);
AD5940_WriteReg(REG_AFE_HSTIACON, hs_sw);
}
void measure_at_freq(float freq) {
FreqParams_Type freq_params = AD5940_GetFreqParameters(freq);
// 1. Update HSDAC Update Rate based on frequency
uint32_t hsdac_rate = 0x1B;
if(freq >= 80000.0f) {
hsdac_rate = 0x07;
}
uint32_t hsdaccon = AD5940_ReadReg(REG_AFE_HSDACCON);
hsdaccon &= ~BITM_AFE_HSDACCON_RATE;
hsdaccon |= (hsdac_rate << BITP_AFE_HSDACCON_RATE);
AD5940_WriteReg(REG_AFE_HSDACCON, hsdaccon);
// 2. Update Waveform Generator Frequency
uint32_t freq_word = AD5940_WGFreqWordCal(freq, 16000000.0f);
AD5940_WriteReg(REG_AFE_WGFCW, freq_word);
// 3. Update Filter Settings
ADCFilterCfg_Type filter_cfg; ADCFilterCfg_Type filter_cfg;
memset(&filter_cfg, 0, sizeof(filter_cfg)); // Initialize struct to 0 to avoid garbage
filter_cfg.ADCSinc3Osr = freq_params.ADCSinc3Osr;
filter_cfg.ADCSinc3Osr = cfg.Sinc3Osr; filter_cfg.ADCSinc2Osr = freq_params.ADCSinc2Osr;
filter_cfg.ADCSinc2Osr = cfg.Sinc2Osr;
filter_cfg.ADCAvgNum = ADCAVGNUM_16; filter_cfg.ADCAvgNum = ADCAVGNUM_16;
filter_cfg.ADCRate = ADCRATE_800KHZ; filter_cfg.ADCRate = ADCRATE_800KHZ;
filter_cfg.BpNotch = bTRUE; filter_cfg.BpNotch = bTRUE;
filter_cfg.BpSinc3 = bFALSE; filter_cfg.BpSinc3 = bFALSE;
filter_cfg.Sinc2NotchEnable = bTRUE; filter_cfg.Sinc2NotchEnable = bTRUE;
// Explicitly Enable Clocks
filter_cfg.DFTClkEnable = bTRUE; filter_cfg.DFTClkEnable = bTRUE;
filter_cfg.WGClkEnable = bTRUE; filter_cfg.WGClkEnable = bTRUE;
filter_cfg.Sinc3ClkEnable = bTRUE; filter_cfg.Sinc3ClkEnable = bTRUE;
filter_cfg.Sinc2NotchClkEnable = bTRUE; filter_cfg.Sinc2NotchClkEnable = bTRUE;
AD5940_ADCFilterCfgS(&filter_cfg); AD5940_ADCFilterCfgS(&filter_cfg);
// Configure ADC Buffers
AD5940_WriteReg(REG_AFE_ADCBUFCON, 0x005F3D0F);
// 9. Waveform
AD5940_WriteReg(REG_AFE_WGFCW, AD5940_WGFreqWordCal(freq, 16000000.0));
AD5940_WriteReg(REG_AFE_WGAMPLITUDE, 2047);
AD5940_WriteReg(REG_AFE_WGOFFSET, 0);
AD5940_WriteReg(REG_AFE_WGPHASE, 0);
AD5940_WriteReg(REG_AFE_WGCON, WGTYPE_SIN);
// 10. DFT
DFTCfg_Type dft_cfg; DFTCfg_Type dft_cfg;
dft_cfg.DftNum = cfg.DftNum; dft_cfg.DftNum = freq_params.DftNum;
dft_cfg.DftSrc = cfg.DftSrc; dft_cfg.DftSrc = freq_params.DftSrc;
dft_cfg.HanWinEn = bTRUE; dft_cfg.HanWinEn = bTRUE;
AD5940_DFTCfgS(&dft_cfg); AD5940_DFTCfgS(&dft_cfg);
// 11. Configure FIFO for DFT // 4. Perform Measurement
// Ensure we capture DFT results in FIFO // RCAL
FIFOCfg_Type fifo_cfg; set_sw_matrix(0, SWP_RCAL0 | SWN_RCAL1);
fifo_cfg.FIFOEn = bTRUE; sleep_ms(5);
fifo_cfg.FIFOMode = FIFOMODE_FIFO; float mag_cal = perform_dft();
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = 4;
AD5940_FIFOCfg(&fifo_cfg);
// 12. Run // Z
set_sw_matrix(0, SWP_CE0 | SWP_RE0 | SWN_SE0 | SWT_DE0);
sleep_ms(5);
float mag_z = perform_dft();
// Power Up Analog Blocks if (mag_z > 0.01f && mag_cal > 0.01f) {
AD5940_AFECtrlS(AFECTRL_HSDACPWR|AFECTRL_HSTIAPWR|AFECTRL_ADCPWR|AFECTRL_DACREFPWR|AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR, bTRUE); float impedance = (mag_cal / mag_z) * RCAL_VALUE;
printf("DATA,%.2f,%.2f,0,0,0\n", freq, impedance);
sleep_ms(20); } else {
printf("DATA,%.2f,0,0,0,0\n", freq);
// CRITICAL FIX: Force AFECON Clean
uint32_t current_afecon = AD5940_ReadReg(REG_AFE_AFECON);
current_afecon &= ~(0x00003000); // Clear Temp bits
AD5940_WriteReg(REG_AFE_AFECON, current_afecon);
// Enable ADC Repeat Mode
AD5940_ADCRepeatCfgS(0xffffffff);
// Clear and Enable Interrupts
AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF);
AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF);
// Start Conversion
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bTRUE);
// Calculate Timeout using Helper
uint32_t sample_count = GetDftSamples(cfg.DftNum);
float duration = (float)sample_count / cfg.SampleRate;
uint32_t timeout_us = (uint32_t)(duration * 1000000.0f) + 2000000;
// Polling Loop with Exit Condition
uint32_t start_time = to_us_since_boot(get_absolute_time());
bool success = false;
// Results
int32_t real = 0;
int32_t image = 0;
while((to_us_since_boot(get_absolute_time()) - start_time) < timeout_us) {
// Polling Strategy: Check FIFO Count instead of Interrupt Flags
// One DFT result = 2 words (Real + Image). We wait for >= 2 words.
uint32_t fifo_cnt = (AD5940_ReadReg(REG_AFE_FIFOCNTSTA) & 0x07FF0000) >> 16;
if(fifo_cnt >= 2) {
// Read result from FIFO
uint32_t buffer[2];
AD5940_FIFORd(buffer, 2);
real = (int32_t)buffer[0];
image = (int32_t)buffer[1];
AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF);
success = true;
break;
}
sleep_us(100);
}
if (!success) {
uint32_t flags = AD5940_ReadReg(REG_INTC_INTCFLAG0);
uint32_t afecon_read = AD5940_ReadReg(REG_AFE_AFECON);
uint32_t fifo_cnt = (AD5940_ReadReg(REG_AFE_FIFOCNTSTA) & 0x07FF0000) >> 16;
printf("LOG,Error: Measurement Timeout (HS). Exp Duration: %.2fs. Flags: 0x%08X, AFECON: 0x%08X, FIFO: %d\n", duration, flags, afecon_read, fifo_cnt);
}
// 13. Process Result
// Sign Extend 18-bit to 32-bit
// Store original raw values before processing
int32_t raw_real = real;
int32_t raw_image = image;
if(real & (1<<17)) real |= 0xFFFC0000;
if(image & (1<<17)) image |= 0xFFFC0000;
float mag = sqrt((float)real*real + (float)image*image);
float rtia_val = (freq > 1000.0f) ? 1000.0f : 5000.0f;
float impedance = (mag > 0) ? (2047.0f / mag) * rtia_val : 0;
float phase = atan2((float)image, (float)real);
// Updated Output Format: Freq, Impedance, Phase, RawReal, RawImag
printf("DATA,%.2f,%.4f,%.4f,%d,%d\n", freq, impedance, phase, raw_real, raw_image);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
}
// --------------------------------------------------------------------------
// Main
// --------------------------------------------------------------------------
void process_command(char* cmd) {
char* token = strtok(cmd, " ");
if (!token) return;
if (strcmp(token, "CAL") == 0) {
AppADCPgaCal();
}
else if (strcmp(token, "MEAS") == 0) {
char* arg1 = strtok(NULL, " ");
if (arg1) {
float freq = strtof(arg1, NULL);
if (freq < 10.0f) {
freq = 10.0f;
}
MeasureHighSpeed(freq);
} }
} }
else if (strcmp(token, "ID") == 0) {
uint32_t chip_id = AD5940_ReadReg(REG_AFECON_CHIPID); void run_sweep(float start_freq, float end_freq, int steps) {
printf("LOG,Chip ID: 0x%04X\n", chip_id); float log_start = log10f(start_freq);
float log_end = log10f(end_freq);
float step_size = (steps > 1) ? (log_end - log_start) / (steps - 1) : 0;
printf("START_SWEEP\n");
for (int i = 0; i < steps; ++i) {
float freq = powf(10.0f, log_start + (i * step_size));
measure_at_freq(freq);
} }
printf("END_SWEEP\n");
}
void system_init(void) {
setup_pins();
// Hardware Reset AD5940
AD5940_RstClr();
sleep_ms(10);
AD5940_RstSet();
sleep_ms(10);
AD5940_Initialize();
configure_afe();
} }
int main() { int main() {
stdio_init_all(); stdio_init_all();
sleep_ms(1000); sleep_ms(2000);
spi_init(SPI_PORT, SPI_BAUDRATE_HIGH); system_init();
spi_set_format(SPI_PORT, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); printf("SYSTEM_READY\n");
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
gpio_init(PIN_CS);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_put(PIN_CS, 1);
gpio_init(PIN_RESET);
gpio_set_dir(PIN_RESET, GPIO_OUT);
gpio_put(PIN_RESET, 1);
gpio_init(PIN_AD_INTERRUPT);
gpio_set_dir(PIN_AD_INTERRUPT, GPIO_IN);
gpio_pull_up(PIN_AD_INTERRUPT);
// Initial Start
AD5940_PulseReset();
AD5940_Initialize();
AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF);
AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF);
AD5940_SEQGenInit(AppBuff, 512);
char buffer[64];
int pos = 0;
while (true) { while (true) {
int c = getchar_timeout_us(1000); int c = getchar_timeout_us(100);
if (c != PICO_ERROR_TIMEOUT) { if (c == 'm') {
if (c == '\n' || c == '\r') { run_sweep(100.0f, 100000.0f, 50);
if (pos > 0) {
buffer[pos] = 0;
process_command(buffer);
pos = 0;
}
} else if (pos < 63) {
buffer[pos++] = (char)c;
} }
if (c == 'z') {
system_init();
printf("RESET_DONE\n");
} }
} }
return 0;
} }