oh sweet bejusus finally a little morsel of progress
This commit is contained in:
parent
9eff1ecd36
commit
6e1d6cc970
|
|
@ -6,7 +6,7 @@ build
|
|||
*.swp
|
||||
*.cmake
|
||||
requirements.txt
|
||||
ad5940*
|
||||
ad5940.c
|
||||
pico_sdk_import.cmake
|
||||
build*
|
||||
*.png
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
# File: CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(EIS C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
pico_sdk_init()
|
||||
|
||||
# Add ad5940.c to the build
|
||||
# Changed main.cpp to main.c
|
||||
add_executable(EIS
|
||||
main.c
|
||||
ad5940.c
|
||||
)
|
||||
|
||||
# Define CHIPSEL_594X for the library
|
||||
target_compile_definitions(EIS PRIVATE CHIPSEL_594X)
|
||||
|
||||
# REQUIRED: Tell CMake where to find headers
|
||||
target_include_directories(EIS PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
target_link_libraries(EIS
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
* */
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
* */
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
* */
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
# File: host/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
project(EISConfigurator VERSION 1.0 LANGUAGES CXX C)
|
||||
|
|
@ -83,7 +81,8 @@ set(PROJECT_HEADERS
|
|||
if(ANDROID)
|
||||
add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS})
|
||||
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()
|
||||
|
||||
if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE)
|
||||
|
|
|
|||
|
|
@ -1,89 +1,148 @@
|
|||
#include "GraphWidget.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) {
|
||||
layout = new QVBoxLayout(this);
|
||||
plot = new QCustomPlot(this);
|
||||
|
||||
// Setup Layout
|
||||
layout->addWidget(plot);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
plot = new JKQTPlotter(this);
|
||||
layout->addWidget(plot);
|
||||
// Setup Graphs
|
||||
graph1 = plot->addGraph();
|
||||
graph2 = plot->addGraph(plot->xAxis, plot->yAxis2);
|
||||
|
||||
// Initialize Graphs
|
||||
graphY1 = new JKQTPXYLineGraph(plot);
|
||||
graphY2 = new JKQTPXYLineGraph(plot);
|
||||
// Style Graph 1 (Primary - Left Axis)
|
||||
QPen pen1(Qt::blue);
|
||||
pen1.setWidth(2);
|
||||
graph1->setPen(pen1);
|
||||
graph1->setLineStyle(QCPGraph::lsLine);
|
||||
graph1->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 3));
|
||||
graph1->setName("Primary");
|
||||
|
||||
// Styling Y1 (e.g., Blue)
|
||||
graphY1->setColor(QColor("blue"));
|
||||
graphY1->setSymbolColor(QColor("blue"));
|
||||
graphY1->setSymbol(JKQTPCircle);
|
||||
graphY1->setSymbolSize(7);
|
||||
graphY1->setLineWidth(2);
|
||||
// Style Graph 2 (Secondary - Right Axis)
|
||||
QPen pen2(Qt::red);
|
||||
pen2.setWidth(2);
|
||||
graph2->setPen(pen2);
|
||||
graph2->setLineStyle(QCPGraph::lsLine);
|
||||
graph2->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, 3));
|
||||
graph2->setName("Secondary");
|
||||
|
||||
// Styling Y2 (e.g., Red)
|
||||
graphY2->setColor(QColor("red"));
|
||||
graphY2->setSymbolColor(QColor("red"));
|
||||
graphY2->setSymbol(JKQTPTriangle);
|
||||
graphY2->setSymbolSize(7);
|
||||
graphY2->setLineWidth(2);
|
||||
// Enable Right Axis
|
||||
plot->yAxis2->setVisible(true);
|
||||
plot->yAxis2->setTickLabels(true);
|
||||
|
||||
plot->addGraph(graphY1);
|
||||
plot->addGraph(graphY2);
|
||||
// Interactions
|
||||
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)
|
||||
{
|
||||
// Basic Settings
|
||||
plot->getPlotter()->setPlotLabel(title);
|
||||
plot->getXAxis()->setAxisLabel(xLabel);
|
||||
void GraphWidget::configureBodePlot() {
|
||||
clear();
|
||||
|
||||
// Y-Axis setup
|
||||
plot->getYAxis()->setAxisLabel(y1Label); // Left Axis
|
||||
// X Axis: Frequency (Log)
|
||||
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,
|
||||
// but for simplicity here we might plot on the same if scales are similar,
|
||||
// or rely on user interpretation.
|
||||
// For Bode (Ohm vs Degree), scales are vastly different.
|
||||
// Ideally, Y2 should be on the right axis.
|
||||
// Y Axis 1: Impedance (Log)
|
||||
plot->yAxis->setLabel("Impedance (Ohms)");
|
||||
plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
|
||||
plot->yAxis->setTicker(logTicker);
|
||||
plot->yAxis->setNumberFormat("eb");
|
||||
|
||||
// Assign Y1 to Left Axis
|
||||
graphY1->setTitle(y1Label);
|
||||
// Y Axis 2: Phase (Linear)
|
||||
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)
|
||||
// Assuming simple usage for now:
|
||||
graphY2->setTitle(y2Label);
|
||||
graph1->setName("Impedance");
|
||||
graph2->setName("Phase");
|
||||
|
||||
// Enable Legend
|
||||
plot->getPlotter()->setShowKey(true);
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
void GraphWidget::setLogAxis(bool logX, bool logY)
|
||||
{
|
||||
plot->getXAxis()->setLogAxis(logX);
|
||||
plot->getYAxis()->setLogAxis(logY);
|
||||
void GraphWidget::configureRawPlot() {
|
||||
clear();
|
||||
|
||||
// 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)
|
||||
{
|
||||
xData.append(x);
|
||||
y1Data.append(y1);
|
||||
y2Data.append(y2);
|
||||
void GraphWidget::addData(double freq, double val1, double val2) {
|
||||
// 1. Validate X-Axis (Frequency)
|
||||
// If Log scale, Frequency must be > 0.
|
||||
if (plot->xAxis->scaleType() == QCPAxis::stLogarithmic && freq <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update graph data pointers
|
||||
graphY1->setXColumn(xData);
|
||||
graphY1->setYColumn(y1Data);
|
||||
// 2. Validate Y-Axis 1 (Impedance/Real)
|
||||
// If Log scale (Impedance), value must be > 0.
|
||||
if (plot->yAxis->scaleType() == QCPAxis::stLogarithmic) {
|
||||
// If we have a zero/negative impedance in log mode, skip it.
|
||||
// Clamping to 1e-12 causes massive axis scaling issues (squished plots).
|
||||
if (val1 <= 1e-7) {
|
||||
val1 = std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
|
||||
graphY2->setXColumn(xData);
|
||||
graphY2->setYColumn(y2Data);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Redraw
|
||||
plot->redrawPlot();
|
||||
// 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()
|
||||
{
|
||||
xData.clear();
|
||||
y1Data.clear();
|
||||
y2Data.clear();
|
||||
plot->redrawPlot();
|
||||
void GraphWidget::clear() {
|
||||
graph1->data()->clear();
|
||||
graph2->data()->clear();
|
||||
plot->replot();
|
||||
}
|
||||
|
|
@ -1,39 +1,26 @@
|
|||
#ifndef GRAPHWIDGET_H
|
||||
#define GRAPHWIDGET_H
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVector>
|
||||
#include "jkqtplotter/jkqtplotter.h"
|
||||
#include "jkqtplotter/graphs/jkqtpigraph.h"
|
||||
#include "jkqtplotter/graphs/jkqtpimage.h"
|
||||
#include "jkqtplotter/graphs/jkqtpscatter.h"
|
||||
#include "jkqtplotter/graphs/jkqtplyines.h"
|
||||
#include <QVBoxLayout>
|
||||
#include "qcustomplot.h"
|
||||
|
||||
class GraphWidget : public QWidget
|
||||
{
|
||||
class GraphWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GraphWidget(QWidget *parent = nullptr);
|
||||
|
||||
// Configuration
|
||||
void setupPlot(QString title, QString xLabel, QString y1Label, QString y2Label);
|
||||
void setLogAxis(bool logX, bool logY);
|
||||
|
||||
// Data Access
|
||||
void addData(double x, double y1, double y2);
|
||||
// Data Handling
|
||||
void addData(double freq, double val1, double val2);
|
||||
void clear();
|
||||
|
||||
// View Configurations
|
||||
void configureBodePlot();
|
||||
void configureRawPlot();
|
||||
|
||||
private:
|
||||
JKQTPlotter *plot;
|
||||
|
||||
// Internal Data Storage
|
||||
QVector<double> xData;
|
||||
QVector<double> y1Data;
|
||||
QVector<double> y2Data;
|
||||
|
||||
// Graph Objects
|
||||
JKQTPXYLineGraph *graphY1;
|
||||
JKQTPXYLineGraph *graphY2;
|
||||
QVBoxLayout *layout;
|
||||
QCustomPlot *plot;
|
||||
QCPGraph *graph1;
|
||||
QCPGraph *graph2;
|
||||
};
|
||||
|
||||
#endif // GRAPHWIDGET_H
|
||||
|
|
@ -1,228 +1,288 @@
|
|||
#include "MainWindow.h"
|
||||
#include <QSerialPortInfo>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QToolBar>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QScroller>
|
||||
#include <QGesture>
|
||||
#include <QSplitter>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <cmath>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
{
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), currentSweepIndex(0), isSweeping(false) {
|
||||
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();
|
||||
// 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()
|
||||
{
|
||||
if (serial->isOpen())
|
||||
MainWindow::~MainWindow() {
|
||||
if (serial->isOpen()) {
|
||||
serial->close();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setupUi()
|
||||
{
|
||||
centralWidget = new QWidget(this);
|
||||
setCentralWidget(centralWidget);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
|
||||
void MainWindow::setupUi() {
|
||||
// Central Layout: Splitter (Graphs Top, Log Bottom)
|
||||
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
|
||||
setCentralWidget(splitter);
|
||||
|
||||
// --- Toolbar Area ---
|
||||
QHBoxLayout *toolLayout = new QHBoxLayout();
|
||||
// Tab Widget for Graphs
|
||||
tabWidget = new QTabWidget(this);
|
||||
finalGraph = new GraphWidget(this);
|
||||
finalGraph->configureBodePlot();
|
||||
rawGraph = new GraphWidget(this);
|
||||
rawGraph->configureRawPlot();
|
||||
|
||||
portSelector = new QComboBox();
|
||||
const auto ports = QSerialPortInfo::availablePorts();
|
||||
for (const QSerialPortInfo &info : ports) {
|
||||
portSelector->addItem(info.portName());
|
||||
}
|
||||
|
||||
btnConnect = new QPushButton("Connect");
|
||||
connect(btnConnect, &QPushButton::clicked, [this]() {
|
||||
onPortToggled(serial->isOpen());
|
||||
});
|
||||
|
||||
btnMeasure = new QPushButton("Measure");
|
||||
btnMeasure->setEnabled(false);
|
||||
connect(btnMeasure, &QPushButton::clicked, this, &MainWindow::onMeasureClicked);
|
||||
|
||||
btnCalibrate = new QPushButton("Calibrate");
|
||||
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(finalGraph, "Bode Plot");
|
||||
tabWidget->addTab(rawGraph, "Raw Data");
|
||||
|
||||
// 2. Splitter (Desktop Style)
|
||||
splitter = new QSplitter(Qt::Horizontal);
|
||||
// Note: We can't add the same widget to two parents.
|
||||
// We will reparent them in onViewModeChanged.
|
||||
// Log Widget
|
||||
logWidget = new QTextEdit(this);
|
||||
logWidget->setReadOnly(true);
|
||||
logWidget->setFont(QFont("Monospace"));
|
||||
logWidget->setPlaceholderText("Scanning for 0xCAFE EIS Device...");
|
||||
QScroller::grabGesture(logWidget->viewport(), QScroller::TouchGesture);
|
||||
|
||||
// Default to Tabs (Mobile friendly default)
|
||||
mainLayout->addWidget(tabWidget);
|
||||
splitter->addWidget(tabWidget);
|
||||
splitter->addWidget(logWidget);
|
||||
splitter->setStretchFactor(0, 2);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
// Status Bar
|
||||
statusLabel = new QLabel("Ready");
|
||||
statusBar()->addWidget(statusLabel);
|
||||
// Toolbar Construction
|
||||
toolbar = addToolBar("Connection");
|
||||
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)
|
||||
viewSelector->setCurrentIndex(0); // Tabs
|
||||
viewSelector->setVisible(false); // Hide selector on mobile, force tabs
|
||||
#else
|
||||
viewSelector->setCurrentIndex(1); // Default to Split on Desktop
|
||||
toolbar->setIconSize(QSize(32, 32));
|
||||
QFont font = portSelector->font();
|
||||
font.setPointSize(14);
|
||||
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
|
||||
}
|
||||
|
||||
void MainWindow::onViewModeChanged(int index)
|
||||
{
|
||||
// 0 = Tabs, 1 = Split
|
||||
|
||||
// Remove graphs from their current parents
|
||||
finalGraph->setParent(nullptr);
|
||||
rawGraph->setParent(nullptr);
|
||||
|
||||
QVBoxLayout *layout = qobject_cast<QVBoxLayout*>(centralWidget->layout());
|
||||
|
||||
if (index == 0) { // Tabs
|
||||
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::refreshPorts() {
|
||||
portSelector->clear();
|
||||
const auto infos = QSerialPortInfo::availablePorts();
|
||||
for (const QSerialPortInfo &info : infos) {
|
||||
portSelector->addItem(info.portName());
|
||||
// Auto-connect to device with 0xCAFE Vendor ID
|
||||
if (info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE) {
|
||||
logWidget->append(">> Found EIS Device (0xCAFE) on " + info.portName());
|
||||
portSelector->setCurrentText(info.portName());
|
||||
if (!serial->isOpen()) {
|
||||
connectToPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onPortToggled(bool connected)
|
||||
{
|
||||
if (connected) {
|
||||
void MainWindow::connectToPort() {
|
||||
if (serial->isOpen()) {
|
||||
serial->close();
|
||||
btnConnect->setText("Connect");
|
||||
btnMeasure->setEnabled(false);
|
||||
btnCalibrate->setEnabled(false);
|
||||
statusLabel->setText("Disconnected");
|
||||
connectBtn->setText("Connect");
|
||||
logWidget->append("--- Disconnected ---");
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
sweepBtn->setEnabled(false);
|
||||
isSweeping = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (portSelector->currentText().isEmpty()) return;
|
||||
|
||||
serial->setPortName(portSelector->currentText());
|
||||
serial->setBaudRate(500000);
|
||||
|
||||
if (serial->open(QIODevice::ReadWrite)) {
|
||||
connectBtn->setText("Disconnect");
|
||||
logWidget->append("--- Connected and Synchronized ---");
|
||||
checkIdBtn->setEnabled(true);
|
||||
calibrateBtn->setEnabled(true);
|
||||
sweepBtn->setEnabled(true);
|
||||
} else {
|
||||
serial->setPortName(portSelector->currentText());
|
||||
serial->setBaudRate(115200); // Or whatever your Pico uses, standard 115200 usually
|
||||
|
||||
if (serial->open(QIODevice::ReadWrite)) {
|
||||
btnConnect->setText("Disconnect");
|
||||
btnMeasure->setEnabled(true);
|
||||
btnCalibrate->setEnabled(true);
|
||||
statusLabel->setText("Connected");
|
||||
|
||||
// Clear old data on connect
|
||||
finalGraph->clear();
|
||||
rawGraph->clear();
|
||||
} 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()) {
|
||||
finalGraph->clear();
|
||||
rawGraph->clear();
|
||||
// Trigger a sweep or single measurement
|
||||
// Example command logic
|
||||
// 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");
|
||||
logWidget->append(">> Checking ID (v)...");
|
||||
serial->write("v");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onCalibrateClicked()
|
||||
{
|
||||
void MainWindow::runCalibration() {
|
||||
if (serial->isOpen()) {
|
||||
serial->write("CAL\n");
|
||||
logWidget->append(">> Running Calibration (c)...");
|
||||
serial->write("c");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onReadData()
|
||||
{
|
||||
void MainWindow::startSweep() {
|
||||
if (!serial->isOpen()) return;
|
||||
isSweeping = true;
|
||||
currentSweepIndex = 0;
|
||||
finalGraph->clear();
|
||||
rawGraph->clear();
|
||||
logWidget->append(">> Starting Ratio-metric Sweep...");
|
||||
sendNextSweepPoint();
|
||||
}
|
||||
|
||||
void MainWindow::sendNextSweepPoint() {
|
||||
if (!isSweeping || !serial->isOpen()) return;
|
||||
if (currentSweepIndex >= sweepPoints.size()) {
|
||||
isSweeping = false;
|
||||
logWidget->append(">> Sweep Complete.");
|
||||
return;
|
||||
}
|
||||
serial->write("m");
|
||||
}
|
||||
|
||||
void MainWindow::handleSerialData() {
|
||||
while (serial->canReadLine()) {
|
||||
QByteArray line = serial->readLine().trimmed();
|
||||
processLine(line);
|
||||
QByteArray line = serial->readLine();
|
||||
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)
|
||||
{
|
||||
if (line.startsWith("DATA")) {
|
||||
// Format: DATA,freq,impedance,phase,raw_real,raw_imag
|
||||
float freq, imp, phase;
|
||||
int raw_real, raw_imag;
|
||||
void MainWindow::parseData(const QString &data) {
|
||||
QStringList parts = data.split(',');
|
||||
if (parts.size() < 6) return;
|
||||
|
||||
// sscanf is dangerous with comma delimited strings if not careful, but works for fixed format
|
||||
int count = sscanf(line.constData(), "DATA,%f,%f,%f,%d,%d", &freq, &imp, &phase, &raw_real, &raw_imag);
|
||||
bool okF, okM, okP, okR, okI;
|
||||
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) {
|
||||
// We have at least the final data
|
||||
finalGraph->addData(freq, imp, phase); // Y1=Imp, Y2=Phase
|
||||
}
|
||||
if (okF && okM && okP) finalGraph->addData(freq, mag, phase);
|
||||
if (okF && okR && okI) rawGraph->addData(freq, real, imag);
|
||||
|
||||
if (count >= 5) {
|
||||
// We have raw data too
|
||||
rawGraph->addData(freq, (double)raw_real, (double)raw_imag);
|
||||
}
|
||||
|
||||
statusLabel->setText(QString("Received: %1 Hz").arg(freq));
|
||||
}
|
||||
else if (line.startsWith("LOG")) {
|
||||
statusLabel->setText(line);
|
||||
qDebug() << "Device Log:" << line;
|
||||
if (isSweeping) {
|
||||
currentSweepIndex++;
|
||||
QTimer::singleShot(50, this, &MainWindow::sendNextSweepPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::generateSweepPoints() {
|
||||
sweepPoints.clear();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::event(QEvent *event) {
|
||||
if (event->type() == QEvent::Gesture) {
|
||||
QGestureEvent *ge = static_cast<QGestureEvent*>(event);
|
||||
if (QGesture *swipe = ge->gesture(Qt::SwipeGesture)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +1,66 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSerialPort>
|
||||
#include <QTabWidget>
|
||||
#include <QSplitter>
|
||||
#include <QTextEdit>
|
||||
#include <QToolBar>
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QEvent>
|
||||
#include <QGestureEvent>
|
||||
#include <QSwipeGesture>
|
||||
#include <QTimer>
|
||||
#include "GraphWidget.h"
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void onPortToggled(bool connected);
|
||||
void onReadData();
|
||||
void onMeasureClicked();
|
||||
void onCalibrateClicked();
|
||||
void onViewModeChanged(int index); // New slot for desktop view switching
|
||||
void handleSerialData();
|
||||
void connectToPort();
|
||||
void refreshPorts();
|
||||
void onPortError(QSerialPort::SerialPortError error);
|
||||
|
||||
// Action Slots
|
||||
void checkDeviceId();
|
||||
void runCalibration();
|
||||
void startSweep();
|
||||
void sendNextSweepPoint();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void processLine(const QByteArray &line);
|
||||
void parseData(const QString &data);
|
||||
void handleSwipe(QSwipeGesture *gesture);
|
||||
void generateSweepPoints();
|
||||
|
||||
// Hardware
|
||||
QSerialPort *serial;
|
||||
|
||||
// UI Elements
|
||||
QComboBox *portSelector;
|
||||
QPushButton *btnConnect;
|
||||
QPushButton *btnMeasure;
|
||||
QPushButton *btnCalibrate;
|
||||
QLabel *statusLabel;
|
||||
|
||||
// Graphs
|
||||
// Views
|
||||
GraphWidget *finalGraph; // Bode Plot
|
||||
GraphWidget *rawGraph; // Raw Real/Imag Plot
|
||||
GraphWidget *rawGraph; // Raw Data
|
||||
QTextEdit *logWidget; // Serial Log
|
||||
|
||||
// Layout Containers
|
||||
QWidget *centralWidget;
|
||||
QTabWidget *tabWidget; // For Mobile/Tab Mode
|
||||
QSplitter *splitter; // For Desktop/Split Mode
|
||||
QComboBox *viewSelector; // For Desktop to toggle modes
|
||||
// Layout
|
||||
QTabWidget *tabWidget;
|
||||
QToolBar *toolbar;
|
||||
QComboBox *portSelector;
|
||||
QPushButton *connectBtn;
|
||||
QPushButton *checkIdBtn;
|
||||
QPushButton *calibrateBtn;
|
||||
QPushButton *sweepBtn;
|
||||
|
||||
// Sweep Logic
|
||||
QList<double> sweepPoints;
|
||||
int currentSweepIndex;
|
||||
bool isSweeping;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
564
main.c
564
main.c
|
|
@ -1,474 +1,258 @@
|
|||
// File: EIS/main.c
|
||||
// File: main.c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "ad5940.h"
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Pin Definitions
|
||||
// --------------------------------------------------------------------------
|
||||
#define PIN_MISO 0
|
||||
#define PIN_CS 1
|
||||
#define PIN_SCK 2
|
||||
#define PIN_MOSI 3
|
||||
#define PIN_RESET 9
|
||||
#define PIN_AD_INTERRUPT 29
|
||||
#define PIN_MISO 0
|
||||
#define PIN_CS 1
|
||||
#define PIN_SCK 2
|
||||
#define PIN_MOSI 3
|
||||
#define PIN_RST 9
|
||||
#define PIN_INT 29
|
||||
|
||||
#define SPI_PORT spi0
|
||||
#define SPI_BAUDRATE_HIGH 500000
|
||||
#define RCAL_VALUE 100.0f
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Switch Matrix Bit Definitions
|
||||
// --------------------------------------------------------------------------
|
||||
#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
|
||||
// --------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform Interface Implementation (Required by AD5940 Lib)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void AD5940_CsClr(void) {
|
||||
gpio_put(PIN_CS, 0);
|
||||
sleep_us(1);
|
||||
}
|
||||
|
||||
void AD5940_CsSet(void) {
|
||||
sleep_us(1);
|
||||
gpio_put(PIN_CS, 1);
|
||||
sleep_us(1);
|
||||
}
|
||||
|
||||
void AD5940_RstClr(void) { gpio_put(PIN_RESET, 0); }
|
||||
void AD5940_RstSet(void) { gpio_put(PIN_RESET, 1); }
|
||||
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_RstClr(void) {
|
||||
gpio_put(PIN_RST, 0);
|
||||
}
|
||||
|
||||
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuffer, unsigned long length) {
|
||||
if (pRecvBuffer == NULL) {
|
||||
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_RstSet(void) {
|
||||
gpio_put(PIN_RST, 1);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
void AD5940_PulseReset(void) {
|
||||
AD5940_RstClr();
|
||||
sleep_ms(10);
|
||||
AD5940_RstSet();
|
||||
sleep_ms(50); // Increased wait for boot stability
|
||||
void AD5940_Delay10us(uint32_t time) {
|
||||
sleep_us(time * 10);
|
||||
}
|
||||
|
||||
// Helper to map Register Macro to Actual Sample Count for calculations
|
||||
uint32_t GetDftSamples(uint32_t dft_num_macro) {
|
||||
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
|
||||
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, unsigned long length) {
|
||||
spi_write_read_blocking(spi0, pSendBuffer, pRecvBuff, length);
|
||||
}
|
||||
|
||||
// Structure to hold calculated configuration
|
||||
typedef struct {
|
||||
uint32_t DftNum;
|
||||
uint32_t DftSrc;
|
||||
uint32_t Sinc3Osr;
|
||||
uint32_t Sinc2Osr;
|
||||
float SampleRate; // Estimated
|
||||
} FilterConfig_Type;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Application Logic
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Calculates optimal DFT and Filter settings based on frequency
|
||||
// Optimized for High Speed Loop (HFOSC 16MHz) only
|
||||
void AD5940_GetFilterCfg(float freq, FilterConfig_Type *pCfg) {
|
||||
// Strategy:
|
||||
// For > 50Hz, use SINC3 (Fast, ~200kHz)
|
||||
// For <= 50Hz, use SINC2 path (Slower, ~9kHz) to capture full waves
|
||||
void setup_pins(void) {
|
||||
// SPI0 at 1MHz
|
||||
spi_init(spi0, 1000000);
|
||||
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
|
||||
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
|
||||
|
||||
if (freq >= 50.0f) {
|
||||
pCfg->DftSrc = DFTSRC_SINC3;
|
||||
pCfg->Sinc3Osr = ADCSINC3OSR_4;
|
||||
pCfg->Sinc2Osr = ADCSINC2OSR_22; // Don't care for SINC3 src
|
||||
pCfg->SampleRate = 200000.0f; // 800k / 4
|
||||
// CS
|
||||
gpio_init(PIN_CS);
|
||||
gpio_set_dir(PIN_CS, GPIO_OUT);
|
||||
gpio_put(PIN_CS, 1);
|
||||
|
||||
// Adjust DFT size for speed vs accuracy at high freq
|
||||
if (freq > 40000.0f) pCfg->DftNum = DFTNUM_2048;
|
||||
else if (freq > 1000.0f) pCfg->DftNum = DFTNUM_4096;
|
||||
else pCfg->DftNum = DFTNUM_8192;
|
||||
}
|
||||
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;
|
||||
// RST
|
||||
gpio_init(PIN_RST);
|
||||
gpio_set_dir(PIN_RST, GPIO_OUT);
|
||||
gpio_put(PIN_RST, 1);
|
||||
|
||||
// 8192 samples @ 9kHz = ~0.9 seconds
|
||||
// Covers 9 cycles of 10Hz. Perfect.
|
||||
pCfg->DftNum = DFTNUM_8192;
|
||||
}
|
||||
// INT
|
||||
gpio_init(PIN_INT);
|
||||
gpio_set_dir(PIN_INT, GPIO_IN);
|
||||
gpio_pull_up(PIN_INT);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Measurement Functions
|
||||
// --------------------------------------------------------------------------
|
||||
void configure_afe(void) {
|
||||
HSDACCfg_Type hsdac_cfg;
|
||||
HSTIACfg_Type hsrtia_cfg;
|
||||
HSLoopCfg_Type hsloop_cfg;
|
||||
|
||||
static uint32_t AppBuff[512];
|
||||
|
||||
// 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.
|
||||
// Reset AFE control
|
||||
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
|
||||
|
||||
// CRITICAL: Disable Sequencer
|
||||
AD5940_SEQCtrlS(bFALSE);
|
||||
// Configure High Speed DAC
|
||||
hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_2;
|
||||
hsdac_cfg.HsDacGain = HSDACGAIN_1;
|
||||
hsdac_cfg.HsDacUpdateRate = 0x1B;
|
||||
AD5940_HSDacCfgS(&hsdac_cfg);
|
||||
|
||||
// 3. Clock Configuration
|
||||
AD5940_WriteReg(REG_ALLON_OSCKEY, 0xCB14);
|
||||
AD5940_WriteReg(REG_ALLON_OSCCON, 0x0003); // Enable HFOSC
|
||||
sleep_ms(2);
|
||||
// Configure High Speed TIA
|
||||
hsrtia_cfg.HstiaDeRtia = HSTIADERTIA_1K;
|
||||
hsrtia_cfg.HstiaCtia = 31; // 31pF
|
||||
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);
|
||||
AD5940_WriteReg(REG_AFE_LPMODECLKSEL, 0); // Select HFOSC
|
||||
// Configure High Speed Loop
|
||||
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
|
||||
AD5940_WriteReg(REG_AFE_LPTIASW0, 0x0000);
|
||||
AD5940_WriteReg(REG_AFE_LPDACSW0, 0x0000);
|
||||
hsloop_cfg.WgCfg.WgType = WGTYPE_SIN;
|
||||
hsloop_cfg.WgCfg.GainCalEn = bFALSE;
|
||||
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
|
||||
HSLoopCfg_Type hs_loop;
|
||||
hs_loop.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
|
||||
hs_loop.HsDacCfg.HsDacGain = HSDACGAIN_1;
|
||||
hs_loop.HsDacCfg.HsDacUpdateRate = 7;
|
||||
AD5940_HSLoopCfgS(&hsloop_cfg);
|
||||
|
||||
if (freq > 1000.0f) {
|
||||
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_1K;
|
||||
} else {
|
||||
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_5K;
|
||||
// Enable Power
|
||||
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_HSTIAPWR | AFECTRL_HSDACPWR | AFECTRL_HPREFPWR, bTRUE);
|
||||
|
||||
// Enable Interrupts
|
||||
AD5940_WriteReg(REG_INTC_INTCSEL0, BITM_INTC_INTCSEL0_INTSEL14);
|
||||
}
|
||||
|
||||
float perform_dft(void) {
|
||||
iImpCar_Type dft_res;
|
||||
fImpCar_Type f_res;
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
|
||||
|
||||
// Wait for interrupt or timeout
|
||||
uint32_t timeout = 50000;
|
||||
while(gpio_get(PIN_INT) && timeout--) {
|
||||
sleep_us(10);
|
||||
}
|
||||
|
||||
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
|
||||
hs_loop.HsTiaCfg.HstiaCtia = 16;
|
||||
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
|
||||
dft_res.Real = (int32_t)AD5940_ReadReg(REG_AFE_DFTREAL);
|
||||
dft_res.Image = (int32_t)AD5940_ReadReg(REG_AFE_DFTIMAG);
|
||||
|
||||
AD5940_HSLoopCfgS(&hs_loop);
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bFALSE);
|
||||
AD5940_WriteReg(REG_INTC_INTCFLAG0, BITM_INTC_INTCFLAG0_FLAG14);
|
||||
|
||||
// 6. Switch Matrix (S+/S-)
|
||||
AD5940_WriteReg(REG_AFE_SWCON, BITM_AFE_SWCON_SWSOURCESEL);
|
||||
AD5940_WriteReg(REG_AFE_DSWFULLCON, SWD_CE0);
|
||||
AD5940_WriteReg(REG_AFE_PSWFULLCON, 0x0000);
|
||||
AD5940_WriteReg(REG_AFE_NSWFULLCON, 0x0000);
|
||||
AD5940_WriteReg(REG_AFE_TSWFULLCON, SWT_SE0 | SWT_T9);
|
||||
f_res.Real = (float)dft_res.Real;
|
||||
f_res.Image = (float)dft_res.Image;
|
||||
|
||||
// 7. Get Dynamic Configuration
|
||||
FilterConfig_Type cfg;
|
||||
AD5940_GetFilterCfg(freq, &cfg);
|
||||
return AD5940_ComplexMag(&f_res);
|
||||
}
|
||||
|
||||
// 8. ADC Configuration
|
||||
ADCBaseCfg_Type adc_cfg;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// Initialize and Enable Clocks
|
||||
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;
|
||||
memset(&filter_cfg, 0, sizeof(filter_cfg));
|
||||
|
||||
filter_cfg.ADCSinc3Osr = cfg.Sinc3Osr;
|
||||
filter_cfg.ADCSinc2Osr = cfg.Sinc2Osr;
|
||||
// Initialize struct to 0 to avoid garbage
|
||||
filter_cfg.ADCSinc3Osr = freq_params.ADCSinc3Osr;
|
||||
filter_cfg.ADCSinc2Osr = freq_params.ADCSinc2Osr;
|
||||
filter_cfg.ADCAvgNum = ADCAVGNUM_16;
|
||||
filter_cfg.ADCRate = ADCRATE_800KHZ;
|
||||
filter_cfg.BpNotch = bTRUE;
|
||||
filter_cfg.BpSinc3 = bFALSE;
|
||||
filter_cfg.Sinc2NotchEnable = bTRUE;
|
||||
|
||||
// Explicitly Enable Clocks
|
||||
filter_cfg.DFTClkEnable = bTRUE;
|
||||
filter_cfg.WGClkEnable = bTRUE;
|
||||
filter_cfg.Sinc3ClkEnable = bTRUE;
|
||||
filter_cfg.Sinc2NotchClkEnable = bTRUE;
|
||||
|
||||
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;
|
||||
dft_cfg.DftNum = cfg.DftNum;
|
||||
dft_cfg.DftSrc = cfg.DftSrc;
|
||||
dft_cfg.DftNum = freq_params.DftNum;
|
||||
dft_cfg.DftSrc = freq_params.DftSrc;
|
||||
dft_cfg.HanWinEn = bTRUE;
|
||||
AD5940_DFTCfgS(&dft_cfg);
|
||||
|
||||
// 11. Configure FIFO for DFT
|
||||
// Ensure we capture DFT results in FIFO
|
||||
FIFOCfg_Type fifo_cfg;
|
||||
fifo_cfg.FIFOEn = bTRUE;
|
||||
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
|
||||
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
|
||||
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
|
||||
fifo_cfg.FIFOThresh = 4;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
// 4. Perform Measurement
|
||||
// RCAL
|
||||
set_sw_matrix(0, SWP_RCAL0 | SWN_RCAL1);
|
||||
sleep_ms(5);
|
||||
float mag_cal = perform_dft();
|
||||
|
||||
// 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
|
||||
AD5940_AFECtrlS(AFECTRL_HSDACPWR|AFECTRL_HSTIAPWR|AFECTRL_ADCPWR|AFECTRL_DACREFPWR|AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR, bTRUE);
|
||||
|
||||
sleep_ms(20);
|
||||
|
||||
// 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 (mag_z > 0.01f && mag_cal > 0.01f) {
|
||||
float impedance = (mag_cal / mag_z) * RCAL_VALUE;
|
||||
printf("DATA,%.2f,%.2f,0,0,0\n", freq, impedance);
|
||||
} else {
|
||||
printf("DATA,%.2f,0,0,0,0\n", freq);
|
||||
}
|
||||
|
||||
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 run_sweep(float start_freq, float end_freq, int steps) {
|
||||
float log_start = log10f(start_freq);
|
||||
float log_end = log10f(end_freq);
|
||||
float step_size = (steps > 1) ? (log_end - log_start) / (steps - 1) : 0;
|
||||
|
||||
void process_command(char* cmd) {
|
||||
char* token = strtok(cmd, " ");
|
||||
if (!token) return;
|
||||
printf("START_SWEEP\n");
|
||||
|
||||
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);
|
||||
printf("LOG,Chip ID: 0x%04X\n", chip_id);
|
||||
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() {
|
||||
stdio_init_all();
|
||||
sleep_ms(1000);
|
||||
sleep_ms(2000);
|
||||
|
||||
spi_init(SPI_PORT, SPI_BAUDRATE_HIGH);
|
||||
spi_set_format(SPI_PORT, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
system_init();
|
||||
|
||||
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
|
||||
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;
|
||||
printf("SYSTEM_READY\n");
|
||||
|
||||
while (true) {
|
||||
int c = getchar_timeout_us(1000);
|
||||
if (c != PICO_ERROR_TIMEOUT) {
|
||||
if (c == '\n' || c == '\r') {
|
||||
if (pos > 0) {
|
||||
buffer[pos] = 0;
|
||||
process_command(buffer);
|
||||
pos = 0;
|
||||
}
|
||||
} else if (pos < 63) {
|
||||
buffer[pos++] = (char)c;
|
||||
}
|
||||
int c = getchar_timeout_us(100);
|
||||
if (c == 'm') {
|
||||
run_sweep(100.0f, 100000.0f, 50);
|
||||
}
|
||||
if (c == 'z') {
|
||||
system_init();
|
||||
printf("RESET_DONE\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue